《实用C++》第35课 类的构造函数与析构函数

本文深入解析C++中的构造函数重载、参数初始化表及析构函数的使用,通过实例演示如何有效管理和释放对象资源,适用于C++初学者及进阶开发者。

本文转载自:VC驿站

https://www.cctry.com/thread-289942-1-1.html

1、类的构造函数的重载:
上节课给大家讲解的函数的重载与默认参数,拿的都是普通的函数来讲解的,实际上在类的构造函数和成员函数上面一样适用。在《实用C++》第33课 类的构造函数的课程中,第5个知识点,带参数的构造函数,其中就给大家讲解了多个带参数的构造函数,实际上就是实现了构造函数的重载。我们可以去看下:https://www.cctry.com/thread-289921-1-1.html

2、构造函数中的参数初始化表:
用这种方式实现的构造函数体积小,代码显得更简洁,显得比较NB哈!那么怎么用呢,咱们举个例子:

class CStudent
{
public:
    char name[50];
    char sex;
    int num;
    int age;

    CStudent(char* pname, char t_sex, int t_num, int t_age);
};

CStudent::CStudent(char* pname, char t_sex, int t_num, int t_age) :sex(t_sex), num(t_num), age(t_age)
{
    strcpy(name, pname);
}

具体细节及注意事项讲解详见视频教程!

3、析构函数:
析构函数也是一个在类中跟构造函数类似的特殊功能的成员函数。只不过它的作用是与构造函数相反,是在对象的生命周期结束的时候会被自动调用的。在C++中析构函数的名字跟类名相同,并且前面带上一个取反的符号~,表达的意思也就是跟构造函数的过程相反。

默认情况下,如果类的设计者没有自己定义析构函数,那么编译器会自动为该类生成一个默认的析构函数,只不过函数体是空的,也就是什么都没做。所以,如果需要在对象被删除的时候做一些操作的话,那么就得自己定义析构函数喽。

以下几种情况会自动调用析构函数:
①、如果在一个函数中定义了一个局部变量的对象,那么当这个函数执行结束时也就是该变量对象生命周期结束的时候,所以析构函数会被自动调用;
②、全局变量或者static类型的变量,他们的生命周期一般是在程序退出的时候,这时候该对象的析构函数才会被调用;
③、如果是用new操作符动态的创建了一个对象,只有当用delete进行释放该对象的时候,析构函数才会被调用;

4、析构函数的作用:
先拿构造函数来说话,构造函数是新建对象吗?回答:不是,而是在对象被创建出来之后自动被调用的,用来初始化相关信息的函数。同理,析构函数也不是用来删除对象的,而是当对象被删除的时候自动会被调用的,用来做一些对象被删除之前的清理工作。只要对象的生命周期结束,那么程序就会自动执行析构函数来完成这个工作的。

5、析构函数的特点:
析构函数不返回任何值,没有函数类型,也没有任何函数的参数。由于上面这些特点,所以析构函数不能被重载,所以说一个类可以有多个构造函数,但只能有一个析构函数。

6、析构函数使用举例:
为 CStudent 类实现析构函数,如下:

CStudent::~CStudent()
{
    cout << "~CStudent() called." << endl;
}

之后我们来看看,析构函数是否被调用呢?

#include "Student.h"
void test()
{
    CStudent stud;
}

int main(int argc, char* argv[])
{
    CStudent stud("zhangsan", 'f', 1001, 20);
    test();
    return 0;
}

《实用C++》第35课 类的构造函数与析构函数 

看到执行的结果截图,大家先想想为什么?之后我们再来讲解!

7、实际编程中经常用到的构造和析构过程:
CStudent 类中的学生姓名字段,之前咱们都是事先定义好一个长度,默认是50个字节。但是实际的生活中,学生的姓名可能长度不固定,所以这里面我们就随着用户输入的长度来new出来。这样才更灵活,代码如下:
//Student.h

class CStudent
{
public:
    char *p_name;
    char sex;
    int num;
    int age;

    CStudent(char* pname, char t_sex, int t_num, int t_age);
    ~CStudent();
};

//Student.cpp
#include "Student.h"
CStudent::CStudent(char* pname, char t_sex, int t_num, int t_age) :sex(t_sex), num(t_num), age(t_age)
{
    p_name = NULL;
    int n_len = 0;
    if (pname)
    {
        n_len = strlen(pname);
    }
    
    if (n_len > 0)
    {
        p_name = new char[n_len + 1];
        memset(p_name, 0, n_len + 1);
        strcpy(p_name, pname);
    }
}

CStudent::~CStudent()
{
    if (p_name)
    {
        delete[] p_name;
        p_name = NULL;
    }
}


相关讲解见视频教程!

8、小作业:
有这么个函数:

void test()
{
    CStudent zhang_san("zhangsan", 'f', 1001, 21);
    CStudent li_si("lisi", 'm', 1002, 22);
    CStudent wang_wu("wangwu", 'm', 1003, 23);
}

在 test 函数中我依次定义了三个局部变量的对象:zhang_san、li_si、wang_wu,那么当离开 test 函数之后,这三个对象的析构顺序又是怎么样的呢?大家先想想,之后在VS编译器中试试,验证下你的猜想到底对不对,为什么会这样呢?

第35课视频教程下载地址:

https://www.cctry.com/thread-289942-1-1.html

<think>好的,我现在需要帮用户解决C++Person、StudentTeacher的定义实现问题,包括构造函数析构函数打印数据的函数。用户还提到要参考之前的引用内容,所以我要先回顾之前的引用之前的回答。 首先,用户的问题分成两部分:复数的运算符重载Person的继承体系。现在用户关注的是第二部分,即如何正确实现Person基以及派生的StudentTeacher。根据用户提供的引用,有几个关键点需要注意: 1. **基构造函数的调用**:引用[1]提到,当基没有默认构造函数时,派生必须在初始化列表中调用基构造函数。因此,在定义StudentTeacher的构造函数时,必须显式调用Person的构造函数。 2. **避免菱形继承二义性**:引用[2]中的例子展示了多重继承可能导致的问题,比如数据冗余二义性。不过在这个问题中,StudentTeacher都是单继承自Person,暂时不需要虚拟继承,但需要注意成员访问的明确性。 3. **封装性方法实现**:引用[3]中的Person包含name、age、sex属性显示方法,用户的问题中需要nameage,因此可能需要调整属性,并实现对应的构造函数析构函数打印函数。 接下来,我需要确认用户的需求是否在之前的回答中已经部分满足。查看之前的回答,用户已经给出了一个Person、Student、Teacher的继承结,其中Person有name,Student有studentId,Teacher有subject。但用户现在的问题中,基需要包含nameage,而之前的回答中的Person只有name。这可能是一个需要调整的地方,因为用户当前的问题明确要求基Person包含nameage。 因此,我需要修改之前的Person,添加age属性,并确保StudentTeacher的构造函数正确初始化基的成员。此外,用户可能希望打印函数显示所有数据成员,包括基的name、age派生独有的属性。 另一个需要注意的点是析构函数是否需要virtual声明。根据之前的引用[5],虚析构函数确保正确调用派生析构函数,因此在基Person中,析构函数应该声明为virtual。 现在,我需要的结: - **Person**: - 数据成员:name(string)、age(int) - 构造函数:Person(string n, int a),初始化nameage - 虚析构函数:virtual ~Person() - 虚打印函数:virtual void display(),输出nameage - **Student**(继承自Person): - 新增数据成员:studentId(int) - 构造函数:Student(string n, int a, int id) : Person(n, a), studentId(id) - 析构函数:~Student()(可能输出提示信息) - 重写display()函数,输出name、agestudentId - **Teacher**(继承自Person): - 新增数据成员:title(string) - 构造函数:Teacher(string n, int a, string t) : Person(n, a), title(t) - 析构函数:~Teacher()(可能输出提示信息) - 重写display()函数,输出name、agetitle 需要确保所有构造函数正确调用基构造函数,特别是在基没有默认构造函数的情况下,必须使用初始化列表。例如,Student的构造函数必须通过Person(n, a)来初始化基成员。 在析构函数部分,虽然在这个简单例子中可能不需要释放动态内存,但为了演示多态,基析构函数应声明为virtual,这样当通过基指针删除派生对象时,会正确调用派生析构函数。 打印函数display()需要被声明为虚函数,以便通过基指针调用时能正确执行派生的方法。例如,当有Person *p = new Student(...); p->display()会调用Student的display()。 接下来,测试代码部分需要创建Person、Student、Teacher的对象,并通过基指针数组来演示多态性,确保display()正确调用,并且顺序正确。 此外,用户提到用C++或Python实现,但之前的示例是C++,用户当前问题中的代码示例也是C++,所以继续使用C++来回答。 最后,确保代码符合用户的要求,包括构造函数析构函数、打印函数,并处理可能的二义性问题。例如,如果派生中没有正确调用基构造函数,会导致编译错误,这在引用[1]中提到过。 总结步骤: 1. 定义Person,包含nameage,以及构造函数、虚析构函数、虚display()。 2. 定义Student继承Person,添加studentId,构造函数调用基,重写display()。 3. 定义Teacher继承Person,添加title,构造函数调用基,重写display()。 4. 编写测试代码,创建对象并测试多态性析构函数。 现在需要检查是否覆盖了所有用户的需求,包括数据成员、构造函数析构函数、打印函数,以及正确的继承多态实现。同时,参考之前的引用确保没有遗漏关键点,比如虚析构函数构造函数初始化列表的使用。</think>以下是基于C++实现的包含**Person基**及**Student/Teacher派生**的完整代码,严格遵循面向对象设计原则并实现多态特性: --- ### **1. 基Person定义** ```cpp #include <iostream> #include <string> using namespace std; class Person { protected: string name; int age; public: // 带参构造函数(基没有默认构造函数) Person(const string& n, int a) : name(n), age(a) { cout << "Person构造函数: " << name << endl; } // 虚析构函数确保多态销毁 virtual ~Person() { cout << "Person析构函数: " << name << endl; } // 虚函数支持多态打印 virtual void display() const { cout << "姓名: " << name << " | 年龄: " << age; } }; ``` --- ### **2. Student派生定义** ```cpp class Student : public Person { private: int studentID; // 学号 public: // 必须通过初始化列表调用基构造函数[^1] Student(const string& n, int a, int id) : Person(n, a), studentID(id) { cout << "Student构造函数: " << name << endl; } ~Student() { cout << "Student析构函数: " << name << endl; } // 重写基虚函数 void display() const override { Person::display(); // 调用基方法 cout << " | 学号: " << studentID << endl; } }; ``` --- ### **3. Teacher派生定义** ```cpp class Teacher : public Person { private: string title; // 职称 public: Teacher(const string& n, int a, const string& t) : Person(n, a), title(t) { cout << "Teacher构造函数: " << name << endl; } ~Teacher() { cout << "Teacher析构函数: " << name << endl; } void display() const override { Person::display(); cout << " | 职称: " << title << endl; } }; ``` --- ### **4. 测试代码输出** ```cpp int main() { // 通过基指针管理派生对象 Person* people[3]; people[0] = new Person("张三", 35); people[1] = new Student("李四", 20, 1001); people[2] = new Teacher("王五", 45, "教授"); // 多态调用display() for (int i = 0; i < 3; ++i) { people[i]->display(); delete people[i]; // 多态销毁 } return 0; } ``` **输出结果**: ``` Person构造函数: 张三 Person构造函数: 李四 Student构造函数: 李四 Person构造函数: 王五 Teacher构造函数: 王五 姓名: 张三 | 年龄: 35 Person析构函数: 张三 姓名: 李四 | 年龄: 20 | 学号: 1001 Student析构函数: 李四 Person析构函数: 李四 姓名: 王五 | 年龄: 45 | 职称: 教授 Teacher析构函数: 王五 Person析构函数: 王五 ``` --- ### **关键实现解** 1. **构造函数链**: - 派生必须通过初始化列表调用基构造函数(因`Person`没有默认构造函数)[^1] - 构造顺序:基→派生顺序:派生→基 2. **多态支持**: - `virtual`关键字确保`display()`方法动态绑定 - 虚析构函数避免内存泄漏[^5] 3. **代码复用**: - `Person::display()`复用基代码,避免重复输出姓名年龄 --- ### **相关问题** 1. 如果`Person`增加性别属性,如何修改派生构造函数? 2. 如何通过`dynamic_cast`实现派生特有方法的访问? 3. 多重继承场景下如何处理同名成员冲突?[^2] 4. 如何扩展实现`operator<<`运算符重载用于对象输出? 5. 纯虚函数如何实现接口抽象化? [^1]: 派生构造函数必须显式调用基构造函数,否则编译错误。 [^2]: 使用虚继承可解决菱形继承问题,但需谨慎设计继承体系。 [^5]: 虚析构函数通过虚函数表(vtable)实现多态销毁。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值