• 【C++编程语言】之类和对象---对象初始化和清除


    1.构造函数和析构函数-----对象的创建和清除

    ​ 对象的初始化和清除也是非常重要的安全问题

    ​ 一个对象或者变量没有初始化,对其使用后果是未知

    ​ 同样的使用完一个对象或变量,没有及时清除,也会造成一定的安全问题。

    ​ C++利用了构造函数和析构函数解决上述问题,这个函数将会被编译器自动调用,完成对象初始化和清除工作。对象的初始化和清除工作是编译器强制我们做的事情,因此如果我们不提供构造和析构函数,编译器会提供编译器创造的构造函数和析构函数是空实现。

    构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数有编译器自动调用,无需手动调用。

    析构函数:主要作用于对像销毁前系统自动调用,执行一些清理工作。

    构造函数语法:类名(){}

    ​ 1.构造函数,没有返回值也不写void

    ​ 2.函数名称与类名相同

    ​ 3.构造函数可以有参数,因此可以发生重载

    ​ 4.程序在调用对象时候会自动调用构造,无需手动调用,而且只调用一次。

    析构函数语法 :~类名(){}

    ​ 1.析构函数,没有返回值也不写void

    ​ 2.函数名称与类名相同,在名称前面加上符号~

    ​ 3.析构函数不可以有参数,因此不可以发生重载

    ​ 4.程序在对象销毁前会自动调用函数,需手动调用,而且只调用一次。

    //对象的初始化和清理
    //1.构造函数,进行初始化操作
    class Person{
    	public:
        	Person(){
                cout <<"Person的构造函数"<<endl;
            }
        	~Person(){
                 cout <<"Person的析构函数"<<endl;
            }
    }
    void test01(){
         Person p;
    }
    int main(){
         test01();
        //输出:Person的构造函数 Person的析构函数  
        /* test01函数的数据存放在栈区,内存在函数调用完释放,
        	所有对象的析构函数也调用了。
        */
         Person p;//输出:Person的构造函数
        //对象创建在主函数中,不会立即释放
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    2.构造函数的分类及调用

    ​ 两种分类方式:

    ​ 按参数分类:有参构造和无参构造(默认构造)

    ​ 按类型分类:普通类型和拷贝类型

    ​ 三种调用方式:

    ​ 括号法,显示法,隐式转换

    class Perosn{
        public:
        	//无参构造,普通类
        	Person(){
                cout<<"Person的无参构造"<<endl;
            }
        	//有参构造,普通类
        	Person(int a){
                int age = a;
                cout<<"Person的有参构造"<<endl;
            }
        	//拷贝构造函数
        	Person(const Person &p){
                int age = p.a;//将传去的人身上的所有属性,拷贝到我身上 
                cout<<"Person的拷贝构造"<<endl;
            }
    }
    void main(){
     	//1.括号法
        Person p1;//调用默认构造函数
        /*
    		注意:调用默认构造函数时不要加(),如果加了Person p1(); 
    			  编译器会认为是一个函数声明,不会创建对象
    	*/
        Person p2(10);//调用有参构造函数
        Person p3(p1)//调用拷贝构造函数
            
        //2.显示法
         Person p4;//调用默认构造函数
         Person p5 = Person(10);//调用有参构造函数
         Person p6 = Person(p5);//调用拷贝构造函数
         //Person(10);单独拿出,是一个匿名对象。
         			//特点:当前行执行结束后,系统会立即回收掉匿名对象
        Person(p4);//是错误的,会报p4重定义
        /*
    		注意:不要利用拷贝函数初始化匿名对象 
    		编译器会认为Person(p4);等价于 Person p4;所以重定义p4了 
    	*/
        //3.隐式转换法
        Person p7= 10;//相当于 Person p7 = Person(10); 
        Person p8= p7;//拷贝构造
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    3.拷贝构造函数调用时机

    ​ C++中拷贝构造函数调用时机通常有三种情况

    ​ 1.使用一个已经创建完毕的对象来初始化一个新对象。

    ​ 2.值传递的方式给函数参数传值

    ​ 3.以值方式返回局部对象

    class Person{
        public:
        	int m_Age;
        	Person(){
                cout<<"Person的无参构造"<<endl;
            }
        	Person(int age){
                m_Age = age;
                cout<<"Person的有参构造"<<endl;
            }
        	Person(const Person &p){
                cout<<"Person的拷贝构造"<<endl;
            }
        	~Person(){
                cout<<"Person的析构构造"<<endl;
            }
    }
    
    //2.值传递的方式给函数参数传值
    void doWork(Person p){
        
    }
    
    //3.以值方式返回局部对象
    Person doWork2(){
        Person p;
        return p;//此时的p是一个局部对象,当次函数调用结束就会销毁,
        		 //所以要把值拷贝给p3。
    }
    void main(){
        //1.使用一个已经创建完毕的对象来初始化一个新对象。
        Person p1(20);
        Person p2(p1);
        cout << p2.m_Age<<emdl;//20
        
    	//2.值传递的方式给函数参数传值
        Person p;
    	doWork(p);//会调用拷贝构造函数,因为当把实参的值传给函数的形参时
    			  //实际上是把实参的值复制给形参,也就是拷贝
        
    	//3.以值方式返回局部对象
        Person p3 = doWork2();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    4.构造函数的调用规则

    ​ 默认情况下,C++编译器至少给一个类添加三个函数

    ​ 1.默认构造函数(无参,函数体空)

    ​ 2.默认析构函数(无参,函数体空)

    ​ 3.默认拷贝构造函数,对属性进行值拷贝(值拷贝)

    构造函数调用规则:

    ​ 1.如果用户定义有参构造函数,c++不提供默认无参构造,但会提供默认拷贝构造

    ​ 2.如果用户定义拷贝构造函数,c++不会提供其他普通构造函数。

    5.深拷贝和浅拷贝

    深浅拷贝是面试经典问题,也是常见的一个坑。

    ​ 浅拷贝:简单的赋值拷贝操作。

    ​ 深拷贝:在堆区重新申请空间,进行拷贝操作。

    class Person{
        public:
        	int m_Age;
        	int *m_Height;
        	Person(int age){
                m_Age = age;
            }
        	Person(int age,int height){
                m_Age = age;
                m_Height = new int(height);//把数据存放到堆区  
            }
       		~Person(){
                //析构函数,将堆区开辟堆区数据做释放操作
                if(m_Height!=NULL){
                    delete m_Height;
                    m_Height = NULL;
                }
             	cout << "析构 "<<endl;   
            }
        	//自己实现拷贝函数,解决浅拷贝带来的问题
        	Person(const Person &p){
                  m_Age = p.m_Age;
                  m_Height = new int(*p.m_Height);
            }
    };
    void main(){
        Person p1(18,160);
        cout << "p1的年龄为:"<< p1.m_Age<<endl;
        //调用系统提供的拷贝函数,浅拷贝
        Person p2(p1);
        
        /*
    	当数据写在堆区用浅拷贝会带来一种问题是,堆区的数据会重复释放以此报错
    	*/
        Person p3(18,182);
        cout << p3.m_Age<< *p3.m_Height <<endl;
        //自己重写拷贝函数,把堆区的数据换地存放
        Person p4(p3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    浅拷贝造成错误的原因如下图所示: 当对象p2释放内存是会把堆区的内存也一起释放,但是当轮到对象p1是否内存是,堆区的内存已经释放了,以此造成错误。

    在这里插入图片描述

    6.初始化列表(不重要)

    ​ 作用:C++提供了初始化列表语法,用来初始化属性

    ​ 语法:构造函数():属性1(值1),属性2(值2)…{}

    class Person{
        public:
        	int m_A;
        	int m_B;
        	int m_C;
        	//传统初始化操作
        	Person(int a, int b, int c){
                m_A = a;
                m_B = b;
                m_C = c;
            }
        
        	//初始化列表
        	Person():m_A(10),m_A(20),m_A(30){
                
            }
        
        	//两者结合
        	Person(int a, int b, int c):m_A(a),m_A(b),m_A(c){
                
            }
    }
    void main(){
        //传统初始化操作
        Person p(10,20,30);
        
        //初始化列表
        Person p;
        
        //两者结合初始化列表
        Person p(10,20,30);
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    数据仓库与数据湖的区别以及数据入湖方式
    SpringBoot集成PageHelper分页实现
    多线程交替打印 [8种方式控制先后]
    聚焦“碳中和”,成都超算中心牵手重庆大学唱好“成渝双城记”
    【完美世界】石昊再借刀斩人,连杀3位真神,2位超级强者结局登场
    数据库主键设计
    git 分支代码合并到master主分支上或者master合并到开发分支
    【深度学习】实验6布置:图像自然语言描述生成(让计算机“看图说话”)
    为了简写这行代码,我竟使用静态和动态编译技术
    C++图的建立---邻接矩阵-----邻接表
  • 原文地址:https://blog.csdn.net/kaszxc/article/details/126678307