1.71k likes | 1.79k Vues
第三章面向对象的程序设计. 本章导读 掌握类与对象的概念,类与对象的定义方法及二者间的区别。 掌握类的成员函数的定义方法、保存方法及调用方法。掌握类中成员的访问机制和方法。 了解对象的作用域和生存期。 理解并掌握构造函数、析构函数、拷贝构造函数、默认构造函数和缺省参数的构造函数的含义、定义方法以及在对象的构造和撤消中的作用。 理解并掌握当一个类的对象作为另一个类的数据成员时,利用初始化表调用构造函数的方法、构造函数的执行顺序。. 本章导读. 理解继承的概念和意义,理解单一继承、多重继承。理解并掌握派生类构造函数的编写要求,以及派生类对象的构造过程和机理。
E N D
第三章面向对象的程序设计 • 本章导读 • 掌握类与对象的概念,类与对象的定义方法及二者间的区别。 • 掌握类的成员函数的定义方法、保存方法及调用方法。掌握类中成员的访问机制和方法。 • 了解对象的作用域和生存期。 • 理解并掌握构造函数、析构函数、拷贝构造函数、默认构造函数和缺省参数的构造函数的含义、定义方法以及在对象的构造和撤消中的作用。 • 理解并掌握当一个类的对象作为另一个类的数据成员时,利用初始化表调用构造函数的方法、构造函数的执行顺序。
本章导读 • 理解继承的概念和意义,理解单一继承、多重继承。理解并掌握派生类构造函数的编写要求,以及派生类对象的构造过程和机理。 • 掌握虚函数和多态性的概念,掌握虚函数的定义方法、调用方法及其在实现多态性方面所起到的作用。了解纯虚函数与抽象基类的概念。 • 了解类的静态成员(静态数据成员和静态成员函数)的概念、定义方法及其作用。 • 了解友元函数与友元类的概念、定义方法及其作用。 • 了解运算符重载及在程序中实现运算符重载的方法。 • 了解模板的概念,在程序中如何定义类模板和函数模板。
3.1 类与对象的定义 • 类和对象是面向对象程序设计(OOP)的两个最基本概念。所谓对象就是客观事物在计算机中的抽象描述;类是对具有相似属性和行为的一组对象的统一描述。 • 3.1.1 类的定义 • C++的类是在结构体的基础上扩充而来的。类是把各种不同类型的数据(称为数据成员)和对数据的操作(成员函数)组织在一起而形成的用户自定义的数据类型。 • C++中,类定义包括类说明和类实现两大部分。说明部分提供了对该类所有数据成员和成员函数的描述,而实现部分提供了所有成员函数的实现代码。
3.1 类与对象的定义 • 类定义的一般形式为: • class 类名 • {private: • 数据成员或成员函数 • protected: • 数据成员或成员函数 • public: • 数据成员或成员函数 • }; • <各成员函数的实现代码>
3.1 类与对象的定义 • 说明: • 1. class是定义类的关键字,类名由用户自己定名,必须是C++的有效标识符,但一般首字母大写。 • 2. 大括号的部分是类的成员(数据成员和函数成员),它们分成三部分,分别由private、public、proctected三个关键字后跟冒号来指定。这三部分可以任何顺序出现,且在一个类的定义中,这三部分并非必须同时出现。 • (1)如果数据成员或成员函数在类的private部分,那么在类之外是不能存取的,只有类中的成员函数才能存取private的数据成员和成员函数。 • (2)在一个类的public部分说明的数据成员或成员函数可被程序中的任何函数或语句存取,public成员多为成员函数,用来提供一个与外界的接口,外界只有通过这个接口才可以实现对private成员的存取。
3.1 类与对象的定义 • (3)在类的protected部分说明的数据成员和成员函数是不能在类之外存取的,只有类的成员函数及其子类(派生类)可以存取protected的成员。 • (4)当定义类时,当未指明成员是哪部分时,默认是属于private成员,但一般不要采用默认形式。 • 如:下例中定义描述图书的类定义 • class Record • { private: //private成员 • char bookname[20]; //数据成员bookname, • // 用于表示图书的名称 • int number; //数据成员number,表示图书编号
3.1 类与对象的定义 • public: //public成员 • void regist(char *a,int b); //成员函数regist,用于给 • //各数据成员赋值 • void show(); //成员函数show,显示各数据成员的值 • }; • 要特别注意,在类的定义中,类的说明部分的右边大括号后面必须有一 “;”. • 根据类的定义,可看出:类是实现封装的工具,所谓封装就是将类的成员按使用或存取的方式分类,有条件地限制对类成员的使用,而封装是通过public和private与成员函数实现的。private的成员构成类的内部状态,public的成员则构成与外界通信的接口,通过public的成员函数来使用private的数据成员,从而在C++中实现了封装。
3.1 类与对象的定义 • 3.1.2 成员函数的定义 • 类中的成员函数可以在以下两处定义: • (1)将成员函数的定义直接写在类中: • 如:对于前面定义的图书类Record来说,其成员函数regist和show的定义可直接写在类的定义体中。 • class Record • { private: • char bookname[20]; • int number;
3.1 类与对象的定义 • public: • void regist(char *a,int b) //成员函数regist()的定义 • { strcpy(bookname,a); //给数据成员bookname赋值 • number=b; //给数据成员number赋值 • } • void show() //成员函数show()的定义 • { cout<<”名称:”<<bookname<<endl; • cout<<”号码:”<<number<<endl; • } • };
3.1 类与对象的定义 • 在类中直接定义成员函数的情况一般适合于成员函数规模较小的情况,也就是说它们一般为内联函数,即使没有明确用inline关键字。 • (2)在类的定义体中只写出成员函数的原型说明,而成员函数的定义写在类的定义之后,这种情况比较适合于成员函数体较大的情况,但这时要求在定义成员函数时,在函数的名称之前加上其所属性类名及作用域运算符“::”。 • 定义成员函数的一般类型为: • 返回值类型 类名::成员函数名(参数说明) • { 类体 }
3.1 类与对象的定义 • 此处的::符号叫作用域运算符,用它来指明哪个函数属于哪个类或哪个数据属于哪个类,所以使用类中成员的全名是:类名::成员名。 • 而如果没有类名,则为全局数据或全局函数(非成员函数),也就是说类名是其成员名的一部分。 • 如class Record • { private: • char bookname[20]; • int number; • public: • void regist(char *a,int b);//成员函数regist的原型 • void show(); //成员函数show的原型 • }; //定义图书类Record
3.1 类与对象的定义 • void Record::regist(char *a,int b) //regist()是类Record的 • //成员函数 • { strcpy(bookname,a); • number=b; } • void Record::show() // show()是类Record的成员函数 • { cout<<”名称:”<<bookname<<endl; • cout<<”号码:”<<number<<endl; } • 此外,目前开发程序的通常将类的定义写在一个头文件(.h文件)中,成员函数的定义写在一个程序文件(.cpp文件)中,这样,就相当于把类的定义(头文件)看成是类的外部接口,类的成员函数的定义看成类的内
3.1 类与对象的定义 • 部实现。如:对上例可改成将类的定义体写在myapp.h文件中,而成员函数的定义体写在另外一个文件myapp.cpp中: • //myapp.h文件 • class Record • { private: • char bookname[20]; • int number; • public: • void regist(char *a,int b); • void show(); • };
3.1 类与对象的定义 • //myapp.cpp文件 • #include “iostream.h” • #include “myapp.h” //一定不要忘记嵌入该头文件 • void record::regist(char *a,int b) • { strcpy(bookname,a); • number=b; • } • void record::show() • { cout<<”名称:”<<bookname<<endl; • cout<<”号码:”<<number<<endl; • }
3.1 类与对象的定义 • 3.1.3 对象的定义 • 对象是类的实例,定义对象的一般格式为: • 类名 变量名表; • 或 类名 对象名; • 如:上例中已定义了类Record,则: • Record book1,book2; //此处的book1,book2就是Record • //类型,也就是类的两个对象 • 类是抽象的概念,而对象是具体的,类只是一种数据类型,而对象是属于该类(数据类型)的一个变量,占用了各自的存储单元,每个对象各自具有了该类的一套数据成员(静态成员除外),而所有成员函数是所有对象共有的。每个对象的函数成员都通过指针指向同一个代码空间。
3.1 类与对象的定义 • 3.1.4 访问对象的成员 • 访问对象的成员包括读写对象的数据成员和调用它的成员函数,其访问格式是: • 对象名.成员名 • 如上例中,对象的主函数如下: • void main() • { Record book1,book2; //定义对象book1和book2 • //调用成员函数regist,给book1的两个数据成员 • //bookname和number赋值 • book1.regist(“C++编程教程”, 1001); • //调用成员函数regist,给book2的两个数据成员赋值 • book2.regist(“C++语言参考”, 1002);
3.1 类与对象的定义 • //调用成员函数show,显示book1对象的数据成员 • //bookname和number的值 • book1.show(); • //调用成员函数show,显示book2对象的数据成员 • //bookname和number的值 • book2.show(); • } • 如改为下面的代码,则错误: • void main() • { Record book1,book2; • //由于bookname和number是类Record的私有成员,在类外//不能直接使用
3.1 类与对象的定义 • strcpy(book1.bookname,“C++编程教程”); • book1.number=1001; • strcpy(book2.bookname,“C++语言参考”); • book2.number=1002; • book1.show(); • book2.show(); • } • 注意: • 1. 对于类的私有成员,只能通过其成员函数来访问,不能在类外对私有成员访问。
3.1 类与对象的定义 • 2. 调用成员函数时要在函数名之前加上对象名和"."即可,即先指明对象,再指明成员。也可以采用指向对象的指针来访问,但要在函数名前加上指针变量名和“->”。 • 3. 任何对对象私有数据的访问都必须通过向对象发送消息来实现,而且所发送的消息还必须是该对象能够识别和接受的。在C++中,消息发送正是通过公有成员函数的调用来实现的。由于类接口隐藏了对象的内部细节,用户只能通过类接口访问对象,因此,在类设计中必须提供足够的公有接口以捕获对象的全部行为,这正是类设计中的一个最基本的要求。 • 4.上例中,在对象调用book1.regist(“C++编程教程”, 1001);时,成员函数regist除了接受两个实参外,还接
3.1 类与对象的定义 • 受了一个对象book1的地址,这个地址被一个隐含的形参this指针所获取,它等同于执行this=&book1,所以所有对数据成员的访问都隐含地被加上前缀:this->,因此,在成员函数体regist中,执行 • strcpy(bookname,a);number=b;就等价于 • strcpy(this->bookname,a); this->number=b; • 这样,上例中的成员函数regist也可这样定义: • void record::regist(char *a,int b) • { strcpy(this->bookname,a); • this->number=b; • } • 通过以上手段就确保了不同对象调用成员函数时访问的是不同对象的数据,而它们之间没有干扰。
3.1 类与对象的定义 • 3.1.5 对象赋值语句 • 对于同一个类生成的两个对象,可以进行赋值,其功能是将一个对象的数据成员赋值到另一个对象中去,赋值语句的左右两边各是一个对象名: • 【例3-1】对于类example的两个对象obj1和obj2,让obj2的成员数据的值等于obj1的成员数据的值(假定obj1的成员数据num已经存有数据215)。
3.1 类与对象的定义 • # include <windows.h> • # include <iostream.h> • //定义类 • class example • { private: // 数据成员 • int num; • public: // 函数成员说明 • void set ( int i ) • { num=i ; } • void disp ( ) • { cout << "\n num = " << num ; } • };
3.1 类与对象的定义 • // 主程序 • void main () • { example obj1, obj2 ; • obj1.set(215); • obj1.disp (); • obj2=obj1; // 对象赋值语句 • obj2.disp () ; • cout<<endl<<endl; } • 3.1.6 对象的作用域与生存期 • 对象是类的实例,它实质就是某种数据类型的变量,在不同的位置以不同的方式定义对象时,其作用域和生存期是不同的。
3.1 类与对象的定义 • 如:class Desk //定义Desk类 • { public: • int weight; • int high; • int width; • int length; }; • class Stool //定义Stool类 • { public: • int weight; • int high; • int width; • int length;};
3.1 类与对象的定义 • desk da; //定义全局对象 • Stool sa; • void fn() • { static Stool ss; // 静态局部对象 • desk da; // 定义局部对象 • //…… • } • 1.局部对象(不包括局部静态对象) • 其作用域是定义它的函数体,生存期从函数调用开始到函数调用结束,下一次再重新调用函数时,再重新构造对象。 • 构造局部对象的次序(即分配存储单元)是按它们在函数体中声明的顺序。
3.1 类与对象的定义 • 2.静态对象(局部静态和全局静态) • 其作用域是定义它的函数体或程序文件,其生存期是整个程序。构造静态对象的次序是按它们在程序中出现的次序先后,并在整个程序运行开始时(即在主函数运行前)只构造一次。 • 3.全局对象 • 全局对象的作用域是整个程序,生存期是整个程序的运行时间。它也是在程序运行前(即在主函数运行前)只构造一次。 • 4.类中成员的构造次序是以类中声明成员的次序进行。 • 构造函数和析构函数是类的两种特殊的成员函数。
3.2 构造函数与析构函数 • 3.2.1 构造函数 • 构造函数(constructor)是与类名同名的特殊的成员函数,当定义该类的对象时,构造函数将被自动调用以实现对该对象的初始化。构造函数不能有返回值,因而不能指定包括void在内的任何返回值类型。构造函数的定义体可与其它成员函数成员一样,放在类内或类外都可。构造函数的定义格式为: • 类名(形参说明) • { 函数体 } • 构造函数既可定义成有参函数,也可义成无参函数,要根据问题的需要来定。全局变量和静态变量在定义时,将自动赋初值为0;局部变量在定义时,其初始值不固定的。而当对象被定义时,由于对象的意义表达了现实世界的实
3.2 构造函数与析构函数 • 体,所以一旦定义对象,就必须有一个有意义的初始值,在C++中,在定义对象的同时,给该对象初始化的方法就是利用构造函数。如: • 【例3-2】类person包括4个数据成员,用来记录人员信息。生成对象obj,并使用构造函数为obj赋予初始值。 • # include <windows.h> • # include <iostream.h> • class Person //定义类 • { private: //类Person的数据成员 • char name [10] ; //姓名 • int age ; //年龄 • int salary ; //薪金 • char tel[8]; //电话
3.2 构造函数与析构函数 • public: //构造函数Person • Person ( char *xname, int xage,int xsalary, char *xtel ) ; • void disp () ; }; • //函数Person的定义 • Person :: Person ( char *xname,int xage, int xsalary, char *xtel ) • { strcpy (name, xname) ; //给各数据成员提供初值 • age = xage ; • salary = xsalary ; • strcpy (tel, xtel) ; • }
3.2 构造函数与析构函数 • //函数disp的定义 • void Person::disp() • { cout<<endl; • cout << " 姓名:" << name << endl ; • cout << " 年龄:" << age << endl ; • cout << " 工资:" << salary << endl ; • cout << " 电话:" << tel << endl<<endl ;} • // 主函数 • void main( ) • { //生成对象obj并初始化 • Person obj ("张立三", 25, 850,"45672314"); • //显示obj • obj.disp ( ) ; }
3.2 构造函数与析构函数 • 程序的执行结果是: • 姓名:张立三 • 年龄:25 • 工资:850 • 电话:45672314 • 在主函数中的Person obj ("张立三", 25, 850,"45672314");中完成了以下几个功能: • 1. 定义并生成了对象obj。 • 2.在生成对象obj的同时,自动调用相应类的构造函数Person • 3.将初始值"张立三", 25, 850,"45672314"传递给构造函数Person相应的形参xname, xage,xsalary, xtel。 • 4. 执行构造函数体,将相应的值赋给相应的数据成员。
3.2 构造函数与析构函数 • 3.2.2 构造函数的重载 • 如果一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。 • 【例3-3】类rec定义两个重载函数,其中一个是无参函数,另一个是有参函数。它们都是构造函数。 • # include <windows.h> • # include <iostream.h> • //定义类 • class Rec • { private: • char bookname[30]; • int number;
3.2 构造函数与析构函数 • public: • Rec(); //第1个构造函数说明 • Rec (char *a, int b); //第2个构造函数说明 • void show(); • }; • Rec :: Rec () //第1个构造函数定义 • { strcpy(bookname, '\0'); • number=0; } • Rec :: Rec (char *a, int b ) //第2个构造函数定义 • { strcpy(bookname, a); • number=b; }
3.2 构造函数与析构函数 • void Rec :: show ( ) //show的函数定义 • { cout<<"bookname is :"<<bookname<<endl; • cout<<"booknumber is:"<<number<<endl; • } • void main() //主程序 • { Rec mybook(“Visual C++6.0”,10020); //自动调用构造 • //函数Rec(char *a,int b) • mybook.show(); • Rec yourbook; //自动调用构造函数Rec() • yourbook.show(); • }
3.2 构造函数与析构函数 • 程序的执行结果是: • bookname is :Visual C++6.0 • booknumber is:10020 • bookname is :no name • booknumber is:0 • 可见,当出现构造函数重载时,其匹配方式同普通函数重载时的匹配方式。
3.2 构造函数与析构函数 • 3.2.3 默认构造函数与缺省构造函数 • C++规定,每个类必须有一个构造函数。如果在类中没有显式定义构造函数时,则C++编译系统在编译时为该类提供一个默认的构造函数,该默认构造函数是个无参函数,它仅负责创建对象,而不做任何初始化工作。 • 只要一个类定义了一个构造函数(不一定是无参构造函数),C++编译系统就不再提供默认的构造函数。 • 与变量定义相似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的默认值为0,否则对象的初始值是不定的。 • 当构造函数有缺省参数时,称为具有缺省参数的构造函数,在使用时要防止二义性。
3.2 构造函数与析构函数 • 如: • class Myclass //定义类Myclass • { private: • int member; • public: • Myclass(); • Myclass(int i); • …… • } • Myclass:Myclass() //构造函数Myclass • { member=10; }
3.2 构造函数与析构函数 • Myclass:Myclass(int i=10) //构造函数Myclass(int i),该函数 • // 的形参i为缺省参数 • { member=i; } • void main() • { Myclass x(20); • Myclass y; //产生二义性,无法确定自动调用哪个构造 • //函数完成对象的构造 • …… • }
3.2 构造函数与析构函数 • 3.2.4 析构函数 • 当一个对象被定义时,系统自动调用构造函数为该对象分配相应的资源,当对象使用完毕后,这些系统资源需要在对象消失前被释放。 • 析构函数是类的一个特殊成员函数,其函数名称是在类名的前面加上~,它没有返回值,没有参数,不能随意调用,也没有重载,只是在类对象生命期结束时,系统自动调用。析构函数的定义方式为: • ~类名() • { 函数体 } • 注:(1)一个类中只能拥有一个析构函数。
3.2 构造函数与析构函数 • (2)如果程序员在定义类时,没有为类提供析构函数,则系统会自动创建一个默认的析构函数,其形式为: • ~类名(){ } • (3)对于一个简单的类来说,大多可以直接使用系统提供的默认析构函数。但是,如果在类的对象中分配有动态内存(如:用new申请分配的内容)时,就必须为该类提供适当的析构函数,完成清理工作。 • (4)对象被析构的顺序与对象建立时的顺序正好相反。即最后构造的对象先被析构。
3.2 构造函数与析构函数 • 【例3-4】类Teacher的构造函数为name申请存储空间,在析构函数中释放该空间。 • # include <windows.h> • # include <iostream.h> • //定义类 • class Teacher • { private: • char * name; • int age; • public: • //说明构造函数Teacher
3.2 构造函数与析构函数 • Teacher(char *i, int a ) • { name=new char[strlen(i)+1] ; • //用new为name成员分配堆内存 • strcpy (name, i); • age = a; • cout << "\n 执行构造函数Teacher "<< endl; }; • //说明析构函数~Teacher • ~ Teacher ( ) • { delete name ; • cout << " 执行析构函数~Teacher "<< endl<<endl; } ; • void show(); };
3.2 构造函数与析构函数 • void Teacher :: show () • { cout << " 姓名:"<<name<<" "<<"年龄:"<<age<< endl; } • void main() //主程序 • { Teacher obj ("张立三", 25); • obj.show(); • } • 程序的执行结果是: • 执行构造函数Teacher • 姓名:张立三 年龄: 25 • 执行析构函数~Teacher
3.2 构造函数与析构函数 • 3.2.5 拷贝构造函数 • 拷贝构造函数是C++中引入的一种新的构造函数。定义一个拷贝构造函数的方式是: • 类名(const 类名 &形式参数) • { 函数体 } • 由此可看出: • (1)拷贝构造函数的名称与类的名称相同,且它只有一个参数,该参数就是对该类对象的引用。 • (2)拷贝构造函数的功能是用于实现对象值的拷贝,通过将一个同类对象的值拷贝给一个新对象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。
3.2 构造函数与析构函数 • 【例3-5】 Example是一个人员信息类。用普通构造函数生成obj1,用拷贝构造函数生成obj2。 • # include <windows.h> • # include <iostream.h> • class Example • { private: • char *name; • int num; • public: • example(int i, char *str ) // 构造函数定义 • { name=str; • num=i; }
3.2 构造函数与析构函数 • example(const Example &x) // 拷贝构造函数定义 • { num=x.num; } • void list() // 定义显示函数list • { cout<<"\数据成员num的值="<<num<<endl<<endl; } • }; • void main () • { example obj1(215, “张立三”); • //调用函数Example(int i,char *str )构造obj1 • example obj2(obj1); //使用拷贝构造函数构造obj2 • obj2.list(); //显示obj2的值 • …… //其它程序部分 • }
3.2 构造函数与析构函数 • 程序的执行结果是: • 数据成员num的值=215 • 数据成员num的值=215 • 说明: • (1)上例中在main函数中的语句Example obj2(obj1);在执行时,系统会自动调用类Example的拷贝构造函数完成对obj2对象的构造。 • (2)如果程序员没有为所设计的类提供显式的拷贝构造函数,则系统会自动提供一个默认的拷贝构造函数,其功能是:把作为参数的对象的数据成员逐个拷贝到目标变量中,这称为成员级复制(或浅拷贝)。
3.2 构造函数与析构函数 • 3.2.6 一个类的对象作为另一个类的数据成员 • 一个类中的数据成员除了可以是int, char, float等这些基本的数据类型外,还可以是某一个类的一个对象。用子对象创建新类。 • 在C++中,当把一个类的对象作为新类的数据员时,则新类的定义格式可表示为: • class X • { 类名1 成员名1; • 类名2 成员名2; • …… • 类名n 成员名n; • ……//其它成员 • };
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) • { ……}
3.2 构造函数与析构函数 • 其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。 • 在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。