对象的构造和析构函数

  构造函数和析构函数,分别对应变量的初始化和清理,变量没有初始化,使用后果未知;没有清理,则会内存管理出现安全问题。

构造函数和析构函数写法

  构造函数:与类名相同,没有返回值,不写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 }

 

posted @ 2019-01-07 19:33  两猿社  阅读(592)  评论(0编辑  收藏  举报