1.构造函数和析构函数-----对象的创建和清除
对象的初始化和清除也是非常重要的安全问题
一个对象或者变量没有初始化,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清除,也会造成一定的安全问题。
C++利用了构造函数和析构函数解决上述问题,这个函数将会被编译器自动调用,完成对象初始化和清除工作。对象的初始化和清除工作是编译器强制我们做的事情,因此如果我们不提供构造和析构函数,编译器会提供编译器创造的构造函数和析构函数是空实现。
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数有编译器自动调用,无需手动调用。
析构函数:主要作用于对像销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无需手动调用,而且只调用一次。
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前面加上符号~
.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用函数,需手动调用,而且只调用一次。
//对象的初始化和清理//1.构造函数,进行初始化操作classPerson{public:Person(){cout"Person的构造函数"endl;}~Person(){cout"Person的析构函数"endl;}}voidtest01(){Personp;}intmain(){test01();//输出:Person的构造函数Person的析构函数//test01函数的数据存放在栈区,内存在函数调用完释放,所有对象的析构函数也调用了。Personp;//输出:Person的构造函数//对象创建在主函数中,不会立即释放}2.构造函数的分类及调用
两种分类方式:
按参数分类:有参构造和无参构造(默认构造)
按类型分类:普通类型和拷贝类型
三种调用方式:
括号法,显示法,隐式转换法
classPerosn{public://无参构造,普通类Person(){cout"Person的无参构造"endl;}//有参构造,普通类Person(inta){intage=a;cout"Person的有参构造"endl;}//拷贝构造函数Person(constPersonp){intage=p.a;//将传去的人身上的所有属性,拷贝到我身上cout"Person的拷贝构造"endl;}}voidmain(){//1.括号法Personp1;//调用默认构造函数//注意:调用默认构造函数时不要加(),如果加了Personp1();编译器会认为是一个函数声明,不会创建对象Personp2(10);//调用有参构造函数Personp(p1)//调用拷贝构造函数//2.显示法Personp4;//调用默认构造函数Personp5=Person(10);//调用有参构造函数Personp6=Person(p5);//调用拷贝构造函数//Person(10);单独拿出,是一个匿名对象。特点:当前行执行结束后,系统会立即回收掉匿名对象Person(p4);//是错误的,会报p4重定义//注意:不要利用拷贝函数初始化匿名对象编译器会认为Person(p4);等价于Personp4;所以重定义p4了//.隐式转换法Personp7=10;//相当于Personp7=Person(10);Personp8=p7;//拷贝构造}.拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
1.使用一个已经创建完毕的对象来初始化一个新对象。
2.值传递的方式给函数参数传值
.以值方式返回局部对象
classPerson{public:intm_Age;Person(){cout"Person的无参构造"endl;}Person(intage){m_Age=age;cout"Person的有参构造"endl;}Person(constPersonp){cout"Person的拷贝构造"endl;}~Person(){cout"Person的析构构造"endl;}}//2.值传递的方式给函数参数传值voiddoWork(Personp){}//.以值方式返回局部对象PersondoWork2(){Personp;returnp;//此时的p是一个局部对象,当次函数调用结束就会销毁,所以要把值拷贝给p。}voidmain(){//1.使用一个已经创建完毕的对象来初始化一个新对象。Personp1(20);Personp2(p1);coutp2.m_Ageemdl;//20//2.值传递的方式给函数参数传值Personp;doWork(p);//会调用拷贝构造函数,因为当把实参的值传给函数的形参时实际上是把实参的值复制给形参,也就是拷贝//.以值方式返回局部对象Personp=doWork2();}4.构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体空)
2.默认析构函数(无参,函数体空)
.默认拷贝构造函数,对属性进行值拷贝(值拷贝)
构造函数调用规则:
1.如果用户定义有参构造函数,c++不提供默认无参构造,但会提供默认拷贝构造
2.如果用户定义拷贝构造函数,c++不会提供其他普通构造函数。
5.深拷贝和浅拷贝深浅拷贝是面试经典问题,也是常见的一个坑。
浅拷贝:简单的赋值拷贝操作。
深拷贝:在堆区重新申请空间,进行拷贝操作。
classPerson{public:intm_Age;int*m_Height;Person(intage){m_Age=age;}Person(intage,intheight){m_Age=age;m_Height=newint(height);//把数据存放到堆区}~Person(){//析构函数,将堆区开辟堆区数据做释放操作if(m_Height!=NULL){deletem_Height;m_Height=NULL;}cout"析构"endl;}//自己实现拷贝函数,解决浅拷贝带来的问题Person(constPersonp){m_Age=p.m_Age;m_Height=newint(*p.m_Height);}};voidmain(){Personp1(18,);cout"p1的年龄为:"p1.m_Ageendl;//调用系统提供的拷贝函数,浅拷贝Personp2(p1);//当数据写在堆区用浅拷贝会带来一种问题是,堆区的数据会重复释放以此报错。Personp(18,);coutp.m_Age*p.m_Heightendl;//自己重写拷贝函数,把堆区的数据换地存放Personp4(p);}
浅拷贝造成错误的原因如下图所示:当对象p2释放内存是会把堆区的内存也一起释放,但是当轮到对象p1是否内存是,堆区的内存已经释放了,以此造成错误。
6.初始化列表(不重要)
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2).....{}
classPerson{public:intm_A;intm_B;intm_C;//传统初始化操作Person(inta,intb,intc){m_A=a;m_B=b;m_C=c;}//初始化列表Person():m_A(10),m_A(20),m_A(0){}//两者结合Person(inta,intb,intc):m_A(a),m_A(b),m_A(c){}}voidmain(){//传统初始化操作Personp(10,20,0);//初始化列表Personp;//两者结合初始化列表Personp(10,20,0);}
#构造函数#