500 likes | 708 Views
第 2 章 C++ 面向对象程序设计. 在传统的结构化程序设计方法中,数据和处理数据的程序是分离的。当对某段程序进行修改或删除时,整个程序中所有与其相关的部分都要进行相应的修改,从而程序代码的维护比较困难。为了避免这种情况的发生, C++ 引用了面向对象的设计方法,它是将数据及处理数据的相应函数“封装”到一个“类”中,类的实例称为“对象”。在一个对象内,只有属于该对象的函数才可以存取该对象的数据。这样,其他函数就不会无意中破坏它的内容,从而达到保护和隐藏数据的效果。. 2.1.1 类的定义.
E N D
第2章C++面向对象程序设计 在传统的结构化程序设计方法中,数据和处理数据的程序是分离的。当对某段程序进行修改或删除时,整个程序中所有与其相关的部分都要进行相应的修改,从而程序代码的维护比较困难。为了避免这种情况的发生,C++引用了面向对象的设计方法,它是将数据及处理数据的相应函数“封装”到一个“类”中,类的实例称为“对象”。在一个对象内,只有属于该对象的函数才可以存取该对象的数据。这样,其他函数就不会无意中破坏它的内容,从而达到保护和隐藏数据的效果。
2.1.1 类的定义 类的定义一般地分为声明部分和实现部分。声明部分是用来声明该类中的成员,包含数据成员(或称“成员变量”)的声明和成员函数的声明。 概括说来,声明部分将告诉使用者“干什么”,而实现部分是 告诉使用者“怎么干”。 成员函数是用来对数据成员进行操作的,又称为“方法”。 实现部分是用来对成员函数的定义。
C++中定义类的一般格式如下: class <类名> { private: [<私有数据和函数>] public: [<公有数据和函数>] }; <各个成员函数的实现> 其中,class是定义类的关键字,class的后面是用户定义的类名,通常用大写的 C字母开始的标识符作为类名,C用来表示类(Class),以与对象、函数及其他数 据类型相区别。类中的数据和函数是类的成员,分别称为数据成员和成员函数。
2.1.1 类的定义 定义类时还应注意: • 类中的数据成员的数据类型可以是任意的,但不允许对所定义的数据成员进行初始化, 例如类CMeter中,下面的定义是错误的: class CMeter { ... private: int m_nPos = 10; // 错误 ... }; (2) 在“public:”或“private:”后面定义的所有成员都是公有或私有的,直到下一个“public:”或 “private:”出现为止。若成员前面没有类似“public:”或“private:”,则所定义的成员是 private(私有),这是类的默认设置。 (3) 关键字public和private可以在类中出现多次,且前后的顺序没有关系;但最好先声明公 有成员,后声明私有成员,因为public成员是用户最关心的。 (4) 除了public和private外,关键字protected(保护)也可修饰成员的类型,它与private两者 基本相似,但在类的继承时有所不同(后面还会讲到)。 (5) 数据成员的类型可以是任意的,包含整型、浮点型、字符型、数组、指针和引用等,也 可以是另一个类的对象。 (6) 尽量将类单独存放在一个文件中或将类的声明放在.h文件中而将成员函数的实现放在 与.h文件同名的.cpp文件中。以后将会看到,Visual C++ 6.0为用户创建的应用程序框架中 都是将各个类以.h和同名的.cpp文件组织的。
2.1.2 对象的定义 一个类定义后,就可以定义该类的对象,如下面的格式: <类名> <对象名表> 其中,类名是用户已定义过的类的标识符,对象名可以有一个或多个,多个时要 用逗号分隔。被定义的对象既可以是一个普通对象,也可以是一个数组对象或指 针对象。例如: CMeter myMeter, *Meter, Meters[2]; 这时,myMeter是类CMeter的一个普通对象,Meter和Meters分别是该类的一个 指针对象和对象数组。 一个对象的成员就是该对象的类所定义的数据成员(成员变量)和成员函数。访问 对象的成员变量和成员函数和访问变量和函数的方法是一样的,只不过要在成员 前面加上对象名和成员运算符“.”,其表示方式如下: <对象名>.<成员变量> <对象名>.<成员函数>(<参数表>)
2.1.3 构造函数和析构函数 1. 构造函数 前面已提及,在类的定义中是不能对数据成员进行初始化的。为了能给数据成员自动设置 某些初始值,这时就要使用类的特殊成员函数——构造函数。构造函数的最大特点是在对 象建立时它会被自动执行,因此用于变量、对象的初始化代码一般放在构造函数中。 C++规定:构造函数必须与相应的类同名,它可以带参数,也可以不带参数,与一般的成 员函数定义相同,也可以重载。例如: class CMeter { public: CMeter(int nPos ) // 带参数的构造函数 { m_nPos = nPos; } ... } 这样若有: CMeter oMeter(10), oTick(20); 则会自动调用构造函数CMeter(int nPos ),从而使得对象oMeter中的私有成员m_nPos的 值为10;使得对象oTick中的私有成员m_nPos的值为20。
2.1.3 构造函数和析构函数 2. 析构函数 与构造函数相对应的是析构函数。析构函数是另一个特殊的C++成员函数,它只 是在类名称前面加上一个“~”符号。每一个类只有一个析构函数,没有任何参 数,也不返回任何值。例如: class CMeter { public: ... ~CMeter( ) { } // 析构函数 ... } 析构函数只有在下列两种情况下才会被自动调用: (1) 当对象定义在一个函数体中,该函数调用结束后,析构函数被自动调用。 (2) 用new为对象分配动态内存,当使用delete释放对象时,析构函数被自动调 用。
2.1.3 构造函数和析构函数 3. 默认构造函数和析构函数 系统自动生成的默认构造函数和析构函数如下所示: CMeter( ) // 默认构造函数的形式 { } ~CMeter( ) // 默认析构函数的形式 { } 需要说明的是,在用户定义一个对象时,编译器会自动根据对象定义的格式选择相应的构造函数。例如: CMeter m1, m2; 由于m1和m2不带任何参数,当类没有用户定义的构造函数时,则编译器就会使用默认构造函数对m1和m2进行初始化。用默认构造函数对对象进行初始化时,则将对象的所有数据成员都初始化为零或空。
2.1.3 构造函数和析构函数 4. 构造函数的重载 构造函数可以被重载,C++会根据对象定义中的参数选择合适的构造函数。例如: [例Ex_ConOverLoad] 构造函数的重载 #include <iostream.h> class CDate { public: CDate(); CDate(int day); CDate(int month, int day); CDate(int year, int month, int day); // 其他公共成员 private: int nYear, nMonth, nDay; }; CDate::CDate() { nMonth = 7; nDay = 30; nYear = 2002; cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } CDate::CDate(int day)
{ nMonth = 7; nDay = day; nYear = 2002; cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } CDate::CDate(int month, int day) { nMonth = month; nDay = day; nYear = 2002; cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } CDate::CDate(int year, int month, int day) { nYear = year; nMonth = month; nDay = day; cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } void main() { CDate day1; CDate day2(28); CDate day3(8, 1); CDate day4(2003, 3, 10); } 运行结果为:
2.1.3 构造函数和析构函数 [例Ex_ConDefault] 带默认参数的构造函数 #include <iostream.h> class CDate { public: CDate(int year = 2002, int month = 7, int day = 30) { nYear = year; nMonth = month; nDay = day; cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } // 其他公共成员 private: int nYear, nMonth, nDay; }; void main() { CDate day1; CDate day2(2002, 8); } 运行结果为:
2.1.3 构造函数和析构函数 5. 拷贝构造函数 拷贝构造函数是一种特殊的成员函数,它的功能是用一个已知的对象来初始化一 个被创建的同类的对象。拷贝构造函数的函数名与构造函数一样,也是它所属类 的类名。不过与一般的构造函数有一点不同,即它只有一个参数,且参数是同类 的一个对象的引用。 定义一个拷贝构造函数的一般形式如下: <类名>::<拷贝构造函数名>(const<类名>&<引用名>) 其中,const是一个类型修饰符,被它修饰的对象是一个不能被更新的常量。例 如:
2.1.3 构造函数和析构函数 [例Ex_ConCopy] 拷贝构造函数的使用 #include <iostream.h> class CDate { public: CDate(int year = 2002, int month = 7, int day = 30) { cout<<"调用构造函数"<<endl; nYear = year; nMonth = month; nDay = day; cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } CDate(const CDate &ymd) // 定义的拷贝构造函数 { cout<<"调用拷贝构造函数"<<endl; cout<<ymd.nYear<<"-"<<ymd.nMonth<<"-"<<ymd.nDay<<endl; } // 其他公共成员 private: int nYear, nMonth, nDay; }; void main() { CDate day1(2002, 8); CDate day2(day1); }
2.1.3 构造函数和析构函数 [例Ex_ConCopy] 拷贝构造函数的使用 运行结果为:
2.1.3 构造函数和析构函数 5. 拷贝构造函数 实际上,如果类中没有声明拷贝构造函数,则编译器自动生成一个默认的拷贝构造函数。例如: [例Ex_ConCopyDefault] 默认拷贝构造函数的使用 #include <iostream.h> class CDate public: CDate(int year = 2002, int month = 7, int day = 30) { cout<<"调用构造函数"<<endl; nYear = year; nMonth = month; nDay = day; } void output() { cout<<nYear<<"-"<<nMonth<<"-"<<nDay<<endl; } private: int nYear, nMonth, nDay; }; void main() { CDate day1(2002, 8); CDate day2(day1); // 调用默认的拷贝函数 day1.output(); day2.output(); }
2.1.3 构造函数和析构函数 [例Ex_ConCopyDefault] 默认拷贝构造函数的使用 运行结果为:
2.1.4 对象成员初始化 前面所遇到的都是单独的一个类,但在实际应用中往往需要多个类,这时就可能 把一个已定义类的对象作为另一个类的成员。为了能对这些对象成员进行初始 化,C++允许采用这样的构造函数定义格式: <类名>::<构造函数名>(形参表):对象1(参数表), 对象2(参数表), …, 对象n(参数表) { } 其中,对象1、对象2、…、对象n就是该类使用的其他类的对象,冒号“:”后面 的列表称为成员初始化列表。下面来看一个示例:
2.1.4 对象成员初始化 [例Ex_InitMultObject] 对象成员的初始化 #include <iostream.h> class CPoint { public: CPoint(int x, int y) { nPosX = x; nPosY = y; } void ShowPos() { cout<<"当前位置:x = "<<nPosX<<", y = "<<nPosY<<endl; } private: int nPosX, nPosY; }; class CSize { public: CSize(int l, int w) { nLength = l; nWidth = w; } void ShowSize() { cout<<"当前大小:l = "<<nLength<<", w = "<<nWidth<<endl; } private: int nLength, nWidth; };
class CRect { public: CRect(int left, int top, int right, int bottom) :size(right-left, bottom-top), ptCenter((left+right)/2, (top+bottom)/2) { } void Show() { ptCenter.ShowPos(); size.ShowSize(); } private: CPoint ptCenter; CSize size; }; void main() { CRect rc(10, 100, 80, 250); rc.Show(); } 运行结果为: 代码中,声明类CRect的构造函数时,将成员CPoint类对象ptCenter和CSize类对象size按CRect构造函数的形参进行初始化。
2.1.5 静态成员 静态成员的提出是为了解决数据共享的问题。 1. 静态数据成员 它是这样定义的: (1) 使用关键字static声明静态数据成员。 (2) 对静态数据成员进行初始化。由于静态数据成员要实际在分配空间,因此不能在类声明中进行初始化。静态数据成员初始化在类的外部进行,且与一般数据 成员初始化不同,它的格式如下: <数据类型><类名>::<静态数据成员名>=<值> 例如:
2.1.5 静态成员 [例Ex_StaticData] 静态数据成员的使用 #include <iostream.h> class CSum { public: CSum(int a = 0, int b = 0) { nSum += a+b; } int GetSum() { return nSum; } void SetSum(int sum) { nSum = sum; } private: static int nSum; // 声明静态数据成员 }; int CSum::nSum = 0; // 静态数据成员的初始化 void main() { CSum one(10, 2), two; cout<<"sum = "<<one.GetSum()<<endl; one.SetSum(5); cout<<"sum = "<<one.GetSum()<<endl; cout<<"sum = "<<two.GetSum()<<endl; } 运行结果为:
2.1.5 静态成员 2. 静态成员函数 [例Ex_StaticFunc] 静态成员函数的使用 #include <iostream.h> class CSum { public: CSum(int a = 0, int b = 0) { nSum += a+b; } int GetSum() { return nSum; } void SetSum(int sum) { nSum = sum; } static void ShowData(CSum one); // 声明静态成员函数 private: static int nSum; }; void CSum::ShowData(CSum one) // 静态成员函数的实现 { cout<<"直接使用静态成员"<<endl; cout<<"sum = "<<nSum<<endl; cout<<"使用同类的对象"<<endl; cout<<"sum = "<<one.GetSum()<<endl; }
int CSum::nSum = 0; void main() { CSum one(10, 2); CSum::ShowData(one); // 直接访问 one.SetSum(8); one.ShowData(one); // 通过对象访问 } 运行结果为:
2.1.6 友元 [例Ex_FriendFunc] 友元函数的使用 #include <iostream.h> class CPoint { public: CPoint() { m_x = m_y = 0; } CPoint( unsigned x, unsigned y ) { m_x = x; m_y = y; } void Print() { cout << "Point(" << m_x << ", " << m_y << ")"<< endl; } friend CPoint Inflate(CPoint &pt, int nOffset); // 声明一个友元函数 private: unsigned m_x, m_y; }; CPoint Inflate ( CPoint &pt, int nOffset ) // 友元函数的定义 { CPoint ptTemp = pt; ptTemp.m_x += nOffset; // 直接改变私有数据成员m_x和m_y
ptTemp.m_y += nOffset; return ptTemp; } void main() { CPoint pt( 10, 20 ); pt.Print(); pt = Inflate(pt, 3); // 调用友元函数 pt.Print(); } 运行结果为:
2.1.7 常类型 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能 被更新的。因此,定义或说明常类型时必须进行初始化。 1. 常对象 常对象是指对象常量,定义格式如下: <类名> const <对象名> 定义常对象时,修饰符const可以放在类名后面,也可以放在类名前面。例如: class COne { public: COne(int a, int b) { x = a; y = b; } … private: int x, y; }; const COne a(3,4); COne const b(5,6); 其中,a和b都是COne对象常量,初始化后就不能再被更新。
2.1.7 常类型 2. 常指针和常引用 常指针也是使用关键字const来修饰的。但需要说明的是,const的位置不同,其含意也不同, 它有三种形式。 第一种形式是将const放在指针变量的类型之前,表示声明一个指向常量的指针。此时,在程 序中不能通过指针来改变它所指向的数据值,但可以改变指针本身的值。例如: int a = 1, b = 2; const int *p1 = &a; // 声明指向int型常的指针p1, 指针地址为a的地址 *p1 = 2; // 错误,不能更改指针所指向的数据值 p1 = &b; // 正确,指向常量的指针本身的值是可以改变的 第二种形式是将const放在指针定义语句的指针名前,表示指针本身是一个常量,称为指针常 量或常指针。因此,不能改变这种指针变量的值,但可以改变指变量所指向的数据值。例如: int a = 1, b = 2; int * const p1 = &a; // 声明指向int型常的指针p1, 指针地址为a的地址 int * const p2; // 错误,在声明指针常量时,必须初始化 *p1 = 2; // 正确,指针所指向的数据值可以改变 p1 = &b; // 错误,指针常量本身的值是不可改变的 第三种形式是将const在上述两个地方都加,表示声明一个指向常量的指针常量,指针本身的 值不可改变,而且它所指向的数据的值也不能通过指针改变。例如: int a = 1, b = 2; const int * const pp = &a; *pp = 2; // 错误 pp = &b; // 错误
2.1.7 常类型 [例Ex_ConstPara] 常参数的函数传递 #include <iostream.h> class COne { public: void print(const int *p, int n) // 使用常参数 { cout<<"{"<<*p; for (int i = 1; i<n; i++) cout<<", "<<*(p+i); cout<<"}"<<endl; } }; void main() { int array[6] = {1, 2, 3, 4, 5, 6}; COne one; one.print(array, 6); } 运行结果为:
2.1.7 常类型 3. 常成员函数 使用const关键字进行声明的成员函数,称为“常成员函数”。只有常成员 函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数 不能用来操作常对象。常成员函数说明格式如下: <类型说明符> <函数名> (<参数表>) const; 其中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组 成部分,因此,在函数实现部分也要带const关键字。例如:
2.1.7 常类型 [例Ex_ConstFunc] 常成员函数的使用 #include <iostream.h> class COne { public: COne(int a, int b) { x = a; y = b; } void print(); void print() const; // 声明常成员函数 private: int x, y; }; void COne::print() { cout<<x<<", "<<y<<endl; } void COne::print() const { cout<<"使用常成员函数:"<<x<<", "<<y<<endl; } void main() { COne one(5, 4); one.print(); const COne two(20, 52); two.print(); } 运行结果为: 程序中,类COne声明了两个重载成员函数,一个带const,一个不带。语句“one.print();”调用成员函数“void print();”,而“two.print();”调用常成员函数“void print() const;”。
2.1.7 常类型 4. 常数据成员 类型修饰符const不仅可以说明成员函数,也可以说明数据成员。由于const类型对象必须被初始化,并 且不能更新,此,在类中声明了const数据成员时,只能通过成员初始化列表的方式来生成构造函数对 数据成员初始化。例如: [例Ex_ConstData] 常数据成员的使用 #include <iostream.h> class COne { public: COne(int a):x(a),r(x) // 常数据成员的初始化 { } void print(); const int &r; // 引用类型的常数据成员 private: const int x; // 常数据成员 static const int y; // 静态常数据成员 }; const int COne::y = 10; // 静态数据成员的初始化 void COne::print() { cout<<"x = "<<x<<", y = "<<y<<", r = "<<r<<endl; } void main() { COne one(100); one.print(); } 该程序的运行结果为:
2.1.8 this指针 this指针是一个仅能被类的非静态成员函数所能访问的特殊指针。当一个对象调 用成员函数时,编译器先将对象的地址赋给this指针,然后调用成员函数。例 如,当下列成员函数调用时: one.copy(two); 它实际上被解释成: copy( &one, two); 只不过,&one参数为隐藏了。需要说明的是,通过*this可以判断是哪个对象来 调用该成员函数或重新指定对象。例如:
2.1.8 this指针 [例Ex_This] this指针的使用 #include <iostream.h> class COne { public: COne() { x = y = 0; } COne(int a, int b) { x = a; y = b; } void copy(COne &a); // 对象引用作函数参数 void print() {cout<<x<<" , "<<y<<endl; } private: int x, y; }; void COne::copy(COne &a) { if (this == &a) return; *this = a; } void main() {
2.1.8 this指针 COne one, two(3, 4); one.print(); one.copy(two); one.print(); } 运行结果: 程序中,使用this指针的函数是copy,它在copy函数中出现了2次。“if(this == &a)”中的this是操作该成员函数的对象的地址,从main函数中的“one.copy(two);”可以看出这个对象就是one。copy函数中的语句 *this = a; 是将形参a(对象的引用)赋给操作该成员函数的对象。在本例中,就是将对象two赋给对象one。因此,main函数中最后的语句“one.print();”实际上就是“two.print();”。
2.1.9 类的作用域和对象的生存期 具体地讲,某个类A中某个成员M在下列情况下具有类A的作用域: (1) 成员M出现在类A的某个成员函数中,并且该成员函数没有定义同名标识符。 (2) 成员M出现在a.M或A::M表达式中,其中a是A的对象。 (3) 成员M出现在pa->M这样的表达式中,其中pa是一个指向A类对象的指针。
2.1.9 类的作用域和对象的生存期 对象的生存期是指对象从被创建开始到被释放为止的时间。按生存期的不同,对 象可分为如下三种: (1) 局部对象:当对象被定义时调用构造函数,该对象被创建,当程序退出定义该对象所在的函数体或程序块时,调用析构函数,释放该对象。 (2)静态对象:当程序第一次执行所定义的静态对象时,该对象被创建,当程序结束时,该对象被释放。 (3) 全局对象:当程序开始时,调用构造函数创建该对象,当程序结束时调用析构函数释放该对象。
2.2 继承和派生类 2.2.1. 单继承 从一个基类定义一个派生类可按下列格式: class <派生类名> : [<继承方式>] <基类名> { [<派生类的成员>] }; 其中,继承方式有3种:public(公有)、private(私有)及protected(保护),若继承方式没有指定,则被指定为默认的public方式。继承方式决定了派生类的继承基类属性的使用权限,下面分别说明。
2.2.1. 单继承 1. 公有继承(public) 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持 原有的状态,而基类的私有成员仍然是私有的。例如: class CStick : public CMeter { int m_nStickNum; // 声明一个私有数据成员 public: void DispStick(); // 声明一个公有成员函数 }; // 注意分号不能省略 void CStick:: DispStick() { m_nStickNum = GetPos(); // 调用基类CMeter的成员函数 cout<<m_nStickNum<<’ ’; } 这时,从基类CMeter派生的CStick类除具有CMeter所有公有成员和保护成员 外,还有自身的私有数据成员m_nStickNum和公有成员函数DispStick()。这个完 整的示例如下:
2.2.1. 单继承 [例Ex_ClassPublicDerived] 派生类的公有继承示例 #include <iostream.h> class CMeter { public: CMeter(int nPos = 10) { m_nPos = nPos; } ~CMeter() { } void StepIt(){ m_nPos++; } int GetPos(){ return m_nPos; } protected: void SetPos(int nPos) { m_nPos = nPos; } private: int m_nPos; }; class CStick : public CMeter // 从CMeter派生,公有继承 { int m_nStickNum; // 声明一个私有数据成员 public: void DispStick(); // 声明一个公有成员函数 void SetStick(int nPos) { SetPos(nPos); // 类中调用基类的保护成员 } };
void CStick:: DispStick() { m_nStickNum = GetPos(); // 调用基类CMeter的成员函数 cout<<m_nStickNum<<' '; } void main() { CMeter oMeter(20); CStick oStick; cout<<"CMeter:"<<oMeter.GetPos()<<",CStick:"<<oStick.GetPos()<<endl; oMeter.StepIt(); cout<<"CMeter:"<<oMeter.GetPos()<<",CStick:"<<oStick.GetPos()<<endl; oStick.StepIt(); out<<"CMeter:"<<oMeter.GetPos()<<",CStick:"<<oStick.GetPos()<<endl; oStick.DispStick(); oStick.StepIt(); oStick.DispStick(); } 运行结果为:
2.2.1. 单继承 2. 私有继承(private) 私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个 派生类的子类所访问。例如: [例Ex_ClassPrivateDerived] 派生类的私有继承示例 #include <iostream.h> class CMeter { public: CMeter(int nPos = 10) { m_nPos = nPos; } ~CMeter() { } void StepIt(){ m_nPos++; } int GetPos(){ return m_nPos; } protected: void SetPos(int nPos) { m_nPos = nPos; } private: int m_nPos; }; class CStick : private CMeter // 从CMeter派生,私有继承 { int m_nStickNum; // 声明一个私有数据成员 public: void DispStick(); // 声明一个公有成员函数 void SetStick(int nPos)
{ SetPos(nPos); // 调用基类的保护成员 } int GetStick() { return GetPos(); // 调用基类的公有成员 } }; void CStick:: DispStick() { m_nStickNum = GetPos(); // 调用基类CMeter的成员函数 cout<<m_nStickNum<<' '; } void main() { CMeter oMeter(20); CStick oStick; cout<<"CMeter:"<<oMeter.GetPos()<<",CStick:"<<oStick.GetStick()<<endl; oMeter.StepIt(); cout<<"CMeter:"<<oMeter.GetPos()<<",CStick:"<<oStick.GetStick()<<endl; oStick.DispStick(); } 运行结果为:
2.2.1. 单继承 3. 保护继承(protected) 保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员, 并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。 表2.1列出三种不同的继承方式的基类特性和派生类特性。 表2.1 不同继承方式的基类特性和派生类特性
2.2.2 派生类的构造函数和析构函数 [例Ex_ClassDerived] 派生类的构造函数和析构函数的示例 #include <iostream.h> #include <string.h> class CAnimal { public: CAnimal(char *pName = "noname"); ~CAnimal(); void setName(char *pName) { strncpy(name, pName, sizeof(name)); } char *getName(void) { return name; } private: char name[20]; }; CAnimal::CAnimal(char *pName) { setName(pName); cout<<"调用CAnimal的构造函数!"<<endl; } CAnimal::~CAnimal() { cout<<"调用CAnimal的析构函数!"<<endl; } class CCat : public CAnimal
{ public: CCat() { cout<<"调用CCat的构造函数!"<<endl; } ~CCat() { cout<<"调用CCat的析造函数!"<<endl; } void DispName() { cout<<"猫的名字是:"<<getName()<<endl; } }; void main() { CCat cat; cat.DispName(); cat.setName("Snoopy"); cat.DispName(); } 运行结果为:
2.2.2 派生类的构造函数和析构函数 例如,在Ex_ClassPublicDerived示例中,CStick的构造函数可这样定义: class CStick : public CMeter { ... public: CStick():CMeter(30) { } ... }; 此时再重新运行程序,结果就会变为:
2.2.3 多继承 多继承下派生类的定义是按下面的格式: class <派生类名> : [<继承方式1>] <基类名1>,[<继承方式2>] <基类名2>,... { [<派生类的成员>] }; 其中的继承方式还是前面的3种:public、private和protected。 例如: class A {...} class B {...} class C:public A,private B {...} 由于派生类C继承了基类A和B,具有多继承性,因此派生类C的成员包含了基类 A中成员和B中成员以及该类本身的成员。
2.2.4虚基类 一般说来,在派生类中对基类成员的访问应该是惟一的,但是,由于多继承情况下,可能造成对 基类中某成员的访问出现了不惟一的情况,这种情况称为基类成员调用的二义性。例如: [例Ex_Conflict] 基类成员调用的二义性 #include <iostream.h> class A { public: int x; A(int a = 0) { x = a; } }; class B1 : public A { public: int y1; B1( int a = 0, int b = 0):A(b) { y1 = a; } }; class B2 : public A { public: int y2; B2( int a = 0, int b = 0):A(b) { y2 = a; }
}; class C : public B1, public B2 { public: int z; C(int a, int b, int d, int e, int m):B1(a,b), B2(d,e) { z = m; } void print() { cout<<"x = "<<x<<endl; // 编译出错的地方 cout<<"y1 = "<<y1<<", y2 = "<<y2<<endl; cout<<"z = "<<z<<endl; } }; void main() { C c1(100,200,300,400,500); c1.print(); }
2.2.4虚基类 程序中,派生类B1和B2都从基类A继承,这时在派生类中就有两个基类A的拷 贝。当编译器编译到“cout<<”x = “<<x<<endl;”语句时,因无法确定成员x是从类 B1中继承来的,还是从类B2继承来,产生了二义性,从而出现编译错误。 解决这个问题的方法之一是使用域作用运算符“::”来消除二义性,例如若将print() 函数实现代码变为: void print() { cout<<"B1::x = "<<B1::x<<endl; cout<<"B2::x = "<<B2::x<<endl; cout<<"y1 = "<<y1<<", y2 = "<<y2<<endl; cout<<"z = "<<z<<endl; } 重新运行,结果为: