第10讲 构造函数和析构函数 构造函数 析构函数 This 指针
学习目标 理解构造函数和析构函数的作用; 熟练掌握构造函数和析构函数的定义和使用; 掌握构造函数的重载; 理解动态建立对象和对象数组的方法 理解This指针的作用;
第9讲 构造函数和析构函数 构造函数 析构函数 This 指针
构造函数的引入 如何实现对象成员数据的快速初始化? class A { float x,y; public: float m,n; void Setxy( float a, float b ){ x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } }; 如何实现对象成员数据的快速初始化? void main(void) { A a1; a1.m=10; a1.n=20; a1.Setxy(2.0 , 5.0); a1.Print(); } 对a1对象的公有 成员数据赋初值 对a1对象的私有 成员数据赋初值
构造函数与析构函数 构造函数和析构函数是在类体中说明的两种特殊的成员函数。 构造函数是在创建对象时,使用给定的值来将对象初始化。
构造函数 构造函数的函数名必须与类名相同。构造 函数的主要作用是完成初始化对象的数据 成员以及其它的初始化工作。 在定义构造函数时,不能指定函数返回值 的类型,也不能指定为void类型。 一个类可以定义若干个构造函数。当定义 多个构造函数时,必须满足函数重载的原 则。
构造函数的定义 class <类名> { public: <类名> (参数表) {函数体} } 构造函数必须 为公有函数 构造函数名与 所属类名相同 构造函数 无返回值 构造函数的函 数体
构造函数 构造函数可以指定参数的缺省值。 若定义的类要说明该类的对象时,构造函 数必须是公有的成员函数。如果定义的类 仅用于派生其它类时,则可将构造函数定 义为保护的成员函数。 由于构造函数属于类的成员函数,它对私 有数据成员、保护的数据成员和公有的数 据成员均能进行初始化。
构造函数 属性 构造函数 目的 类的公有成员函数 实现成员数据初始化 实现 私有数据成员 保护数据成员 公有数据成员
例1: x=2 y=3 x=1 y=2 class A { float x,y; 构造函数, 初始化对象 public: A(float a,float b){ x=a; y=b;}// float Sum(void) { return x+y;} Print(void) { cout<<"x="<<x<<'\t'<<"y="<<y<<endl;} }; void main(void) { A a1(2.0, 3.0); A a2(1.0,2.0); a1.Print(); a2.Print(); } 构造函数, 初始化对象 定义时调用构造 函数进行成员数 据的初始化 x=2 y=3 x=1 y=2
每一个对象必须要有相应的构造函数 class A{ float x,y; public: A(float a, float b=10) { x=a; y=b; } A() { x=0; y=0;} void Print(void) { cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1, a2(20.0), a3(3.0, 7.0); a1.Print(); a2.Print(); a3.Print(); } 带缺省参数的构造函数 不带参数的构造函数 0 0 20 10 3 7 每一个对象必须要有相应的构造函数
若没有显式定义构造函数,系统默认缺省的构造函数。 每一个对象必须要有相应的构造函数 若没有显式定义构造函数,系统默认缺省的构造函数。 class A{ float x,y; public: A() {} void Print(void) { cout<<x<<'\t'<<y<<endl; } }; 对象开辟了空间,但没有初始化 隐含的缺省的构造函数 只允许这样定义对象 A a1, a2;
构造函数 关于缺省的构造函数,说明以下几点: 在定义类时,只要显式定义了一个类的构造函数,则编译器就不产生缺省的构造函数 所有的对象在定义时,必须调用构造函数 不存在没有构造函数的对象! 缺省的构造函数并不对所产生对象的数据成员赋初值;即新产生对象的数据成员的值是不确定的。
构造函数的特点 存在性 唯一性 每一个对象必须要有与之对应的构造函数 所有的对象在定义时,必须调用构造函数 不存在没有构造函数的对象 任一对象调用的构造函数必须唯一
构造函数的特点 缺省的构造函数 若无显式定义构造函数,系统产生默认缺省构造函数 若已显式定义构造函数,系统不产生默认缺省构造函数 1.不带参数的构造函数 A( ){ } 缺省的构造函数 2.带默认缺省值的构造函数 A(int a=3 ){ }
例3: class A { float x,y; public: void Print(void) cout<<x<<'\t'<<y<<endl; } }; 系统生成的隐含缺省的构造函数 无显式定义的构造函数 A() { } 缺省构造函数时只许这样定义对象 A a1, a2;
例4: class A { float x,y; public: A(float a=0,float b=5) { x=a;y=b; } void Print(void) { cout<<x<<'\t'<<y<<endl;} }; void main(void) { A a1; A a2(3.0,30.0); } 显式定义了构造函数 不产生隐含的构造函数A() { } 含有默认参数值 调用默认参数的构造函数 调用含参数的构造函数
class A{ float x,y; public: A(){ }//缺省的构造函数,编译器自动产生,可以不写 float Sum(void) { return x+y; } void Set(float a,float b) { x=a; y=b;} void Print(void) { cout<<"x="<<x<<'\t'<<"y="<<y<<endl; } }; void main(void) { A a1,a2;//产生对象时,自动调用缺省的构造函数,不赋值 a1.Set (2.0,4.0); cout<<"a1: "; a1.Print (); cout<<"a1.sum="<<a1.Sum ()<<endl; a2.Print();//打印随机值 }
显式定义了构造函数,不产生缺省的构造函数 class A{ float x,y; public: A(float a,float b) { x=a; y=b; } void Print(void){ cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1; A a2(3.0,30.0); } 显式定义了构造函数,不产生缺省的构造函数 error,定义时,没有构造函数可供调用
构造函数 在类中,若定义了没有参数的构造函数,或各参 数均有缺省值的构造函数也称为缺省的构造函数 ,缺省的构造函数只能有一个。 在类中,若定义了没有参数的构造函数,或各参 数均有缺省值的构造函数也称为缺省的构造函数 ,缺省的构造函数只能有一个。 产生对象时,系统必定要调用构造函数。所以任 一对象的构造函数必须唯一。
举例 class A{ float x,y; public: A(float a=10,float b=20){ x=a; y=b; } void Print(void){ cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1; A a2(3.0,30.0); } 两个函数均为缺省的构造函数 两个构造函数均可供调用,构造函数不唯一
1 2 3 4 动态建立对象时的构造函数 用new建立类的对象时,可以 使用参数初始化动态空间。 使用new运算符来动态地建立对象 STEP 1 STEP 2 STEP 3 STEP 4 使用new运算符来动态地建立对象 自动调用构造函数,完成初始化对象的数据成员 返回动态对象的起始地址 不再使用动态对象时,用delete运算符来释放对象所占用的存储空间 用new建立类的对象时,可以 使用参数初始化动态空间。
用new动态开辟空间,调用构造函数初始化 class A{ float x,y; public: A(float a, float b){ x=a;y=b; } A(){ x=0; y=0; } void Print(void){ cout<<x<<'\t'<<y<<endl; } }; void main(void) { A *pa1,*pa2; pa1=new A(3.0, 5.0); pa2=new A; pa1->Print(); pa2->Print(); delete pa1; //用delete释放空间 delete pa2; //用delete释放空间 } 输出:3 5 0 0 用new动态开辟对象空间,初始化 用new动态开辟空间,调用构造函数初始化
第9讲 构造函数和析构函数 构造函数 析构函数 This 指针
析构函数 析构函数的作用与构造函数正好相反,是在对象的生命 期结束时,释放系统为对象所分配的空间,即要撤消 一个对象。 析构函数也是类的成员函数,类体外定义析构函数的格 式为: ClassName::~ClassName( ) { ...... // 函数体; }
析构函数的特点如下: 析构函数是成员函数,函数体可写在类体内,也可写在类体外。 析构函数是一个特殊的成员函数,函数名必须与类名相同,并 在其前面加上字符“~”,以便和构造函数名相区别。 析构函数不能带有任何参数,不能有返回值,不指定函数类型。 一个类中,只能定义一个析构函数,析构函数不允许重载。 析构函数是在撤消对象时由系统自动调用的。 在程序的执行过程中,当遇到某一对象的生存期结束时,系统 自动调用析构函数,然后再收回为对象分配的存储空间。
析构函数调用 调用缺省的构造函数 调用非缺省的构造函数 退出主函数 调用析构函数 调用析构函数 class A{ float x,y; public: A(float a,float b) { x=a;y=b;cout<<"调用非缺省的构造函数\n";} A() { x=0; y=0; cout<<"调用缺省的构造函数\n" ;} ~A() { cout<<"调用析构函数\n";} void Print(void) { cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1; A a2(3.0,30.0); cout<<"退出主函数\n"; } 调用缺省的构造函数 调用非缺省的构造函数 退出主函数 调用析构函数 调用析构函数
动态开辟内存的情况 在撤消对象时,系统自动收回为对象所分配 的存储空间,而不能自动收回由new分配的动 态存储空间。 在程序的执行过程中,对象如果用new运算符 开辟了空间,则在类中应该定义一个析构函 数,并在析构函数中使用delete删除由new分 配的内存空间。
{ if(string){ Length=strlen(string); Sp=new char[Length+1]; class Str{ char *Sp; int Length; public: Str(char *string) { if(string){ Length=strlen(string); Sp=new char[Length+1]; strcpy(Sp,string); } else Sp=0; void Show(void){ cout<<Sp<<endl; } ~Str() { if(Sp) delete Sp; } }; void main(void) { Str s1("Study C++"); s1.Show(); 在构造函数中将成员数据指针指向动态开辟的内存 用初值为开辟的内存赋值 析构函数,当释放对象时收回用new开辟的空间
用new开辟空间 ‘S’ ‘t’ ‘u’ ‘d’ ‘y’ ‘ ’ ‘C’ ‘+’ ‘\0’ ‘S’ ‘t’ ‘u’ ‘d’ ‘y’ ‘ ’ string ‘S’ ‘t’ ‘u’ ‘d’ ‘y’ ‘ ’ ‘C’ ‘+’ ‘\0’ new开辟的空间 Sp Length=strlen(string); strcpy(Sp,string); Sp=new char[Length+1];
不同存储类型的对象调用构造函数及析构函数 对于全局定义的对象(在函数外定义的对象), 在程序开始执行时,调用构造函数;到程序结束 时,调用析构函数。 对于局部定义的对象(在函数内定义的对象), 当程序执行到定义对象的地方时,调用构造函数 ;在退出对象的作用域时,调用析构函数。 用static定义的局部对象,在首次到达对象的定义 时调用构造函数;到文件结束时,调用析构函数
class A{ float x,y; public: A(float a, float b){x=a;y=b;cout<<"初始化自动局部对象\n";} A(){ x=0; y=0; cout<<"初始化静态局部对象\n";} A(float a){ x=a; y=0; cout<<"初始化全局对象\n"; } ~A(){ cout<<“调用析构函数”<<endl; } }; A a0(100.0);//定义全局对象 void f(void) { cout<<" 进入f()函数\n"; A ab(10.0, 20.0);//定义局部自动对象 static A a3; //初始化局部静态对象 } void main(void) { cout<<"进入main函数\n"; f(); f(); } 初始化全局对象 进入main函数 进入f()函数 初始化自动局部对象 初始化静态局部对象 调用析构函数 进入f()函数 初始化自动局部对象 调用析构函数 调用析构函数 调用析构函数
动态构造及析构对象数组 用new运算符来动态生成对象数组时,自动调 用构造函数,而用delete运算符来释放p1所指 向的对象数组占用的存储空间时,在指针变量 的前面必须加上[ ], 才能将数组元素所占用的 空间全部释放。否则,只释放第0个元素所占 用的空间。 pa1=new A[3]; ..... delete [ ]pa1;
class A{ float x,y; public: A(float a=0, float b=0){x=a; y=b;cout<<"调用了构造函数\n";} void Print(void){ cout<<x<<'\t'<<y<<endl; } ~A() { cout<<"调用了析构函数\n"; } }; void main(void) { cout<<"进入main()函数\n"; A *pa1; pa1=new A[3];//开辟数组空间 cout<<"\n完成开辟数组空间\n\n"; delete [ ]pa1; //必须用[]删除开辟的空间 cout<<"退出main()函数\n"; } 进入main()函数 调用了构造函数 调用了构造函数 调用了构造函数 完成开辟数组空间 调用了析构函数 调用了析构函数 调用了析构函数 退出main()函数
缺省的析构函数 ClassName::~ClassName() { }; 若在类的定义中没有显式地定义析构函数时, 则编译器自动地产生一个缺省的析构函数,其 格式为: ClassName::~ClassName() { }; 任何对象都必须有构造函数和析构函数,但在 撤消对象时,要释放对象的数据成员用new运 算符分配的动态空间时,必须显式地定义析构 函数。
完成拷贝功能的构造函数 完成拷贝功能的构造函数的一般格式为: 可以在定义一个对象的时候用另一个对象为其初 始化,即构造函数的参数是另一个对象的引用, 这种构造函数常为完成拷贝功能的构造函数。 完成拷贝功能的构造函数的一般格式为: ClassName::ClassName(ClassName &<变量名>) { ...... // 函数体完成对应数据成员的赋值 }
A(float a=0, float b=0){x=a; y=b;} A(A &a) { x=a.x; y=a.y;} }; class A{ float x,y; public: A(float a=0, float b=0){x=a; y=b;} A(A &a) { x=a.x; y=a.y;} }; 形参必须是同类型对象的引用 void main(void) { A a1(1.0,2.0); A a2(a1); } 实参是同类型的对象
class A{ float x,y; public: A(float a=0, float b=0){x=a; y=b; cout<<"调用了构造函数\n";} A(A &a) { x=a.x;y=a.y;cout<<“调用了完成拷贝功能的构造函数\n”; } void Print(void){ cout<<x<<'\t'<<y<<endl; } ~A() { cout<<"调用了析构函数\n"; } }; void main(void) { A a1(1.0,2.0); A a2(a1); a1.Print(); a2.Print(); } 用已有的对象中的数据为新创建的对象赋值 调用了构造函数 调用了完成拷贝功能的构造函数 1 2 调用了析构函数
隐含的完成拷贝的构造函数 如果没有定义完成拷贝功能的构造函数,编译器自动生成一个隐含的完成拷贝功能的构造函数,依次完成类中对应数据成员的拷贝。 隐含的构造函数 A::A(A &a) { x=a.x; y=a.y; }
class A{ float x,y; public: A(float a=0, float b=0){x=a; y=b;cout<<"调用了构造函数 \n";} void Print(void){ cout<<x<<'\t'<<y<<endl; } ~A() { cout<<"调用了析构函数\n"; } }; void main(void) { A a1(1.0,2.0); A a2(a1); A a3=a1; a1.Print(); a2.Print(); a3.Print(); } 调用了构造函数 1 2 调用了析构函数
第9讲 构造函数和析构函数 构造函数 析构函数 This指针
This 指针 用类定义一个对象时,由系统自动建立指 向该对象的指针称为this指针。 this指针的默认定义格式: <类名> * const this
This 指针 this是每个成员函数的一个隐含参数,通过该 参数,成员函数可以获知当前对象的地址,从 而可以操纵对象所拥有的数据成员。
This 指针 this作用域是在类内部,编译器会自动将对象 本身的地址作为一个隐含参数传递给函数。也 就是说,即使你没有写上this指针,编译器在 编译的时候也是加上this的。对各成员的访问 均通过this进行。 例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .
This 指针 当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。 #include<iostream.h> class Point { int x, y; public: Point(int a, int b) { x=a; y=b;} void MovePoint( int a, int b){ x+=a; y+=b;} void print(){ cout<<"x="<<x<<"y="<<y<<endl;} }; void main( ) Point point1( 10,10); point1.MovePoint(2,2); point1.print( ); } 当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。
This指针 int Box::volume() { return(height * width * length);} C++把它处理成 int Box::volume (Box * this) { return (this->height * this->width * this->length)} 即在成员函数的形参列表中增加一个this指针。在调 用该成员函数时,实际上是用以下方式调用: a.volume (&a);
第9讲 构造函数和析构函数 构造函数 析构函数 This指针
作业题: “完数”,是指一个数恰好等于它的因子之和。如 6=1+2+3,6是完数;28=1+2+4+7+14,28是完数。 目前已找到48 个完数。 未解之谜:共 有多少完数? 毕达哥拉斯 尼克马修斯 设计构造函数,编程求10000之内的所有“完数”
作业题: 要求: 用面向对象的方法编程,即尽可能地将变量和运算 放在类中完成,主函数应保持最简。 要用到构造函数和析构函数 用面向对象的方法编程,即尽可能地将变量和运算 放在类中完成,主函数应保持最简。 要用到构造函数和析构函数 要将全部完数的加法表达式(如6=1+2+3 )输出在 屏幕上,每行仅输出一式。