构造函数和析构函数
对象的初始化和清理是两个非常重要的安全问题,一个对象没有初始化状态,对其使用后果是未知的。同样使用完一个变量或对象,没有及时清理,也会造成一定的安全问题,如内存泄漏。
C++利用构造函数和析构函数解决上述的问题,这两个函数在类的对象中会被编译器自动调用,完成对象初始化和清理工作。
- 构造函数:主要用于在创建对象时为对象的成员属性赋值,默认的构造函数由编译器自动调用,且是空实现。
- 析构函数:主要用于进行清理工作,工作于对象销毁前,,默认的析构函数由编译器自动调用,且是空实现。
构造函数语法:类名(){}
- 没有返回值,也不写void;
- 函数名称于与类名相同;
- 构造函数可以有参数,因此可以发生重载;
- 程序在调用对象时候会自动调用构造函数,且只调用一次;
析构函数语法:~类名(){}
- 没有返回值,也不写void;
- 函数名称于与类名相同,且在名称前加~;
- 析构函数没有参数,因此不可以发生重载;
- 程序在销毁对象时候会自动调用析构函数,且只调用一次;
构造函数的分类和调用
两种分类方式:
按参数分为:有参构造函数和无参构造函数(默认构造函数)
按类型分为:普通构造函数和复制构造函数
复制构造函数的作用是将一个对象的所有属性值复制一遍到另一个对象,参数是一个对象类型的实例。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
int age;
string name;
Person() {
cout << "Person无参数的构造函数" << endl;
}
Person(int a, string b) {
age = a;
name = b;
cout << "Person有参数的构造函数" << endl;
}
Person(const Person &a) {
age = a.age;
name = a.name;
cout << "Person的复制构造函数" << endl;
}
~Person() {
cout << "Person的析构函数" << endl;
}
};
int main() {
Person A;//调用默认的构造函数
Person B(18,"wl");//调用有参数的构造函数
Person C = B;//调用复制构造函数
return 1;
}
Person无参数的构造函数
Person有参数的构造函数
Person的复制构造函数
Person的析构函数
Person的析构函数
Person的析构函数
构造函数的调用有三种方式:
1、括号法
2、显示法
3、隐式转换法
//括号法
Person A; //调用默认的构造函数
Person B(18, "QQ");//调用有参数的构造函数
Person C(B);//调用复制构造函数
注意事项:调用默认构造函数不用加(),例如:Person D(); 虽然不会报错,但编译器会认为这是一个函数声明,不会认为是在创建对象。
//显示法
Person D; //调用默认的构造函数
Person E = Person(18, "QQ"); //调用有参数的构造函数
Person F = Person(E);//调用复制构造函数
Person(18, “QQ”)单独拿出来叫匿名对象,特点:当前行执行结束后,系统会立即回收匿名对象。
注意事项:不要利用复制构造函数初始化匿名对象,编译器会认为Person(F) === Person F;,即对象的声明,从而造成重定义。
//隐式转换法
Person F = E;//调用复制构造函数
复制构造函数调用时机
在C++中,复制构造函数调用通常有三种情况:
- 使用一个已经创建的对象来初始化一个新对象;
- 值传递的方式给函数参数传值;
- 以值方式返回局部对象;
//第一种
Person A(18, "QQ");
Person B = A;
void Pr(Person a) {
cout << a.name << "的年龄为:" << (int)a.age << endl;
}
//第二种
Person A(18,"QQ");
Pr(A);
Person Pr() {
Person A;
return A;
}
//第三种
Pr();
构造函数调用规则
默认情况下,C++编译器至少给一个类添加了三个函数。
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认复制构造函数(有参,对默认值进行拷贝)
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不在提供默认的构造函数,但是会提供默认复制构造函数。
- 如果用户定义复制构造函数,C++不会再提供其他构造函数。
Person A(18,"QQ");
Person B = A;
Person有参数的构造函数
Person的复制构造函数
Person的析构函数
Person的析构函数
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
编译器默认的赋值构造函数属于浅拷贝
下面的代码在进行复制构造函数调用时,出现错误。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
int age;
string *name;
Person() {
cout << "Person无参数的构造函数" << endl;
}
Person(int a,string b) {
age = a;
name = new string(b);
cout << "Person有参数的构造函数" << endl;
}
~Person() {
if (name != NULL) {
delete name;
name = NULL;
}
cout << "Person的析构函数" << endl;
}
};
int main() {
Person A(18,"QQ");
cout << *A.name << "的年龄为:" << A.age << endl;
Person B = A;
cout << *B.name <<"的年龄为:" << B.age << endl;
return 1;
}
报错的原因是,指针属性name所存放的地址是一样的,但在第一次析构函数中,就已经使用delete语句释放了改内存空间,故而第二次delete,就会报错,造成内存重复释放。
解决办法:将默认复制构造函数的浅拷贝改为深拷贝。
Person类自定义复制构造函数
Person(const Person &a) {
age = a.age;
name = new string(*a.name);
cout << "Person的复制构造函数" << endl;
}
主函数测试:
Person A(18,"QQ");
cout << *A.name << "的年龄为:" << A.age << endl;
Person B = A;
cout << *B.name <<"的年龄为:" << B.age << endl;
Person有参数的构造函数
QQ的年龄为:18
Person的复制构造函数
QQ的年龄为:18
Person的析构函数
Person的析构函数
重写复制构造函数,使得复制的指针变量name所指的内存地址不同,从而避免最后重复释放。
初始化列表
C++提供了初始化语法,用来初始化属性。
语法:构造函数():属性1(值1),属性2(值2),属性3(值3)…{}
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
int age;
int height;
string name;
Person():age(18),height(180),name("QQ") {
cout << "Person无参数的构造函数" << endl;
}
Person(int a,int b,string c) :age(a), height(b), name(c) {
cout << "Person有参数的构造函数" << endl;
}
};
int main() {
Person A;
cout << A.name << "的年龄为:" << A.age << ",身高为:" << A.height << endl;
Person B(22,175,"WL");
cout << B.name << "的年龄为:" << B.age << ",身高为:" << B.height << endl;
return 1;
}
Person无参数的构造函数
QQ的年龄为:18,身高为:180
Person有参数的构造函数
WL的年龄为:22,身高为:175
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。下面代码中,类Person中有一个成员为Phone的对象。
问题:先调用Person的构造函数还是Phone的调用函数?先调用Person的析构函数还是Phone的析构函数?
#include <iostream>
#include <string>
using namespace std;
class Phone {
public:
string label; //手机品牌类型
Phone(string a) {
cout << "Phone的构造函数" << endl;
label = a;
}
~Phone() {
cout << "Phone的析构函数" << endl;
}
};
class Person {
public:
string name; //姓名
Phone phone; //手机类型
//Phone phone = b 隐式转换法
Person(string a, string b) :name(a), phone(b) {
cout << "Person的构造函数"<<endl;
}
~Person() {
cout<<"Person的析构函数" << endl;
}
};
答案:当其他类对象作为本类的成员,构造先调用其他类对象,再调用本类构造函数。析构函数调用顺序与构造函数相反。
Person a("张三", "小米10");
cout << a.name <<"的手机类型为:"<< a.phone.label<< endl;
Phone的构造函数
Person的构造函数
张三的手机类型为:小米10
Person的析构函数
Phone的析构函数