付费下载
下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第九章关于类和对象的进一步讨论第九章关于类和对象的进一步讨论构造函数与析构函数对象数组与对象指针对象的动态建立与释放对象的赋值和复制友元9.1
构造函数1.对象的初始化在上一章的例子中,我们通过成员函数来对对象中的数据成员赋初值。这种赋值效率低。类的数据成员能不能在定义类的时候初始化?如classTime{inthour=0;intminute=0;intsec=0;}×9.1
构造函数如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化。如classTime{public://声明为公用成员
hour;minute;sec;};Timet1={14,56,30};//将t1初始化为14:56:30如果包含私有的数据成员,怎么初始化?9.1
构造函数C++提供构造函数处理对象的初始化例:classTime {private: inthour;intminute;intsec;
public:
Time()
//定义构造成员函数,函数名与类名相同{hour=0;
//利用构造函数对对象中的数据成员赋初值minute=0;sec=0;}voidset_time();voidshow_time();}intmain(){Timet1;//建立对象t1,同时调用构造函数t1.Time()t1.set_time();//对t1的数据成员赋值t1.show_time();//显示t1的数据成员的值return0;}赋值语句写在构造函数函数体内,调用构造函数时才对数据成员赋值。构造函数函数体也可以写在类外(需要加类名和作用域限定符)。构造函数的特点构造函数是一种特殊的成员函数,它不需用户调用,,也不能被用户调用,是在建立对象时自动执行一次。构造函数的函数名与类名相同,用户不能随意命名。构造函数不具有任何类型,不返回任何值。构造函数的实现是由用户定义的,其函数体中不仅可以对数据成员赋初值,而且可以包含其他语句(不建议)。如果用户自己没有定义构造函数,C++系统自动生成函数体为空的构造函数(缺省的构造函数)。可以用一个类对象初始化另一个类对象。Timet1;Timet2=t1;缺省的构造函数在定义类时,若没有定义类的构造函数,则编译器自动产生一个缺省的构造函数,其格式为:className::className(){}缺省的构造函数并没有给对象的数据成员赋初值;即新产生对象的数据成员的值是不确定的。classA{ floatx,y;public:
A(){ }//缺省的构造函数,编译器自动产生,可以不写
floatSum(void){returnx+y;} voidSet(floata,floatb){x=a; y=b;}voidPrint(void){ cout<<"x="<<x<<'\t'<<"y="<<y<<endl;}};voidmain(void){ Aa1,a2;//产生对象时,自动调用缺省的构造函数,不赋值
a1.Set(2.0,4.0); cout<<"a1:"; a1.Print(); cout<<"a1.sum="<<a1.Sum()<<endl; a2.Print();//打印随机值}构造函数的作用构造函数的作用就是在对象被创建时,利用特定的值构造对象,将对象初始化为一个特定的状态,使该对象具有区别于其它对象的特征。3.带参数的构造函数前面的构造函数不带参数,在函数体中对数据成员赋初值。每一个对象得到的是同一组初值如果用户希望对不同的对象赋予不同的初值,可以采用带参数的构造函数,将不同的数据传递给构造函数,赋需要的值。classTime {private: inthour;intminute;intsec;
public:
Time()
{hour=0;
minute=0;sec=0;}voidset_time();voidshow_time();}3.带参数的构造函数带参数构造函数首部的一般格式:
构造函数名(类型1形参1,类型2形参2,…)用户不能调用构造函数,无法采用常规的调用函数的方法给出实参。实参是在定义对象时给出的。定义对象时给出实参,一般格式:
类名对象名(实参1,实参2,…)例9.2有两个长方体,其长、宽、高分别为:(1)12,25,30;(2)15,30,21。求它们的体积。编一个基于对象的程序,在类中用带参数的构造函数。classBox{private:intheight;intwidth;intlength;public:
Box(int,int,int);
intvolume();};Box∷Box(intlen,intw,inth){length=len;width=w;height=h;}intBox∷volume(){return(height*width*length);}intmain(){Boxbox1(12,25,30);//定义对象时给出构造函数的实参
cout<<box1.volume()<<endl;Boxbox2(15,30,21);
cout<<box2.volume()<<endl;return0;}关于构造函数,说明以下几点:1、在定义类时,只要显式定义了一个类的构造函数,则编译器就不产生缺省的构造函数2、所有的对象在定义时,都调用了构造函数不存在没有构造函数的对象!classA{ floatx,y;public:A(floata,floatb) { x=a; y=b; }voidPrint(void){ cout<<x<<'\t'<<y<<endl; }};voidmain(void){ Aa1;
Aa2(3.0,30.0);}显式定义了构造函数,不产生缺省的构造函数:
A(){}error,定义时,没有构造函数可供调用定义对象时要注意和构造函数的形式相符!4.用参数初始化表对数据成员初始化前面介绍的数据成员初始化是在函数体中用赋值语句实现还可以用参数初始化表来实现对数据成员初始化,在构造函数的首部实现例:Box::Box(inth,intw,intlen):height(h),width(w),length(len){}例子p250(有错)等价于Box∷Box(inth,intw,intlen){height=h;width=w;length=len;}5.构造函数的重载在一个类中可以定义多个构造函数,以便对类对象提供不同的初始化方法.多个构造函数仍使用相同的名字,只是参数的个数或参数类型不同,这就是构造函数的重载.例:classBox{private:intheight;intwidth;intlength;public:
Box();
Box(inth,intw,intlen):height(h),width(w),length(len){}
intvolume();};Box∷Box(){height=10;width=10;length=10;}intmain(){Boxbox1;//建立对象box1,不指定实参Boxbox2(15,30,25);//建立对象box2,指定3个实参return0;}说明:(1)调用构造函数时,把不需要给出实参的构造函数,称为默认构造函数(defaultconstructor)。显然,无参的构造函数属于默认构造函数。一个类只能有一个默认构造函数。(2)如果在建立对象时选用的是无参构造函数,应注意正确书写定义对象的语句。Boxbox1;不能写成Boxbox1();(3)虽然在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并不是每个构造函数都被执行。Box∷Box(){height=10;width=10;length=10;}说明:系统根据定义对象时给出的实参形式,决定调用相应的构造函数.例p251:产生对象时,系统一定要调用构造函数。所以任何一个对象对应的构造函数必须唯一。intmain(){Boxbox1;cout<<box1.volume()<<endl;Boxbox2(15,30,25);cout<<box2.volume()<<endl;6.使用默认参数的构造函数构造函数中参数的值可以通过实参传递,也可以指定为某些默认值。即在声明构造函数时指定形参的值,如果构造函数被调用时用户没有传递实参,系统就使形参取声明时指定的值,称为默认值。所以也可以是默认的构造函数:classBox{private:intheight;intwidth;intlength;public:
Box(inth=10,intw=10,intlen=10);
intvolume();};Box∷Box(inth,intw,intlen){height=h;width=w;length=len;}intmain(){Boxbox1;cout<<box1.volume()<<endl;Boxbox2(15);//只给box2.height赋值,其余取默认值cout<<box2.volume()<<endl;}调用有默认参数的函数时,实参的个数可以与形参的个数不同,实参未给定的,从形参的默认值得到值。详细规定见p104~106,p2536.使用默认参数的构造函数在构造函数中使用默认参数是很方便的,它提供了建立对象时的多种选择,它的作用相当于好几个重载的构造函数。它的好处是:即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初始化。尤其在希望对每一个对象做同样的初始化时,这种方法更为方便。classBox{private:intheight;intwidth;intlength;public:
Box(inth=10,intw=10,intlen=10);
intvolume();};Box∷Box(inth,intw,intlen){height=h;width=w;length=len;}说明:(1)应该在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。(原因p254)(2)在声明构造函数时,形参名可以省略(3)
如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。说明:我们把调用构造函数时不需要给出实参的构造函数称为默认构造函数。显然,无参的构造函数是默认构造函数,全部参数都指定了默认值的构造函数也是默认构造函数。一个类只能有一个默认构造函数。classBox{private:intheight;intwidth;intlength;public:Box();//无参构造函数
Box(inth=10,intw=10,intlen=10);//全部参数都给定的构造函数
intvolume();};×因为定义对象Boxbox1;编译系统无法识别应该调用哪个构造函数说明:(4)在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。一般使用有默认参数的构造函数时,不进行构造函数的重载Box();//无参构造函数1
Box(inth=10,intw=10,intlen=10);//全部参数都给定的构造函数2
Box(inth,intw);//有2个参数的构造函数3Boxbox1;//无法判别调用构造函数1还是构造函数2Boxbox2(15,30);//无法判别调用构造函数2还是构造函数3×对局部对象,全局对象的初始化:对于局部对象,每次定义对象时,都要调用构造函数。对于全局对象,是在main函数执行之前调用构造函数的。classA{intx,y;public:
A(inta){x=a;cout<<“1\n”;}A(inta,intb){x=a,y=b;cout<<“2\n”;}};Aa1(3);//全局对象voidf(void){Ab(2,3);}voidmain(void){Aa2(4,5);f();f();}12229.2析构函数1.析构函数的特点析构函数也是一个特殊的成员函数。函数体可写在类体内,也可写在类体外。析构函数名字为类名前加“~”。如Box类的析构函数声明为:~Box();
定义为:Box::~Box(){……}
析构函数没有参数,不能有返回值,不能指定返回值类型。一个类中只能定义一个析构函数,析构函数不能重载。1.函数名是标识符,标识符不含”~”2.
Adestructorisamemberfunctionthatisinvokedautomaticallywhentheobjectgoesoutofscopeorisexplicitlydestroyedbyacallto
delete.Adestructorhasthesamenameastheclass,precededbyatilde”~”.9.2析构函数当一个对象生命期结束时,系统自动调用析构函数。具体地说如果出现以下几种情况,程序就会执行析构函数:①如果在一个函数中定义了一个对象(它是局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。②如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或程序结束)时,调用该全局对象的析构函数。9.2析构函数析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。如果用户没有定义析构函数,C++编译系统会自动生成一个析构函数,但它只是具有析构函数的名称和形式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。例9.5包含构造函数和析构函数的C++程序。p256classStudent{private:intnum;stringname;charsex;public:Student(intn,stringnam,chars);
~Student();voiddisplay();};Student::Student(intn,stringnam,chars){num=n;name=nam;sex=s;cout<<″Constructorcalled.″<<endl;}Student::~Student(){cout<<″Destructorcalled.″<<num<<
endl;}例9.5包含构造函数和析构函数的C++程序。intmain(){Studentstud1(10010,″Wang_li″,′f′);stud1.display();Studentstud2(10011,″Zhang_fun″,′m′);stud2.display();return0;}voidStudent::display(){cout<<″num:″<<num<<endl;cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl<<endl;}程序运行结果如下:Constructorcalled.(执行stud1的构造函数)num:10010(执行stud1的display函数)name:Wang_lisex:fConstructorcalled.(执行stud2的构造函数)num:10011(执行stud2的display函数)name:Zhang_funsex:mDestructorcalled.10011(执行stud2的析构函数)Destructorcalled.10010(执行stud1的析构函数)classA{ floatx,y;public:
A(floata,floatb){ x=a;y=b;cout<<“调用非默认的构造函数\n";}A() {x=0;y=0;cout<<“调用默认的构造函数\n";}
~A(){ cout<<"调用析构函数\n";}voidPrint(void){cout<<x<<'\t'<<y<<endl; }};voidmain(void){ Aa1; Aa2(3.0,30.0);
cout<<"退出主函数\n";}调用默认的构造函数调用非默认的构造函数退出主函数调用析构函数调用析构函数9.3调用构造函数和析构函数的顺序一般情况下,对同一存储类别的对象,调用析构函数的次序和调用构造函数的次序相反。对于不同作用域和存储类别的对象构造函数和析构函数的调用顺序全局对象的构造函数,在文件中所有函数执行前调用;主程序执行完调用析构函数。函数中定义的局部对象,在建立对象时调用其构造函数,函数执行结束调用析构函数。9.4对象数组
对象数组:数组中的每一个元素都是类的对象。声明一个一维对象数组的一般形式
类名数组名[常量表达式];引用对象数组元素的公有成员
数组名[下标].成员名;9.4对象数组建立数组时,同样要调用构造函数。如果有50个元素,需要调用50次构造函数。在需要时可以在定义数组时提供实参以实现初始化。如果构造函数只有一个参数,在定义数组时可以直接在等号后面的花括号内提供实参。如Studentstud[3]={60,70,78};//合法,3个实参分别传递给3个数组元素的构造函数如果构造函数有多个参数,对象数组的初始化的方法:花括号中分别调用构造函数,指定实参,对每个元素初始化。每一个元素的实参分别用括号括起来如:Boxa[3]={Box(10,12,15),Box(15,16,17),Box(16,20,26)};例9.6对象数组的使用classBox{private:intheight;intwidth;intlength;public:Box(inth=10,intw=12,intlen=15):height(h),width(w),length(len){}intvolume();};intBox∷volume(){return(height*width*length);}intmain(){Boxa[3]={Box(10,12,15),Box(15,18,20),Box(16,20,26)};cout<<a[0].volume()<<endl;cout<<a[1].volume()<<endl;cout<<a[2].volume()<<endl;}#include<iostream.h>#include<string.h>classCStudent{private: intnumber; charname[10]; intage;public: CStudent(intxh,char*xm,inta); intGetAge();};CStudent::CStudent(intxh,char*xm,inta){ number=xh; strcpy(name,xm); age=a;}intCStudent::GetAge(){ returnage;}例:求学生的平均年龄voidmain(){ intsum=0;
CStudents[5]={CStudent(10001,"AAAAAA",20), CStudent(10002,"BBBBBB",22), CStudent(10003,"CCCCCC",24), CStudent(10004,"DDDDDD",26), CStudent(10005,"EEEEEE",28)
}; for(inti=0;i<5;i++) { sum+=s[i].GetAge(); } cout<<sum/5<<endl;}程序运行结果为:249.5对象指针9.5.1.指向对象的指针对象指针:一个变量,存放对象在内存中的起始地址。声明对象指针的一般形式
类名*对象指针名;
例如:
Box
*p;Boxa(10,12,20);p=&a;通过对象指针访问成员的方法
对象指针名->成员名或(*对象指针名).成员名9.5.2.指向对象成员的指针指向对象数据成员的指针变量定义
数据类型名*指针变量名;例:classTime{public:inthour;intminute;intsec;Time(int,int,int);voidget_time();};Time∷Time(inth,intm,ints){hour=h;minute=m;sec=s;}voidTime∷get_time(){cout<<hour<<″:″<<minute<<″:″<<sec<<endl;}intmain(){Timet1(10,13,56);
int*p1=&t1.hour;Time*p2=&t1;
cout<<*p1<<endl;t1.get_time();p2->get_time();return0;
}9.7对象的动态建立和释放用new运算符动态建立对象,用delete运算符撤销对象.new运算符动态分配内存后,返回指向新对象的指针,需要定义一个指向该类对象的指针变量存放新对象指针,以便对其访问.例:Box*pt;
pt=newBox;pt=newBox(12,13,14);//调用构造函数初始化cout<<pt->height<<endl;//输出该对象的成员
deletept;//调用析构函数有时希望需要时建立对象,不需要时释放空间,提高内存利用率。9.7对象的动态建立和释放在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。9.8对象的赋值和复制9.8.1.对象的赋值同类对象之间可以互相赋值(对象中所有数据成员的值),赋值运算符“=”。对象赋值的一般形式为:
对象名1=对象名2;注意:√对象名1和对象名2必须属于同一类;
√对象的赋值只对数据成员赋值;
√类的数据成员中不能包括动态分配的数据.例9.9对象的赋值。#include<iostream>usingnamespacestd;classBox{public:Box(int=10,int=10,int=10);//声明有默认参数的构造函数intvolume();private:intheight;intwidth;intlength;};Box∷Box(inth,intw,intlen){height=h;width=w;length=len;}intBox∷volume(){return(height*width*length);//返回体积}intmain(){Boxbox1(15,30,25),box2;//定义两个对象box1和box2cout<<″Thevolumeofbox1is″<<box1.volume()<<endl;box2=box1;//将box1的值赋给box2cout<<″Thevolumeofbox2is″<<box2.volume()<<endl;return0;}运行结果如下:Thevolumeofbox1is11250Thevolumeofbox2is112509.8.2.对象的复制在建立一个新对象时,复制另一对象的所有数据成员.一般形式类名对象2(对象1);或类名对象名2=对象名1;对象的复制与前面介绍过的定义对象方式类似,但是括号中给出的参数不是一般的变量,而是对象。在建立对象时调用一个特殊的构造函数——复制构造函数(copyconstructor)。9.8.2.对象的复制复制构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员。如果用户自己没有定义复制构造函数,则编译系统会自动提供一个默认的复制构造函数,其作用只是简单地复制类中每个数据成员。对象复制与赋值的区别对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。Boxb1(15,20,25),b2;b2=b1;对象的复制则是从无到有地建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)。Boxb1(15,20,25),b2=b1;Boxb3(b1);补充举例
设计一个复数类,两个数据成员分别表示复数的实部(real)和虚部(imag),三个构造函数分别在不同的情况下初始化对象,函数Set()设置复数实部和虚部的值,函数Print()输出复数,函数Add()和函数Sub()分别实现复数的加减法运算。classplex{private: doublereal; doubleimag;public: plex(); plex(doubler,doublei); voidSet(doubler,doublei); voidPrint(); plexAdd(plexc); plexSub(plexc);};plex(){ real=0.0; imag=0.0;}plex(doubler,doublei){ real=r; imag=i;}//设置复数类的实部和虚部voidplex::Set(doubler,doublei){ real=r; imag=i;}//显示复数值voidplex::Print(){ cout<<"("<<real<<","<<imag<<")"<<endl;}plexplex::Add(plexc){ plextemp; temp.real=real+c.real; temp.imag=imag+c.imag; returntemp;}plexplex::Sub(plexc){ plextemp; temp.real=real-c.real; temp.imag=imag-c.imag; returntemp;}voidmain(void){ plexa,b(3.0,4.0),c; plexd=b; cout<<"a="; a.Print(); cout<<"b="; b.Print(); cout<<"d="; d.Print(); c=b.Add(d); d=a.Sub(d); cout<<"c="; c.Print(); cout<<"d="; d.Print();}程序运行结果为:a=(0,0)b=(3,4)d=(3,4)c=(6,8)d=(-3,-4)9.10友元在一个类中可以有公用的(public)成员和私有的(private)成员。在类外可以访问公用成员,只有本类中的函数可以访问本类的私有成员。例外——友元(friend)。友元可以访问与其有友好关系的类中的私有成员.友元分为友元函数和友元类。
友元函数:一般函数或类的成员函数.
友元类:友元类的所有成员函数都自动成为友元函数.9.10.1友元函数如果在本类以外的其他地方定义了一个函数(这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数),在类体中用friend对其进行声明,该函数就称为本类的友元函数。友元函数可以访问这个类中的私有成员。9.10.1友元函数声明友元函数,只要在函数原型前加入关键字friend,并将函数原型放在类中.
一般形式为:
friend函数类型友元函数名(参数列表);友元函数可以是一个普通函数,也可以是其他类的成员函数,在其函数体中可以通过对象名直接访问这个类的私有成员。例9.12友元函数的简单例子。classTime{private:inthour;intminute;intsec;public:Time(int,int,int);friend
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 老年护理学:老年护理实践技能
- 胸科手术并发症观察与处理
- 4s店前台绩效考核制度
- 审计存货管理制度
- 京东方审计监察制度
- 中医病房绩效考核制度
- 审计信息专报制度
- 京东专员绩效考核制度
- 外部审计日常管理制度
- 审计工作回访制度
- DB23∕T 2583-2020 固体矿产勘查放射性检查技术要求
- 【《森吉米尔二十辊轧机探析及建模仿真探究》17000字】
- 上海市上海交大附中2026届化学高一上期末教学质量检测试题含解析
- 2025年北京建筑大学专升本城市轨道交通车辆构造考试真题及答案
- 2026甘肃省公务员考试题及答案题型
- 2026河北省考行测题量试题及答案
- 台球室合同转让协议书
- 《弹簧测力计》教案
- 2025年无人机驾驶员职业技能考核试卷:无人机维修与故障排除试题
- 2025至2030中国公路勘察设计行业发展研究与产业战略规划分析评估报告
- 2025年大学辅导员招聘考试题库(教育心理)简答题
评论
0/150
提交评论