零碎的c++二

虚函数

虚函数是C++中实现多态的一种机制,它允许通过基类指针或引用来调用派生类的成员函数。虚函数的作用是实现动态绑定,即在运行时根据对象的实际类型来确定调用哪个函数。虚函数的声明方式是在函数前加上关键字virtual,如:

class Base {
public:
    virtual void func(); // 声明一个虚函数
};

虚函数可以有以下几种场景用到:

  • 当我们需要定义一个通用的基类,为不同的派生类提供一个统一的接口时,可以使用虚函数。例如,我们可以定义一个Animal类,为所有动物提供一个虚函数makeSound(),然后让不同的派生类如Dog、Cat等重写这个虚函数,实现各自的叫声。这样,我们就可以通过一个Animal指针或引用来调用任何动物的makeSound()函数,而不需要知道它们的具体类型。
  • 当我们需要利用多态特性来实现一些设计模式时,可以使用虚函数。例如,我们可以使用虚函数来实现模板方法模式,这是一种行为型设计模式,它定义了一个算法的骨架,并将一些步骤延迟到子类中实现。这样,我们可以通过改变子类来改变算法的某些部分,而不影响算法的结构。例如,我们可以定义一个Sorter类,为所有排序算法提供一个虚函数sort(),然后让不同的派生类如BubbleSorter、QuickSorter等重写这个虚函数,实现各自的排序方法。这样,我们就可以通过一个Sorter指针或引用来调用任何排序算法的sort()函数,而不需要知道它们的具体类型。
  • 当我们需要在析构函数中释放一些资源时,可以使用虚函数。如果我们有一个基类指针或引用指向一个派生类对象,并且想要通过它来删除这个对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源泄漏或内存错误。为了避免这种情况,我们应该将基类的析构函数声明为虚函数,这样就会先调用派生类的析构函数,然后再调用基类的析构函数,从而正确地释放资源。

虚函数和纯虚函数的区别

  • 虚函数是在基类中声明并定义的函数,它在函数前加上关键字virtual,如:
class Base {
public:
    virtual void func(); // 声明一个虚函数
};

虚函数可以在派生类中被override,也就是用不同的实现来替换基类的实现。如果派生类没有override虚函数,那么就会继承基类的实现。虚函数的作用是实现动态绑定,即在运行时根据对象的实际类型来确定调用哪个函数。例如,我们可以定义一个Shape类,为所有图形提供一个虚函数area(),然后让不同的派生类如Circle、Rectangle等override这个虚函数,实现各自的面积计算方法。这样,我们就可以通过一个Shape指针或引用来调用任何图形的area()函数,而不需要知道它们的具体类型。

  • 纯虚函数是在基类中声明但不定义的函数,它在函数后加上=0,如:
class Base {
public:
    virtual void func() = 0; // 声明一个纯虚函数
};

纯虚函数没有默认的实现,它要求任何派生类都必须提供自己的实现方法。如果派生类没有提供纯虚函数的实现,那么这个派生类也不能被实例化。纯虚函数的作用是定义一个接口,规范派生类的行为。例如,我们可以定义一个Animal类,为所有动物提供一个纯虚函数makeSound(),然后要求所有派生类如Dog、Cat等必须实现这个纯虚函数,提供各自的叫声方法。这样,我们就可以通过一个Animal指针或引用来调用任何动物的makeSound()函数,而不需要知道它们的具体类型。

  • 虚函数和纯虚函数的区别主要有以下几点:

    • 虚函数可以有默认的实现,纯虚函数没有默认的实现。
    • 虚函数可以被派生类继承或override,纯虚函数必须被派生类override。
    • 虚函数可以通过基类指针或引用调用基类或派生类的实现,纯虚函数只能通过基类指针或引用调用派生类的实现。
    • 包含虚函数的类可以被实例化,包含纯虚函数的类不能被实例化,称为抽象类。
  • 虚函数和纯虚函数的使用场景主要有以下几种:

    • 当我们需要定义一个通用的基类,为不同的派生类提供一个统一的接口时,可以使用虚函数。例如,我们可以定义一个Shape类,为所有图形提供一个虚函数area()。
    • 当我们需要利用多态特性来实现一些设计模式时,可以使用虚函数。例如,我们可以使用虚函数来实现模板方法模式,这是一种行为型设计模式,它定义了一个算法的骨架,并将一些步骤延迟到子类中实现。
    • 当我们需要在析构函数中释放一些资源时,应该使用虚析构函数。如果我们有一个基类指针或引用指向一个派生类对象,并且想要通过它来删除这个对象时,如果基类的析构函数不是虚析构函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源泄漏或内存错误。为了避免这种情况,我们应该将基类的析构函数声明为虚析构函数,这样就会先调用派生类的析构函数,然后再调用基类的析构函数,从而正确地释放资源。
    • 当我们需要定义一个抽象的基类,规范派生类的行为,但不提供默认的实现时,可以使用纯虚函数。例如,我们可以定义一个Animal类,为所有动物提供一个纯虚函数makeSound()。

深拷贝和浅拷贝

C++中的深拷贝和浅拷贝是指在对象复制时,对于类中的资源(如动态内存、文件句柄等)的处理方式。具体来说:

  • 浅拷贝是指只复制对象的基本类型成员变量和指针类型成员变量的值,而不复制指针所指向的资源。这样,原对象和新对象会共享同一块资源,如果其中一个对象修改或释放了资源,会影响另一个对象的状态。浅拷贝是编译器默认提供的拷贝行为,一般适用于类中没有资源或不需要管理资源的情况。
  • 深拷贝是指除了复制对象的基本类型成员变量和指针类型成员变量的值外,还会为指针所指向的资源重新分配内存,并复制资源内容。这样,原对象和新对象会拥有各自独立的资源,互不影响。深拷贝需要程序员显式地定义拷贝构造函数和赋值运算符重载函数,以实现自定义的拷贝行为。深拷贝一般适用于类中有资源并需要管理资源的情况。

下面是一个简单的例子来说明深拷贝和浅拷贝的区别:

#include 
using namespace std;

class Person {
public:
    // 有参构造函数
    Person(int age, int height) {
        m_age = age;
        m_height = new int(height); // 动态分配内存
    }
    // 拷贝构造函数
    Person(const Person& p) {
        m_age = p.m_age;
        // m_height = p.m_height; // 浅拷贝
        m_height = new int(*p.m_height); // 深拷贝
    }
    // 析构函数
    ~Person() {
        if (m_height != NULL) {
            delete m_height; // 释放内存
            m_height = NULL;
        }
    }
    // 打印信息
    void show() {
        cout << "age: " << m_age << ", height: " << *m_height << endl;
    }
private:
    int m_age; // 年龄
    int* m_height; // 身高
};

int main() {
    Person p1(18, 180); // 创建一个Person对象
    p1.show(); // 打印信息
    Person p2(p1); // 用p1初始化p2,调用拷贝构造函数
    p2.show(); // 打印信息
    *p2.m_height = 190; // 修改p2的身高
    p2.show(); // 打印信息
    p1.show(); // 打印信息
    return 0;
}

如果使用浅拷贝,那么输出结果为:

age: 18, height: 180
age: 18, height: 180
age: 18, height: 190
age: 18, height: 190

可以看到,修改p2的身高也影响了p1的身高,这是因为p1和p2共享了同一块内存。另外,在析构函数中释放内存时,也会出现重复释放或野指针的问题。

如果使用深拷贝,那么输出结果为:

age: 18, height: 180
age: 18, height: 180
age: 18, height: 190
age: 18, height: 180

可以看到,修改p2的身高不影响p1的身高,这是因为p1和p2各自拥有了独立的内存。另外,在析构函数中释放内存时,也不会出现问题。

你可能感兴趣的:(c++)