0
点赞
收藏
分享

微信扫一扫

【AI应用探讨】—chatGPT小型化应用场景

ZGtheGreat 2024-06-24 阅读 23

一、多态

1、通过指针创建对象(动态分配)

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出:Derived class show
    delete b;
    return 0;
}

在这个程序中,Base 类指针 b 被分配为指向一个 Derived 类对象。因为 show 是一个虚函数,并且 b 实际上指向的是一个 Derived 类对象,所以当调用 b->show() 时,会调用 Derived 类中的 show 方法,输出 "Derived class show"。

2.直接创建对象(静态分配)

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base b;
    b.show(); // 输出:Base class show
    return 0;
}

在这个程序中,Base 类对象 b 是静态分配的。虽然 Base 类有一个虚函数 show,但是 bBase 类的对象,而不是 Derived 类的对象。因此,当调用 b.show() 时,会调用 Base 类中的 show 方法,输出 "Base class show"。 

2.1静态分配具体怎么实现的

在C++中,静态分配意味着对象的内存分配在编译时就已经确定了。这种分配方式通常在栈上进行,当函数执行结束时,这些对象的内存会自动释放。以下是静态分配的详细解释和实现方式:

静态分配的实现

  1. 声明对象:在函数内部或类的成员变量中直接声明对象。这些对象在声明时就分配了内存,并在作用域结束时自动释放。

  2. 作用域管理:静态分配的对象在它们声明的作用域内是有效的。一旦超出作用域,对象的析构函数(如果有)将自动调用,并释放内存。

  3. 示例代码

    以下是一个使用静态分配的示例代码:

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base b;
    b.show(); // 输出:Base class show
    return 0;
}

对象声明

  1. Base b;
  2. 这行代码在栈上分配一个 Base 类对象 b

  3. 构造函数调用

    • 当对象 b 被声明时,Base 类的构造函数被调用,输出 "Base constructor"。
  4. 虚函数调用

    • 当调用 b.show() 时,因为 bBase 类的对象,而不是 Derived 类的对象,所以调用的是 Base 类的 show 方法,输出 "Base class show"。
  5. 析构函数调用

    • main 函数结束时,b 超出其作用域,Base 类的析构函数被调用,输出 "Base destructor"。

静态分配的特点

  • 内存管理简单:静态分配的对象在作用域结束时自动释放,不需要显式删除。
  • 生命周期受作用域控制:对象的生命周期由它们声明的作用域决定。
  • 栈上分配:静态分配通常在栈上进行,这意味着对象的分配和释放速度很快,但受限于栈的大小。

与动态分配的对比

动态分配则在堆上进行,需要显式的内存管理(例如 newdelete)。它允许在运行时决定对象的生存周期,但也增加了内存泄漏的风险。如果没有正确释放动态分配的内存,程序会导致内存泄漏。

在上面的动态分配示例中,需要手动释放通过 new 分配的内存(delete b),否则会导致内存泄漏。

 2.2调用构造函数是静态分配吗?

     调用构造函数本身不是静态分配,但构造函数的调用与对象的创建方式有关。静态分配和动态分配分别决定了构造函数在何时以及如何被调用。让我们更详细地探讨静态分配的过程。

静态分配的详细过程

静态分配指的是在编译时确定内存分配,并在栈上分配内存。当在函数内部声明对象时,会自动调用其构造函数进行初始化。静态分配的对象在作用域结束时会自动调用析构函数释放内存。

  • 静态分配:在栈上分配,编译时确定,作用域结束时自动释放。
  • 动态分配:在堆上分配,运行时确定,需要手动释放。

构造函数的调用与对象的创建方式(静态或动态分配)相关,在对象创建时自动调用,无论是静态分配还是动态分配。

2.3对象的创建时,构造函数就自动执行吗?

是的,在C++中,当对象被创建时,构造函数会自动执行以初始化该对象。无论是静态分配还是动态分配,构造函数都会在对象创建时被调用。

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base* b = new Derived(); // 动态分配,b 指向 Derived 对象
    b->show(); // 输出:Derived class show
    delete b;  // 释放内存,调用 Derived 和 Base 的析构函数
    return 0;
}

运行结果: 

Base constructor
Derived constructor
Derived class show
Derived destructor
Base destructor

解释

总结

  • 静态分配:对象在栈上分配,声明时自动调用构造函数,作用域结束时自动调用析构函数。
  • 动态分配:对象在堆上分配,new 操作符执行时自动调用构造函数,delete 操作符执行时自动调用析构函数。

在对象创建时,构造函数会自动执行,无论是静态分配还是动态分配。这是对象初始化过程的一个重要部分,确保对象在使用前被正确初始化。

2.4构造函数在项目中的具体运用

1. 初始化成员变量

在对象创建时通过构造函数初始化成员变量,以确保对象在使用前处于有效状态。

2. 资源分配和管理

构造函数可以用于分配资源,例如打开文件、分配动态内存、建立数据库连接等。析构函数用于释放这些资源,防止内存泄漏和资源泄漏。

3. 依赖注入

构造函数可以接受参数,用于注入对象依赖的其他对象。这样可以提高代码的灵活性和可测试性。

4. 单例模式

构造函数可以用于实现设计模式,例如单例模式,确保一个类只有一个实例,并提供一个全局访问点。

2.5纯虚函数和虚函数的区别

(1)虚函数的使用

#include <iostream>
using namespace std;

// 抽象基类
class Shape {
public:
    virtual void draw() {};
    virtual double area() { return 0; };
    virtual ~Shape() {} // 虚析构函数
};


class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    void draw()  {
        cout << "Drawing Circle" << endl;
    }
    double area()  {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {
        cout << "构造函数的实现" << endl;
    }
    void draw() {
        cout << "Drawing Rectangle" << endl;
    }
    double area()  {
        return width * height;
    }
};


int main() {
    Shape* shapes[2];
    shapes[0] = new Circle(5.0);
    shapes[1] = new Rectangle(4.0, 6.0);

    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw();
        cout << "Area: " << shapes[i]->area() << endl;
        delete shapes[i]; // 记得删除动态分配的对象
    }

    return 0;
}


运行结果:

(2)纯虚函数的使用

#include <iostream>
using namespace std;

// 抽象基类
class Shape {
public:
    // 纯虚函数,必须在派生类中实现
    virtual void draw() const = 0;
    virtual double area() const = 0;
    virtual ~Shape() { cout << "基类析构函数的实现" << endl; } // 虚析构函数
};


class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) { cout << "Circle构造函数的实现" << endl; }
    void draw() const override {
        cout << "Drawing Circle" << endl;
    }
    double area() const override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {
    
    }
    void draw() const override {
        cout << "Drawing Rectangle" << endl;
    }
    double area() const override {
        return width * height;
    }
};


int main() {
    Shape* shapes[2];
    shapes[0] = new Circle(5.0);
    shapes[1] = new Rectangle(4.0, 6.0);

    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw();
        cout << "Area: " << shapes[i]->area() << endl;
        delete shapes[i]; // 记得删除动态分配的对象
    }

    return 0;
}


运行结果: 

区别

  1. 定义方式

    • 虚函数:virtual void show() { ... }
    • 纯虚函数:virtual void show() = 0;
  2. 实现

    • 虚函数:可以在基类中提供实现,也可以在派生类中重写。
    • 纯虚函数:不能在基类中提供实现,必须在派生类中实现。
  3. 实例化

    • 虚函数:基类可以实例化。
    • 纯虚函数:包含纯虚函数的类是抽象类,不能实例化。
  4. 用途

    • 虚函数:用于实现多态性。
    • 纯虚函数:用于定义接口,强制派生类实现特定的函数。
什么时候使用虚函数?
  • 多态行为:当你希望基类提供一个默认的行为,但允许派生类重写时,使用虚函数。
  • 可选重写:当派生类不一定需要提供自己的实现时,可以在基类中提供一个虚函数的默认实现。
什么时候使用纯虚函数?
  • 抽象接口:当你希望基类只定义接口,而不提供任何实现时,使用纯虚函数。
  • 强制实现:当你希望所有派生类都必须提供自己的实现时,使用纯虚函数。

2.6虚函数映射表

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base show" << endl;
    }
    virtual void display() {
        cout << "Base display" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived show" << endl;
    }
    void display() override {
        cout << "Derived display" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出:Derived show
    b->display(); // 输出:Derived display
    delete b;
    return 0;
}

   当创建 Derived 对象时,编译器会为 Derived 类生成一个虚函数表,并在对象中存储一个指向该表的指针。虚函数表包含 Derived 类中虚函数 showdisplay 的地址。

多态调用
  • b->show()b->display() 是通过基类指针 b 调用的。
  • 程序会通过 b 的 V-Table Pointer 查找 Derived 类的虚函数表,然后从表中找到 showdisplay 函数的地址并调用它们。
注意事项
  • V-Table 的生成:虚函数表是在编译时生成的,每个类只有一张虚函数表。
  • 对象的 V-Table Pointer:每个对象包含一个指向相应虚函数表的指针(隐藏在对象中),该指针在对象构造时被初始化。
  • 内存开销:使用虚函数表会增加一些内存开销(存储虚函数表指针和虚函数表本身),并稍微增加函数调用的时间开销(间接调用)。

2.7为什么要使用虚析构函数

(1)通常情况的析构函数

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() { // 虚析构函数
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
};

int main() {
    Base* b = new Derived();
    delete b; // 正确调用派生类的析构函数
    return 0;
}

可以看到释放资源的时候,delete b只是调用了基类的析构函数,并没有对派生类进行内存释放。

(2)使用虚函数的析构函数

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
   virtual ~Base() { // 虚析构函数
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
};

int main() {
    Base* b = new Derived();
    delete b; // 正确调用派生类的析构函数
    return 0;
}

举报

相关推荐

0 条评论