目录
特点
没有返回值,函数名与类名相同,可以有重载,只调用一次
作用
用于在创建对象时,给对象中的成员初始化
分类
调用规则
-
是否自定义构造函数
是——调用自定义构造函数,不再提供默认构造函数
否——编译器自动调用默认构造函数
class Test{
public:
Test()
{
cout<<"执行带参的构造函数"<<endl;
}
~Test()
{
cout<<"执行析构函数"<<endl;
}
};
int main()
{
{
Test t;
}
system("pause");
return 0;
}
-
自定义的构造函数是否有参数
是——不再提供默认的无参构造函数,但是依然提供默认的拷贝构造函数
class Test{
public:
Test(int a)
{
cout<<"执行带参的构造函数"<<endl;
}
~Test()
{
cout<<"执行析构函数"<<endl;
}
};
int main()
{
{
Test t;
}
system("pause");
return 0;
}
编译不通过,产生如下错误
class Test{
public:
Test(int a)
{
cout<<"执行带参的构造函数"<<endl;
}
~Test()
{
cout<<"执行析构函数"<<endl;
}
};
int main()
{
{
Test t1(10);
Test t2(t1);
}
system("pause");
return 0;
}
可以通过编译,输出,可以看到构造函数只执行了一次,说明对象t1调用了带参的构造函数,对象t2调用了默认的拷贝构造函数
-
是否自定义拷贝构造函数
是——调用自定义的拷贝构造函数
class Test{
public:
Test(int a)
{
cout<<"执行带参的构造函数"<<endl;
}
Test(const Test & a)
{
cout<<"执行自定义的拷贝构造函数"<<endl;
}
~Test()
{
cout<<"执行析构函数"<<endl;
}
};
int main()
{
{
Test t1(10);
Test t2(t1);
}
system("pause");
return 0;
}
输出
初始化列表
-
作用
与构造函数相同,用于在创建对象时,给对象中的成员初始化
-
必须使用初始化列表的情况
解释一下对象成员为什么必须要在初始化列表中初始化
class CMember
{
public:
CMember(int a)
{
cout<<"CMember(int a)"<<endl;
}
};
class CClass
{
public:
CMember member;
public:
CClass()
{
cout<<"CClass()"<<endl;
}
};
int main (){
CClass cl;
system("pause");
return 0;
}
编译不通过,会显示
当一个类中有带参数的构造函数时候,不再提供默认的无参构造函数
在另一个类中需要定义它的对象,找不到对应的构造函数来进行初始化,又不能在定义时直接赋值,因此必须在CClass的初始化列表中对其进行初始化才能通过编译
如果成员是一个常量对象或者引用,是一样的道理,因此必须使用初始化列表
-
使用初始化列表的原因
必要性:上述说到必须使用初始化列表的情况
考虑效率:
进入构造函数体后,进行的是计算,是对成员变量的赋值操作,而赋值和初始化是不同的,需要执行一次构造函数,和一次赋值操作
而调用初始化列表,会少产生一次构造函数的调用,并且少一次赋值的操作
用构造函数初始化
class CFather {
public:
CFather()
{
cout << "CFather()" << endl;
}
CFather(int a)
{
cout << "CFather(int)" << endl;
}
CFather & operator = (const CFather& a)
{
cout<<"CFather & operator ="<<endl;
return *this;
}
};
class CSon : public CFather {
public:
//CSon(): father(2)
//{
// cout << "CSon()" << endl;
//} //初始化列表
CSon()
{
father = 2;
cout << "CSon()" << endl;
} //构造函数
private:
CFather father;
};
int main()
{
CFather father;
CSon son;
system("pause");
return 0;
}
输出为
CFather第一次无参的构造函数,是定义CFather father对象执行的
CFather第二次无参的构造函数,是CSon继承CFather,在初始化列表中执行的
CFather第三次无参的构造函数,是CSon进入构造函数后执行的
CFather第四次带参的构造函数,是通过给对象赋值操作执行的
CFather的重载等号操作符的输出,是通过给对象赋值操作产生的
用初始化列表初始化
输出为
CFather第一次无参的构造函数,是定义CFather father对象执行的
CFather第二次无参的构造函数,是CSon继承CFather,在初始化列表中执行的
CFather第三次带参的构造函数,是CSon在初始化列表中给CFather对象进行初始化执行的
-
初始化列表的执行顺序
class A
{
public:
A(int val):b(val),a(b){}
int a;
int b;
};
int main()
{
A a(3);
cout<<a.a<<" "<<a.b<<endl;
system("pause");
return 0;
}
输出为
原因在于,构造函数初始化列表的执行顺序与初始化列表的书写顺序无关,而是与变量在类中的声明顺序有关
当定义对象A a(3)时,执行A的带参构造函数,由于a在类中是先声明的,先执行a的初始化,此时b并没有一个确定值,所以a被初始化为一个非确定的值
再执行b的初始化,输出结果为b=3
拷贝构造函数
-
调用时机
初始化
使用一个已经创建完毕的对象来初始化一个新对象
CPerson p1(20);
CPerson p2(p1);
值传递
以值传递的方式给函数参数传值
void work(CPerson p){}
int main()
{
CPerson p;
work(p);
}
值返回
class CPerson
{
};
CPerson work()
{
CPerson p;
cout<<(int*)&p<<endl;
return p;
}
int main()
{
CPerson p = work();
cout<<(int*)&p<<endl;
system("pause");
return 0;
}
输出为
可以看出,两个对象并非同一个,在值返回时,返回的是调用拷贝构造函数创建的对象
在work函数中,由CPerson对象位于栈区,函数结束后,局部对象p生命周期结束,销毁
-
浅拷贝与深拷贝
深拷贝与浅拷贝是一个故弄玄虚的概念,其本质上是指针与内存
对于含有指针成员的类
浅拷贝:两个指针指向同一块内存,在析构时,对同一块内存释放两次
深拷贝:两个指针指向不同的内存,在析构时,各自释放自己指向的区域
class Student
{
private:
int num;
char *name;
public:
Student()
{
name = new char(20);
cout << "Student" << endl;
}
~Student()
{
cout << "~Student "<< endl;
delete name;
name = NULL;
}
};
int main()
{
{
Student s1;
Student s2(s1);// 执行默认拷贝构造函数
}
system("pause");
return 0;
}
正常输出,随后程序崩溃
原因在于,s1的构造函数中已经给指针name在堆区申请了一块内存空间,在用s1给s2赋值时,只是简单地用对象s2的name指针指向与s1的name指针相同的内存空间
在s1、s2的生命周期结束后,s1先调用析构函数,将name指向的堆区空间释放,而s2又调用了一次析构函数,此时空间已经被释放一次,因此程序会崩溃
解决办法
class Student
{
private:
int num;
char *name;
public:
Student()
{
name = new char(20);
cout << "Student" << endl;
}
Student(const Student& s)
{
name = new char(20);
memcpy(name,s.name,strlen(s.name));
cout << "Copy Student" << endl;
}
~Student()
{
cout << "~Student "<< endl;
delete name;
name = NULL;
}
};
int main()
{
{
Student s1;
Student s2(s1);// 执行重载的拷贝构造函数
}
system("pause");
return 0;
}
输出
重载拷贝构造函数,指针指向一块新的内存空间,析构时,各自释放自己的空间,程序正常运行
浅拷贝带来问题的本质在于析构函数释放多次堆区内存,使用std::shared_ptr,可以完美解决这个问题
进一步了解请参考我的另一篇文章:C++基础总结系列之【智能指针】