构造函数和析构函数,分别对应变量的初始化和清理,变量没有初始化,使用后果未知;没有清理,则会内存管理出现安全问题。
构造函数和析构函数写法
构造函数:与类名相同,没有返回值,不写void,可以发生重载,可以有参数,编译器自动调用,只调用一次。
析构函数:~类名,没有返回值,不写void,不可以发生重载,不可以有参数,编译器自动调用,只调用一次。
构造函数和析构函数的作用域是public下才可以调用到,不写默认private,调用不到。
如果程序猿不提供,系统会默认提供,构造和析构函数,函数体为空。
1 class Person{ 2 public: 3 Person(){ 4 cout << "无参构造函数" << endl; 5 } 6 ~Person(){ 7 cout << "无参析构函数" << endl; 8 } 9 }; 10 11 void test01(){ 12 Person p; 13 } 14 15 int main(){ 16 17 test01(); 18 system("pause"); 19 return 0; 20 }
构造函数的分类和调用
按照参数分类:有参构造和无参构造;按照类型分类:普通构造和拷贝构造函数。
1 class Person{ 2 public: 3 Person(const Person &a){ 4 age = a.age; 5 cout << "拷贝构造函数" << endl; 6 } 7 ~Person(){ 8 cout << "无参析构函数" << endl; 9 } 10 11 int age; 12 };
拷贝构造函数必须加const,因为防止修改,本来就是用现有的对象初始化新的对象。
1 class Person{ 2 public: 3 Person(){ 4 cout << "无参构造函数" << endl; 5 } 6 Person(int a){ 7 cout << "有参构造函数" << endl; 8 } 9 Person(const Person &a){ 10 cout << "拷贝构造函数" << endl; 11 } 12 ~Person(){ 13 cout << "无参析构函数" << endl; 14 } 15 }; 16 17 void test02(){ 18 19 Person p(1); //有参构造函数 20 Person p1(p); //拷贝构造函数 21 Person p2; //无参构造函数 不能写成Person p2(); 编译器以为是声明,不是构造函数 22 23 //显示调用 24 Person p4 = Person(100); //有参构造函数 25 Person p5 = Person(p4); //拷贝构造函数 26 //Person(100);单独这句叫匿名对象,编译器如果发现对象是匿名的,在这行代码之后就释放对象 27 //Person(p5);不能用拷贝构造函数初始化匿名对象,写成左值,编译器认为是Person p5,对象的声明,写成右值表示赋值构造函数 28 29 //隐式调用 30 Person p7 = 100; //隐式类型转换,相当于Person p7 = Person(100);编译器找到一个有参构造进行转换 31 Person p8 = p7; //相当于Person p8 = Person(p7); 32 }
注意事项:(1)无参构造函数使用:Person p2; 不能写成Person p2(); 编译器以为是声明,不是构造函数;(2)显示调用和隐式调用的区别;(3)匿名对象的特点
拷贝构造函数使用的时机
(1)使用已经创建好的对象初始化新对象;(2)以值传递的方式来给函数参数传值;(3)以值方式返回局部对象(不常用,一般不返回局部对象)
1 //使用已经创建好的对象初始化新对象 2 void test03(){ 3 Person p1; 4 p1.age = 10; 5 Person p2(p1); 6 } 7 8 //以值传递的方式来给函数参数传值 9 void dowork(Person p1){ //值传递方式,实际上形参是一个拷贝,因此Person p1 = Person(p); 10 11 } 12 13 void test04(){ 14 Person p; 15 p.age = 10; 16 dowork(p); 17 } 18 19 //以值方式返回局部对象 20 Person dowork2(){ 21 Person p1; 22 return p1; 23 } 24 25 void test05(){ 26 Person p = dowork2(); 27 }
对于使用已创建好的对象进行初始化对象时的补充:
1 void test01(){ 2 Person p1; //无参构造函数 3 Person *p2 = new Person(p1); //拷贝构造函数 4 5 Person *p3 = new Person(30); //有参构造函数 6 Person *p4 = new Person(*p3); //拷贝构造函数 7 8 Person p5 = p1; //拷贝构造函数 9 Person *p6 = p3; //赋值指针,和p3操作指针地址相同 10 }
注意事项:第三种返回局部对象,一般Debug下是先21行使用无参构造,再22行返回对象的拷贝,然后Person p = p1;但Release下是先26行处理Person p;dowork2(Person &p);然后再21行,只有无参构造。
构造函数的调用规则
系统会默认给一个类提供三个函数:默认构造函数(无参,函数体为空)、默认拷贝构造和析构函数(无参,函数体为空),其中默认拷贝构造可以实现简单的值拷贝。
提供了有参构造函数,就不提供默认构造函数;提供了拷贝构造函数,就不会提供其他构造函数。
深拷贝和浅拷贝
只有当对象的成员属性在堆区开辟空间内存时,才会涉及深浅拷贝,如果仅仅是在栈区开辟内存,则默认的拷贝构造函数和析构函数就可以满足要求。
1 class Person{ 2 public: 3 Person(){ 4 } 5 6 //有参构造 7 Person(char *name_, int age_){ 8 name = (char *)malloc(strlen(name_)+1);//可以直接调用现有的成员属性 9 strcpy(name, name_); 10 11 age = age_; 12 } 13 14 //自定义拷贝构造函数 15 Person(const Person &a){ 16 age = a.age; 17 name = (char *)malloc(strlen(a.name) + 1); 18 strcpy(name, a.name); 19 } 20 21 ~Person(){ 22 if (name != NULL){ 23 free(name); 24 name = NULL; 25 } 26 } 27 28 char *name; 29 int age; 30 }; 31 32 void test05(){ 33 //这样的初始化是栈区初始化,默认拷贝构造函数和默认析构函数也是可以的 34 //Person p; 35 //p.name = "namnana"; 36 //p.age = 23; 37 38 //调用有参构造这是堆区初始化,必须自定义拷贝构造函数和析构函数 39 Person p("namnana", 23); 40 41 Person p1(p); 42 }
具体原因则是默认构造函数只能实现值拷贝,因此涉及堆区开辟内存时,会将两个成员属性指向相同的内存空间,从而在释放时导致内存空间被多次释放,使得程序down掉。(与C语言中结构体的深拷贝和浅拷贝相同)
初始化列表
一般通过有参构造进行初始化,另外也可以通过初始化列表进行初始化
1 class Person{ 2 public: 3 Person(){ 4 } 5 6 //有参构造初始化数据 7 Person(int a_, int b_, int c_){ 8 a = a_; 9 b = b_; 10 c = c_; 11 } 12 13 //1. 初始化列表进行初始化数据 14 Person(int a_, int b_, int c_) : a(a_), b(b_), c(c_){} 15 16 //2. 这种情况是固定参数初始化 17 Person() :a(10), b(20), c(30){} 18 19 int a; 20 int b; 21 int c; 22 }; 23 24 void test05(){ 25 Person p1(10, 20, 30);//针对1. 26 Person p2;//针对2. 27 }
类对象作为类成员属性,构造顺序先将对象一一构造,然后构造自己,析构的顺序是相反的。
explicit关键字
为了防止构造函数中的隐式类型转换,加了explicit,只能显式调用
1 class Person{ 2 public: 3 Person(){ 4 } 5 6 //有参构造初始化数据 7 explicit Person(const char*str_){ 8 str = (char *)malloc(sizeof(char)*100); 9 strcpy(str,str_); 10 } 11 12 ~Person() { 13 if (str != NULL){ 14 free(str); 15 str = NULL; 16 } 17 } 18 19 char *str; 20 }; 21 22 void test05(){ 23 //Person p = "abc"; 隐式调用 24 25 Person p ("abc"); //显式调用 26 }