一.构造函数
类似于java,C++中也有构造函数的概念,相关用法如下:
1.1 构造函数的定义
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
|
#include <iostream> using namespace std; class Student{ private : char *m_name; int m_age; float m_score; public : //声明构造函数 Student( char *name, int age, float score); //声明普通成员函数 void show(); }; //定义构造函数 Student::Student( char *name, int age, float score){ m_name = name; m_age = age; m_score = score; } //定义普通成员函数 void Student::show(){ cout<<m_name<< "的年龄是" <<m_age<< ",成绩是" <<m_score<<endl; } int main(){ //创建对象时向构造函数传参 Student stu( "小明" , 15, 92.5f); stu.show(); //创建对象时向构造函数传参 Student *pstu = new Student( "李华" , 16, 96); pstu -> show(); return 0; } |
运行结果:
小明的年龄是15,成绩是92.5
李华的年龄是16,成绩是96
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
|
#include <iostream> using namespace std; class Student{ private : char *m_name; int m_age; float m_score; public : Student(); Student( char *name, int age, float score); void setname( char *name); void setage( int age); void setscore( float score); void show(); }; Student::Student(){ m_name = NULL; m_age = 0; m_score = 0.0; } Student::Student( char *name, int age, float score){ m_name = name; m_age = age; m_score = score; } void Student::setname( char *name){ m_name = name; } void Student::setage( int age){ m_age = age; } void Student::setscore( float score){ m_score = score; } void Student::show(){ if (m_name == NULL || m_age <= 0){ cout<< "成员变量还未初始化" <<endl; } else { cout<<m_name<< "的年龄是" <<m_age<< ",成绩是" <<m_score<<endl; } } int main(){ //调用构造函数 Student(char *, int, float) Student stu( "小明" , 15, 92.5f); stu.show(); //调用构造函数 Student() Student *pstu = new Student(); pstu -> show(); pstu -> setname( "李华" ); pstu -> setage(16); pstu -> setscore(96); pstu -> show(); return 0; } |
运行结果:
小明的年龄是15,成绩是92.5
成员变量还未初始化
李华的年龄是16,成绩是96
1.3 默认构造函数
类似于java,如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的。
注意:调用没有参数的构造函数也可以省略括号。
Student *stu = new Student;
Student *stu = new Student();
以上两种写法是等价的。
1.4 构造函数的参数初始化表
构造函数的主要目的是用于对成员变量进行初始化, 为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用参数初始化表。具体写法如下:
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
|
#include <iostream> using namespace std; class Student{ private : char *m_name; int m_age; float m_score; public : Student( char *name, int age, float score); void show(); }; //采用参数初始化表 Student::Student( char *name, int age, float score): m_name(name), m_age(age), m_score(score){ //TODO: } void Student::show(){ cout<<m_name<< "的年龄是" <<m_age<< ",成绩是" <<m_score<<endl; } int main(){ Student stu( "小明" , 15, 92.5f); stu.show(); Student *pstu = new Student( "李华" , 16, 96); pstu -> show(); return 0; } |
运行结果:
小明的年龄是15,成绩是92.5
李华的年龄是16,成绩是96
1.5 使用参数初始化表来初始化const成员变量
参数初始化表还有一个很重要的作用,那就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用参数初始化表。例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class VLA { private : const int m_len; int *m_arr; public : VLA( int len); }; VLA::VLA( int len):m_len(len) { m_arr = new int [len]; } |
二.析构函数
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。
注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class VLA { public : VLA( int len); ~VLA(); // 析构函数 private : const int m_len; int *m_arr; }; VLA::VLA( int len):m_len(len) { // 构造函数初始化 if (len > 0) {m_arrz = new int [len];}; else {m_arr = NULL;}; } VLA::~VLA() { delete []m_arr; // 在析构函数中释放堆区申请的内存 } |
C++ 中的 new 和 delete 分别用来分配和释放内存,它们与C语言中 malloc()、free() 最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于类来说是不可或缺的,所以在C++中我们非常鼓励使用 new 和 delete。
下面是其他网友的补充
说实话c++还是以前在学校的时候用过的,从毕业到现在一直用c嵌入式编程,现在重新搬出C++语法 ,如果理解上有错误的地方,还请路过的朋友多指正~~~
构造函数用来构造一个对象,主要完成一些初始化工作,如果类中不提供构造函数,编译器会默认的提供一个默认构造函数(参数为空的构造函数就是默认构造函数) ;析构函数是隐式调用的,delete对象时候会自动调用完成对象的清理工作。
现在主要看看继承中的构造函数和析构函数的调用:
1
2
3
4
5
6
7
8
|
class A {} ; class B : public A {}; class C : public B {}; c * ptr = new C() ; delete ptr ; |
一般来说,上面的代码构造函数是先调用最根父类的构造函数,然后是次一级父类构造函数,依次而来直到派生类本身的构造函数,而且对父类构造函数的调用都是父类的默认构造函数(当然也可以显示地调用父类的非默认构造函数),也就是说派生类在构造本身之前会首先把继承来的父类成分先构造好;
对析构函数的调用是先调用派生类本身的析构函数,然后是上一层父类析构函数,直到根父类析构函数 ,当没有多态的时候,析构函数是这样调用的。
改一下上面的代码:
A * ptr = new C() ;
delete ptr ;
在多态的情况下,如果基类A中的析构函数不是虚构造函数,则当delete ptr的时候只会调用A的析构函数,不会调用B和C中的析构函数;如果A中的析构函数是虚构造函数就会调用所有的析构函数,调用顺序和一般情况一样。
再改一下上面的代码:
B *prt = new C();
delete ptr ;
在多态的情况下,如果A,B中的析构函数都不是虚析构函数,则当delete ptr的时候先调用B的析构函数,再调A的析构函数,不会调用C中的析构函数,如果A或者B中至少有一个是虚析构函数,则析构函数调用和一般情况一样。
因此总结一下规律:
CA * ptr = new CB() ;
delete ptr ;
CB是CA的子类,构造函数的调用一直是一样的,当具备多态的时候:
如果CA及其父类都不具备虚析构函数,则首先调用A的析构函数,然后调用A的父类析构函数直到根父类析构函数,不会调用A以下直到派生类的析构函数 ;如果如果CA及其父类只要有一个具备虚析构函数,则析构函数调用跟一般情况一样。
因此:带有多态性质的基类应该声明虚析构函数 ,这样的基类一般还有其他虚函数;
如果类的设计不是用于基类,而且不具备多态性,则析构函数不应该声明为虚析构函数
小测试代码:
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
|
#include<iostream> using namespace std ; class A { public : A(){cout<< "A constructor" <<endl;} A( char * arp) { cout << "not default " ;} ~CA(){cout<< "A desstructor" <<endl;} }; class B: public A { public : B(){cout<< "B constructor" <<endl;} ~B(){cout<< "B desstructor" <<endl;} }; class C: public B { public : C( char * arp){cout<< "C constructor" <<endl;} ~C(){cout<< "C desstructor" <<endl;} }; int main() { C * ptr = new C( "hello world" ) ; delete ptr ; } |
另外effective C++中提到的:
1、析构函数不能吐出异常,如果析构函数掉用的函数可能产生异常,要在析构函数内部进行捕获进行处理,因为如果析构函数抛出异常的话,比如说vector,当调用各个对象的析构函数进行删除的时候可能导致抛出多个异常,从而使程序进入不确定状态。
2、如果用户需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数执行这个操作。
3、在构造函数和析构函数中都不应该调用虚函数,这是因为当调用构造函数构造对象的时候,首先会调用父类的构造函数,此时对象的类型在编译器看来就是一个父类对象(实际此时子类成员还处于不确定状态),会调用父类的虚函数,而不会调用子类的虚函数。