Download presentation
Presentation is loading. Please wait.
1
常宝宝 北京大学计算机科学与技术系 chbb@pku.edu.cn
2
内容提要 对象的生命周期 构造函数 析构函数 拷贝构造函数
3
对象的生命周期 在面向对象的程序设计环境中,整型、实型以及其它其他基本数据类型的变量均可以被视作为特殊对象。
和C语言中变量的类型一样,C++中对象可分作三类。 全局对象 存在于全局数据区,编译程序负责为其分配存储空间和回收空间。主函数运行前,其存储分配及初始化工作已经完成,主函数运行结束后,其所占存储空间才被回收。 局部对象 存在于栈区,编译程序负责为其分配存储空间和回收空间,程序运行进入其作用域后为其分配存储空间,在离开其作用域时,其所占空间被回收。 动态分配的对象 存在于堆区,程序员在需要时为其分配存储空间并在不需要时回收其所占用的存储空间。
4
对象的生命周期 TDate gd; //定义全局对象 int main() { TDate ld; //定义局部对象
TDate *pd, *pd2; ... pd = new TDate;//建立动态分配的对象 delete pd;//动态分配对象所占空间被回收 pd2 = func(); delete pd2; //局部对象所占空间被回收 } //程序运行结束时,全局对象所占据空间被回收 TDate* func() { TDate ld1; TDate *pd1; ... pd1 = new TDate; return pd1; } //动态对象未回收
5
对象的生命周期 对象从“诞生”到“死亡”通常经历下面的阶段: 对象创建 根据对象类型为对象分配所存储空间。
对象初始化 给整型、实型、字符型、指针变量赋初值;给对象的数据成员赋初值 对象的使用 在程序中存取该对象 对象的善后清理 对象被回收前必要的善后处理,基本类型对象无需此步骤 回收对象所占空间
6
初始化 C++中,初始化基本数据类型变量的规则和C语言相同。 int a=6;//变量初始化 int b; //是否初始化依赖于变量是全局的还是局部的 int* pc = new int; //新分配的整型变量不作初始化 C++中,结构变量初始化可以按照C语言中结构变量初始化的方式进行。 struct student { char* name; char* id; float score; }; …… student s1 = {“王二”, “ ”, 89.3};
7
初始化 由于类的数据成员有不同的存取权限,因而不能象初始化结构那样对类对象进行初始化。
class TDate { public: void Set( int, int, int ); int IsLeapYear(); void Print(); private: int year; int month; int day; }; //下面初始化对象的方式是错误//的,因为客户存取了对象的私//有数据成员。C++中把数据成员//设为私有成员,为的是向客户//隐层对象内部的实现细节 TDate d1 = {2003, 9, 27};
8
初始化 可以在定义类的时侯,专门定义初始化对象的成员函数,负责对象的初始化工作。例如: void TDate::Set( int m, int d, int y ) { month = m; day = d; year = y; } 但是这样程序员需要在每次创建对象后调用用于初始化的成员函数。例如: TDate d; d.Set(9,27,2003); TDate *pd = new TDate; pd->Set(9,29,2003); 面向对象的程序设计思想认为对象的初始化工作应由编译程序负责处理,应该在对象创建后立即进行初始化。
9
构造函数 C++要求为每个类定义构造函数,负责该类对象的初始化工作。
构造函数是类的一个特殊成员函数。函数名和类名相同,且没有返回值,可以在类的内部进行定义,也可以在外部进行定义。 class TDate { public: TDate(); int IsLeapYear(); void Print(); private: int year; int month; int day; }; TDate::TDate() { year = 2000; month = 1; day = 1; } void TDate::TDate() { year = 2000; month = 1; day = 1; }
10
构造函数 构造函数在对象创建时自动被调用。程序员不能在程序中调用构造函数。 TDate gd; //构造函数TDate()被自动调用
int main() { TDate ld; //构造函数TDate()被自动调用 ... TDate *pd = new TDate; //构造函数TDate()被自动调用 delete pd; }
11
构造函数的重载 构造函数可以重载以满足不同的对象初始化需求。(即为一个类定义多个构造函数,但各个构造函数带不同类型或数量的参数)
class TDate { public: TDate(); TDate(int, int, int ); int IsLeapYear(); void Print(); private: int year; int month; int day; }; TDate::TDate() { year = 2000; month = 1; day = 1; } TDate::TDate(int m, int d, int y) { year = y; month = m; day = d;
12
构造函数的重载 如果定义了有参数的构造函数,则定义对象时,可以给定参数。编译程序根据重载函数的规则决定应该调用哪个构造函数。例如:
TDate d1(9,27,2003);//调用构造函数TDate(int, int, int) TDate d2;//调用构造函数TDate() TDate d3(2003);//错误,编译程序找不到只有一个参数的构造函数 程序员不能直接显式调用构造函数。 TDate d2; d2.TDate(9,27,2003);//错误,编译出错
13
构造函数的重载 可以为构造函数的参数指定默认值。但应注意重载和默认值有可能发生冲突。
TDate::TDate(int m, int d = 1, int y = 2000) { year = y; month = m; day = d; } ... TDate d(5);//调用构造函数Tdate(5,1,2000)
14
默认构造函数 每个类必须有构造函数。没有构造函数,对象不能被创建。
如果程序员没有提供任何构造函数,编译程序将自动创建一个没有参数的构造函数,该构造函数即为默认构造函数。 默认构造函数不作任何初始化工作。其函数体为空。 class TDate { public: void Set( int, int, int ); int IsLeapYear(); void Print(); private: int year; int month; int day; }; TDate::TDate() {//默认构造函数 }
15
默认构造函数 程序员一旦定义了构造函数,不管是否有参数。编译程序不再提供默认构造函数。 class TDate { public:
TDate(int, int, int );//带参数 int IsLeapYear(); void Print(); private: int year; int month; int day; }; ... TDate d; //编译会出错,没有可用的构造//函数,对象不能被创建
16
析构函数 面向对象的程序设计思想认为在对象所占存储空间被回收前,应对对象作必要的善后清理,且善后工作应由编译程序负责处理。
C++中可以为每个类定义析构函数,负责该类对象所占存储空间回收前的清理工作。 析构函数是类的一个特殊成员函数。函数名由~加类名构成,没有返回值,没有参数,不能重载,可以在类的内部进行定义,也可以在外部进行定义。 析构函数在对象生命结束时自动被调用(即所占存储空间被回收前)。 程序员不能直接显式调用析构函数。
17
析构函数 class TDate { public: TDate(int, int, int ); int IsLeapYear();
void Print(); ~TDate();//析构函数 private: int year; int month; int day; }; TDate::~TDate() { cout<<“bye!”<<endl; } int TDate::~TDate() { cout<<“bye!”<<endl; } TDate::~TDate(int i) { cout<<“bye!”<<endl; }
18
析构函数 TDate gd; int main() { TDate ld; ... TDate *pd = new TDate;
delete pd;//为动态分配的对象(pd指向的对象)自动调用析构函数~TDate() //为局部对象ld自动调用析构函数~TDate() } //为全局对象gd自动调用析构函数~TDate()
19
析构函数 析构函数有时候显得很重要,例如在类中有指针成员,且该指针指向了一片动态分配的存储空间,此时应定义析构函数,并且在析构函数中释放动态分配的存储空间,否则就会引起内存泄漏问题。 XYZ::XYZ() { cptr = new char[100]; } XYZ::~XYZ() { delete[] cptr ; class XYZ { public: XYZ(); ~XYZ(); private: char* cptr; }; int func() { XYZ a; ... }
20
new和delete 为什么C++中不使用malloc(…)和free(…)而使用new和delete来创建动态对象和回收动态对象? 用new创建动态对象自动调用构造函数。而malloc(…)不自动调用构造函数。 用delete回收动态对象自动调用析构函数。而free(…)不自动调用析构函数。
21
对象数组的构造和析构 可以定义对象的数组,此时要为每一个数组元素调用构造函数。
TDate d[100]; //调用100 次构造函数 对象数组生命期终结时,也要为每一个数组元素调用析构函数。 void func() { TDate d[100]; //调用100 次构造函数 … //调用100 次析构函数 }
22
对象成员的构造和析构 C++中,每个类的构造函数只负责初始化该类的对象,若类中某个成员也是对象,则该成员由成员自身的构造函数完成初始化。
若类中某个成员是一个对象,则调用该类的构造造函数前会自动调用对象成员的构造函数。 class A { public: A() { a = 0; cout <<"in A."<< endl; } private: B x;//成员x是一个B类对象 int a; }; class B { public: B() { b = 0; cout<<"in B."<< endl; } private: int b; }; int main() { A obj1; // obj1.x.b == ? obj1.a == ? // 程序运行后,控制台窗口会如何显示 } //显示如下: in B. in A.
23
对象成员的构造和析构 若类中某个成员是一个对象,则调用该类的析构函数后会自动调用对象成员的析构函数。(和构造函数相比,调用顺序相反)
class A { public: A() { a = 0; cout<<"in A."<<endl; } ~A() { cout<<"out A."<<endl; private: B x;//成员x是对象 int a; }; class B { public: B() { b = 0; cout <<"in B."<< endl; } ~B() { cout <<"out B."<< endl; private: int b; }; int main() { A obj1; // obj1.x.b == ? obj1.a == ? // 程序运行后,控制台窗口会如何显示 } //显示如下: in B. in A. out A. out B.
24
对象成员的构造和析构 如何调用对象成员的有参数的构造函数? class A { public: A() { a = 0;
cout<<“a="<<a<<endl; } A( int ai) { a = ai; cout <<“a="<<a<<endl; A( int ai, int bi):x(bi) { private: B x;//成员x是一个B类对象 int a; }; 如何调用对象成员的有参数的构造函数? int main() { A obj1; A obj2(10); A obj3(10, 20); // 程序运行后,控制台窗口会如何显示 } //显示如下: b=0 a=0 a=10 b=20 class B { public: B() { b = 0; cout<<"b="<<b<<endl; } B( int bi ) { b = bi; private: int b; };
25
拷贝构造函数 在C语言中,可以用已定义的变量初始化同类型变量。如: int a = 5; int b = a;
在C++中,也可用已定义的对象初始化同类对象。如: TDate d1(2,10,1997); TDate d2(d1); 也可以写做: TDate d1(2,10,1997); TDate d2 = d1; 用一个对象初始化另一个对象,通常意味着数据成员的逐个复制。 用一个对象初始化另一个对象需要为类定义拷贝构造函数。在初始化时会自动调用拷贝构造函数,程序员不能直接显式调用拷贝构造函数。
26
拷贝构造函数 拷贝构造函数是类的构造函数,带有一个参数,参数是是该类对象的引用。 class T { int a; public:
T(T& t) { //拷贝构造函数 a = t.a } ... };
27
拷贝构造函数 class TDate { public: TDate(); TDate(int, int, int );
int IsLeapYear(); void Print(); ~TDate(); private: int year; int month; int day; }; ... TDate::TDate( TDate& d ) { year = d.year; month = d.month; day = d.day; cout<<“copying...”<<endl; }
28
拷贝构造函数 在下列情况下,会自动调用拷贝构造函数: a)用一个已经存在的对象初始化一个新创建的同类对象。
TDate func() { TDate td; return td; } int main() { TDate ld=func(); } //输出如下: copying... 在下列情况下,会自动调用拷贝构造函数: a)用一个已经存在的对象初始化一个新创建的同类对象。 b)对象作为函数参数时,如果采用传值调用,将建立对象的副本,该副本的构建需要调用拷贝构造函数。 int func( TDate d) { … } int main() { TDate ld; func(ld); } //输出为 copying... c)某个函数的返回值是一个对象,也要创建对象副本,此时也需要自动调用拷贝构造函数。
29
默认拷贝构造函数 程序员如果没有为类定义拷贝构造函数,编译程序会自动定义默认拷贝构造函数。
默认拷贝构造函数的语义是(数据)成员逐个依次拷贝。 如果程序员没有为类TDate定义拷贝构造函数,则编译程序定义的默认拷贝构造函数如下: TDate::TDate( TDate& d ) { year = d.year; month = d.month; day = d.day; }
30
默认拷贝构造函数 有时候默认拷贝构造函数会引发错误。例如在类中有指针成员,且该指针指向了一片动态分配的存储空间,此时应定义拷贝构造函数,采用默认的拷贝构造函数会引起位于两个对象中指针成员指向相同的存储空间。 Person::Person( char* pn ) { pName = new char[ strlen(pn)+1 ]; if ( pName != NULL ) strcpy(pName, pn); } Person::~Person() { delete[] pName; class Person { public: Person( char* ); ~Person(); protected: char* pName; }; //正确拷贝构造函数 Person::Person( Person& p ) { pName = new char[strlen(p.pName)+1]; if ( pName != 0 ) strcpy( pName, p.pName); } //默认拷贝构造函数 Person::Person( Person& p ) { pName = p.pName; }
31
默认拷贝构造函数 void main() { Person p1("王二"); Person p2(p1); ... }
32
上机练习内容 《C++程序设计教程》p.288 练习12-1 、12.2、12.3、12.4
Similar presentations