版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C++程序设计教程(第二版)第九章对象生灭11/17/20221C++程序设计教程(第二版)第九章对象生灭11/9/20第九章内容构造函数设计构造函数的重载类成员初始化构造顺序拷贝构造函数析构函数对象转型与赋值11/17/20222第九章内容构造函数设计11/9/202221.构造函数设计
初始化要求定义一个对象,其形式与定义一个变量相似,但更具意义。对象与变量的不同在于对象对应于事物,要求从诞生之时起便有明确的意义.对象必须建立一种初始化机制,以满足在编程中可以针对实际问题所提出的要求而赋给一个有意义的初值。创建一个对象,若创建的是全局对象,则以全0的模式表示对象,若创建局部对象,则以随机值表示对象。11/17/202231.构造函数设计
初始化要求定义一个对象,对象的初始化不是简单的参数与成员对应,而是联系参数到成员的过程.封装性要求StructPoint{intx,y;};Pointd={2,3};classPoint{intx,y;//…};Pointd={2,3};正确错误封装性的要求杜绝了这种初始化形式,事实上,这种形式根本不能完成对象的初始化过程中的校验和计算工作。对象通常是一个复杂的实体,在构建对象过程中,并不一定是一个初值对应一个对象分量。许多时候,一个初值只是传递一个信息,告诉对象的创建工作应怎么进行。封装性要求对象创建中按传递的信息进行一个过程化的初始化工作。11/17/20224对象的初始化不是简单的参数与成员对应,而是联系函数形式构造函数(constructor)是与类名同名的特殊的成员函数,当定义该类的对象时,构造函数将被自动调用以实现对该对象的初始化。构造函数不能有返回值,因而不能指定包括void在内的任何返回值类型。构造函数的定义体可与其它成员函数成员一样,放在类内或类外都可。构造函数的定义格式为:类名(形参说明){函数体}构造函数既可定义成有参函数,也可义成无参函数,要根据问题的需要来定。全局变量和静态变量在定义时,将自动赋初值为0;局部变量在定义时,其初始值不固定的。而当对象被定义时,由于对象的意义表达了现实世界的实体,所以一旦定义对象,就必须有一个有意义的初始值,在C++中,在定义对象的同时,给该对象初始化的方法就是利用构造函数。11/17/20225函数形式构造函数(constructor)是与类名同名的特殊例:类person包括4个数据成员,用来记录人员信息。生成对象obj,并使用构造函数为obj赋予初始值。#include<windows.h>#include<iostream.h>classPerson //定义类{private: //类Person的数据成员charname[10]; //姓名intage; //年龄intsalary; //薪金chartel[8]; //电话public: //构造函数PersonPerson(char*xname,intxage,intxsalary,char*xtel);voiddisp();};//函数Person的定义Person::Person(char*xname,intxage,intxsalary,char*xtel){strcpy(name,xname);//给各数据成员提供初值age=xage;salary=xsalary;strcpy(tel,xtel);}11/17/20226例:类person包括4个数据成员,用来记录人员//函数disp的定义voidPerson::disp(){cout<<endl;cout<<"姓名:"<<name<<endl;cout<<"年龄:"<<age<<endl;cout<<"工资:"<<salary<<endl;cout<<"电话:"<<tel<<endl<<endl;}//主函数voidmain(){//生成对象obj并初始化Personobj("张立三",25,850,"45672314");//显示objobj.disp();}11/17/20227//函数disp的定义11/9/20227程序的执行结果是:姓名:张立三年龄:25工资:850电话:45672314在主函数中的Personobj("张立三",25,850,"45672314");中完成了以下几个功能:1.定义并生成了对象obj。2.在生成对象obj的同时,自动调用相应类的构造函数Person3.将初始值"张立三",25,850,"45672314"传递给构造函数Person相应的形参xname,xage,xsalary,xtel。4.执行构造函数体,将相应的值赋给相应的数据成员。11/17/20228程序的执行结果是:11/9/20228一次性对象创建对象时如果不给出对象名,直接以类名调用构造函数,则产生一个无名对象。无名对象经常在参数传递时使用。例如:cout<<Date(2003,12,23);Date(2003,12,23)是一个对象,该对象在做了<<操作后便烟消云散了,所以这种对象一般用在创建后不需要反复使用的场合。11/17/20229一次性对象创建对象时如果不给出对象名,直接以
2.构造函数重载
构造函数毕竟是函数,是函数就可以重载。不但可以重载,还可以设置默认参数。如果一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。classDate{public:Date(conststring&s);//重载Date(inty=2003,intm=12,intd=1);//参数默认//...};intmain(){Dated(“2006-12-26”);Datee(2000,12,25);Datef(2001,10);Dateg(2002);Dateh;//...}11/17/202210
2.构造函数重载
构造函数毕竟是函数,是函Dateh;这行代码似乎是一个错误,对于对象的创建,即对于无参的构造函数的调用,应该是g(),加上类名,即写成:Dateg();但是,从语法上来讲,这个语句也是名叫g的返回临时Date对象的函数声明!因此,C++语言在设计时,为了区别无初始化的对象定义和返回类对象的无参函数声明的差别,也为了保持无初始化变量定义与无初始化(无参)对象定义的一致性,规定无参对象定义语句为:inta;//变量定义intb;//返回整型值的无参函数声明Dateg;//无参对象定义,加空括号后就成了无参函数声明Dateg();//返回类对象的无参函数声明11/17/202211Dateh;这行代码似乎是一个错误,对于对象的创建,即对对于返回对象值的有参函数声明和有初始化的对象定义语句,由于函数声明中的参数有类型引导,而对象定义的初始化中无类型引导,它们本身(或在编译器看来)是可以区分的:Datee(2002);//对象定义Datee(inty);//函数声明11/17/202212对于返回对象值的有参函数声明和有初始化的对象C++规定,每个类必须有一个构造函数。如果在类中没有显式定义构造函数时,则C++编译系统在编译时为该类提供一个默认的构造函数,该默认构造函数是个无参函数,它仅负责创建对象,而不做任何初始化工作。classDate{public://相当于定义了Date(){}};intmain(){Dated;//ok//...}与变量定义相似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的默认值为0,否则对象的初始值是不定的。11/17/202213C++规定,每个类必须有一个构造函数。如果在类中没有显式定只要一个类定义了一个构造函数(不一定是无参构造函数),C++编译系统就不再提供默认的构造函数。任何其他的构造函数定义,都将阻止默认无参空函数的产生:classDate{public:Date(inty,intm,intd){}//...};intmain(){Dated;//error//...}11/17/202214只要一个类定义了一个构造函数(不一定是无参构造函数),11/当构造函数有缺省参数时,称为具有缺省参数的构造函数,在使用时要防止二义性。如:classMyclass//定义类Myclass{private:intmember;public:Myclass();Myclass(inti);……}Myclass:Myclass()//构造函数Myclass{member=10;}11/17/202215当构造函数有缺省参数时,称为具有缺省参数的构Myclass:Myclass(inti=10)//构造函数Myclass(inti),该函数//的形参i为缺省参数{member=i;}voidmain(){Myclassx(20);Myclassy; //产生二义性,无法确定自动调用哪个构造//函数完成对象的构造……}11/17/202216Myclass:Myclass(inti=10)//构造回顾:上节课主要讲了构造函数的概念、意义及定义方式、构造函数的重载。11/17/202217回顾:11/9/202217
3.类成员初始化
默认调用的无参构造函数在类定义中有数据成员和成员函数,数据成员可以是内部数据类型的变量实体,也可以是对象实体。如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。这样就会面临一些问题。例:有一个学号类和一个学生类,学生类中包含了学号类的对象,因此在构造学生类对象时,面临着学号类对象的构造。11/17/202218
3.类成员初始化
默认调用的无参构造函数classStudentID{intvalue;public:
StudentID(){staticintnextStudentID=0;value=++nextStudentID;cout<<"Assigningstudentid"<<value<<"\n";}};//-----------------------------------classStudent{stringname;
StudentIDid;public:Student(stringn="noName"){cout<<"Constructingstudent"+n+"\n";name=n;}};//-----------------------------------intmain(){Students("Randy");}//==============================在学生类的构造函数中并没有看到学号类对象初始化的痕迹,而数据成员name倒被赋了初值。从运行的结果看,当学生类对象被构造时,一个学号对象也创建了,而且学号类的构造函数先于学生类对象的构造函数体的执行而执行。11/17/202219classStudentID{在学生类的构对于Students(“Randy”);其内部的执行顺序是这样的:(1)先分配学生类对象s的空间,调用Student构造函数;(2)在Student构造函数体尚未执行时,由于看到了类的对象成员ID,转而去调用学号类的无参构造函数;相当于执行定义语句:StudentIDid;(3)执行了学号类构造函数体,输出结果的第1行信息,返回到Student构造函数;(4)执行Student构造函数体,输出结果的第2行信息,完成全部构造工作。说明:先成员构造,后自身构造.成员构造不见显式调用,而是悄悄调用无参构造函数.对学号类构造函数的调用时默认的,默认调用便是调用无参构造函数,而正好学号类设计的构造函数就是无参构造函数。11/17/202220对于Students(“Randy”);其内部的执行顺序初始化的困惑在刚刚的实例中,只是初始化了名称,而没有给定学号。如果,在学生对象创建中,既要初始化名称,又要给定一个指定的初始化学号,也就是不要默认调用无参构造函数,那么类该如何操作呢?如果不能在创建StudentID的过程中初始化对象,就不能将对象值设置到位。下面给出一个不正确的初始化尝试,还是对刚才的例子作修改:11/17/202221初始化的困惑在刚刚的实例中,只是初始化了名称classStudentID{intvalue;public:
StudentID(intid=0){value=id;cout<<"Assigningstudentid"<<value<<"\n";}};//-----------------------------------classStudent{stringname;StudentIDid;public:Student(stringn="noName",intssID=0){cout<<"Constructingstudent"+n+"\n";name=n;
StudentIDid(ssID);}};//-----------------------------------intmain(){Students("Randy",58);}//====================================运行结果:11/17/202222classStudentID{运行结果:11/9/2022S对象初始化的学号为58,重新设计了Student构造函数,将该值传递给了它,所以形参ssID的值也为58。但是我们看到转而调用的StudentID构造函数执行体却没有输出58,而是默认的0学号。这说明转而调用的仍然是无参构造函数。将StudentID的构造函数写成参数默认是为了让无参构造函数调用也能通过,以便让整个程序顺利运行。乃至后来,要想在Student类内,通过构造函数体中的对象初始化创建的定义语句来改变id的值,发现这仅仅只是区别于s对象中的id对象的一个局部对象,等到构造函数结束时,该局部对象的局部生命期到期而不销毁。11/17/202223S对象初始化的学号为58,重新设计了St如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。
如果类A的构造函数为有参函数时,则在程序中必须在类B的构造函数的括号后面加一“:”和被调用的类A的构造函数,且调用类A的构造函数时的实参值必须来自类B的形参表中的形参。这种方法称为初始化表的方式调用构造函数。
这种在构造函数的参数列表的右括号后面,花括号前面,用冒号引出构造函数的调用表,可以省略类型名称,但却行创建对象之职。初始化表调用构造函数11/17/202224如果一个类A的对象作为另一个类B的数据成员,则类X的构造函数的定义格式应为:X::X(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n){……}其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。11/17/202225类X的构造函数的定义格式应为:11/9/202例:#include<iostream>usingnamespacestd;//-------------------------------------classStudentID{intvalue;public:
StudentID(intid=0)//有参构造函数{value=id;cout<<"Assigningstudentid"<<value<<endl;}};//-----------------------------------对刚才的例子进行改进11/17/202226例:#include<iostream>对刚才的例子进行改进classStudent{stringname;StudentIDid;public:Student(stringn="noname",intssID=0):id(ssID),name(n){cout<<"Constructingstudent"<<n<<"\n";}};//-----------------------------------intmain(){Students("Randy",98);Studentt("Jenny");}//==========================11/17/202227classStudent{11/9/202227
4.构造顺序
在程序中,各种作用域的对象很多,有些对象还包含在别的对象里面,有些对象早在main函数开始运行之前就已经建立了。创建对象的惟一途径是调用构造函数。构造函数时一段程序,所以构造对象的先后顺序不同,直接影响程序执行的先后顺序,导致不同的运行结果。C++给构造对象的顺序做了专门的规定。局部对象局部和静态对象是指块作用域(局部作用域)和文件作用域的对象。它们的声明顺序与它们在程序中出现的顺序是一致的。见课本307页例9.811/17/202228
4.构造顺序
在程序中,各种作用域的对象在C中,所有的局部变量(没有对象)都是在函数开始执行时统一创建的,创建的顺序是根据变量在程序中按语句行出现的顺序。而C++却不同,它是根据运行中定义对象的顺序来决定对象创建的顺序。而且,静态对象只创建一次。全局对象和全局变量一样,所有全局对象在主函数启动之前,全部已近被构造。因为构造过程是程序语句的执行过程,所以,可以想象在程序启动之前,已经有程序语句(构造函数)在那里被执行过了。11/17/202229在C中,所有的局部变量(没有对象)都是在函数开同一工程不同代码文件全局对象的创建没有明确顺序规定.对策:不要让不同文件的全局对象互为依赖.因为依赖具有先后性,而其全局对象的创建不能保证该依赖性发挥作用.全局对象在main函数启动之前生成,而调试则在main函数启动之后.对策:调试时,应先将全局对象作为局部对象来运行观察.或者,在构造函数中添加输出语句来观察运行过程.11/17/202230同一工程不同代码文件全局对象的创建没有明确顺序规定.11/9成员对象的构造顺序按类定义的出现顺序成员对象classA{public:A(intx){cout<<"A:"<<x<<"->";}};//-----------------------------------classB{public:B(intx){cout<<"B:"<<x<<"->";}};//-----------------------------------classC{Aa;Bb;public:C(intx,inty):b(x),a(y){cout<<"C\n";}};//-----------------------------------intmain(){Cc(15,9);}//============================classC按A、B的顺序定义对象成员,其构造函数又按b、a的顺序进行构造。到底听谁的,以定义顺序说了算。11/17/202231成员对象的构造顺序按类定义的出现顺序成员对象classA{构造位置全局数据区:全局对象,静态全局对象,静态局部对象,常对象类的静态数据成员也存放在该数据区。目的是为了让对象的生命周期与运行的程序等寿命。栈区:局部对象在函数中定义好、局部对象,随着被调用的返回而析构(销毁)。动态存储区(也称堆区):用new申请的对象用delete销毁。特殊地址空间除此之外,还可以指定特殊地址空间,存放对象11/17/202232构造位置全局数据区:11/9/202232回顾11/17/202233回顾11/9/202233
5.拷贝构造函数对象本体与对象实体:对象本体也是对象主体,对象实体则还包括属于对象的衍生物,如,某个人体是人类对象的主体,然而某人还拥有父母,房产等属于某人的世系或资产,描述人的属性不仅仅只是人体数据.从形式上看,对象除了包括数据成员,还包括指向数据的指针.对象本体与对象实体是不一致就是说有时我们会需要把a对象的属性全部转给b对象,
对象本体与对象实体一致就是你要将a对象的属性赋给a对象本身。11/17/202234
5.拷贝构造函数对象本体与对象实体:11/9/20223对于一个简单的变量的初始化方法是用一个常量或变量初始化另一个变量,例如:doublex=12.36;doubley=x;疑问:前面介绍了用构造函数初始化对象,那么能不能像简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?答案是肯定的。例:以定义的一个Point类为例,说明用一个对象来初始化另一个对象。Pointpt1(10,20);Pointpt2=pt1;那么,后一句也可写成Pointpt2(pt1);它是用pt1初始化pt2,此时,pt2各个成员的值与pt1各个成员的值相同,也就是说,pt1各个成员的值被复制到pt2相应的成员当中。在这个初始化过程当中,实际上调用了一个拷贝构造函数。11/17/202235对于一个简单的变量的初始化方法是用一个常量或变量初始化另一个拷贝构造函数:拷贝构造函数是C++中引入的一种新的构造函数。定义一个拷贝构造函数的方式是:类名(const类名&形式参数){函数体}由此可看出拷贝构造函数的特点:(1)拷贝构造函数的名称与类的名称相同,且它只有一个参数,该参数就是对该类对象的引用。(2)拷贝构造函数的功能是用于实现对象值的拷贝,通过将一个同类对象的值拷贝给一个新对象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。(3)每个类都有一个拷贝构造函数11/17/202236拷贝构造函数:11/9/202236以本类对象为常量引用参数的构造函数:classDate{public:Date();
Date(constDate&d);//...};Datex;//调用无参构造函数Datey(x);//调用拷贝构造函数例:11/17/202237以本类对象为常量引用参数的构造函数:例:11/9/20223默认拷贝构造函数:若类中没有定义拷贝构造函数,则系统会悄悄定义一个默认空拷贝构造函数:Date(constDate&d){}默认拷贝构造函数体一定是空的.空拷贝构造函数负责将传递的对象到新创的对象做对象本体的位对位拷贝.(甚至连指针值都相等,即与参数对象拥有共同的资源)11/17/202238默认拷贝构造函数:11/9/202238自定义拷贝构造函数:为了达到对象实体也就是对象整体复制目的,就需要另外定义一个拷贝构造函数,以覆盖默认的拷贝构造函数:见课本例315页9.13自定义拷贝构造名也是类名,它是构造函数的重载,一旦自定义了拷贝构造函数,默认的拷贝构造函数就不再起作用了。在自定义拷贝构造函数之前,我们进行拷贝对象构造时,都是在用默认的拷贝构造函数,因为那时的对象本体与对象实体是一致的。所以,自定义拷贝构造函数在对象本体与对象实体不一致时,便是需要的,否则无此需要。11/17/202239自定义拷贝构造函数:为了达到对象实体也就是对象自定义拷贝构造函数体的工作不负责位对位对象复制,一般来说,它负责资源分配和由此而来的指针修改.classPerson{
char*pName;public:Person(char*pN="noName"){pName=newchar[strlen(pN)+1];
if(pName)strcpy(pName,pN);}Person(constPerson&s){pName=newchar[strlen(s.pName)+1];
if(pName)strcpy(pName,s.pName);}~Person(){
delete[]pName;}};11/17/202240自定义拷贝构造函数体的工作不负责位对位对象复制,一般来说,它6.析构函数当一个对象被定义时,系统自动调用构造函数为该对象分配相应的资源,当对象使用完毕后,这些系统资源需要在对象消失前被释放。人为的动态内存释放工作由析构函数来完成,析构函数时一种特殊的成员函数,它的意义是做关于对象失效之前瞬间的善后工作。它的功能与构造函数的工作正好相反,是用来释放一个对象。我们知道有内存申请,也就有内存释放。一般来说,需要定义构造函数的类也需要定义析构函数,不需要构造函数的类,也无须定义析构函数。所以析构函数和构造函数是成对出现的。
11/17/2022416.析构函数当一个对象被定义时,系统自动调用析构函数的定义方式为:~类名(){函数体}定义:特点:(1)析构函数名与构造函数名相同,但它前面必须加一个“~”,用以与析构函数相区别;(2)在定义析构函数时,不能指定任何返回类型,也没有参数,而且不能重载。因此在一个类中只能有一个析构函数。(3)析构函数可以被用户调用,也可以被系统调用。在下面两种情况下,析构函数会被自动调用11/17/202242析构函数的定义方式为:定义:特点:11/9/202242a、如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用。b、当一个对象是使用new运算符动态创建时的,在使用delete运算符自动释放时,delete将会自动调用析构函数。每一个类若没有显示地定义为一个类定义析构函数,编译系统会自动地生成一个默认的析构函数,默认析构函数时一个空函数,实际上什么操作都不进行。例:程序中三个类A、B、C分别有一个析构函数。代码如下:11/17/202243a、如果一个对象被定义在一个函数体内,则当这个函数结束时,该classA{public:A(){cout<<"A->";}~A(){cout<<"<-~A";}};//-------------------------classB{public:B(){cout<<"B->";}~B(){cout<<"<-~B";}};//-------------------------classC{public:C(){cout<<"C->";}~C(){cout<<"<-~C";}};//-------------------------voidfunc(){cout<<"\nfunc:";Aa;cout<<"ok->";staticBb;Cc;}//----------------------------intmain(){cout<<"main:";for(inti=1;i<=2;++i){for(intj=1;j<=2;++j)if(i==2)Cc;elseAa;Bb;}func();func();}//=================11/17/202244classA{voidfunc(){11/9/20224注:(1)析构函数总是出现在对象的生命期结束之时。静态对象在程序运行结束时析构。(2)对于一个简单的类来说,大多可以直接使用系统提供的默认析构函数。但是,如果在类的对象中分配有动态内存(如:用new申请分配的内容)时,就必须为该类提供适当的析构函数,完成清理工作。(3)对象被析构的顺序与对象建立时的顺序正好相反。对象析构与构造函数的关系是栈数据结构中的入栈和出栈的关系,即最后构造的对象先被析构。栈底封闭指针11/17/202245注:栈底封闭指针11/9/2022457.对象转型与赋值用于转型的构造函数5/8与5.0/8的结果是不同的,原因是C++执行了两种不同的操作。编译器会将5.0/8中的整数8自动转换成double,匹配两个double数的除法操作。这是内部数据类型所具有的转换功能。但是,对于类类型,其自动转换的功能必须编程实现。那就是定义一个含有一个参数的构造函数。例:classStudent{public:Student(conststring&n);};voidfn(Student&s);intmain(){stringt=“jenny”;fn(t);//参数为string,却能匹配Student类型}11/17/2022467.对象转型与赋值用于转型的构造函数5/刚才的例子也是在告知,如何将string对象转换成一个Student对象。如果有重载函数:voidfn(string&s){cout<<“ok\n”;}则main函数中的fn(t)函数调用将会马上匹配。否则,匹配了voidfn(Student&s)函数。从fn(Student&)和Student(conststring)可以推得fn(string)调用,这就是构造函数用来从一种类型转换成另一种类型的能力。对象转型的规则:只会尝试含有一个参数的构造函数如果有二义性,则会放弃尝试推导是一次性的,不允许多步推导11/17/202247刚才的例子也是在告知,如何将string对象对象拷贝对象赋值即对象拷贝:两个已经存在的对象之间的复制Persond(“Ranny”)Persong;d=g;//对象赋值对象赋值便是使用类中的赋值操作符.如果类中没有定义赋值操作符,则系统悄悄地定义一个默认的赋值操作符:Person&operator=(constPerson&p){memcpy(*this,*p,sizeof(p));}11/17/202248对象拷贝对象赋值即对象拷贝:两个已经存在的对象之11/9/2当对象本体与对象实体不同时,则对象赋值操作符与拷贝构造函数一样,必须自定义:classPerson{
char*pName;public:Person(char*pN="noName");Person(constPerson&s);Person&operator=(constPerson&s){
if(this==&s)returns;
delete[]pName;pName=newchar[strlen(s.pName)+1];
if(pName)strcpy(pName,s.pName);
return*this;}~Person(){delete[]pName;}};定义赋值操作符:1排除客体对象与本对象同一的情况2释放本对象的资源3申请客体对象相同大小的资源空间4拷贝客体对象的资源到本对象11/17/202249当对象本体与对象实体不同时,则对象赋值操作符与拷贝构造函数一8.this指针用类定义对象时,系统会为每一个对象分配存储空间。如果一个类包括数据和函数,要分别为数据和函数的代码分配存储空间。每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间。例如:ClassPoint{intxVal,yVal;public:
point(intx,inty);point(point&pt);voidshowpoint(){cout<<“(“<<xVal<<‘,’<<yVal<<“)”;}}Pointa,b,c;11/17/2022508.this指针用类定义对象时,系统会为对象aintxValIntyVal对象bintxValIntyVal对象cintxValIntyVal成员函数:Point(intx,inty);Point(Point&pt);voidShowPoint();对象存储示意图对象a、b、c在内存中各占8个字节存储数据成员,而只用一段空间来存放这个共同的函数代码段。疑问:当不同对象的成员函数引用数据成员时,怎么能保证引用的是所指定的对象的数据成员呢?11/17/202251对象aintxValIntyVal对象bintxV实际上在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。它是指向本类对象的指针,它隐含在类的成员函数中,用来指向成员函数所述类的正在被操作的对象。例如,当调用成员函数a.ShowPoint()时,编译系统就把对象a的起始地址付给this指针,于是在成员函数引用数据成员时,就按照this的指向找到对象a的数据成员,由于当前this指向a,因此相当于执行cout<<“(“<<a.xVal<<“,”<<b.xVal<<“)”;归纳:(1)This指针也是一个指向对象的指针。(2)This指针是C++语言实现封装的一种机制,它将数据成员和成员函数连接在一起,当一个成员函数对数据成员进行操作时,用this指针来表示数据成员所在的对象。(3)当程序操作不同对象的成员函数时,this指针也指向不同的对象(4)静态成员函数没有this指针。11/17/202252实际上在每一个成员函数中都包含一个特殊的指C++程序设计教程(第二版)第九章对象生灭11/17/202253C++程序设计教程(第二版)第九章对象生灭11/9/20第九章内容构造函数设计构造函数的重载类成员初始化构造顺序拷贝构造函数析构函数对象转型与赋值11/17/202254第九章内容构造函数设计11/9/202221.构造函数设计
初始化要求定义一个对象,其形式与定义一个变量相似,但更具意义。对象与变量的不同在于对象对应于事物,要求从诞生之时起便有明确的意义.对象必须建立一种初始化机制,以满足在编程中可以针对实际问题所提出的要求而赋给一个有意义的初值。创建一个对象,若创建的是全局对象,则以全0的模式表示对象,若创建局部对象,则以随机值表示对象。11/17/2022551.构造函数设计
初始化要求定义一个对象,对象的初始化不是简单的参数与成员对应,而是联系参数到成员的过程.封装性要求StructPoint{intx,y;};Pointd={2,3};classPoint{intx,y;//…};Pointd={2,3};正确错误封装性的要求杜绝了这种初始化形式,事实上,这种形式根本不能完成对象的初始化过程中的校验和计算工作。对象通常是一个复杂的实体,在构建对象过程中,并不一定是一个初值对应一个对象分量。许多时候,一个初值只是传递一个信息,告诉对象的创建工作应怎么进行。封装性要求对象创建中按传递的信息进行一个过程化的初始化工作。11/17/202256对象的初始化不是简单的参数与成员对应,而是联系函数形式构造函数(constructor)是与类名同名的特殊的成员函数,当定义该类的对象时,构造函数将被自动调用以实现对该对象的初始化。构造函数不能有返回值,因而不能指定包括void在内的任何返回值类型。构造函数的定义体可与其它成员函数成员一样,放在类内或类外都可。构造函数的定义格式为:类名(形参说明){函数体}构造函数既可定义成有参函数,也可义成无参函数,要根据问题的需要来定。全局变量和静态变量在定义时,将自动赋初值为0;局部变量在定义时,其初始值不固定的。而当对象被定义时,由于对象的意义表达了现实世界的实体,所以一旦定义对象,就必须有一个有意义的初始值,在C++中,在定义对象的同时,给该对象初始化的方法就是利用构造函数。11/17/202257函数形式构造函数(constructor)是与类名同名的特殊例:类person包括4个数据成员,用来记录人员信息。生成对象obj,并使用构造函数为obj赋予初始值。#include<windows.h>#include<iostream.h>classPerson //定义类{private: //类Person的数据成员charname[10]; //姓名intage; //年龄intsalary; //薪金chartel[8]; //电话public: //构造函数PersonPerson(char*xname,intxage,intxsalary,char*xtel);voiddisp();};//函数Person的定义Person::Person(char*xname,intxage,intxsalary,char*xtel){strcpy(name,xname);//给各数据成员提供初值age=xage;salary=xsalary;strcpy(tel,xtel);}11/17/202258例:类person包括4个数据成员,用来记录人员//函数disp的定义voidPerson::disp(){cout<<endl;cout<<"姓名:"<<name<<endl;cout<<"年龄:"<<age<<endl;cout<<"工资:"<<salary<<endl;cout<<"电话:"<<tel<<endl<<endl;}//主函数voidmain(){//生成对象obj并初始化Personobj("张立三",25,850,"45672314");//显示objobj.disp();}11/17/202259//函数disp的定义11/9/20227程序的执行结果是:姓名:张立三年龄:25工资:850电话:45672314在主函数中的Personobj("张立三",25,850,"45672314");中完成了以下几个功能:1.定义并生成了对象obj。2.在生成对象obj的同时,自动调用相应类的构造函数Person3.将初始值"张立三",25,850,"45672314"传递给构造函数Person相应的形参xname,xage,xsalary,xtel。4.执行构造函数体,将相应的值赋给相应的数据成员。11/17/202260程序的执行结果是:11/9/20228一次性对象创建对象时如果不给出对象名,直接以类名调用构造函数,则产生一个无名对象。无名对象经常在参数传递时使用。例如:cout<<Date(2003,12,23);Date(2003,12,23)是一个对象,该对象在做了<<操作后便烟消云散了,所以这种对象一般用在创建后不需要反复使用的场合。11/17/202261一次性对象创建对象时如果不给出对象名,直接以
2.构造函数重载
构造函数毕竟是函数,是函数就可以重载。不但可以重载,还可以设置默认参数。如果一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。classDate{public:Date(conststring&s);//重载Date(inty=2003,intm=12,intd=1);//参数默认//...};intmain(){Dated(“2006-12-26”);Datee(2000,12,25);Datef(2001,10);Dateg(2002);Dateh;//...}11/17/202262
2.构造函数重载
构造函数毕竟是函数,是函Dateh;这行代码似乎是一个错误,对于对象的创建,即对于无参的构造函数的调用,应该是g(),加上类名,即写成:Dateg();但是,从语法上来讲,这个语句也是名叫g的返回临时Date对象的函数声明!因此,C++语言在设计时,为了区别无初始化的对象定义和返回类对象的无参函数声明的差别,也为了保持无初始化变量定义与无初始化(无参)对象定义的一致性,规定无参对象定义语句为:inta;//变量定义intb;//返回整型值的无参函数声明Dateg;//无参对象定义,加空括号后就成了无参函数声明Dateg();//返回类对象的无参函数声明11/17/202263Dateh;这行代码似乎是一个错误,对于对象的创建,即对对于返回对象值的有参函数声明和有初始化的对象定义语句,由于函数声明中的参数有类型引导,而对象定义的初始化中无类型引导,它们本身(或在编译器看来)是可以区分的:Datee(2002);//对象定义Datee(inty);//函数声明11/17/202264对于返回对象值的有参函数声明和有初始化的对象C++规定,每个类必须有一个构造函数。如果在类中没有显式定义构造函数时,则C++编译系统在编译时为该类提供一个默认的构造函数,该默认构造函数是个无参函数,它仅负责创建对象,而不做任何初始化工作。classDate{public://相当于定义了Date(){}};intmain(){Dated;//ok//...}与变量定义相似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的默认值为0,否则对象的初始值是不定的。11/17/202265C++规定,每个类必须有一个构造函数。如果在类中没有显式定只要一个类定义了一个构造函数(不一定是无参构造函数),C++编译系统就不再提供默认的构造函数。任何其他的构造函数定义,都将阻止默认无参空函数的产生:classDate{public:Date(inty,intm,intd){}//...};intmain(){Dated;//error//...}11/17/202266只要一个类定义了一个构造函数(不一定是无参构造函数),11/当构造函数有缺省参数时,称为具有缺省参数的构造函数,在使用时要防止二义性。如:classMyclass//定义类Myclass{private:intmember;public:Myclass();Myclass(inti);……}Myclass:Myclass()//构造函数Myclass{member=10;}11/17/202267当构造函数有缺省参数时,称为具有缺省参数的构Myclass:Myclass(inti=10)//构造函数Myclass(inti),该函数//的形参i为缺省参数{member=i;}voidmain(){Myclassx(20);Myclassy; //产生二义性,无法确定自动调用哪个构造//函数完成对象的构造……}11/17/202268Myclass:Myclass(inti=10)//构造回顾:上节课主要讲了构造函数的概念、意义及定义方式、构造函数的重载。11/17/202269回顾:11/9/202217
3.类成员初始化
默认调用的无参构造函数在类定义中有数据成员和成员函数,数据成员可以是内部数据类型的变量实体,也可以是对象实体。如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。这样就会面临一些问题。例:有一个学号类和一个学生类,学生类中包含了学号类的对象,因此在构造学生类对象时,面临着学号类对象的构造。11/17/202270
3.类成员初始化
默认调用的无参构造函数classStudentID{intvalue;public:
StudentID(){staticintnextStudentID=0;value=++nextStudentID;cout<<"Assigningstudentid"<<value<<"\n";}};//-----------------------------------classStudent{stringname;
StudentIDid;public:Student(stringn="noName"){cout<<"Constructingstudent"+n+"\n";name=n;}};//-----------------------------------intmain(){Students("Randy");}//==============================在学生类的构造函数中并没有看到学号类对象初始化的痕迹,而数据成员name倒被赋了初值。从运行的结果看,当学生类对象被构造时,一个学号对象也创建了,而且学号类的构造函数先于学生类对象的构造函数体的执行而执行。11/17/202271classStudentID{在学生类的构对于Students(“Randy”);其内部的执行顺序是这样的:(1)先分配学生类对象s的空间,调用Student构造函数;(2)在Student构造函数体尚未执行时,由于看到了类的对象成员ID,转而去调用学号类的无参构造函数;相当于执行定义语句:StudentIDid;(3)执行了学号类构造函数体,输出结果的第1行信息,返回到Student构造函数;(4)执行Student构造函数体,输出结果的第2行信息,完成全部构造工作。说明:先成员构造,后自身构造.成员构造不见显式调用,而是悄悄调用无参构造函数.对学号类构造函数的调用时默认的,默认调用便是调用无参构造函数,而正好学号类设计的构造函数就是无参构造函数。11/17/202272对于Students(“Randy”);其内部的执行顺序初始化的困惑在刚刚的实例中,只是初始化了名称,而没有给定学号。如果,在学生对象创建中,既要初始化名称,又要给定一个指定的初始化学号,也就是不要默认调用无参构造函数,那么类该如何操作呢?如果不能在创建StudentID的过程中初始化对象,就不能将对象值设置到位。下面给出一个不正确的初始化尝试,还是对刚才的例子作修改:11/17/202273初始化的困惑在刚刚的实例中,只是初始化了名称classStudentID{intvalue;public:
StudentID(intid=0){value=id;cout<<"Assigningstudentid"<<value<<"\n";}};//-----------------------------------classStudent{stringname;StudentIDid;public:Student(stringn="noName",intssID=0){cout<<"Constructingstudent"+n+"\n";name=n;
StudentIDid(ssID);}};//-----------------------------------intmain(){Students("Randy",58);}//====================================运行结果:11/17/202274classStudentID{运行结果:11/9/2022S对象初始化的学号为58,重新设计了Student构造函数,将该值传递给了它,所以形参ssID的值也为58。但是我们看到转而调用的StudentID构造函数执行体却没有输出58,而是默认的0学号。这说明转而调用的仍然是无参构造函数。将StudentID的构造函数写成参数默认是为了让无参构造函数调用也能通过,以便让整个程序顺利运行。乃至后来,要想在Student类内,通过构造函数体中的对象初始化创建的定义语句来改变id的值,发现这仅仅只是区别于s对象中的id对象的一个局部对象,等到构造函数结束时,该局部对象的局部生命期到期而不销毁。11/17/202275S对象初始化的学号为58,重新设计了St如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。
如果类A的构造函数为有参函数时,则在程序中必须在类B的构造函数的括号后面加一“:”和被调用的类A的构造函数,且调用类A的构造函数时的实参值必须来自类B的形参表中的形参。这种方法称为初始化表的方式调用构造函数。
这种在构造函数的参数列表的右括号后面,花括号前面,用冒号引出构造函数的调用表,可以省略类型名称,但却行创建对象之职。初始化表调用构造函数11/17/202276如果一个类A的对象作为另一个类B的数据成员,则类X的构造函数的定义格式应为:X::X(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n){……}其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。11/17/202277类X的构造函数的定义格式应为:11/9/202例:#include<iostream>usingnamespacestd;//-------------------------------------classStudentID{intvalue;public:
StudentID(intid=0)//有参构造函数{value=id;cout<<"Assigningstudentid"<<value<<endl;}};//-----------------------------------对刚才的例子进行改进11/17/202278例:#include<iostream>对刚才的例子进行改进classStudent{stringname;StudentIDid;public:Student(stringn="noname",intssID=0):id(ssID),name(n){cout<<"Constructingstudent"<<n<<"\n";}};//-----------------------------------intmain(){Students("Randy",98);Studentt("Jenny");}//==========================11/17/202279classStudent{11/9/202227
4.构造顺序
在程序中,各种作用域的对象很多,有些对象还包含在别的对象里面,有些对象早在main函数开始运行之前就已经建立了。创建对象的惟一途径是调用构造函数。构造函数时一段程序,所以构造对象的先后顺序不同,直接影响程序执行的先后顺序,导致不同的运行结果。C++给构造对象的顺序做了专门的规定。局部对象局部和静态对象是指块作用域(局部作用域)和文件作用域的对象。它们的声明顺序与它们在程序中出现的顺序是一致的。见课本307页例9.811/17/202280
4.构造顺序
在程序中,各种作用域的对象在C中,所有的局部变量(没有对象)都是在函数开始执行时统一创建的,创建的顺序是根据变量在程序中按语句行出现的顺序。而C++却不同,它是根据运行中定义对象的顺序来决定对象创建的顺序。而且,静态对象只创建一次。全局对象和全局变量一样,所有全局对象在主函数启动之前,全部已近被构造。因为构造过程是程序语句的执行过程,所以,可以想象在程序启动之前,已经有程序语句(构造函数)在那里被执行过了。11/17/202281在C中,所有的局部变量(没有对象)都是在函数开同一工程不同代码文件全局对象的创建没有明确顺序规定.对策:不要让不同文件的全局对象互为依赖.因为依赖具有先后性,而其全局对象的创建不能保证该依赖性发挥作用.全局对象在main函数启动之前生成,而调试则在main函数启动之后.对策:调试时,应先将全局对象作为局部对象来运行观察.或者,在构造函数中添加输出语句来观察运行过程.11/17/202282同一工程不同代码文件全局对象的创建没有明确顺序规定.11/9成员对象的构造顺序按类定义的出现顺序成员对象classA{public:A(intx){cout<<"A:"<<x<<"->";}};//-----------------------------------classB{public:B(intx){cout<<"B:"<<x<<"->";}};//-----------------------------------classC{Aa;Bb;public:C(intx,inty):b(x),a(y){cout<<"C\n";}};//-----------------------------------intmain(){Cc(15,9);}//============================classC按A、B的顺序定义对象成员,其构造函数又按b、a的顺序进行构造。到底听谁的,以定义顺序说了算。11/17/202283成员对象的构造顺序按类定义的出现顺序成员对象classA{构造位置全局数据区:全局对象,静态全局对象,静态局部对象,常对象类的静态数据成员也存放在该数据区。目的是为了让对象的生命周期与运行的程序等寿命。栈区:局部对象在函数中定义好、局部对象,随着被调用的返回而析构(销毁)。动态存储区(也称堆区):用new申请的对象用delete销毁。特殊地址空间除此之外,还可以指定特殊地址空间,存放对象11/17/202284构造位置全局数据区:11/9/202232回顾11/17/202285回顾11/9/202233
5.拷贝构造函数对象本体与对象实体:对象本体也是对象主体,对象实体则还包括属于对象的衍生物,如,某个人体是人类对象的主体,然而某人还拥有父母,房产等属于某人的世系或资产,描述人的属性不仅仅只是人体数据.从形式上看,对象除了包括数据成员,还包括指向数据的指针.对象本体与对象实体是不一致就是说有时我们会需要把a对象的属性全部转给b对象,
对象本体与对象实体一致就是你要将a对象的属性赋给a对象本身。11/17/202286
5.拷贝构造函数对象本体与对象实体:11/9/20223对于一个简单的变量的初始化方法是用一个常量或变量初始化另一个变量,例如:doublex=12.36;doubley=x;疑问:前面介绍了用构造函数初始化对象,那么能不能像简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?答案是肯定的。例:以定义的一个Point类为例,说明用一个对象来初始化另一个对象。Pointpt1(10,20);Pointpt2=pt1;那么,后一句也可写成Pointpt2(pt1);它是用pt1初始化pt2,此时,pt2各个成员的值与pt1各个成员的值相同,也就是说,pt1各个成员的值被复制到pt2相应的成员当中。在这个初始化过程当中,实际上调用了一个拷贝构造函数。11/17/202287对于一个简单的变量的初始化方法是用一个常量或变量初始化另一个拷贝构造函数:拷贝构造函数是C++中引入的一种新的构造函数。定义一个拷贝构造函数的方式是:类名(const类名&形式参数){函数体}由此可看出拷贝构造函数的特点:(1)拷贝构造函数的名称与类的名称相同,且它只有一个参数,该参数就是对该类对象的引用。(2)拷贝构造函数的功能是用于实现对象值的拷贝,通过将一个同类对象的值拷贝给一个新对象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。(3)每个类都有一个拷贝构造函数11/17/202288拷贝构造函数:11/9/202236以本类对象为常量引用参数的构造函数:classDate{public:Date();
Date(const
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024版劳务加工承包合同范本
- 2024年艺术品买卖合同赔偿条例
- 2025年度新型城镇化租赁住房建设合同4篇
- 2025年度智能家居项目瓷砖材料供应合同4篇
- 2025年度体育场馆搭棚施工及维护管理合同4篇
- 2024版镍氢电池产品销售合同
- 2025年度学校食堂及餐饮服务承包合同范本4篇
- 2025年度新能源汽车购置合同示范文本4篇
- 2025年度特色农家乐经营权转让合同范本3篇
- 2025年度智能窗帘控制系统研发与市场推广合同4篇
- 特种设备行业团队建设工作方案
- 眼内炎患者护理查房课件
- 肯德基经营策略分析报告总结
- 买卖合同签订和履行风险控制
- 中央空调现场施工技术总结(附图)
- 水质-浊度的测定原始记录
- 数字美的智慧工业白皮书-2023.09
- -安规知识培训
- 2021-2022学年四川省成都市武侯区部编版四年级上册期末考试语文试卷(解析版)
- 污水处理厂设备安装施工方案
- 噪声监测记录表
评论
0/150
提交评论