C++面向对象速览(三)

多态的基本语法53

1,静态多态:

地址早绑定,编译阶段确定函数地址。例如函数重载,运算符重载都属于静态多态,复用函数名。

2,动态多态:

地址晚绑定,运行阶段确定函数地址。用派生类和虚函数实现运行时多态。

虚函数:子类重写父类函数(返回值,参数列表,函数名相同),调用时父类对象的引用作为形参,在父类被重写函数前加上virtual关键字,使之成为虚函数,则可以根据传入参数的对象类型选择对应类的函数调用。

  • 虚函数:使得基类指针或引用指向派生类对象时,可以调用派生类的重写函数,实现多态。
  • 动态联编(Dynamic Binding) :指的是程序运行时,系统动态决定调用哪个函数,常常与虚函数一起使用。
  • 滞后联编(Late Binding) :与动态联编是同义的,强调函数调用的决定是在程序运行时,而不是编译时。

两者本质上是相同的,滞后联编更多是对"运行时选择函数"过程的描述,而动态联编则是虚函数机制的一部分,指代了在运行时基于对象的实际类型来选择函数的调用。

子类重写函数前virtual可加可不加。

csharp 复制代码
class animals {
public:
    virtual void speak() {
        cout << "anilmals' speak" << endl;
    }
};
​
class cats : public  animals{
public:
    void speak() {
        cout << "cats' speak" << endl;
    }
};
​
class dogs : public  animals {
public:
    void speak() {
        cout << "dogs' speak" << endl;
    }
};
​
void dospeak(animals & animal) {
    animal.speak();
}
​
void tc01() {
    animals ani;
    cats c01;
    dogs d01;
    dospeak(ani);
    dospeak(c01);
    dospeak(d01);
}

多态的原理剖析54

使用virtual关键字修饰父类函数后,父类中存储一个虚函数指针(vfptr) 指向虚函数表(vftable),当子类重写出现时,子类的虚函数指针指向子类的重写后的虚函数。sizeof验证都为8

c 复制代码
cout << "size of anis " << sizeof(animals) << endl;
cout << "size of ani " << sizeof(ani) << endl;
cout << "size of cat " << sizeof(cats) << endl;
cout << "size of c01 " << sizeof(c01) << endl;
cout << "size of dog " << sizeof(dogs) << endl;
cout << "size of d01 " << sizeof(d01) << endl;

多态案例之计算器55

注意,多态的应用方式有两种:父类的引用/指针指向子类对象

利用多态的特性,我们可以在扩展程序功能时,尽可能不修改源码,也就是"开放扩展,关闭修改"。

多态的代码组织下,结构清晰,代码可读性高。多态的应用方法为父类的指针/引用指向子类对象。

ini 复制代码
class calculator {
public:
    int num_1;
    int num_2;
​
    int getRes(string oper) {
        if (oper == "+") {
            return num_1 + num_2;
        }
        else if (oper == "-") {
            return num_1 - num_2;
        }
    }
};
​
class abstractCalc {
public:
    int num_1;
    int num_2;
​
    virtual int getRes() {
        return 0;
    }
};
​
class addcalc : public abstractCalc {
public:
    int getRes() {
        return num_1 + num_2;
    }
};
​
class subcalc : public abstractCalc {
public:
    int getRes() {
        return num_1 - num_2;
    }
};
​
void test() {
    calculator c;
    c.num_1 = 10;
    c.num_2 = 10;
    cout << c.getRes("+") << endl;
    cout << c.getRes("-") << endl;
​
​
    //Method of application of polymorphic
    //父类指针/引用指向子类对象
​
    abstractCalc *ac = new addcalc;
    ac->num_1 = 10;
    ac->num_2 = 10;
    cout << ac->getRes() << endl;
​
    delete ac;
​
    abstractCalc* ac2 = new subcalc;
    ac2->num_1 = 10;
    ac2->num_2 = 10;
    cout << ac2->getRes() << endl;
    delete ac2;
​
}

纯虚函数和抽象类56

之前coding的时候我就有这样的疑问,实例化父类和子类的关系,在这里有了答案。我们可以将父类成员函数变为纯虚函数,将父类变为抽象类,这样父类无法实例化。子类必须重写父类的纯虚函数,否则也会变成纯虚函数。

csharp 复制代码
class animals{
    public:
    int age;
    virtual void name() = 0;
};
​
class dogs {
    public:
    void name(){
        age = 10;
    }
}

制作饮品57

csharp 复制代码
class make{
  public:
    virtual procedure01() = 0;
    virtual procedure02() = 0;
    virtual procedure03() = 0;
    virtual procedure04() = 0;
};
​
class teamake: public make{
  public:
    procedure01(){
        cout<<"tea01"<<endl;
    }
    procedure02(){
        cout<<"tea02"<<endl;
    }
    procedure03(){
        cout<<"tea03"<<endl;
    }
    procedure04(){
        cout<<"tea04"<<endl;
    }
};
​
void test(){
    teamake tm;
    tm.procedure01();
}

虚析构函数和纯虚析构函数58

和虚函数,纯虚函数类似,虚析构可以让父类指针释放子类对象(根据子类的析构函数),纯虚析构需要在类外初始化,并且一旦定义纯虚析构,该类即为抽象类,无法实例化对象。

arduino 复制代码
class Animals{
    public:
    int age;
    virtual ~Animals() = 0;
};
​
Animals::~Animals(){
    cout<<"pure virtual func call"<<endl;
}
​
class dogs{
  public:
    string* dname;
    ~dogs(){
        if(dname != NULL){
            delete dname;
            dname = NULL;
        }
    }
}

多态案例组装电脑59/60

ini 复制代码
//polymorphic practice
​
class CPU {
public:
    virtual void cpuwork() = 0;
};
​
class GPU {
public:
    virtual void gpuwork() = 0;
};
​
class computers {
public:
​
    
​
    void dowork() {
        m_cpu->cpuwork();
        m_gpu->gpuwork();
    }
​
    computers(CPU* cpu, GPU* gpu) : m_cpu(cpu),m_gpu(gpu) {}
​
    ~computers() {
        if (m_cpu != NULL) {
            delete m_cpu;
            m_cpu = NULL;
        }
        if (m_gpu != NULL) {
            delete m_gpu;
            m_gpu = NULL;
        }
    }
​
private:
    CPU* m_cpu;
    GPU* m_gpu;
};
​
class intelCPU : public CPU {
public:
    void cpuwork() {
        cout << "InterCPU is working" << endl;
    }
};
​
class AMDCPU : public CPU {
public:
    void cpuwork() {
        cout << "AMDCPU is working" << endl;
    }
};
​
class AMDGPU : public GPU {
public:
    void gpuwork() {
        cout << "AMDGPU is working" << endl;
    }
};
​
class NvidaGPU : public GPU {
public:
    void gpuwork() {
        cout << "NvidaGPU is working" << endl;
    }
};
​
void polytest() {
    intelCPU* intelc01 = new intelCPU;
    AMDCPU* AMDc01 = new AMDCPU;
    AMDGPU* AMDg01 = new AMDGPU;
    NvidaGPU* Ng01 = new NvidaGPU;
​
    CPU* intelc = new intelCPU;
    CPU* AMDc = new AMDCPU;
    GPU* AMDg = new AMDGPU;
    GPU* Ng = new NvidaGPU;
​
​
    computers * com01= new computers(AMDc01, AMDg01);
    computers* com02 = new computers(intelc, AMDg);
    com01->dowork();
    com02->dowork();
    delete com01;
    delete com02;
​
    computers* com03 = new computers(new intelCPU, new NvidaGPU);
    com03->dowork();
    delete com03;
}
ChatGPT评价道:

polytest() 中,com01com02 使用了不同的方式来创建 computers 对象,其区别主要体现在多态性内存管理两个方面。

1. 父类指针指向子类对象(com02)
ini 复制代码
CPU* intelc = new intelCPU;
GPU* AMDg = new AMDGPU;
computers* com02 = new computers(intelc, AMDg);

特点:

  • 使用 父类指针指向子类对象 ,体现了多态性
  • computers 中的 m_cpum_gpu 被声明为 CPU*GPU*,因此传入父类指针符合多态的设计。
  • 在实际运行时,通过虚函数表(vtable)实现动态绑定,会执行 intelCPU::cpuwork()AMDGPU::gpuwork()
  • 推荐这种方式,符合面向对象的多态原则,使代码更加灵活。

内存管理注意:

  • delete com02; 时,会执行 computers 的析构函数,调用 delete m_cpudelete m_gpu
  • 父类指针删除子类对象时,必须确保基类有虚析构函数,否则可能会造成内存泄漏或未完全析构子类对象。

2. 子类指针指向子类对象(com01)
ini 复制代码
AMDCPU* AMDc01 = new AMDCPU;
AMDGPU* AMDg01 = new AMDGPU;
computers* com01 = new computers(AMDc01, AMDg01);

特点:

  • 使用 子类指针指向子类对象 ,在对象传入 computers 的构造函数时,会发生隐式向上转型
  • 这种方式虽然也能实现多态行为,但丧失了父类指针的灵活性。
  • 在程序中不太推荐直接使用子类指针来管理对象,因为它不具备多态的优势。

内存管理注意:

  • computers 析构函数中依然会调用 delete 删除对象。虽然子类对象会正确析构,但这种管理方式容易导致代码的可维护性下降。
  • 如果后期需要更换不同的 CPUGPU,这种写法的灵活性较差。

总结
区别 父类指针指向子类对象 (com02) 子类指针指向子类对象 (com01)
多态性 支持多态,运行时动态绑定 不支持多态,失去灵活性
内存管理 安全性较高,方便统一管理 容易出错,不便于维护
扩展性 便于扩展其他CPU/GPU类 扩展性差,不利于维护
推荐程度 ✅ 推荐使用 ❌ 不推荐

因此,在实际开发中,父类指针指向子类对象是更优的选择。

C++写/读文本文件61/62

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化。C++中对文件读写需要包含头文件

c 复制代码
#include<fstream>
#include<iostream>
#include<string>
using namespace std;
​
void writet() {
    ofstream ofs;
    ofs.open("test.txt", ios::out);
​
    ofs << "名称:张三" << endl;
    ofs << "年龄:18" << endl;
    ofs<< "性别:男"<<endl;
    ofs.close();
    return;
}
​
void readt() {
    ifstream ifs1;
    ifstream ifs2;
    ifstream ifs3;
    ifstream ifs4;
​
    ifs1.open("test.txt", ios::in);
    ifs2.open("test.txt", ios::in);
    ifs3.open("test.txt", ios::in);
    ifs4.open("test.txt", ios::in);
​
    /*if (!ifs.is_open()) {
        cout << "The file open filled" << endl;
    }*/
​
    //method 1
    char buf[1024];
    while (ifs1 >> buf) {
        cout << buf << endl;
    }
​
    cout << endl;
​
    //method 2
    char buf2[1024];
    while (ifs2.getline(buf2, sizeof(buf2))) {
        cout << buf2 << endl;
    }
​
    cout << endl;
​
    //method 3
    string buf3;
    while (getline(ifs3, buf3)) {
        cout << buf3 << endl;
    }
​
    cout << endl;
​
    //method 4
    char buf4;
    while ((buf4 = ifs4.get())!=EOF ) {
        cout << buf4;
    }
​
    ifs1.close();
    ifs2.close();
    ifs3.close();
    ifs4.close();
}
​
int main() {
    writet();
    readt();
​
    return 0;
}

读取方法1遇到空格会自动换行。

C++写/读二进制文件63/64

c 复制代码
#include<iostream>
#include<fstream>
using namespace std;
​
class Person {
public:
    int m_age;
    char m_name[64];
};
​
void test() {
​
    ofstream ofs("Person.txt", ios::out | ios::binary);
    Person p1 = { 89, "张三" };
    ofs.write((const char*)&p1, sizeof(Person));
    ofs.close();
​
    ifstream ifs("Person.txt", ios::in | ios::binary);
    Person p;
    ifs.read((char*)&p, sizeof(Person));
    cout << p.m_age << endl<<p.m_name;
    ifs.close();
}
​
int main() {
    test();
}

注意事项:

1,sizeof(Person) / sizeof(p) / sizeof(p1) 没有区别,这里在类中定义了char的大小为64字节。

2,在写文件时,我们不希望p数据被修改。write 用于写入数据到文件 ,它不会修改传入的数据,所以参数类型是 const char*(表示"只读")。在读文件时,read 用于从文件读取数据 ,它会修改传入的缓冲区(写入数据到 s),所以参数类型是 char*(表示"可修改")。

相关推荐
Merokes6 小时前
关于Gstreamer+MPP硬件加速推流问题:视频输入video0被占用
c++·音视频·rk3588
请来次降维打击!!!7 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
别NULL7 小时前
机试题——统计最少媒体包发送源个数
c++·算法·媒体
嘤国大力士7 小时前
C++11&QT复习 (七)
java·c++·qt
背影疾风7 小时前
C++学习之路:指针基础
c++·学习
x-cmd7 小时前
[250331] Paozhu 发布 1.9.0:C++ Web 框架,比肩脚本语言 | DeaDBeeF 播放器发布 1.10.0
android·linux·开发语言·c++·web·音乐播放器·脚本语言
myloveasuka8 小时前
[Linux]从硬件到软件理解操作系统
linux·开发语言·c++
UpUpUp……8 小时前
特殊类的设计/单例模式
开发语言·c++·笔记·单例模式
苏克贝塔8 小时前
CMake学习--Window下VSCode 中 CMake C++ 代码调试操作方法
c++·vscode·学习
嘤国大力士9 小时前
C++11&QT复习 (十一)
开发语言·c++·qt