C++的构造函数和析构函数是哪个
创始人
2024-12-21 04:40:56
0次

    一、构造函数

    首先,由于类只是一个模板,因此我们在定义类时无法对成员变量初始化,比如下面代码就是错误的:

    class circle{
    public:
        int m_L = 20;	// Error:不允许使用数据成员初始值设定项
    };

    因此,初始化只能发生在类创建对象的过程中,但是由于访问权限的原因,无法在类外访问某些成员变量,因此下面这种做法有时候是无效的:

    circle C1;	// 实例化一个对象 C1
    C1.m_L = 20;	// 通过创建的对象,来给对应变量初始化,但是如果m_L是private访问权限,则失效

    为了解决这个问题,让程序员能像使用标准数据类型一样适用对象,在类内提供了一个特殊的成员函数——“构造函数”,专门用于在创建对象时初始化类对象。之所以说它特殊,是因为C++已经自动为构造函数提供了名称和使用语法,程序员只需要提供方法的定义即可,即:类名(形参列表)。具体来说,构造函数的定义如下:

    class circle{
    public:
        int m_L;
    public: //成员函数(方法)
        circle(const int a)	//通过构造函数对成员变量进行赋值
        {
            m_L = a;
        }
    };
    
    circle C1(20);	//调用格式
    cout << "C1.m_L:" << C1.m_L <

    看上去似乎很简单,但是由于构造函数也是函数,因此所有C++中的形参传递方式,函数特性以及函数调用方法都能用于构造函数。 如前文所讲,C++会自动给类添加一个空的构造函数,但是如果自己在类中实现了有参构造函数,编译器便不再提供无参构造函数。举例如下:

    class circle{
    public:
        int m_L;
    public: //成员函数(方法)
        circle(const int a)	//通过构造函数对成员变量进行赋值
        {
            m_L = a;
        }
    };
    
    circle C1(20);	//调用格式正确,能够通过构造函数赋值
    circle C2; 		//错误,自己定义了有参构造函数,不再提供无参构造函数

    构造函数可以重载,接着上面的例子,如果重载一个空的构造函数,那么两个调用格式都正确:

    class circle{
    public:
        int m_L;
    public: //成员函数(方法)
        circle(){}	// 空构造函数
        circle(const int a)	//通过构造函数对成员变量进行赋值
        {
            m_L = a;
        }
    };
    
    circle C1(20);	//调用格式正确,能够通过构造函数赋值
    circle C2; 		//正确,可以通过重载的空构造函数实现初始化

    构造函数的参数不仅可以是标准数据类型,也可以是类。众所周知,在数值作为函数参数进行传递的时候,会重新拷贝出来一份数据作为参数传递用完即销毁,这种方式不仅浪费了内存空间,而且无法修改原始数据。为了结合这两者之间的优点,于是经常采取引用作为函数的参数。虽然引用是指针的一种特殊情况,但是指针太过于灵活,并且引用在形式上引用与普通的变量地用法并没有什么区别,因此使用起来更加方便。

    class circle{
    public:
        int m_L;
    public: //成员函数(方法)
        circle(){}	// 空构造函数
        circle(const int a)	//通过构造函数对成员变量进行赋值
        {
            m_L = a;
        }
        //引用作为函数参数传递,并用const修饰,节省空间的同时避免修改原数据
        circle(const circle& sub_circle)	
        {
            m_L = sub_circle.m_L;
        }
    };
    
    circle C1(20);	//调用格式正确,能够通过构造函数赋值
    circle C2(C1); 	//正确,可以通过拷贝构造函数进行初始化

    二、C++类的内存模型

    C++中,一个类包括:

    • 成员变量:静态成员变量和普通成员变量

    • 成员函数:静态成员函数和普通成员函数

    虽然为了集成,我们将其写到一个类里面,但是只有普通成员变量真正属于类的对象,类的所有对象共享一份静态成员函数,静态成员变量和普通成员函数。画出了内存模型,如下图所示:

    C++的构造函数和析构函数是哪个

    为了进一步理解,我们举例如下:

    2.1、只定义成员函数

    class person{
    public:
        // 定义一个空的构造函数
    	person(int m_age, int m_ID){
    	}
    };
    person p1(10, 20);
    cout << "p1 所占的空间为:" << sizeof(p1) << endl;

    输出结果为:

    p1 所占的空间为:1

    这个题目在《剑指offer》一书中也提到过,由空类实例化出来的对象所占的内存空间是1个而不是0个字节,因为编译其给对象 p1 分配了一个地址,来表示不同的对象存储在不同的地址空间,因此占用1个字节。

    2.2、往空类中添加静态成员变量

    class person{
        static int age; //静态成员变量,存在全局区,不属于类对象的一部分
        static int ID;  //静态成员变量,存在全局区,不属于类对象的一部分
    public:
        // 定义一个空的构造函数
    	person(int m_age, int m_ID){
    	}
    };
    person p1(10, 20);
    cout << "p1 所占的空间为:" << sizeof(p1) << endl;

    输出结果为:

    p1 所占的空间为:1

    当向类中加入了成员函数与静态成员变量时,类的实例化对象仍然只占用1个字节的空间,足以证明这些函数和变量并不是类对象的一部分。

    2.3、再加入非静态成员变量

    class person
    {
        static int age; //静态成员变量,存在全局区,不属于类对象的一部分
        static int ID;  //静态成员变量,存在全局区,不属于类对象的一部分
        int a;	//非静态成员变量,存在栈区,属于类对象的一部分
    public:
    	person(int m_age, int m_ID){
    	}
    };

    输出结果为:

    p 所占的空间为:4

    因此当向类中加入了非静态成员变量时,类的实例化对象占用4个字节的空间,可以说明,非静态变量属于类对象的一部分。综上:同一个类所有实例化出来的对象共享同一份静态成员变量,所以一改全改。既然同一个类的不同对象共享同一份成员函数,那么成员函数怎么区分该访问哪个对象的普通成员变量呢?

    三、this指针

    接着上一小节的问题,this指针为上述问题提供了一个完美的解决方案,它指向用来调用成员函数的对象(被当作参数隐式地传递给成员函数),我们通过一张图来理解它:

    C++的构造函数和析构函数是哪个

    此外,this指针的另一个用途是当成员函数需要返回对象时,用 return *this; 或者 return this,这种做法能够实现链式编程。比如:

    p2.addPerson(p1).addPerson(p1);

    首先,对象 p2 调用成员函数 addPerson(p1) ,其返回值继续调用 addPerson(p1),此时返回值就必须也是 person 类型才可以,因此使用 this 指针可以完成需求。先来看第一个例子:

    class person{
    public:
    	int age;
    	person(int age)
    	{
    		this->age = age; // this指针区分调用者
    	}
    	// 返回值为person类型,且参数加上了const限制,防止修改原数据
    	person addPerson(const person& p)
    	{
    		this->age += p.age;	// 主要实现两个类对象年龄的相加
    		return *this;	// 由于返回值是person,因此返回 *this
    	}
    };
    
    person p1(20);
    person p2(10);
    person p3 = p2.addPerson(p1).addPerson(p1);
    cout << "p1 age:" << p1.age << endl;
    cout << "p2 age:" << p2.age << endl;
    cout << "p3 age:" << p3.age << endl;

    首先,通过构造函数分别对 p1,p2 赋了初值,然后 p2 调用函数 addPerson(p1) 修改自身的变量 age 。**由于函数通过值传递的方式返回 person 类型,所以将整个 person 类型复制了一份返回,返回值继续调用 addPerson(p1), **最后的结果赋值给了新的对象 p3。所以输出结果为:

    p1 age:20

    p2 age:30

    p3 age:50

    但是如果函数 addPerson() 修改为:

    person& addPerson(const person& p)
    {
        this->age += p.age;	// 主要实现两个类对象年龄的相加
        return *this;	// 虽然返回值是person&,返回值的类型也是 *this
    }
    
    person p1(20);
    person p2(10);
    person p3 = p2.addPerson(p1).addPerson(p1);
    cout << "p1 age:" << p1.age << endl;
    cout << "p2 age:" << p2.age << endl;
    cout << "p3 age:" << p3.age << endl;

    与上例唯一的区别就在于返回值的类型变成了引用,那么每次返回的就变成了该对象本身,而非在值传递中拷贝出来的那一份数据。那么输出就变成了:

    p1 age:20

    p2 age:50

    p3 age:50

    四、析构函数

    用构造函数创建对象后,程序负责跟踪该对象,知道其过期为止。当对象过期时,程序自动调用析构函数完成清理工作。与构造函数一样,C++默认提供了一个空的析构函数,定义为:~类名( )。由于开辟在栈区的变量程序会自动释放,因此不需要析构函数执行清理工作,但是当程序员在堆区开辟空间时,需要手动执行清理工作,这时候需要析构函数来释放堆区内存。比如:

    ~person()
    {
    	// 在析构函数内写入需要执行的代码
    	cout << "调用析构函数" << endl;
    }
    person p1(20);
    person p2(10);	// 在生命周期结束后自动调用析构函数执行清理工作

    输出为:

    调用析构函数

    调用析构函数

    相关内容

    热门资讯

    空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
    空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
    格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
    空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
    格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
    品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
    空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
    空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
    中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
    中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...