构造函数和析构函数省公开课一等奖全国示范课微课金奖课件_第1页
构造函数和析构函数省公开课一等奖全国示范课微课金奖课件_第2页
构造函数和析构函数省公开课一等奖全国示范课微课金奖课件_第3页
构造函数和析构函数省公开课一等奖全国示范课微课金奖课件_第4页
构造函数和析构函数省公开课一等奖全国示范课微课金奖课件_第5页
已阅读5页,还剩35页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第5章结构和析构函数结构函数和析构函数引入带参结构函数和默认结构函数组员初始化列表拷贝结构函数对象结构次序再看new和delete第1页5.1结构函数-背景与结构和其它基本数据类型变量一样:定义一个全局对象会将其所占内存空间清0定义一个局部对象时,不会执行任何初始化动作,其数据组员值完全依赖于堆栈上情况。对象应该表示了现实世界中对应实体,一旦建立对象,其每个数据组员理应有合理初始值。第2页5.1结构函数-背景structRectangle{doublefWidth;doublefHeight;};classStudent{public:intGetAge()const;private:charm_strName[100];intm_nAge;doublem_fScore;}; C语言使用下面语法初始化结构变量:Rectangler={10,20.0};C++中不能使用这种方法,因为一些数据组员可能不是公有组员。//错误:不能在类作用//域外访问非公有数据组员Studentstu={“aa”,10,90};为了防止破坏类封装性,使用特殊组员函数来负责对象初始化。第3页5.1结构函数-关键点与类同名组员函数称为结构函数(Constructor,ctor),此函数在该类对象被创建时会被自动调用,负责完成该对象初始化工作。结构函数不能指定返回类型。每定义一个对象,该对象ctor被自动调用。Students;此句背后动作是首先为对象s分配内存,然后调用ctor初始化对象。classStudent{public:Student();intGetAge()const;private:charm_strName[100];intm_nAge;doublem_fScore;}; Student::Student(){m_nAge=10;m_fScore=100;}第4页5.1结构函数-关键点结构函数不能定义为const,不过能够用来结构常量对象,这是因为只有当结构函数执行完成后,对象常量性才能够建立起来。Rectangle::Rectangle()const{}//错误ctor定义对于常量对象只能调用常组员函数规则,结构函数是个例外。假如一个类对象item是另一个类Container数据组员,则创建Container对象时,会首先自动为item调用结构函数。换句话说,编译器会自动在Container结构函数函数体前插入对item结构函数调用。首先调用对象组员ctor,其次才是本身ctor。[演示]了解结构函数调用时机第5页5.2析构函数-背景一个对象生存期结束(销毁)时可能需要做些清理工作。打开文件需要关闭分配堆内存需要释放假如清理工作对应函数能够被自动调用,就会降低程序员工作量,甚至犯错可能。能够使用析构函数(Destructor,dtor)自动完成清理工作。第6页5.2析构函数-关键点析构函数名必须为~加上类名。示例dtor是一类特殊组员函数,它没有参数,不能重载,不能为其指定返回值。类中至多只有一个dtor。dtor在对象生存期即将结束时由系统自动调用。析构函数返回后,对象结束其生存期。假如没有清理工作要做,则能够不在类中定义析构函数。【示例】析构函数第7页classStudent{public:Student();//ctor

~Student();//dtorintGetAge()const;private:charm_strName[100];intm_nAge;doublem_fScore;}; 第8页5.2析构函数-关键点dtor不能定义为常组员函数,不过常量对象在析构时候一样会调用dtor。Rectangle::~Rectangle()const{}//错误dtor定义对于常量对象只能调用常组员函数规则,dtor是个例外。假如类中包含组员对象,在执行析构函数时将首先执行dtor函数体,然后为类中每个对象组员调用析构函数。换句话说,编译器会在类dtor函数体之后插入对类中每个组员对象dtor调用。对dtor调用次序恰好和对ctor调用次序相反。最先结构对象最终被析构。[演示]了解析构函数调用时机第9页classPerson{public:

Person(){m_strName=newchar[20];}//分配堆空间~Person(){delete[]m_strName;}//释放堆空间voidSetName(constchar*name){strcpy(m_strName,name);}private:char*m_strName;}第10页5.3结构函数重载与dtor不一样,ctor允许带参而且能够带不一样参数,ctor允许重载。教材P273页例子MFC中CString类定义在一个结构函数中调用重载另一个结构函数以简化编程方法是错误。因为结构函数只用于创建对象。这一点和普通重载函数不一样。Tdate::Tdate(intm,intd,inty){….}Tdate::Tdate(intd){//下面语句只是创建//了另一个对象,而且//很快结束生存期。Tdate(4,d,1995);}专门定义一个组员函数,然后全部结构函数调用此组员函数即可。处理方法经过给参数设置默认值,能够将重载结构函数合并为一个。示例第11页classTdate{public:Tdate(intm=4,d=22,y=);private:intmonth;intday;intyear;};Tdate::Tdate(intm/*=4*/,intd/*=22*/,inty/*=*/){ month=m;day=d;year=y; cout<<month<<"/"<<day<<"/"<<year<<endl;}第12页#include<iostream.h>classTdate{public:Tdate() {Init(4,15,1995);}Tdate(intd) {Init(4,d,1995);}Tdate(intm,intd) {Init(m,d,1995);}Tdate(intm,intd,inty) {Init(m,d,y);}

protected:intmonth;intday;intyear;

voidInit(intm,intd,inty){month=m;day=d;year=y;cout<<month<<"/"<<day<<"/"<<year<<endl;}};第13页5.4默认(缺省)结构函数默认结构函数(dctor):不需要用户指定实参就能被调用结构函数。(教材上缺明确定义)Tdate::Tdate(){…}Rectangle::Rectangle(floatw=0.0,floath=0.0);假如没有在类中定义任何ctor,则C++提供一个dctor,该dctor为无参结构函数,不作任何工作。类数据组员假如为简单类型则不会被初始化,假如为其它类对象则自动为其调用结构函数。classStudent{private:charname[20];};等价于classStudent{public:Student(){}private:charname[20];};第14页5.4默认结构函数-示例classStudent{public:Student(char*pName){strcpy(name,pName);}

Student(){name[0]=‘\0’;}private:charname[20];};voidmain(void){

//错误:没有匹配ctorStudents;}只要为类定义了一个带参结构函数,C++就不再提供默认结构函数,假如还需要无参结构函数,则必须显式定义。第15页5.5使用参数初始化对象Tdateadate;//调用默认结构函数(dctor)Tdatebdate(10);//OK,Tdate::Tdate(intd)等价于Tdatebdate=Tdate(10);Tdatecdate(3,12);//OK,Tdate::Tdate(intm,intd)等价于Tdatecdate=Tdate(3,12);Tdateddate();//此句没有语法错误

//Error:leftof'.GetYear'musthaveclass/struct/uniontypeintiy=ddate.GetYear();编译器认为ddate是个函数申明。所以使用dctor结构对象时,对象名后面不能跟()。为何Tdatebdate(10);不看作是函数申明?依据重载函数匹配标准来决定调用哪个结构函数。第16页补充:函数申明和对象定义判别Tdateaa;Tdateaa();Tdateaa(1);Tdateaa(z=1);Tdateaa(intz);Tdateaa(intz=1);判别上面形式是函数申明还是对象定义关键在于括号内字符串能否被看作参数列表,假如能则一定是函数申明。第17页5.6单参数结构函数(1)单参数结构函数是指只用一个参数即能够调用结构函数。能够是只定义了一个参数结构函数,能够是虽定义了多个参数但第一个参数以后全部参数都有缺省值ctor。当使用带一个参数ctor结构对象时,有三种等价写法。最终一个写法只适合用于单参数结构函数。classRational//有理数类{public:Rational(intnumerator=0,intdenominator=1);…};Tdatedate(10);Tdatedate=Tdate(10);Tdatedate=10;Rationalr(2);Rationalr=Rational(2);Rationalr=2;等价等价第18页5.6单参数结构函数(2)缺省情况下,单参数结构函数可用于类型转换。重载函数匹配标准:在严格匹配方式,经内部转换后匹配方式都失败后,将寻找经过用户定义转换后参数匹配函数并调用。单参数结构函数就是其中一个用户定义转换。当同时存在各种可能转换时,重载函数匹配失败,从而造成语法错误。单参数结构函数造成重载函数匹配失败例子classA{public:A(char*);…};voidf(Aa);voidmain(){//OK,结构A一个无//名对象,然后调用ff(“yes?”);}第19页voidf(Bb){…}voidf(Aa){…}voidmain(){

//Error:Ambiguous,二义性//Callf(Aa)?orCallf(Ba)?f(“yes?”);}classA{public:A(char*);…};classB{public:B(char*);…};第20页5.6单参数结构函数(3)单参数ctor用于类型转换时极有可能造成意外错误。[演示]使用关键字explicit能够抑制上述转换行为。使用explicit关键字后,必须显式调用结构函数来初始化对象,也就是说不能再使用Tdatedate=10;这么结构对象方式;不然将造成编译错误。classA{public:

explicitA(char*);…};voidf(Aa);voidmain(){Aa=“Hi”;//ERRORf(“yes?”);//ERROR}Aa=A(“Hi”);//OKf(A(“Yes?”));//OK第21页教材P325错误14.9第一段最终一句:“定义含一个参数结构函数”应为“定义单参数结构函数”;倒数第二行“只会尝试含一个参数结构函数”应为“只会尝试单参数结构函数”;voidfn(Student&s);此代码不能经过编译,应为voidfn(constStudent&s);不然会发生编译错误。第22页5.7组员初始化列表-背景当一个对象是另一个类组员时:Point3daPoint;将自动调用组员m_point2d默认结构函数。//二维平面一个点classPoint2d{public: Point2d(intx=0,inty=0){m_x=x;m_y=y;}private:intm_x;intm_y;};//三维空间一个点classPoint3d{public:Point3d(){m_z=0;}

private:Point2dm_point2d;intm_z;};第23页5.7组员初始化列表-背景Point3dpoint(1,2,3);需要在Point3d结构函数中调用Point2d非默认结构函数,应该怎么做?调用结构函数不可行,因为m_point2d已经结构过了。结构一个无名对象,赋给m_point2d,可行不过效率低。【演示】在Point3d结构函数中结构了一个无名对象,赋给m_point2d,随即将无名对象析构。//三维空间一个点classPoint3d{public:Point3d(){m_z=0;} Point3d(intx,inty,intz){//错误,结构函数不能显式调用m_point2d.Point2d(x,y);m_z=z;}private:Point2dm_point2d;intm_z;};//正确,不过效率低m_point2d=Point2d(x,y);第24页5.7组员初始化列表应使用组员初始化列表(memberinitializationlist)完成上述工作。Point3d::Point3d(intx,inty,intz):m_point2d(x,y),m_z(z){}组员初始化列表必须出现在结构函数参数列表结束(右括号)后和函数体之间,而且必须使用冒号。列表中对数据组员初始化只能使用括号,不能使用等号。每个数据组员在初始化列表中最多只能出现一次。使用组员初始化列表方式将只调用一次Point2dctor。演示第25页补充:赋值和初始化注意区分初始化和赋值:intaInt=10;//初始化intaInt(10);//初始化aInt=8;//赋值Point2dpoint(1,2);//初始化Point2dpoint=Point2d(1,2);//初始化point=Point2d(1,2);//赋值一旦进入结构函数函数体,对象结构已经确立,数据组员就已经存在,在函数体中就只能进行赋值操作。第26页5.7组员初始化列表-使用下面四种情况只能使用组员初始化列表需要调用组员对象带参结构函数时。需要调用基类带参结构函数(后续章节讲解)。需要初始化常量数据组员。需要初始化引用数据组员。推荐使用组员初始化列表来初始化类中每个组员,即使不是上述四种情况之一。Point2d::Point2d(intx=0,inty=0):m_x(x),m_y(y){}第27页5.7组员初始化列表-常数据组员const修饰数据组员不能在类体中显式初始化。//类体中以下代码是错误constfloatfMaxOverdraft=100.0;只能在结构函数初始化列表中初始化。const修饰类数据组员能够经过常量、变量、函数返回值初始化。const修饰数据组员一但初始化就不能再修改,不论是在类体中还是类体外。const修饰数据组员在同一个类不一样对象中能够有不一样值。//Credit.hclassCreditCard//信用卡{public:CreditCard(floate=100.0);…private://最大透支额度constfloatfMaxOverdraft;}//Credit.cpp#include<Credit.h>CreditCard::CreditCard(floata):fMaxOverdraft(a){}#include<Credit.h>CreditCardc1;CreditCardc2(1000.0);第28页5.7组员初始化列表-初始化次序初始化次序并不是按照组员名在初始化列表中次序初始化;而是按照组员在类中申明次序初始化。初始化列表中数据组员总是在ctor函数体中组员赋值前被初始化。Point3d::Point3d(intx,inty,intz):m_z(z),m_point2d(x,y){}初始化次序为m_point2d,m_zPoint3d::Point3d(intx,inty,intz):m_z(z){m_point2d=Point2d(x,y);}初始化次序为m_point2d,m_z再对m_point2d赋值。第29页5.7组员初始化列表-难于发觉错误教材P287ch12_15.cpp例子。因为num先于age初始化,所以num值不正确。当需要使用一个组员去初始化另一个组员时,应该将这么代码放在ctor函数体中。查看修改后代码第30页classA{public:A(intj):age(j){

num=age+1;cout<<"age:"<<age<<endl;cout<<"num:"<<num<<endl;}protected:intnum;intage;};voidmain(){Asa(15);}classA{public:A(intj):age(j),num(age+1){cout<<"age:"<<age<<endl;cout<<"num:"<<num<<endl;}protected:intnum;intage;};voidmain(){Asa(15);}第31页5.8拷贝结构函数-背景对象作为函数参数传递时,因为参数传递传值语义,形参实际上是实参对象一个拷贝,换句话说形参对象是以实参对象为原本结构出来一个新对象。有时候也需要用一个已经有对象去结构一个新对象。支持Undo类对象:在用户对对象执行修改之前,首先备份一个拷贝;当用户需要撤消对象修改时能够使用备份对象恢复。第32页5.8拷贝结构函数-语法拷贝结构函数申明语法:classname(classname&src);或者classname(constclassname&src);参数必须是引用,为何?拷贝结构函数调用时机:使用已经有对象去初始化一个新对象时。Point2dp1(1,2);Point2dp2(p1);或者Point2dp2=p1;从函数中返回对象时。向函数传递对象时。第33页5.8拷贝结构函数-默认行为假如没有显式定义拷贝结构函数,C++将为我们提供一个默认拷贝结构函数从而支持对象以拷贝方式初始化,对象拷贝方式和结构变量一样,都是按组员初始化(MemberwiseCopy)。假如一个类数据组员包含其它类对象,则首先对每个组员对象调用拷贝结构函数或者默认拷贝结构函数,然后再拷贝类中非对象组员。【演示】P318页例子:了解调用时机实际上默认拷贝结构函数初始化方式能够了解为将源对象占用内存空间完整拷贝到目标对象。Point2dp1(1,2);Point2dp2=p1;//或Point2dp2(p1);则p2.m_x为1,p2.m_y为2Point3dp1(1,2,3);Point3dp2=p1;//或Point3dp2(p1);则p2.m_x为1,p2.m_y为2,p2.m_z为3第34页5.8拷贝结构函数-默认行为C++所提供默认拷贝行为在一些情况下是不适当,甚至是错误。【演示】默认拷贝结构函数带来问题默认拷贝行为只是拷贝每个组员,包含指向堆内存指针,从而使得两个对象指向了同一块内存;其中一个对象在析构时释放了该内存,造成了另一个对象组员指针指向了无效内存,成为野指针。第35页5.8拷贝结构函数-自定义拷贝为处理按组员初始化(浅拷贝)存在问题,能够自定义拷贝结构函数。【演示】使用深拷贝处理上一个演示问题当我们需要拷贝行为和默认行为不一样时就需要定义拷贝结构函数。默认拷贝行为造成两个对象拥有对同一个资源全部权时,需要拷贝结构函数。如某个类有一个组员,该组员应该对每个对象都唯一。此时需要定义拷贝结构函数通常假如类需要一个析构函数,则它也需要一个拷贝结构函数,因为一个自定义析构函数意味着额外资源需要在析构之前被释放。类Point3d是否需要我们编写自定义拷贝结构函数?第36页Point3d类定义//二维平面一个点classPoint2d{public: Point2d(intx=0,inty=0):m_x(x),m_y(y){}private:intm_x;intm_y;};//三维空间一个点classPoint3d{public:Point3d(intx=0,inty=0,intz=0):m_point2d(x,y),m_z(z){}private:Point2dm_point2d;intm_z;};第37页5.8拷贝结构函数-调用一但自定义了拷贝结构函数,则类及其组员对象拷贝结构工作全部由自定义拷贝ctor负责。示例当需要调用某个类拷贝结构函数时(用已经有对象初始化一个新对象时,从函数中返回对象时,以传值方式向函数传递对象时):假如对象所属类显式没有定义拷贝结构函数,则执行默认拷贝结构函数(按组员初始化)。假如对象所属类显式定义了拷贝结构函数:假如拷贝结构函数是可访问,就去调用它假如拷贝结构函数是不可访问,就产生编译错误。可利用这一点使得类不允许拷贝结构。示例第38页classA{public:A(){…}

A(constA&){…}…};classB{public:B(){…}

B(constB&b){…}private:Am_a;};voidmain(){Bb1;//为B生成默认拷贝结构函数,将调用A::A(constA&)Bb2=b1;}//将不会调用A::A(constA&),而只是调用B::B(constB&)B(constB&b):m_a(b.m_a){…}//将首先调用A::A(constA&),然后调用B::B(constB&)第39页classA{public:A(){…}private:

//只是申明此函数,并不定义此函数。//使用了C++中延迟错误检验特征,即只有当一个函数//在程序中被调用了,编译器才会去检验函数定义是否//存在。

A(constA&);…};…Aa;//errorC2248:'A::A':cannotaccessprivatememberdeclaredinclass'A'Ab(a);第40页5.9示例P326习题14.23个印刷错误for(inti=0;i<size;i++)cout<<buffer[j]<<endl;buffer[j]=j+1;拷贝结构实现为:Vector::Vector(constVector&v) :size(v.size){buffer=newint[size];for(inti=0;i<size;i++)buffer[i]=v.buffer[i];}Vector::Vector(constVector&v) :size(v.size){buffer=newint[size];memcpy(buffer,v.buffer, size*sizeof(int));}第41页5.9示例使用结构函数和析构函数来完成第四章中类LinkListInitialize函数和Destroy函数所完成功效,并编写拷贝结构函数实现链表类深拷贝。演示使用结构函数和析构函数重写第四章中FileWrapper类。为了预防FileWrapper两个对象引用到同一份打开文件上,将拷贝结构函数申明为私有。演示第42页5.10再看new和deletemalloc和free无能为力地方:void*malloc(size_tsize);voidfree(void*memblock);上述两个函数原型没有包含类型信息,所以在为对象分配内存时无法自动调用结构函数,在释放对象占用内存时无法自动调用析构函数。new和delete用于处理上述问题。在使用new分配内存时提供了类型信息,实现计算类型大小,分配对应数量堆内存,然后依据类名自动调用结构函数。在使用delete释放内存时,首先依据指针类型信息自动调用析构函数,然后释放内存。第43页5.10再看new和delete使用new为创建一个对象时,能够在类名后跟参数,new依据参数匹配标准调用结构函数;假如没有跟参数,则调用默认结构函数。Point2d*p=newPoint2d; deletep;Point2d*p=newPoint2d(); deletep;Point2d*p=newPoint2d(1,2); deletep;Point2d*p2=newPoint2d(*p); deletep2;在堆上分配对象数组时,不能提供参数,所以只能调用类默认ctor。假如该类没有定义默认结构函数,则不能分配这类对象数组。在堆上分配对象数组必须用delete[]释放。演示Point2d*p=newPoint2d[10];delete[]p;第44页5.10示例P326习题14.1#include<iostream.h>classSamp{public:voidSetij(inta,intb){i=a,j=b;}

~Samp(){cout<<"Destroying.."<<i<<endl;}

intGetMulti(){returni*j;}protected:inti;intj;};voidmain(){Samp*p=newSamp[10];if(!p){cout<<"Allocationerror\n";return;}

for(intj=0;j<10;j++)p[j].Setij(j,j);for(intk=0;k<10;k++){cout<<"Multi["<<k<<"]is:"<<p[k].GetMulti()<<endl;delete[]p;}第45页5.11对象结构次序局部自动对象在块(局部)作用域内按照定义次序结构并逆序析构。在局部作用域中最早结构对象最迟析构。局部静态对象在第一次被使用时结构,而且在程序退出时析构。全部全局对象在主函数main之前都将结构完成。假如结构全局对象时发生了错误或者死循环,main函数将得不到控制权。全局(静态或者非静态)对象在文件作用域中按照定义次序结构。不一样文件作用域中全局对象结构次序不定。不应该在一个全局对象中使用另一个全局对象来初始化对象组员按其在所属类中申明次序结构并逆序析构。【演示】不一样作用域对象结构次序第46页5.12暂时对象当函数返回一个对象时,要创建一个暂时变量以存放返回值。是否使用暂时对象是由编译器来决定,C++标准中并没有明确要求什么时候一定要使用暂时变量。教材P323例子将这个例子main函数中代码修改为 Students=fn();//初始化,不是

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论