版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第三章面向对象的程序设计1第1页,课件共171页,创作于2023年2月本章导读理解继承的概念和意义,理解单一继承、多重继承。理解并掌握派生类构造函数的编写要求,以及派生类对象的构造过程和机理。掌握虚函数和多态性的概念,掌握虚函数的定义方法、调用方法及其在实现多态性方面所起到的作用。了解纯虚函数与抽象基类的概念。了解类的静态成员(静态数据成员和静态成员函数)的概念、定义方法及其作用。了解友元函数与友元类的概念、定义方法及其作用。了解运算符重载及在程序中实现运算符重载的方法。了解模板的概念,在程序中如何定义类模板和函数模板。第2页,课件共171页,创作于2023年2月3.1类与对象的定义类和对象是面向对象程序设计(OOP)的两个最基本概念。所谓对象就是客观事物在计算机中的抽象描述;类是对具有相似属性和行为的一组对象的统一描述。3.1.1类的定义C++的类是在结构体的基础上扩充而来的。类是把各种不同类型的数据(称为数据成员)和对数据的操作(成员函数)组织在一起而形成的用户自定义的数据类型。C++中,类定义包括类说明和类实现两大部分。说明部分提供了对该类所有数据成员和成员函数的描述,而实现部分提供了所有成员函数的实现代码。第3页,课件共171页,创作于2023年2月3.1类与对象的定义类定义的一般形式为:class类名{private:数据成员或成员函数protected:数据成员或成员函数public:数据成员或成员函数};
<各成员函数的实现代码>第4页,课件共171页,创作于2023年2月3.1类与对象的定义说明:1.class是定义类的关键字,类名由用户自己定名,必须是C++的有效标识符,但一般首字母大写。2.大括号的部分是类的成员(数据成员和函数成员),它们分成三部分,分别由private、public、proctected三个关键字后跟冒号来指定。这三部分可以任何顺序出现,且在一个类的定义中,这三部分并非必须同时出现。(1)如果数据成员或成员函数在类的private部分,那么在类之外是不能存取的,只有类中的成员函数才能存取private的数据成员和成员函数。(2)在一个类的public部分说明的数据成员或成员函数可被程序中的任何函数或语句存取,public成员多为成员函数,用来提供一个与外界的接口,外界只有通过这个接口才可以实现对private成员的存取。第5页,课件共171页,创作于2023年2月3.1类与对象的定义(3)在类的protected部分说明的数据成员和成员函数是不能在类之外存取的,只有类的成员函数及其子类(派生类)可以存取protected的成员。(4)当定义类时,当未指明成员是哪部分时,默认是属于private成员,但一般不要采用默认形式。如:下例中定义描述图书的类定义classRecord{private://private成员charbookname[20];//数据成员bookname,//用于表示图书的名称intnumber;//数据成员number,表示图书编号第6页,课件共171页,创作于2023年2月3.1类与对象的定义public: //public成员voidregist(char*a,intb);//成员函数regist,用于给//各数据成员赋值voidshow();//成员函数show,显示各数据成员的值};要特别注意,在类的定义中,类的说明部分的右边大括号后面必须有一“;”.
根据类的定义,可看出:类是实现封装的工具,所谓封装就是将类的成员按使用或存取的方式分类,有条件地限制对类成员的使用,而封装是通过public和private与成员函数实现的。private的成员构成类的内部状态,public的成员则构成与外界通信的接口,通过public的成员函数来使用private的数据成员,从而在C++中实现了封装。第7页,课件共171页,创作于2023年2月3.1类与对象的定义3.1.2成员函数的定义类中的成员函数可以在以下两处定义:(1)将成员函数的定义直接写在类中:如:对于前面定义的图书类Record来说,其成员函数regist和show的定义可直接写在类的定义体中。classRecord{private:charbookname[20];intnumber;第8页,课件共171页,创作于2023年2月3.1类与对象的定义public:voidregist(char*a,intb)//成员函数regist()的定义{strcpy(bookname,a);//给数据成员bookname赋值number=b;//给数据成员number赋值}voidshow() //成员函数show()的定义{cout<<”名称:”<<bookname<<endl;cout<<”号码:”<<number<<endl;}};第9页,课件共171页,创作于2023年2月3.1类与对象的定义在类中直接定义成员函数的情况一般适合于成员函数规模较小的情况,也就是说它们一般为内联函数,即使没有明确用inline关键字。(2)在类的定义体中只写出成员函数的原型说明,而成员函数的定义写在类的定义之后,这种情况比较适合于成员函数体较大的情况,但这时要求在定义成员函数时,在函数的名称之前加上其所属性类名及作用域运算符“::”。定义成员函数的一般类型为:返回值类型类名::成员函数名(参数说明){类体}第10页,课件共171页,创作于2023年2月3.1类与对象的定义
此处的::符号叫作用域运算符,用它来指明哪个函数属于哪个类或哪个数据属于哪个类,所以使用类中成员的全名是:类名::成员名。而如果没有类名,则为全局数据或全局函数(非成员函数),也就是说类名是其成员名的一部分。如classRecord{private:charbookname[20];intnumber;public:voidregist(char*a,intb);//成员函数regist的原型voidshow();//成员函数show的原型};//定义图书类Record第11页,课件共171页,创作于2023年2月3.1类与对象的定义voidRecord::regist(char*a,intb)//regist()是类Record的//成员函数{strcpy(bookname,a);number=b;}voidRecord::show()//show()是类Record的成员函数{cout<<”名称:”<<bookname<<endl;cout<<”号码:”<<number<<endl;}此外,目前开发程序的通常将类的定义写在一个头文件(.h文件)中,成员函数的定义写在一个程序文件(.cpp文件)中,这样,就相当于把类的定义(头文件)看成是类的外部接口,类的成员函数的定义看成类的内第12页,课件共171页,创作于2023年2月3.1类与对象的定义部实现。如:对上例可改成将类的定义体写在myapp.h文件中,而成员函数的定义体写在另外一个文件myapp.cpp中://myapp.h文件classRecord{private:charbookname[20];intnumber;public:voidregist(char*a,intb);voidshow();};第13页,课件共171页,创作于2023年2月3.1类与对象的定义//myapp.cpp文件#include“iostream.h”#include“myapp.h” //一定不要忘记嵌入该头文件voidrecord::regist(char*a,intb){strcpy(bookname,a);number=b;}voidrecord::show(){cout<<”名称:”<<bookname<<endl;cout<<”号码:”<<number<<endl;}第14页,课件共171页,创作于2023年2月3.1类与对象的定义3.1.3对象的定义对象是类的实例,定义对象的一般格式为:
类名变量名表;或类名对象名;如:上例中已定义了类Record,则:Recordbook1,book2;//此处的book1,book2就是Record//类型,也就是类的两个对象类是抽象的概念,而对象是具体的,类只是一种数据类型,而对象是属于该类(数据类型)的一个变量,占用了各自的存储单元,每个对象各自具有了该类的一套数据成员(静态成员除外),而所有成员函数是所有对象共有的。每个对象的函数成员都通过指针指向同一个代码空间。第15页,课件共171页,创作于2023年2月3.1类与对象的定义3.1.4访问对象的成员访问对象的成员包括读写对象的数据成员和调用它的成员函数,其访问格式是:对象名.成员名如上例中,对象的主函数如下:voidmain(){Recordbook1,book2;//定义对象book1和book2//调用成员函数regist,给book1的两个数据成员//bookname和number赋值book1.regist(“C++编程教程”,1001);//调用成员函数regist,给book2的两个数据成员赋值book2.regist(“C++语言参考”,1002);第16页,课件共171页,创作于2023年2月3.1类与对象的定义//调用成员函数show,显示book1对象的数据成员//bookname和number的值book1.show();//调用成员函数show,显示book2对象的数据成员//bookname和number的值book2.show();}如改为下面的代码,则错误:voidmain(){Recordbook1,book2;//由于bookname和number是类Record的私有成员,在类外//不能直接使用第17页,课件共171页,创作于2023年2月3.1类与对象的定义strcpy(book1.bookname,“C++编程教程”);book1.number=1001;strcpy(book2.bookname,“C++语言参考”);book2.number=1002;book1.show();book2.show();}注意:1.对于类的私有成员,只能通过其成员函数来访问,不能在类外对私有成员访问。第18页,课件共171页,创作于2023年2月3.1类与对象的定义2.调用成员函数时要在函数名之前加上对象名和"."即可,即先指明对象,再指明成员。也可以采用指向对象的指针来访问,但要在函数名前加上指针变量名和“->”。3.任何对对象私有数据的访问都必须通过向对象发送消息来实现,而且所发送的消息还必须是该对象能够识别和接受的。在C++中,消息发送正是通过公有成员函数的调用来实现的。由于类接口隐藏了对象的内部细节,用户只能通过类接口访问对象,因此,在类设计中必须提供足够的公有接口以捕获对象的全部行为,这正是类设计中的一个最基本的要求。4.上例中,在对象调用book1.regist(“C++编程教程”,1001);时,成员函数regist除了接受两个实参外,还接第19页,课件共171页,创作于2023年2月3.1类与对象的定义受了一个对象book1的地址,这个地址被一个隐含的形参this指针所获取,它等同于执行this=&book1,所以所有对数据成员的访问都隐含地被加上前缀:this->,因此,在成员函数体regist中,执行strcpy(bookname,a);number=b;就等价于strcpy(this->bookname,a);this->number=b;这样,上例中的成员函数regist也可这样定义:voidrecord::regist(char*a,intb){strcpy(this->bookname,a);this->number=b;}通过以上手段就确保了不同对象调用成员函数时访问的是不同对象的数据,而它们之间没有干扰。第20页,课件共171页,创作于2023年2月3.1类与对象的定义3.1.5对象赋值语句对于同一个类生成的两个对象,可以进行赋值,其功能是将一个对象的数据成员赋值到另一个对象中去,赋值语句的左右两边各是一个对象名:【例3-1】对于类example的两个对象obj1和obj2,让obj2的成员数据的值等于obj1的成员数据的值(假定obj1的成员数据num已经存有数据215)。
第21页,课件共171页,创作于2023年2月3.1类与对象的定义#include<windows.h>#include<iostream.h>//定义类classexample{private://数据成员intnum;public://函数成员说明voidset(inti){num=i;}voiddisp(){cout<<"\nnum="<<num;}};第22页,课件共171页,创作于2023年2月3.1类与对象的定义//主程序voidmain(){exampleobj1,obj2;obj1.set(215);obj1.disp();obj2=obj1;//对象赋值语句obj2.disp();cout<<endl<<endl;}3.1.6对象的作用域与生存期对象是类的实例,它实质就是某种数据类型的变量,在不同的位置以不同的方式定义对象时,其作用域和生存期是不同的。第23页,课件共171页,创作于2023年2月3.1类与对象的定义如:classDesk//定义Desk类{public:intweight;inthigh;intwidth;intlength;};classStool//定义Stool类{public:intweight;inthigh;intwidth;intlength;};第24页,课件共171页,创作于2023年2月3.1类与对象的定义deskda; //定义全局对象Stoolsa;voidfn(){staticStoolss; //静态局部对象deskda; //定义局部对象//……}1.局部对象(不包括局部静态对象)其作用域是定义它的函数体,生存期从函数调用开始到函数调用结束,下一次再重新调用函数时,再重新构造对象。构造局部对象的次序(即分配存储单元)是按它们在函数体中声明的顺序。第25页,课件共171页,创作于2023年2月3.1类与对象的定义2.静态对象(局部静态和全局静态)其作用域是定义它的函数体或程序文件,其生存期是整个程序。构造静态对象的次序是按它们在程序中出现的次序先后,并在整个程序运行开始时(即在主函数运行前)只构造一次。3.全局对象全局对象的作用域是整个程序,生存期是整个程序的运行时间。它也是在程序运行前(即在主函数运行前)只构造一次。4.类中成员的构造次序是以类中声明成员的次序进行。构造函数和析构函数是类的两种特殊的成员函数。第26页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.1构造函数构造函数(constructor)是与类名同名的特殊的成员函数,当定义该类的对象时,构造函数将被自动调用以实现对该对象的初始化。构造函数不能有返回值,因而不能指定包括void在内的任何返回值类型。构造函数的定义体可与其它成员函数成员一样,放在类内或类外都可。构造函数的定义格式为:类名(形参说明){函数体}构造函数既可定义成有参函数,也可义成无参函数,要根据问题的需要来定。全局变量和静态变量在定义时,将自动赋初值为0;局部变量在定义时,其初始值不固定的。而当对象被定义时,由于对象的意义表达了现实世界的实第27页,课件共171页,创作于2023年2月3.2构造函数与析构函数体,所以一旦定义对象,就必须有一个有意义的初始值,在C++中,在定义对象的同时,给该对象初始化的方法就是利用构造函数。如:【例3-2】类person包括4个数据成员,用来记录人员信息。生成对象obj,并使用构造函数为obj赋予初始值。#include<windows.h>#include<iostream.h>classPerson //定义类{private: //类Person的数据成员charname[10]; //姓名intage; //年龄intsalary; //薪金chartel[8]; //电话第28页,课件共171页,创作于2023年2月3.2构造函数与析构函数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);}第29页,课件共171页,创作于2023年2月3.2构造函数与析构函数//函数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();}第30页,课件共171页,创作于2023年2月3.2构造函数与析构函数程序的执行结果是:姓名:张立三年龄:25工资:850电话:45672314在主函数中的Personobj("张立三",25,850,"45672314");中完成了以下几个功能:1.定义并生成了对象obj。2.在生成对象obj的同时,自动调用相应类的构造函数Person3.将初始值"张立三",25,850,"45672314"传递给构造函数Person相应的形参xname,xage,xsalary,xtel。4.执行构造函数体,将相应的值赋给相应的数据成员。第31页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.2构造函数的重载如果一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。【例3-3】类rec定义两个重载函数,其中一个是无参函数,另一个是有参函数。它们都是构造函数。#include<windows.h>#include<iostream.h>//定义类classRec{private:charbookname[30];intnumber;第32页,课件共171页,创作于2023年2月3.2构造函数与析构函数public:Rec();//第1个构造函数说明Rec(char*a,intb);//第2个构造函数说明voidshow();};Rec::Rec()//第1个构造函数定义{strcpy(bookname,'\0');number=0;}Rec::Rec(char*a,intb)//第2个构造函数定义{strcpy(bookname,a);number=b;}第33页,课件共171页,创作于2023年2月3.2构造函数与析构函数voidRec::show()//show的函数定义{cout<<"booknameis:"<<bookname<<endl;cout<<"booknumberis:"<<number<<endl;}voidmain()//主程序{Recmybook(“VisualC++6.0”,10020);//自动调用构造//函数Rec(char*a,intb)mybook.show();Recyourbook;//自动调用构造函数Rec()yourbook.show();}第34页,课件共171页,创作于2023年2月3.2构造函数与析构函数程序的执行结果是:booknameis:VisualC++6.0booknumberis:10020booknameis:nonamebooknumberis:0可见,当出现构造函数重载时,其匹配方式同普通函数重载时的匹配方式。第35页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.3默认构造函数与缺省构造函数C++规定,每个类必须有一个构造函数。如果在类中没有显式定义构造函数时,则C++编译系统在编译时为该类提供一个默认的构造函数,该默认构造函数是个无参函数,它仅负责创建对象,而不做任何初始化工作。只要一个类定义了一个构造函数(不一定是无参构造函数),C++编译系统就不再提供默认的构造函数。与变量定义相似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的默认值为0,否则对象的初始值是不定的。当构造函数有缺省参数时,称为具有缺省参数的构造函数,在使用时要防止二义性。第36页,课件共171页,创作于2023年2月3.2构造函数与析构函数如:classMyclass//定义类Myclass{private:intmember;public:Myclass();Myclass(inti);……}Myclass:Myclass()//构造函数Myclass{member=10;}第37页,课件共171页,创作于2023年2月3.2构造函数与析构函数Myclass:Myclass(inti=10)//构造函数Myclass(inti),该函数//的形参i为缺省参数{member=i;}voidmain(){Myclassx(20);Myclassy; //产生二义性,无法确定自动调用哪个构造//函数完成对象的构造……}第38页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.4析构函数当一个对象被定义时,系统自动调用构造函数为该对象分配相应的资源,当对象使用完毕后,这些系统资源需要在对象消失前被释放。析构函数是类的一个特殊成员函数,其函数名称是在类名的前面加上~,它没有返回值,没有参数,不能随意调用,也没有重载,只是在类对象生命期结束时,系统自动调用。析构函数的定义方式为:~类名(){函数体}注:(1)一个类中只能拥有一个析构函数。第39页,课件共171页,创作于2023年2月3.2构造函数与析构函数(2)如果程序员在定义类时,没有为类提供析构函数,则系统会自动创建一个默认的析构函数,其形式为:~类名(){}(3)对于一个简单的类来说,大多可以直接使用系统提供的默认析构函数。但是,如果在类的对象中分配有动态内存(如:用new申请分配的内容)时,就必须为该类提供适当的析构函数,完成清理工作。(4)对象被析构的顺序与对象建立时的顺序正好相反。即最后构造的对象先被析构。第40页,课件共171页,创作于2023年2月3.2构造函数与析构函数【例3-4】类Teacher的构造函数为name申请存储空间,在析构函数中释放该空间。#include<windows.h>#include<iostream.h>//定义类classTeacher{private:char*name;intage;public://说明构造函数Teacher第41页,课件共171页,创作于2023年2月3.2构造函数与析构函数Teacher(char*i,inta){name=newchar[strlen(i)+1];//用new为name成员分配堆内存strcpy(name,i);age=a;cout<<"\n执行构造函数Teacher"<<endl;};//说明析构函数~Teacher~Teacher(){deletename;cout<<"执行析构函数~Teacher"<<endl<<endl;};voidshow();};第42页,课件共171页,创作于2023年2月3.2构造函数与析构函数voidTeacher::show(){cout<<"姓名:"<<name<<""<<"年龄:"<<age<<endl;}voidmain()//主程序{Teacherobj("张立三",25);obj.show();}程序的执行结果是:执行构造函数Teacher姓名:张立三年龄:25执行析构函数~Teacher第43页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.5拷贝构造函数拷贝构造函数是C++中引入的一种新的构造函数。定义一个拷贝构造函数的方式是:类名(const类名&形式参数){函数体}由此可看出:(1)拷贝构造函数的名称与类的名称相同,且它只有一个参数,该参数就是对该类对象的引用。(2)拷贝构造函数的功能是用于实现对象值的拷贝,通过将一个同类对象的值拷贝给一个新对象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。第44页,课件共171页,创作于2023年2月3.2构造函数与析构函数【例3-5】Example是一个人员信息类。用普通构造函数生成obj1,用拷贝构造函数生成obj2。#include<windows.h>#include<iostream.h>classExample{private:char*name;intnum;public:example(inti,char*str)//构造函数定义{name=str;num=i;}第45页,课件共171页,创作于2023年2月3.2构造函数与析构函数example(constExample&x)//拷贝构造函数定义{num=x.num;}voidlist()//定义显示函数list{cout<<"\数据成员num的值="<<num<<endl<<endl;}};voidmain(){exampleobj1(215,“张立三”);//调用函数Example(inti,char*str)构造obj1exampleobj2(obj1);//使用拷贝构造函数构造obj2obj2.list();//显示obj2的值……//其它程序部分}第46页,课件共171页,创作于2023年2月3.2构造函数与析构函数程序的执行结果是:数据成员num的值=215数据成员num的值=215说明:(1)上例中在main函数中的语句Exampleobj2(obj1);在执行时,系统会自动调用类Example的拷贝构造函数完成对obj2对象的构造。(2)如果程序员没有为所设计的类提供显式的拷贝构造函数,则系统会自动提供一个默认的拷贝构造函数,其功能是:把作为参数的对象的数据成员逐个拷贝到目标变量中,这称为成员级复制(或浅拷贝)。第47页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.6一个类的对象作为另一个类的数据成员一个类中的数据成员除了可以是int,char,float等这些基本的数据类型外,还可以是某一个类的一个对象。用子对象创建新类。在C++中,当把一个类的对象作为新类的数据员时,则新类的定义格式可表示为:classX{类名1成员名1;类名2成员名2;……类名n成员名n;……//其它成员};第48页,课件共171页,创作于2023年2月3.2构造函数与析构函数(3)如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。但应注意:如果类A的构造函数为有参函数时,则在程序中必须在类B的构造函数的括号后面加一“:”和被调用的类A的构造函数,且调用类A的构造函数时的实参值必须来自类B的形参表中的形参。这种方法称为初始化表的方式调用构造函数。如:以上面定义的类X为例,在对类X的对象进行初始化时,必须首先初始化其中的子对象,即必须首先调用这些子对象的构造函数。因此,类X的构造函数的定义格式应为:X::X(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n){……}第49页,课件共171页,创作于2023年2月3.2构造函数与析构函数其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。第50页,课件共171页,创作于2023年2月3.2构造函数与析构函数【例3-6】以下定义了三个Student、Teacher和Tourpair,其中Student类的对象和Teacher类的对象作为了Tourpair的数据成员,观察对象的构造过程和构造函数被执行的顺序。#include<iostream.h>classStudent{public:Student(){cout<<”constructstudent.\n”;semeshours=100;gpa=3.5;}第51页,课件共171页,创作于2023年2月3.2构造函数与析构函数protected:intsemeshours;floatgpa;};classTeacher{public:Teacher(){cout<<”constructTeacher.\n”;}};第52页,课件共171页,创作于2023年2月3.2构造函数与析构函数classTourpair{public:Tourpair(){cout<<”constructtourpair.\n”;nomeeting=0;}protected:Studentstudent;Teacherteacher;intnomeeting;};第53页,课件共171页,创作于2023年2月3.2构造函数与析构函数voidmain(){Tourpairtp;cout<<”backinmain.\n”;}其执行结果是:constructstudent.constructteacher.constructtourpair.backinmain.由此可见:主函数main()运行开始时,遇到要创建Tourpair类的对象,于是调用其构造函数Tourpair(),该构造启动时,首先分配对象空间(包含一个Student对第54页,课件共171页,创作于2023年2月3.2构造函数与析构函数象、一个Teacher对象和一个int型数据),然后根据其在类中声明的对象成员的次序依次调用其构造函数。即先调用Student()构造函数,后调用Teacher()构造函数,最后才执行它自己的构造函数的函数体。由于上例中Tourpair类的数据成员student和teacher的构造函数都是无参函数,所以系统在构造student和teacher对象时会自动调用各自的构造函数Student()和Teacher(),而不需要用初始化表的方式去调用。【例3-7】试分析以下程序的执行结果:#include<iostream.h>#include<string.h>第55页,课件共171页,创作于2023年2月3.2构造函数与析构函数classStudent{public:Student(char*pName="Noname"){cout<<"构造新同学:"<<pName<<endl;strncpy(name,pName,sizeof(name));name[sizeof(name)-1]='\0';}Student(Student&s){cout<<"构造copyof"<<<<endl;strcpy(name,"copyof");strcat(name,);}第56页,课件共171页,创作于2023年2月3.2构造函数与析构函数~Student(){cout<<"析构"<<name<<endl;}protected:charname[40];};classTutor{public:Tutor(Student&s):student(s)//此为初始化表,调用//Student的拷贝构造函数{cout<<"构造指导教师\n";}protected:Studentstudent;};第57页,课件共171页,创作于2023年2月3.2构造函数与析构函数voidmain(){Studentst1;//此处调用Student的构造函数Student(char*pName="Noname")Studentst2("zhang");//同上Tutortutor(st2);//此处调用Tutor的构造函数Tutor(Student&s)//在构造tutor对象的过程中,用初始化表调用//Student类的拷贝构造函数Student(Student&s)}执行结果如下:构造新同学:Noname构造新同学:zhang构造copyofzhang第58页,课件共171页,创作于2023年2月3.2构造函数与析构函数构造指导教师析构copyofzhang析构zhang析构Noname3.2.7利用初始化表对常量数据成员或引用成员提供初值如前所述,构造函数可对对象的数据成员进行初始化,但若数据成员为常量成员或引用成员时,就有所不同,如:classSillyclass{public:Sillyclass()//此构造函数对成员ten和refi的初始化错误。{ten=10;refi=i;}第59页,课件共171页,创作于2023年2月3.2构造函数与析构函数protected:constintten;//常量数据成员tenint&refi;//引用refi};说明:1.造成以上错误的原因是在Sillyclass类的构造函数进入之后(开始执行其函数体时),对象结构已经建立,数据成员ten和refi已存在,而其数据成员ten为const,而refi为引用,所以在构造函数体内不能再对其指派新的值。2.解决以上问题的方法是利用初始化表:在构造函数的括号后面加一“:”和初始化表,初始化表的格式是:数据成员名(值),如果有多个时,需要用逗号隔开。第60页,课件共171页,创作于2023年2月3.2构造函数与析构函数【例3-8】类employee中包括私有数据成员x,和2个公有函数成员example、show。程序中使用初始化表是x(215)。#include<windows.h>#include<iostream.h>//定义类employeeclassemployee{private:constintx;public:employee();voidshow();};第61页,课件共171页,创作于2023年2月3.2构造函数与析构函数//employee的类外定义employee::employee():x(215)//初始化表{}//show()的定义。voidemployee::show(){cout<<"\nx的值是:"<<x<<endl;}//主函数voidmain(){//生成对象并为x赋予初始值employeeobj;//调用show显示x的值obj.show();}第62页,课件共171页,创作于2023年2月3.2构造函数与析构函数3.2.8类作用域
类作用域又可称为类域,它是指在类定义中用一对大括号所括起来的范围。由于在程序文件中可包含类,而类中又包含函数,因此,类域显然是一个小于文件域,而大于函数域的概念。由于在一个类中既可定义变量(数据成员),又可定义函数(成员函数),所以,类域在许多方面与文件域相似。但是,在类域中定义的变量不能使用auto、register和extern等修饰符,而且在类域中定义的函数也不能使用extern修饰符。同时,在类域中定义的静态成员和成员函数还具有外部的连接属性。第63页,课件共171页,创作于2023年2月3.2构造函数与析构函数【例3-9】类域及其成员引用举例,设以下程序代码被存放到了一个程序文件中。#include<iostream.h>classMyclass{private:intx;inty;public:Myclass(inta,intb){x=a;y=b;}voidprint();voidmyfunc();};第64页,课件共171页,创作于2023年2月3.2构造函数与析构函数voidMyclass::print(){cout<<"x="<<x<<","<<"y="<<y<<endl;}voidMyclass::myfunc(){intx=9,y=10;cout<<"Inmyfunc:x="<<x<<","<<"y="<<y<<endl;//输出局部变量
//输出类的数据成员cout<<"Myclass::x="<<Myclass::x<<","<<"Myclass::y="<<Myclass::y<<endl;}第65页,课件共171页,创作于2023年2月3.2构造函数与析构函数voidmain(){Myclasstest(100,200),*ptest=&test;test.print();//通过对象名访问公有成员ptest->myfunc();}程序的运行结果为:x=100,y=200Inmyfunc:x=9,y=10Myclass::x=100,Myclass::y=200说明:
(1)类成员函数的原型在类的定义体中声明,具有类作用域,但其实现部分在类的定义体外。由于不同类的成员函数可以具有相同的名字,因此,需要用作用域运算符“::”来指明该成员函数所属的类。第66页,课件共171页,创作于2023年2月3.2构造函数与析构函数(2)类中的成员拥有类作用域,因此在成员函数中可以直接引用类的数据成员。但是,如果在成员函数中定义了同名的局部变量时,则必须用作用域运算符“::”来指定,以免混乱。如:上例中的myfunc()函数中定义了与类的数据成员同名的局部变量x、y,所以在myfunc()函数中要访问类中的数据成员x和y的值时,必须加上作用域运算符。(3)类中的成员拥有类的作用域,如果要从类外访问类的成员时,则必须通过对象名或指向对象的指针。当通过对象名时,应使用圆点成员选择符“.”;当通过指针时,应使用箭头成员选择符“->”。如上例中的test.print();与ptest->myfunc();第67页,课件共171页,创作于2023年2月3.3继承和派生3.3.1继承的概念一个类的数据成员和成员函数,有些是类本身自己定义的,有一些是可继承的或通过模板生成的。所谓继承(inheritance)就是利用已有的数据类型定义出新的数据类型。利用类的“继承”,就可以将原来的程序代码重复使用,从而减少了程序代码的冗余度,符合软件重用的目标。所以说,继承是面向对象程序设计的一个重要机制。另外,在C++中扩充派生类成员的方法是非常灵活的。派生类不仅可以继承原来类的成员,还可以通过以下方式产生新的成员:第68页,课件共171页,创作于2023年2月3.3继承和派生(1)增加新的数据成员;(2)增加新的成员函数;(3)重新定义已有成员函数;(4)改变现有成员的属性。在继承关系中,称被继承的类为基类(baseclass)(或父类),而把通过继承关系定义出来的新类称为派生类(derivedclass)(子类)。由此可见,派生类既可以对基类的性质进行扩展,又可以进行限制,从而得到更加灵活、更加适用的可重用模块,大大缩短程序的开发时间。第69页,课件共171页,创作于2023年2月3.3继承和派生3.3.2单继承1.定义派生类在基类的基础上定义其派生类的定义形式为:class派生类名:访问方式基类名{派生类中的新成员}其中:(1)派生类名由用户自己命名;(2)访问方式即继承方式,可以为public或private,默认为private方式。访问方式为public方式时,这种继承称为公有继承,而访问方式为private方式时,称为私有继承;(3)基类名必须是程序中一个已有的类。第70页,课件共171页,创作于2023年2月3.3继承和派生(4)在冒号“:”后的部分告诉系统,这个派生类是从哪个基类派生的,以及在派生时的继承方式。(5)大括号内的部分是派生类中新定义的成员。2.基类与派生类之间的关系(1)派生类不仅拥有属于自己的数据成员与成员函数,还保持了从基类继承来的数据成员与成员函数;同时派生类可对一些继承来的函数重新定义,以适应新的要求。(2)C++关于类的继承方式的规定,如下表3.1所示:①按private方式继承(即私有继承)时,基类中的公有成员和保护成员在派生类中皆变为私有成员。②按public方式继承(即公有继承)时,基类中的公有成员和保护成员在派生类中不变。第71页,课件共171页,创作于2023年2月3.3继承和派生③无论哪种继承方式,基类的私有成员均不能继承。这与私有成员的定义是一致的,符合数据封装的思想。④在公有继承方式下,基类的公有成员和保护成员被继承为派生类成员时,基访问属性不变。注意:私有成员与不可访问成员是两个不同的概念。某个类的私有成员只能被该类的成员函数所访问,而类的不可访问成员甚至不能被该类自身的成员函数所访问。类的不可访问成员总是从某个基类派生来的,它要么是基类的私有成员,要么是基类的不可访问成员。基类公有派生类私有派生类public成员public成员private成员protected成员protected成员private成员private成员无法继承无法继承第72页,课件共171页,创作于2023年2月3.3继承和派生(3)在C++中,可以根据需要定义多层的继承关系,也可以从一个基类派生出多个类,形成类的层次结构,在类的层次结构中,处于高层的类,表示最一般的特征,而处于底层的类,表示更具体的特征,在多层继承关系中,基类与派生类的关系是相对的,例如:由类A派生出类B,再由类B派生出类C,这里类B相对于类A是派生类,而相对于类C是基类,并称类C是类A的间接派生类,称类A是类C的间接基类;而称具有直接派生关系的两个类分别为直接派生类和直接基类。【例3-9】类Build_1是一个关于楼房数据的类。它的数据成员有posi_x、posi_y和area,分别是楼房位置的经、纬度和建筑面积。它的函数成员只有set1,用于设置数据成员posi_x、posi_y和area的值。让Build_1作为基类,再增加数据成员high、函数成员set2和disp来定义派生类Build_2。第73页,课件共171页,创作于2023年2月3.3继承和派生#include<iostream.h>classBuild_1 //定义基类{protected:intposi_x; //有三个保护型的数据成员intposi_y;intarea;public:voidset1(intx,inty,inta){posi_x=x;posi_y=y;area=a;}};//定义派生类Build_2classBuild_2:publicBuild_1第74页,课件共171页,创作于2023年2月3.3继承和派生{intheight;public:voidset2(inth){height=h;}voiddisp(){cout<<"\n经度:"<<posi_x<<endl;cout<<"纬度:"<<posi_y<<endl;cout<<"高度:"<<height<<endl;cout<<"面积:"<<area<<endl<<endl;}};voidmain(){//用Build_2生成对象obj第75页,课件共171页,创作于2023年2月3.3继承和派生Build_2obj;obj.set1(100,200,300);obj.set2(400);obj.disp();}程序执行的结果是:经度:100纬度:200高度:400面积:300由此可见:派生类Build_2中已继承了基类Build_1中的数据成员posi_x、posi_y、area和基类中的成员函数set1,并同时增加了新的成员height和成员函数set2、disp。第76页,课件共171页,创作于2023年2月3.3继承和派生3.派生类的数据成员和成员函数、构造过程与构造函数(1)派生类的数据成员和成员函数的来源有两个,一个来源是从基类继承来的数据成员和成员函数,对于继承来的数据成员,即使没有用也不能取消,只能不理会它们,但允许对一些继承来的成员函数重新定义,即在原有基类的成员函数的基础上,再增加一些操作,以完成派生类所要求的操作。另一个来源就是由派生类自己定义的数据成员和成员函数,这些成员的定义方法同一般类成员的定义方法基本一样。(2)通过派生类的对象调用一个被重新定义过的基类的成员函数,所调用的是派生类的成员函数,此时,若想调用基类的成员函数,必须在成员函数名前加基类名作用域分隔符“::”。第77页,课件共171页,创作于2023年2月3.3继承和派生(3)在创建派生类的对象时,由于派生类的对象包含了基类的数据成员,因此派生类的构造函数除初始化其自身定义的数据成员外,还必须对基类中的数据成员进行初始化,也就是说,派生类的构造函数要负责调用基类的构造函数。所以派生类的构造函数的定义格式如下:派生类名::派生类构造函数名(参数表):基类构造函数名(参数表){……}(4)虽然派生类可以直接访问基类的保护数据成员,甚至在构造时初始化它们,但是一般不这么做,而是通过基类的接口(成员函数)去访问它们,初始化也是通过基类的构造函数。这样,避免了类与类之间的相互干扰。第78页,课件共171页,创作于2023年2月3.3继承和派生(5)基类的对象只能调用基类的成员函数,不能调用派生类的成员函数。(6)在定义派生类的对象时,系统首先执行基类的构造函数,然后执行派生类的构造函数。而系统执行析构函数的顺序恰恰相反,即先执行派生类的析构函数,再执行基类的析构函数。(7)若在基类中没有定义任何构造函数,这时在派生类的构造函数的定义中可以省略对基类构造函数的调用,此时系统将去调用基类的默认构造函数。如:【例3-10】本例中分别定义一个描述圆的类Ccircle和描述一个圆柱体的类Ccylinder。第79页,课件共171页,创作于2023年2月3.3继承和派生#include"iostream.h"classCcircle//定义圆类{protected:doubleradius;public:Ccircle(doubleradiusval){radius=radiusval;}voidsetradius(doubleradiusval){radius=radiusval;}doublegetradius()const{returnradius;}第80页,课件共171页,创作于2023年2月3.3继承和派生doublearea()const{return3.14*radius*radius;}};classCcylinder:publicCcircle//定义圆柱体类{protected:doubleheight;public:Ccylinder(doubleradiusval,doubleheightval);voidsetheight(doubleheightval){height=heightval;}doublegetheight()const{returnheight;}第81页,课件共171页,创作于2023年2月3.3继承和派生doublearea()const//重新定义area()函数{//此处调用的是基类的成员函数area(),必须加::return2*Ccircle::area()+2*3.14*radius*height;}};Ccylinder::Ccylinder(doubleradiusval,doubleheightval):Ccircle(radiusval)//调用Ccircle类的构造函数对radius初始化//派生类Ccylinder的构造函数不但初始化自身定义的成员height,而且通过调用基类的构造函数Ccircle()初始化从基类继承来的数据成员radius{height=heightval;}第82页,课件共171页,创作于2023年2月3.3继承和派生voidmain(){Ccirclecircle(10);Ccylindercylinder(2,5);cout<<"圆柱体表面积:"<<cylinder.area()<<endl;cout<<"圆柱体底面积:"<<cylinder.Ccircle::area()<<endl;cout<<"圆的面积是:"<<circle.area();}程序的执行结果为:圆柱体表面积:87.92圆柱体底面积:12.56圆的面积是:314第83页,课件共171页,创作于2023年2月3.3继承和派生程序说明:(1)求圆的面积与圆柱体的表面积的方法是不同的。因此,在派生类Ccylinder中重新定义了基类成员函数area()。在主函数中,通过基类和派生类的不同对象,分别调用了这两个area()函数。(2)由于继承关系,在类Ccylinder中存在两个同名的函数arear()。其中一个是从基类Ccircle中继承过来的,另一个是在派生类Ccylinder中新定义的。这样,当通过派生类对象调用area()函数时,C++编译器将沿继承关系搜索,使用离调用对象最近的那个版本的函数。(3)如果确实想通过Ccylinder的对象访问从基类Ccircle继承过来的area()函数,则必须使用作用域运算符“::”显式指明。第84页,课件共171页,创作于2023年2月3.3继承和派生通过以上分析可知,C++中处理同名函数有以下3种基本方法:(1)根据函数的参数的特征进行区分。即编译器根据函数的类型或个数进行区分。如:max(int,int)max(float,float)(2)根据类对象进行区分。如:在上例中的main函数中,cylinder.area()circle.area()其中,cyclinder是Ccylinder的一个对象,circle是Ccircle的一个对象。(3)使用作用域运算符“::”进行区分,如:Ccircle::area()以上三种区分方法都是在程序编译过程中完成的,称为静态联编,除此之外,C++还提供称为动态联编。第85页,课件共171页,创作于2023年2月3.3继承和派生3.3.3多重继承在单一继承关系中,每个派生类最多只有一个直接基类,但它可以有多个间接基类。在C++中不仅支持单一继承,而且也支持多重继承,所谓多重继承,是指派生类从多个基类中派生而来,使派生类继承多个基类的特征,在多重继承关系中,派生类有多个直接基类。定义多重继承类的方式如下:class派生类名:访问方式基类名,访问方式基类名……{……};其中:访问方式为public或private,功能同单一继承。多重继承下派生类的构造函数必须同时负责所有基类构第86页,课件共171页,创作于2023年2月3.3继承和派生造函数的调用,对于派生类构造函数的参数个数必须同时满足多个基类初始化的需要。所以,在多重继承下,派生类的构造函数的定义格式如下:派生类构造函数名(参数表):基类名1(参数表1),……{……}在多重继承下,系统首先执行各基类的构造函数,然后再执行派生类的构造函数,处于同一层次的各基类构造函数的执行顺序与声明派生类时所指定的各基类顺序一致,而与派生类的构造函数定义中所调用基类构造函数的顺序无关。第87页,课件共171页,创作于2023年2月3.3继承和派生【例3-11】测试多重继承关系下,基类和派生类的构造函数的执行顺序。
#include“iostream.h”
classB1{protected:intb1;public:B1(intval1){b1=val1;cout<<”base1iscalled“<<endl;}};第88页,课件共171页,创作于2023年2月3.3继承和派生classB2{protected:intb2;public:B2(intval2){b2=val2;cout<<”base2iscalled”<<endl;}};classD:publicB1,publicB2{protected:intd;第89页,课件共171页,创作于2023年2月3.3继承和派生public:D(intval1,intval2,intval3);};D::D(intval1,intval2,intval3):B1(val1),B2(val2)//如改为D::D(intval1,intval2,intval3):B2(val2),B1(val1)效果一样{d=val3;cout<<”erivedclassiscalled“;}voidmain(){Ddobj(1,2,3);}第90页,课件共171页,创作于2023年2月3.3继承和派生该程序的执行结果是:基类B1的构造函数被调用基类B2的构造函数被调用派生类D的构造函数被调用3.3.4虚基类多重继承下,一个派生类可从多个基类派生出来,又由于一个基类可派生出多个派生类,因此可能会产生一个类是通过多条路径从一个给定的类中派生出来的,如图3.1所示。
BBD1D2D3图3.1多重继承的二义性第91页,课件共171页,创作于2023年2月3.3继承和派生从上图可以看出:派生类D3中将继承两份类B的成员,一份由类D1派生得到,另一份由D2派生而来,这时通过派生类D3的对象访问类D1
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026江苏事业单位统考南通市海安市招聘81人笔试参考题库及答案解析
- 2026上半年舟山市属事业单位招聘38人-统考笔试参考题库及答案解析
- 2026宁夏宝丰储能正极材料厂招聘165人笔试备考试题及答案解析
- 2026年舟山普陀区东港街道招聘工作人员2人笔试备考题库及答案解析
- 2026浙江工贸职业技术学院招聘66人(教研岗位)笔试参考题库及答案解析
- 2026年芜湖市镜湖区荆山社区医院招聘1名笔试备考题库及答案解析
- 2026山东济宁市直教育系统校园招聘81人笔试参考题库及答案解析
- 海南海口市重点达标名校2025-2026学年初三月考(六)语文试题含解析
- 扬州中学教育集团2025-2026学年初三下学期周测物理试题含解析
- 高效率项目执行承诺书(3篇)
- 软件验证的一般原则
- 胶片调色摄影课件
- 抗癫痫发作药物联合使用中国专家共识2025
- 春天的秘密幼儿园教育
- 《医学影像检查技术学》课件-足X线摄影
- 黄金冶炼项目可行性研究报告
- 第15课《十月革命与苏联社会主义建设》中职高一下学期高教版(2023)世界历史全一册
- GB/T 11981-2024建筑用轻钢龙骨
- 2024年高等教育文学类自考-06216中外建筑史考试近5年真题集锦(频考类试题)带答案
- 《AutoCAD 2023基础与应用》 课件全套 劳动 项目1-8 AutoCAD 2023 入门、绘制简单平面图形-综合实训
- 缠论-简单就是美
评论
0/150
提交评论