C++基础知识之"构造和析构"

fengjingtu

创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。注意,类的数据成员是不能在声明类时初始化的。

为了解决这个问题,C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。

概念

构造函数

C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
构造函数在定义时可以有参数;
没有任何返回类型的声明。
一般情况下C++编译器会自动调用构造函数,在一些情况下则需要手工调用构造函数

析构函数

C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法形式是:~ClassName()
析构函数没有参数也没有任何返回类型的声明
析构函数在对象销毁时自动被调用

对象显示初始化和构造函数

为什么要设计构造函数和析构函数?

加假如我们为每个类都提供一个public的initialize函数;对象创建后立即调用initialize函数进行初始化。不可以吗?这种处理方式确实存在问题!

initialize只是一个普通的函数,必须显示的调用,一旦由于失误的原因,对象没有初始化,那么结果将是不确定的,没有初始化的对象,其内部成员变量的值是不定的,这就说明它不能完全解决问题。

我们根据一段代码来理解一下显示调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Test
{
public:
int m;
int getM() const { return m; }
void setM(int val) { m = val; }

int n;
int getN() const { return n; }
void setN(int val) { n = val; }

public:
int init(int m,int n)
{
this->m = m;
this->n = n;
return 0;
}
protected:
private:
};

int main()
{
int rv =0;
Test t1; //无参构造函数的调用方法
Test t2;

t1.init(100, 200);
t2.init(300, 400);
cout<<t1.getM()<<" "<<t1.getN()<<endl;
cout<<t2.getM()<<" "<<t2.getN()<<endl;

//定义对象数组时,没有机会进行显示初始化
Test21 arr[3];
Test arr_2[3] = {Test(1,3), Test(), Test()};

system("pause");
return rv;
}

构造函数的分类和调用

无参构造函数

无参构造函数比较简单,在定义的时候,他没有返回值,没有参数,函数名和类名一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test
{
private:
int a;
int b;
public:
Test()//无参构造函数
{
a = 0;
b = 0;
cout << "执行了无参构造函数" << endl;
}
};

当你定义对象:

1
Test t1,t2;

编译器就会自动调用无参构造函数了。

有参构造函数

下面定义了一个有参构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class Test
{
private:
int a;
int b;
public:
Test(int _a, int _b)//有参构造函数
{
a = _a;
b = _b;
cout << "执行了有参构造函数" << endl;
}
}

有参构造函数有三种调用方式:

括号法

1
Test t1(10);

等号法

1
Test t2=(20);

直接调用法:使用类名

1
Test t3=Test(30);
拷贝构造函数

C++允许类的一个对象通过另一个对象俩初始化,这种情况下使用拷贝构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "iostream"
using namespace std;

class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
Location( const Location & p ) //拷贝构造函数
{
X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;

拷贝构造函数的调用由四种场景

第一种场景

用对象1初始化对象2 ,使用等号

1
2
3
4
5
6
7
void ObjPlay01()
{
Location a1; //变量定义
Location a2 = a1; //定义变量并初始化 //初始化法
Location a3;
a3 = a1; //用a1来=号给a3 编译器给我们提供的浅copy
}

第二种场景:

和第一种场景类似,使用的是括号。

1
2
3
4
5
6
7
void ObjPlay02()
{
Location a1(10); //变量定义
Location a2(a1); //定义变量并初始化 //括号法
//a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
a2.getA();
}

使用等号和括号都可以,如果要是已经定义了一个变量,没有初始化,而是选择用等号赋值,这涉及到浅拷贝的问题

第三种场景:

类的形参和实参之间

1
2
3
4
5
6
7
8
9
10
11

void f ( Location p )
{
cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ;
}

void mainobjplay()
{
Location A ( 1, 2 ) ;
f ( A ) ;//实参变量初始化形参变量时,执行拷贝构造函数
}

第四种场景:

在下面的代码中:对象初始化操作 和 =等号操作 是两个不同的概念。

匿名对象,就是编译器自己偷偷创建的,一般用来在函数参数和返回值接收之间的传递。

匿名对象的去和留,关键看,返回时如何接

若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构

若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Location g()// 注意函数返回值
{
Location A(1, 2);
return A;
}


void mainobjplay()
{
Location B;
B = g();
//用匿名对象赋值给B对象,然后匿名对象析构;
Location B = g();
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
}
默认构造函数

二个特殊的构造函数

1)默认无参构造函数

当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空

2)默认拷贝构造函数

当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制

构造函数调用规则研究

1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数

2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数

3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数

4 )默认拷贝构造函数成员变量简单赋值

总结:只要你写了构造函数,那么你必须用。

构造析构阶段性总结

1)构造函数是C++中用于初始化对象状态的特殊函数

2)构造函数在对象创建时自动被调用

3)构造函数和普通成员函数都遵循重载规则

4)拷贝构造函数是对象正确初始化的重要保证

5)必要的时候,必须手工编写拷贝构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <iostream>

using namespace std;


//构造和析构函数
//默认构造函数:默认无参构造函数,默认拷贝构造函数两种。
//无参构造函数
//有参构造函数
//拷贝(赋值)构造函数
class Test
{
private:
int a;
int b;
public:
Test()//无参构造函数
{
a = 0;
b = 0;
cout << "执行了无参构造函数" << endl;
}
Test(int _a, int _b)//有参构造函数
{
a = _a;
b = _b;
cout << "执行了有参构造函数" << endl;
}
Test(int _a)//有参构造函数
{

}
Test(Test &obj)//拷贝(赋值)构造函数,const可有可无
{
this->a = obj.getA();
this->b = obj.getB();
cout << "执行了拷贝构造函数" << endl;

}
~Test()
{
cout << "执行了析构函数" << endl;
}
void init(int _a, int _b)//手动构造,需要调用,不能再初始化的时候编译器自动调用
{
a = _a;;
b = _b;
}

int getA()
{
return a;
}
int getB()
{
return b;
}
};

void copyConT1(Test &t)
{
cout << "t.a=" << t.getA() << endl;
}
void copyConT2(Test t)
{
cout << "t.a=" << t.getA() << endl;
}

Test copyConT3()//匿名对象,取决于接收方式
{
Test t(5, 6);
return t;
}

void obj_test()
{
//无参构造函数的调用
Test t1, t2;
//注意:一旦写了构造函数,就不会调用默认的构造函数,这样要求在初始化的时候必须匹配。

//有参构造函数的调用
//方法1:括号法
Test t3(10, 20); //Test t4(10);//error

//方法2:等号法
Test t4=(1,2);
//属于等号表达式,只能传入一个参数,这时候如果有一个参数的构造函数,就会调用它

//方法3:直接调用法
Test t5=Test(10,20);
Test t6=Test();
//会产生一个匿名对象,匿名对象的生命周期和接受方式有关。

//拷贝(赋值)构造函数
//方法1:用一个对象初始化另一个对象,等号法
Test t7 = t3;
//方法2:用一个对象初始化另一个对象,括号法
Test t8(t3);
//方法3:用实参初始化形参。
copyConT1(t8);//不会调用拷贝构造函数,因为是引用。
copyConT2(t8);//会调用拷贝构造函数,因为是形参。

//方法4:函数返回值是一个对象,利用函数返回值来初始化一个对象
Test t9=copyConT3();//匿名对象直接转换成了t9
t8 = copyConT3();//匿名对象被析构。
cout << t8.getA() << endl;


}
int main_test()
{
obj_test();
system("pause");
return 0;
}

深拷贝和浅拷贝

默认复制构造函数可以完成对象的数据成员值简单的复制

对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制,C++给我们提供的解决方案是重载=号操作符,不使用编译器提供的浅copy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//浅拷贝现象
class Name
{
public:
Name(const char* n)
{
size = strlen(n);
name = (char *)malloc(size);
strncpy(name, n, size);
cout << "构造函数调用"<< endl;
}
~Name()
{
free(name);
size = 0;
cout << "析构函数调用" << endl;
}
void printName()
{
cout << "name=" << name<<endl;
}
private:
char * name;
int size;
};
void copy_test1()
{
Name n1("Rita");
//Name n2(n1);//浅拷贝,free的是同一个name.
Name n3=n1;//浅拷贝,free的是同一个name.

}
//解决方法:1、重写拷贝构造函数,2、重载运算符=

class Name2
{
private:
char * name;
int size;
public:
Name2(const char* n)
{
size = strlen(n);
name = (char *)malloc(size+1);
strncpy(name, n, size+1);
cout << "构造函数调用" << endl;
}
Name2(const Name2 &obj)
{
size = obj.size;
name = (char*)malloc(size + 1);
strncpy(name,obj.name, size+1);
}
void operator=(Name2 &obj)
{
if (name != NULL)
{
free(name);
name = NULL;
size = 0;
cout << "测试有没有调用" << endl;
}
size = obj.size;
name = (char*)malloc(size + 1);
strncpy(name, obj.name, size+1);
}
~Name2()
{
free(name);
size = 0;
cout << "析构函数调用" << endl;
}
void printName()
{
printf("name=%s\n", name);
}
};
void copy_test2()
{
Name2 n1("Rita");
Name2 n2(n1);//深拷贝,调用自己拷贝构造函数时候会重新开辟空间
Name2 n3 = n1;//深拷贝,使用=号操作符时候会重新开辟空间
Name2 n4("Herald");
n4 = n1;
n1.printName();
n2.printName();
n3.printName();
n4.printName();
return;

}
int main_copytest()
{
//copy_test1();
copy_test2();
cout<<"hello world"<<endl;
system("pause");
return 0;
}

多个对象的构造和析构

对象初始化列表

对象初始化列表出现原因:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。

类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,

因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。

C++中提供初始化列表对成员变量进行初始化

1
2
3
4
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}

注意:

初始化:被初始化的对象正在创建

赋值:被赋值的对象已经存在

成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关

初始化列表先于构造函数的函数体执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//初始化列表
//如果一个类中有成员不使用默认构造函数,有参数
//有const需要初始化
class Name3
{
private:
char * name;
int size;
public:
Name3(const char* n)
{
size = strlen(n);
name = (char *)malloc(size + 1);
strncpy(name, n, size + 1);
cout << "构造函数调用" << endl;
}
Name3(const Name3 &obj)
{
size = obj.size;
name = (char*)malloc(size + 1);
strncpy(name, obj.name, size + 1);
}
void operator=(Name3 &obj)
{
if (name != NULL)
{
free(name);
name = NULL;
size = 0;
cout << "测试有没有调用" << endl;
}
size = obj.size;
name = (char*)malloc(size + 1);
strncpy(name, obj.name, size + 1);
}
~Name3()
{
free(name);
size = 0;
cout << "析构函数调用" << endl;
}
char *getName()
{
return name;
}
};

class age
{
private:
int mage;
public:
void setAge(int a)
{
mage = a;
}
void printAge()
{
cout << "age=" << mage << endl;
}
int getAge()
{
return mage;
}
};

class student
{
public:
student(Name3 &objn,int h):n(objn),hight(h)
{
cout << "调用构造函数" << endl;
}
~student()
{
cout << "调用析构函数" << endl;
}
void printStudent()
{
cout << "name=" <<n.getName() << " age=" << a.getAge() <<" hight="<<hight<< endl;
}
void setAge(int age)
{
a.setAge(age);
}
private:
Name3 n;//必须手动初始化
age a;//可以使用自己的默认构造函数初始化
const int hight;//常量必须初始化
};
void multi_obj_test()
{
Name3 n1("wulei");
student s(n1, 167);
s.setAge(10);
s.printStudent();
return;

}
int main_multi()
{
multi_obj_test();
cout<<"hello world"<<endl;
system("pause");
return 0;
}
//1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;
//之后调用自身类的构造函数
//2)析构函数的调用顺序与对应的构造函数调用顺序相反

构造函数和析构函数的调用顺序

1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后调用自身类的构造函数

2)析构函数的调用顺序与对应的构造函数调用顺序相反

综合练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

#include<iostream>
using namespace std;

class ABC
{
public:
ABC(int a=0, int b=0, int c=0)
{
this->a = a;
this->b = b;
this->c = c;
cout << "ABCD的构造函数" << endl;
}
~ABC()
{
cout << "ABCD的析构函数" << endl;
}
void printfABC()
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
private:
int a;
int b;
int c;
};
class useABC
{
public:
useABC(int a,int b,int c,int m) :u(a,b,c)
{
this->m = m;
cout << "useABC的构造函数" << endl;
}
useABC(const useABC &obj) :u(1,2,3)
{
m = 0;
cout << "useABC的拷贝构造函数" << endl;
}
~useABC()
{
cout << "useABC的析构函数" << endl;
}
void ptintUseABC()
{
u.printfABC();
cout << "m=" << m << endl;
}
private:
ABC u;
int m;
};
int myABC(useABC u)
{
u.ptintUseABC();//打印出来的都是1,2,3因为调用的是拷贝构造函数
return 0;
}
int run()
{
useABC u(4, 5, 6, 7);
u.ptintUseABC();
useABC u2(u);
u2.ptintUseABC();
ABC(6, 7, 8);//构造和析构同时
myABC(u);
myABC(u2);
return 0;
}

class MyTest
{
public:
MyTest(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
}

MyTest(int a, int b)
{
this->a = a;
this->b = b;

MyTest(a, b, 100); //产生新的匿名对象
}
~MyTest()
{
printf("MyTest~:%d, %d, %d\n", a, b, c);
}

protected:
private:
int a;
int b;
int c;

public:
int getC() const { return c; }
void setC(int val) { c = val; }
};

int run2()
{
MyTest t1(1, 2);
printf("c:%d", t1.getC()); //请问c的值是?
system("pause");
return 0;
}

int main_all()
{
run();
run2();
cout<<"hello world"<<endl;
system("pause");
return 0;
}