目录
对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。c++为了给我们提供这种问题的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。
一、构造函数和析构函数
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:
|
析构函数语法:
|
二、 构造函数的分类及调用
- 按参数类型:无参构造函数、有参构造函数
- 按类型分类:普通构造函数、拷贝构造函数(复制构造函数)
例子:创立一个Person类
1、构造函数的分类
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int a)
{
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造 //值传递的本质 就是调用 拷贝构造函数
Person(const Person &p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
2、构造函数的调用
void test02()
{
//1 括号法
Person p1(10); //有参构造函数调用
p1.m_Age = 18;
Person p2(p1); //利用括号法 调用拷贝构造函数
cout << "p2的年龄为: " << p2.m_Age << endl;
//注意事项1 : 不要利用括号法 调用默认构造函数 Person p(); 原因将代码看成 函数的声明,不会认为是在创建对象
Person p();
void func();
//2 显示法
Person p3 = Person(10); //有参构造调用
//显示法 调用 拷贝构造函数
Person p4 = Person(p3);
Person(10); //单独写 Person(10); 称为 匿名对象 特点:当本行执行完毕,立即释放
cout << "aaaaaaaaa" << endl;
//注意事项2 : 不要利用拷贝构造函数 初始化匿名对象
Person(p4); // 当写成Person(p4); 编译器会认为写了 Person p4 如果已经有p4就是重定义 匿名对象放到右值没问题
//3 隐式转换法 可读性低
Person p5 = 10; //编译器隐式将代码转为 Person p5 = Person(10);
//利用隐式转换法 调用拷贝构造函数
Person p6 = p5; // 隐式转为 Person p6 = Person(p5);
}
b为A的实例化对象,A a = A(b) 和 A(b)的区别? 当A(b) 有变量来接的时候,那么编译器认为是一个匿名对象,当没有变量来接的时候,编译器认为A(b) 等价于 A b. |
三、拷贝构造函数的调用时机
值传递的本质:就是调用 拷贝构造函数
3种情况可能用到:
- 对象以值传递的方式传给函数参数
- 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
- 用一个对象初始化另一个对象
用代码解释上面3中情况:
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int a)
{
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造 //值传递的本质 就是调用 拷贝构造函数
Person(const Person &p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
//1、用已经创建好的对象 初始化新的对象
void test01()
{
Person p1;
p1.m_Age = 10;
Person p2(p1);//拷贝构造函数调用
cout << "p2的年龄为: " << p2.m_Age << endl;
}
//2、值传递的方式 给函数参数传值
void doWork( Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3、以值的方式返回局部对象
Person doWork2()
{
Person p;
return p;
}
void test03()
{
Person p = doWork2();
}
四、构造函数调用规则
- 默认情况下,c++编译器至少为我们写的类增加3个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对类中非静态成员属性简单值拷贝
- 如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
- 如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
五、深拷贝和浅拷贝
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。
如果是深拷贝, 堆区的数据可能会被释放多次。
拷贝构造函数例子:
class Person{
public:
Person(char* name,int age){
pName = (char*)malloc(strlen(name) + 1);
strcpy(pName,name);
mAge = age;
}
//增加拷贝构造函数
Person(const Person& person){
pName = (char*)malloc(strlen(person.pName) + 1);
strcpy(pName, person.pName);
mAge = person.mAge;
}
~Person(){
if (pName != NULL){
free(pName);
}
}
private:
char* pName;
int mAge;
};
void test(){
Person p1("Edward",30);
//用对象p1初始化对象p2,调用c++提供的默认拷贝构造函数
Person p2 = p1;
}
六、初始化列表
注意:初始化成员列表(参数列表)只能在构造函数使用。
例子:
class Person{
public:
#if 0
//传统方式初始化
Person(int a,int b,int c){
mA = a;
mB = b;
mC = c;
}
#endif
//初始化列表方式初始化
Person(int a, int b, int c):mA(a),mB(b),mC(c){}
void PrintPerson(){
cout << "mA:" << mA << endl;
cout << "mB:" << mB << endl;
cout << "mC:" << mC << endl;
}
private:
int mA;
int mB;
int mC;
};
七、explicit关键字
[explicit注意]
|
class MyString
{
public:
explicit MyString(int len)
{
cout << "MyString有参构造函数(int )调用" << endl;
}
MyString(char * str)
{
cout << "MyString有参构造函数(char * )调用" << endl;
}
};
void test01()
{
MyString str = "abcde";
MyString str2("abcde");
MyString str3 = MyString("abcde");
//MyString str4 = 10; // 有人会认为是 字符串是 "10" 也有人会认为字符串长度为10
//为了防止这种写法,可以用关键字 explicit
MyString str5(10);
MyString str6 = MyString(10);