语法形式
class 类名{类名(形参表){...}//构造函数
};
特点和作用
在哪里创建对象
通常,根据使用场景的不同,我们可以再栈区和堆区两个地方创建对象
在这两个地方创建对象有什么不同呢?
栈区(stack)
堆区(heap)
代码举例
class A{
public://这里定义了一个有参构造函数,参数为i并给其赋了缺省值为1//也就是说,如果给这个构造函数传入控制,则i的值为1//在构造函数中,我们把i的值传给了成员变量m_iA(int i = 0){m_i = i;}
private:int m_i;
}int main(void){//在栈区创建对象A a1;//调用无参构造//下面两种写法效果都可以认为是一样的A a1(1);A a1 = A(1)//在栈区创建A列列表A arr1[3];A arr2[3] = {A(1), A(2), A(3)};//在堆区创建对象A* a2 = new A;//构造函数不传参A* a3 = new A(2);//构造函数传参//在栈区释放内存delete a2;delete a3;//在堆区创建A列列表A* arr3 = new A[3];A* arr4 = new A[3]{A(1), A(2), A(3)};//在堆区释放内存delete[] arr3;delete[] arr4;//注意:释放堆区的数组时,要加上[]}
如果写一个类的时候,没有定义任何构造函数,为了保证类对象能正常创建出来,
编译器会为其提供一个缺省构造函数。
如果,一个类自己定义了构造函数,那么无论是否有参数,编译器都不会再提供缺省构造函数。所以,后面也是没办法调用缺省构造函数的。
class A{
public:A(){cout << "无参构造" << endl;m_i = 0;}int m_i;
};class B{
public:int m_j;int* m_p;A m_a;
};void defCons_test(){B b;cout << b.m_j << endl;//未知,自己的基本成员变量会被初始化成什么是不确定的。cout << b.m_a.m_i << endl;//类类型会调用自己的默认构造函数进行初始化
一般情况下,当构造函数只有一个参数的时候,都可以看成类型转换构造函数。
这种函数,可以将传入参数的类型转换成目标类类型。
隐式转换
若采用隐式转换形式,编译器会自己去类中寻找能匹配上的构造函数进行调用。以完成类型转换。但是隐式转换代码可读性很不好,一般不建议使用。
显示转换
更推荐使用显示转换,c++风格显示转换的格式和调用类型转换构造函数的格式相同。
同时,在定义类型转换构造函数的时候,前面可以用explicit关键字修饰。表示,这个类型转换只能使用显示类型转换,不能使用隐式类型转换。那么,使用隐式类型转换就会报错。
class Integer{
public:Integer(){cout << "Integer()" << endl; m_i = 0;}//类型转换构造函数//加上explicit表示,类型转换只能显示转换,不能隐式转换explicit Integer(int i){cout << "Integer(int)" << endl;m_i = i+15;}void print(void){cout << m_i << endl;}
private:int m_i;
};void castCons_test(){Integer i;i.print();//explicit Integer(int)//前面有explicit,所有,不能进行隐式的类型转换,顾报错//i = 100;//编译器进行了隐式转换//编译器会在构造函数中找,最匹配的那个构造函数,调用//显示转换(更推荐)i = (Integer)200;//c风格显示转换i = Integer(300);//c++风格显示转换,和构造函数调用写法一样i.print();
}
用一个已经定义的对象构造同类型的副本对象,将调用该类的拷贝构造函数。
//const修饰,避免传入的对象被修改
类名(const 类型& that){//克隆源对象that的副本对象
}类型 对象1;
类名 对象2(对象1);//拷贝构造
类名 对象2 = 对象1;//和上面一样
class A{
public:A(int data = 0):m_data(data){cout << "A(int)" << endl;}A(const A& that){//注意,拷贝构造函数形参,必须加引用,必须加const/*1.为什么必须加引用?*如果没有引用,相当于不是穿这个对象的地址,而是传这个对象的副本。*既然出现了副本,那是不是要对这个对象拷贝一个副本出来。*既然要拷贝一个副本出来,那又要使用拷贝构造了。*既然使用拷贝构造,又要传一个对象副本了*...*死循环了,所以,编译不会通过。*2.为什么必须加const?*如果不加const,那就形参只是一个普通引用。*普通引用只能引用左值。*如果传入右值,则会直接报错。*所以,必须加const,将形参变为右值引用(万能引用)*/cout << "A(const A&)" << endl;m_data = that.m_data;}int m_data;
};void coyCons_test(){A a1(100);//A a2(a1);A a2 = a1;//和上一行意思一样cout << a1.m_data << endl;cout << a2.m_data << endl;
}
如果我们在类中没有定义拷贝构造函数,但是后面使用了拷贝构造。
那么编译器会给我们提供一个缺省的拷贝构造函数。
缺省的拷贝构造函数是浅拷贝的。
class A{
public:A(int data = 0):m_data(data){cout << "A(int)" << endl;}A(const A& that){cout << "A(const &A)" << endl;m_data = that.m_data;}int m_data;
};class B{
public:A m_a;//成员子对象};
void cpCons_test(){/*编译器处理逻辑:*如果,是用无参构造创建对象,*那么,对象中的类成员变量也是用自己的无参构造进行创建。*如果,使用拷贝构造创建对象,*呢么,对象中的类成员变量也是用自己的拷贝构造进行创建。*/B b1;//调用B缺省无参构造函数cout << b1.m_a.m_data << endl;//0B b2(b1);//拷贝构造cout << b2.m_a.m_data << endl;//0}
拷贝构造函数调用时机(笔试会考)
拷贝构造过程风险高效率低,设计时应该尽可能避免
一个笔试题例子:
class A{
public:A(){cout << "A的无参构造" << endl;}A(const A& that){cout << "A的拷贝构造函数" << endl;}
};void func(A a){}A bar(){A a;cout << "&a=" << &a << endl;return a;
}void bishi_cpConst_test(){//问:代码执行期间会调用多少次构造函数?6次A a1;//1,无参构造A a2 = a1;//2,拷贝构造func(a1);//3,func的形参不是引用,所以,传入的是对象的副本,所以,调用一次拷贝构造A s3 = bar();//编译器优化,会优化两次//6,返回值是A,不是引用,所以,返回副本,调用一次拷贝构造//bar函数内部还创建了一个A的对象,调用一次无参构造//最后,这个语句本身是拷贝构造,将bar()返回的对象拷贝构造给了s3cout << "&s3=" << &s3 <
先说什么是浅拷贝
类中缺省的拷贝构造函数,对指针形式的成员变量按照字节复制,不会复制指针所指向的内容,这种拷贝方式被称为浅拷贝。
看下面的例子,第一个对象实例,i1。
他的成员变量m_pi是个指向int的指针。
我们让这个指针指向地址为1001的int,这个int里面存的是100。第二个对象i2,它由i1浅拷贝构造而来,
i2也有个成员变量m_pi也是个指针。由于是浅拷贝构造,i2的成员变量m_pi会复制值i1中m_pi的内容(图中step1)。
那i1中存的是什么呢?是100的地址1001。(这个地址是我编的,为了好理解)
也就是说,i1中的m_pi和i2中的m_pi都指向了同一个地址的100。

会出现这么两种情况
double free深拷贝
为了避免浅拷贝的问题,必须自己定义拷贝构造函数,针对指针形式的成员变量,实现对指针指向内容的赋值,即深拷贝。
也就是说,如果,你定义的类中成员变量有指针,那就必须使用深拷贝,浅拷贝是哒咩的。
那深拷贝要完成什么功能呢?
就行下面图中所展示的。
i1对象有个成员变量m_pi是个指针。
指向的地址为1001,这个地址上存的数字是100。
然后,我要通过i1拷贝构造一个i2,但是是深拷贝。
深拷贝这样操作:
好处:
double free![[深拷贝.png]]
class Integer{
public:Integer(int i = 0){m_pi = new int(i);//类里面在堆区分配了内存,但是用户如果不知道,就忘记销毁了}~Integer(void){cout << "析构函数" << m_pi << endl;delete m_pi;}/*Integer(const Integer& that){cout<< "缺省浅拷贝构造" << endl;m_pi = that.m_pi;}*///自定义深拷贝Integer(const Integer& that){cout <<"自定义深拷贝构造"<< endl;m_pi = new int;//先给新的对象的成员变量分配一块内存(分配新的地址)*m_pi = *that.m_pi;//再进行复制运算}void print(void)const{cout << *m_pi << endl;}void Set(){*(this->m_pi) =199;}
private:int* m_pi;
};void constructor_test(){Integer i1(100);Integer i2(i1);i1.print();i2.print();i2.Set();i1.print();i2.print();
}
为什么要说拷贝赋值呢?
因为拷贝赋值和拷贝构造一样,也有深赋值和浅赋值,原理也是一样的,所以一起分析。
浅赋值问题
double free所以这时候,我们要自己定义深拷贝赋值函数
#include
using namespace std;int main(void){int i1 = 100;int i2 = 0;i2 = i1;//赋值运算符,如果看成是一个函数,那这个函数的返回值就是等号左边的//返回结果是个左值(左值是非const的,和右值const区分)(i2 = i1) = 300;//这样写是没有问题的//相当于先给i2赋值成100,再对i2赋值300i2 = 100;cout << i1 << "," << i2 << endl;
}
自定义深拷贝赋值操作符函数
A& operator=(const A& that){if(this != &that)//防止自赋值{//释放旧资源,因为我们要赋值的可能和旧内存大小不一样//分配新资源//拷贝新数据}return *this;
}
class Integer{
public:Integer(int i = 0){m_pi = new int(i);//类里面在堆区分配了内存,但是用户如果不知道,就忘记销毁了}~Integer(void){cout << "析构函数" << m_pi << endl;delete m_pi;}/*Integer(const Integer& that){cout<< "缺省浅拷贝构造" << endl;m_pi = that.m_pi;}*///自定义深拷贝构造Integer(const Integer& that){cout <<"自定义深拷贝构造"<< endl;m_pi = new int;//先给新的对象的成员变量分配一块内存(分配新的地址)*m_pi = *that.m_pi;//再进行复制运算}//i3 = i2 -> i3.operator=(i2)//形参用const修饰,表示可以传入左值,也可以传入右值,也就是可以直接传个int//形参是引用,为了提升效率//返回值是个引用表示返回这个对象本身,而不是副本/*Integer& operator=(const Integer& that){cout << "缺省拷贝赋值函数" << endl;//也是浅拷贝m_pi = that.m_pi;return *this;//返回调用这个函数的对象自身}*///深拷贝赋值函数Integer& operator=(const Integer& that){cout << "自定义拷贝赋值函数" << endl;if(this != &that ){delete m_pi;//释放旧内存m_pi = new int;//分配新内存*m_pi = *that.m_pi;//赋值}return *this;//返回自引用}void print(void)const{cout << *m_pi << endl;}void Set(){*(this->m_pi) =199;}
private:int* m_pi;
};void constructor_test(){Integer i1(100);Integer i2(i1);Integer i3;i3.print();//对于两个自定义的类,编译器不知道怎们对其进行赋值=操作//编译器会为其提供一个函数:operator=//这个函数如果咱没自己定义,编译器会为咱们提供的缺省函数//下面代码相当于i3.operator=(i2)//其中i3是函数的调用者,i2是传入函数的参数(左调右参)i3 = i2;//i3=100,拷贝赋值(浅)//i3.operator=(i2);//这一行和上面意思相同i1.print();i2.print();i2.Set();//将i2=199i1.print();i2.print();i3.print();//按理说应该是100,但是是199,改变i2就会改变i3
}
下一篇:李白经典语录