创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。注意,类的数据成员是不能在声明类时初始化的。
为了解决这个问题,C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
概念
构造函数
C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
构造函数在定义时可以有参数;
没有任何返回类型的声明。
一般情况下C++编译器会自动调用构造函数,在一些情况下则需要手工调用构造函数
析构函数
C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法形式是:~ClassName()
析构函数没有参数也没有任何返回类型的声明
析构函数在对象销毁时自动被调用
对象显示初始化和构造函数
为什么要设计构造函数和析构函数?
加假如我们为每个类都提供一个public的initialize函数;对象创建后立即调用initialize函数进行初始化。不可以吗?这种处理方式确实存在问题!
initialize只是一个普通的函数,必须显示的调用,一旦由于失误的原因,对象没有初始化,那么结果将是不确定的,没有初始化的对象,其内部成员变量的值是不定的,这就说明它不能完全解决问题。
我们根据一段代码来理解一下显示调用:
1 | class Test |
构造函数的分类和调用
无参构造函数
无参构造函数比较简单,在定义的时候,他没有返回值,没有参数,函数名和类名一样。
1 | class Test |
当你定义对象:
1 | Test t1,t2; |
编译器就会自动调用无参构造函数了。
有参构造函数
下面定义了一个有参构造函数
1 |
|
有参构造函数有三种调用方式:
括号法
1 | Test t1(10); |
等号法
1 | Test t2=(20); |
直接调用法:使用类名
1 | Test t3=Test(30); |
拷贝构造函数
C++允许类的一个对象通过另一个对象俩初始化,这种情况下使用拷贝构造函数:
1 |
|
拷贝构造函数的调用由四种场景
第一种场景
用对象1初始化对象2 ,使用等号
1 | void ObjPlay01() |
第二种场景:
和第一种场景类似,使用的是括号。
1 | void ObjPlay02() |
使用等号和括号都可以,如果要是已经定义了一个变量,没有初始化,而是选择用等号赋值,这涉及到浅拷贝的问题
第三种场景:
类的形参和实参之间
1 |
|
第四种场景:
在下面的代码中:对象初始化操作 和 =等号操作 是两个不同的概念。
匿名对象,就是编译器自己偷偷创建的,一般用来在函数参数和返回值接收之间的传递。
匿名对象的去和留,关键看,返回时如何接
若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
1 | Location g()// 注意函数返回值 |
默认构造函数
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
构造函数调用规则研究
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数
1 |
|
深拷贝和浅拷贝
默认复制构造函数可以完成对象的数据成员值简单的复制
对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制,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
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 | Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3) |
注意:
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行
1 |
|
构造函数和析构函数的调用顺序
1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后调用自身类的构造函数
2)析构函数的调用顺序与对应的构造函数调用顺序相反
综合练习
1 |
|