C++的构造函数和析构函数,使类的对象能够轻易的被创建和撤销。构造函数创建类对象,初始化其成员,析构函数撤销类对象。构造函数和析构函数是类的特殊成员函数,他们的设计与应用,直接影响编译程序处理对象的方式。构造函数和析构函数的实现使C++的类机制得以充分显示。所以本章内容是C++的重点之一。
学习了本章之后,要求理解类与对象的区别,掌握定义构造函数和析构函数的方法,把握默认构造函数的意义,了解类成员初始化的问题,掌握构造类成员的方法。
1、类与对象:
一个类是描述一类型的事物,描述这些事物所应具有的属性,如人有身长,体重,文化,性别,年龄,等。
一个对象是类的一个实例,它具有确定的属性。
人类只有一个,人类的实例就会有无数个。
对象可以被创建和销毁,但类是无所不在的。
(1)定义对象:
属于不同的类的对象在不同的时刻,不同的地方分别被建立。全局对象在主函数开始执行前首先被建立,局部对象在程序执行到他们的对象定义时才被建立。与定义变量类似,定义对象时,C++为其分配空间。
(2)对象的初始化:
根据变量的定义,全局变量和静态变量在定义(分配内存空间)时,将位模式清零,局部变量在定义时,分配的内存空间内容保持原样,故为随机数。
对象定义时,情况不一样。对象的意义表达了现实世界的实体,因此,一旦建立对象,须有一个有意义的初始值。C++建立和初始化对象的过程专门有该类的构造函数来完成。这个构造函数很特殊,只要对象建立,它马上被调用,给对象分配空间和初始化。例如一旦打造了一张桌子,桌子就应该有长宽高。 因此,在桌子对象建立时,构造函数的任务是赋予一组值给该桌子对象。如果一个类没有专门定义构造函数,那么C++就仅仅创建对象而不作任何初始化。
2、构造函数的需要性:
变量初始化的方法如下所示:
int a=1; int * pa=&a;
但是对类对象来说,如此初始化不行,这是由类的特殊性所决定的。(其中拥有私有的成员)
类的封装性,就体现在一部分数据是不能让外界访问的。所以直接在非成员函数中访问类对象的保护或私有数据是不允许的。
类对象的初始化任务,就落在了类的成员函数身上,因为他们可以访问保护和私有数据成员。于是将初始化构想成下面的形式:
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> # include <string> using namespace std; #ifndef STUDENT_H #define STUDENT_H class Student { public : Student(); int addcoures( int sh, int s) { somehours = sh; score = s; return 0 ; } ~Student(); private : int somehours; float score; }; Student::Student() { } Student::~Student() { } #endif |
类的保护数据在外界是不能访问的,所以对这些数据的维护是类的分内的工作。将初始化工作交给addcoures()成员函数无可非议,但却让系统多了一道处理初始化的解释与执行,因为他意味着在编写应用程序中每当建立对象时,都要人为的增加书写代码。这样实现的类机制,并不理想。
所以,我们要求在建立对象的同时,自动调用构造函数,省去认为调用的麻烦,也就是说,类对象的定义:student ss;
明确表达了为对象分配空间和初始化的意向。这些工作由构造函数来完成。
类对象定义了student ss;涉及到一个类名和一个对象名。类只有一个名字,但对象名可以任意多,每个对象创建时,每个对象都要调用该类的构造函数,类的唯一性和对象的多样性,使我们马上想到用类名而不是对象名来作为构造函数名是比较合适的。
3、构造函数的使用:
C++规定与类同名的成员函数是构造函数,在该类的对象创建时,自动被调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Student { public : Student() { somehours = 0 ; score = 0 ; } int addcoures( int sh, int s) { somehours = sh; score = s; return 0 ; } ~Student(); private : int somehours; float score; }; |
构造函数可以放在类的外部定义:
例如:
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
|
class Student { public : Student(); int addcoures( int sh, int s) { somehours = sh; score = s; return 0 ; } ~Student(); private : int somehours; float score; }; Student::Student() { } Student::Student() { somehours = 0 ; score = 0 ; } Student::~Student() { } |
放在外部定义的构造函数,其函数名之前要加上类名::,这个别的成员函数的定义一样。因为在类定义的外部,可能有各种函数定义,为了区分成员与非成员函数,区分此类成员函数和彼类成员函数,所以加上类名::是必要的。
构造函数另一个特殊之处是他们没有返回类型,函数体中也不允许返回值,但可以有无值返回语句return。
如果一个类对象是另一个类的数据成员,则在那个类的对象创建所调用的构造函数中,对该成员(对象)自动调用其构造函数。
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> # include <string> # include "teacher.h" # include "xuesheng.h" using namespace std; #ifndef TUTOPAIR_H #define TUTOPAIR_H class Tutopair { public : Tutopair(); ~Tutopair(); private : Teacher tea; Xuesheng stu; int cnt; //会晤次数 }; Tutopair::Tutopair() { cout << "tutopair" << endl; cnt = 0 ; } Tutopair::~Tutopair() { } #endif |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# include <iostream> # include <string> using namespace std; #ifndef TEACHER_H #define TEACHER_H class Teacher { public : Teacher(); ~Teacher(); private : }; Teacher::Teacher() { cout << "teacher " << endl; } Teacher::~Teacher() { } #endif |
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
|
# include <iostream> # include <string> using namespace std; #ifndef XUESHENG_H #define XUESHENG_H class Xuesheng { public : Xuesheng(); ~Xuesheng(); private : int somehours; int score; }; Xuesheng::Xuesheng() { cout << "xuesheng" << endl; somehours = 100 ; score = 90 ; } Xuesheng::~Xuesheng() { } #endif |
1
2
3
4
5
6
7
8
9
10
11
|
# include "teacher.h" # include "Tutopair.h" # include "xuesheng.h" # include <iostream> using namespace std; int main() { Tutopair tp; system( "pause" ); return 0 ; } |
这个例子,告诉我们。类在工作过程中,分工十分明确,每个类只负责初始化自己的对象。当totupair类要初始化成员xuesheng类对象的时候,马上调用学生构造函数,而不是由自己去包办。正所谓”你做你的事,我做我的事情。“
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
|
# include <iostream> # include <string> using namespace std; #ifndef XUESHENG_H #define XUESHENG_H class Xuesheng { public : Xuesheng(char* pname); ~Xuesheng(); private : int somehours; int score; char name[ 21 ]; }; Xuesheng::Xuesheng(char * pname) { cout << "construct xuesheng" << pname << endl; strncpy(pname, name, sizeof(name)); name[sizeof(name)- 1 ] = ' ' ; somehours = 100 ; score = 90 ; } Xuesheng::~Xuesheng() { cout << "distroy xuesheng" << name << endl; } #endif |
6、重载构造函数。
构造函数可以被重载,C++根据声明中的参数选择合适的构造函数。
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
|
# include <iostream> # include <string> using namespace std; #ifndef DATA_H #define DATA_H class Data { public : Data(); Data( int a); Data( int a, int b); Data( int a, int b, int c); ~Data(); private : int month; int day; int year; }; Data::Data() { month = 8 ; day = 28 ; year = 1996 ; cout << day << "/" << month << "/" << year << endl; } Data::Data( int a) { month = 8 ; day = a; year = 1996 ; cout << day << "/" << month << "/" << year << endl; } Data::Data( int a, int b) { month = b; day = a; year = 1996 ; cout << day << "/" << month << "/" << year << endl; } Data::Data( int a, int b, int c) { month = b; day = a; year = c; cout << day << "/" << month << "/" << year << endl; } Data::~Data() { } #endif |
1
2
3
4
5
6
7
8
9
10
11
12
|
# include "data.h" # include <iostream> using namespace std; int main() { Data(); Data( 28 ); Data( 28 , 8 ); Data( 28 , 8 , 1996 ); system( "pause" ); return 0 ; } |
7、默认构造函数:
(1)C++规定,每个类必须有一个构造函数,没有构造函数,就不能创建任何对象。
(2)若没有提供一个类的构造函数,则C++提供一个默认的构造函数,该默认的构造函数是个无参的构造函数,它仅仅负责创建对象,而不做任何初始化工作。
(3)只要一个类定义了一个构造函数(不一定是无参构造函数),C++就不需要在提供默认的构造函数了。也就是说,如果一个类定义了一个带参数的构造函数,但还是想要定义一个无参的构造函数,则必须自己定义。
(4)与变量定义类似,再用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为0.否则,对象值是随机的。
8、类成员初始化的困惑:
”类与类之间互不干涉内政“
9、构造类成员:
我们需要一个机制来表示构造已分配了空间的对象成员,而不是创建一个新的对象成员。
10、构造对象的顺序。
再一个大的程序中,各种作用域的对象很多,有些对象包含在别的对象里面,有些对象早在主函数开始之前就已经建立。创建对象的唯一途径就是调用构造函数。构造函数是一段程序,所以构造对象的先后顺序有所不同,直接影响程序执行的先后顺序,导致不同的运行结果。C++对给构造函数的顺序做了专门的规定。