目录
1、引入(使用公有继承)
/*关闭隐式转换的原因*/
Student doh("xiaoming",10); //使用构造函数Student(const std::string s,int n)创建Stident对象doh
doh = 5;
//如果没有关键字explict,doh=5 将调用Student(int n) : name("NUll name"),scores(n) {},并使用NUll name来设置name的值
//如果加上explicit则不会进行隐式转换
/*valarray类的介绍*/
01)需包含头文件#include <valarray>
02)使用时,需要在valarray后面加上一对尖括号并在里面写上要使用的数据类型
valarray<int> q_values; //an array of int
valarray<double> weights; //an array of double
03)使用valarray类构造函数创建对象的例子:
double gpa = {3.1,4.2,5.4,3.2,5.6,7.8}; //创建一个double数组
valarray<double> v1; //an array of double,size 0
valarray<int> v2(8); //an array of int,size 8
valarray<int> v3(10,8); //an array of int,size 8,each set to 10
valarray<double> v4(gpa,4); //an array of double,size 4,each set to first 4 elements of gpa
valarray<int> v5 = {10,9,8,7}; //c++11
04)valarray类方法
operator[]() //访问各个元素
size(); //返回包含元素数
sum(); //返回包含元素和
max(); //返回包含元素最大值
min(); //返回包含元素最小值
05)在第四章中介绍的vector和array也可以存储数据,但是这些泪提供的算术支持没有valarray多
1 /*Student类公有继承*/ 2 #ifndef STUDENT_H 3 #define STUDENT_H 4 5 #include <iostream> 6 #include <string> 7 #include <valarray> 8 9 class Student 10 { 11 private: 12 typedef std::valarray<double> ArrayDb; //给std::valarray<double>起别名为ArrayDb 13 14 std::string name; //name为一个包含对象 15 ArrayDb scores; //scores为一个包含对象 16 std::ostream & arr_out(std::ostream & os) const; //私有方法 17 public: 18 Student() : name("Null Student"),scores() {} //定义构造函数,并使用成员初始化列表对name和scores进行初始化 19 explicit Student(const std::string s) : name(s),scores() {} //explicit表示关闭隐式转换 20 explicit Student(int n) : name("NUll name"),scores(n) {} //set name to Null name and creat array of n elements 21 Student(const std::string s,int n) : name(s),scores(n) {} 22 Student(const std::string s, const ArrayDb & a) : name(s),scores(a) {} 23 Student(const char* str,const double* pd,int n) : name(s),scores(pd,n) {} 24 ~Student() {} //析构函数 25 26 double Average() const; 27 const std::string & Name() const; 28 double & operator[](int i); //对[]的重载 29 double operator[](int i) const; //对[]的重载 30 31 //friends 32 friend std::istream & operator>>(std::istream & is, Student & stu); //输入一个单词 33 friend std::istream & getline(std::istream & is, Student & stu); //输入一行 34 friend std::ostream & operator<<(std::ostream & os,Student & stu); //输出 35 36 }; 37 38 #endif 39 40 /*关闭隐式转换的原因*/ 41 /* 42 Student doh("xiaoming",10); //使用构造函数Student(const std::string s,int n)创建Stident对象doh 43 doh = 5; 44 //如果没有关键字explict,doh=5 将调用Student(int n) : name("NUll name"),scores(n) {},并使用NUll name来设置name的值 45 //如果加上explicit则不会进行隐式转换 46 */ 47 48 /*valarray类的介绍*/ 49 /* 50 01)需包含头文件#include <valarray> 51 02)使用时,需要在valarray后面加上一对尖括号并在里面写上要使用的数据类型 52 valarray<int> q_values; //an array of int 53 valarray<double> weights; //an array of double 54 03)使用valarray类构造函数创建对象的例子: 55 double gpa = {3.1,4.2,5.4,3.2,5.6,7.8}; //创建一个double数组 56 valarray<double> v1; //an array of double,size 0 57 valarray<int> v2(8); //an array of int,size 8 58 valarray<int> v3(10,8); //an array of int,size 8,each set to 10 59 valarray<double> v4(gpa,4); //an array of double,size 4,each set to first 4 elements of gpa 60 valarray<int> v5 = {10,9,8,7}; //c++11 61 04)valarray类方法 62 operator[]() //访问各个元素 63 size(); //返回包含元素数 64 sum(); //返回包含元素和 65 max(); //返回包含元素最大值 66 min(); //返回包含元素最小值 67 05)在第四章中介绍的vector和array也可以存储数据,但是这些泪提供的算术支持没有valarray多 68 */
1 /*公有继承Student.cpp实现*/ 2 #include "Student.h" 3 4 using std::ostream; 5 using std::istream; 6 using std::endl; 7 using std::string; 8 9 /*私有方法定义*/ 10 std::ostream & arr_out(std::ostream & os) const 11 { 12 int i; 13 int lim = scores.size(); 14 if(lim>0) 15 { 16 for(i=0; i<lim; i++) 17 { 18 os<<scores[i]<<" " 19 if(i%5 == 4) //scores中的四个数据为一行 20 os<<endl; 21 } 22 if(i%5!=0) 23 os<<endl; 24 } 25 else 26 os<<"Empty array"; 27 return os; 28 } 29 30 /*计算分数平均值*/ 31 double Student::Average() const 32 { 33 if(scores.size()>0) 34 return scores.sum()/scores.size(); //直接调用valarray类中的方法 35 else 36 return 0; 37 } 38 39 /*利用类方法访问私有数据*/ 40 const std::string & Student::Name() const 41 { 42 return name; 43 } 44 45 /*对[]的重载*/ 46 double & Student::operator[](int i) 47 { 48 return scores[i]; //同样直接使用valarray类中的方法operator[]() 49 } 50 /*对[]的重载*/ 51 double Student::operator[](int i) const 52 { 53 return scores[i]; //同样直接使用valarray类中的方法operator[]() 54 } 55 /*友元函数定义--输入一个单词*/ 56 std::istream & operator>>(std::istream & is, Student & stu) 57 { 58 is>>stu.name; 59 return is; 60 } 61 62 /*友元函数定义--输入一行*/ 63 std::istream & getline(std::istream & is, Student & stu) 64 { 65 getline(is,stu.name); 66 return is; 67 } 68 69 /*友元函数定义--输出*/ 70 std::ostream & operator<<(std::ostream & os,Student & stu) 71 { 72 os<<"Scores for "<<stu.name<<":\n"; 73 stu.arr_out(os); //使用私有方法 74 return os; 75 }
1 /*user_stu.cpp*/ 2 #include <iostream> 3 #include "Student.h" 4 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 9 void set(Student &sa,int n); //在主函数内声明方法 10 const int pupils = 3; //定义常数 11 const int quizzes = 5; 12 13 int main() 14 { 15 Student ada[pupils] = {Student(quizzes),Student(quizzes),Student(quizzes)}; //定义Student类矩阵,内包含了三个Student类对象 16 int i; 17 for(i = 0;i<pupils; i++) 18 set(ada[i],quizzes); 19 cout<<"\nStudent List:\n"; 20 for(i=0;i<pupils;i++) 21 cout<<ada[i].Name()<<endl; 22 cout<<"Results:\n"; 23 for(i=0;i<quizzes;i++) 24 { 25 cout<<endl<<ada[i]; //这里的<<ada[i]将会首先调用std::ostream & operator<<(std::ostream & os,Student & stu)这个函数,在该函数内继续调用arr_out()函数 26 cout<<"average: "<<ada[i].Average()<<endl; 27 } 28 29 cout<<"Done.\n" 30 return 0; 31 } 32 33 /*子函数定义*/ 34 void set(Student &sa,int n); 35 { 36 cout<<"Please input the student's name: "; 37 getline(cin,sa); 38 cout<<"Please input "<<n<<"quiz scires:\n"; 39 for(i=0;i<n;i++) 40 cin>>sa[i]; //这里将会调用double & Student::operator[](int i)函数,返回的是scores[i] 41 while(cin.get() !='\n') 42 continue; //如果没有接收到换行符,那么继续去接收换行符;接收到了换行符则推出此while循环 43 }
注意:在主函数中,使用了sa[i],这个是使用了对[]的重载函数,返回的是scores[i],详见user_main.cpp,刚开始的没看明白
2、包含(公有继承)和私有继承
3、is-a关系和has-a关系(复习回顾)
01)is-a关系即派生类对象也是一个基类对象,可以对基类对象执行任何操作;例如有一个Fruit类,保存水果的重量和热量,
因为apple是一种水果,所以可以从Fruit类中派生出apple类,apple类将继承Fruit类所有的数据成员(重量和热量),也可以有属于自己的
数据成员,但是属于apple的数据成员不属于Fruit类;公有继承创建is-a关系,它是一种包含关系。
02)has-a关系:例如午餐可能包含水果(Fruit类),但是午餐并不是水果,所以不能从Fruit类中派生出Lunch类来在午餐中添加水果;在午餐中
添加水果的正确方法是创建has-a关系,即午餐中有水果,最容易的方式是,将Fruit对象做为Lunch类的数据成员,即私有继承。
4、私有继承
01)使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员;这意味着基类方法不会成为派生类的
对象公有接口的一部分,但是可以在派生类的成员函数中使用它们;
02)包含(公有继承)将对象做为一个命名的成员对象添加到类中,而私有继承将对象做为一个未被命名的继承对象
添加到类中;
03)声明方法:
1 class Student : private std::string, private std::valarray 2 { 3 public: 4 ... 5 };
(1)其中private可以省略,默认值为private
(2)私有继承不需要私有数据,因为两个基类已经提供了所需的数据成员。
包含版本(公有继承)提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的自对象成员。
04)公有继承构造函数初始化基类变量方法:
Student(const char *str, const double *pd,int n) : name(str),scores(pd,n) {} //公有继承构造函数定义方法
私有继承构造函数初始化基类变量方法:
Student(const char *str, const double *pd,int n) : std::string(str),std::valarray<double>(pd,n) {}//私有继承构造函数定义方法
05)公有继承访问基类中的方法(函数):
double Student::Average() const { if(scores.size()>0) return scores.sum()/scores.size(); else return 0; }
私有继承访问基类中的方法(函数):
double Student::Average() const { if(ArrayDb::size()>0) return ArrayDb::sum()/ArrayDb::size(); else return 0; }
注意:typedef std::valarray<double> ArrayDb
06)访问基类对象方法
由于string类对象在私有继承中没有名字,此时使用强制转换将Student类转换为string类
const string & Student::Name() const { return (const string &) *this; //转换结果是继承而来的string对象 }
上述方法返回一个引用,改引用指向用于调用该方法的Student对象中继承而来的string对象
07)以下是Student.h等代码使用私有继承方法实现
1 #ifndef STUDENT_H 2 #define STUDENT_H 3 4 #include <iostream> 5 #include <string> 6 #include <valarray> 7 8 class Student : private std::string,std::valarray 9 { 10 private: 11 typedef std::valarray<double> ArrayDb; //名称的替换 12 std::ostream & arr_out(std::ostream & os) const; //声明私有方法 13 public: 14 Student() : std::string("Null name"), ArrayDb() {} //定义构造函数 15 explicit Student(const std::string & s) : std::string(s), ArrayDb() {} ////定义带一个string参数的构造函数 16 explicit Student(int n) : std::string("Null name"), ArrayDb(n) {} //explicit表示不可以进行隐式转换 ArrayDb(n)表示创建包含n个double数据的集合 17 Student(const std::string & s, int n) : std::string(s),ArrayDb(n) {} 18 Student(const char *str,const double *pd,int n) : std::string(str),ArrayDb(pd,n) {} 19 ~Student() {} 20 21 double Average() const; //返回ArrayDb中数据的平均值 22 double & operator[](int i); //使用Student类对象访问ArrayDb中的数据 23 double operator[](int i) const; //使用Student类对象访问ArrayDb中的数据 24 const std::string & Name() const; //返回数据std::string(类似返回公有继承中的name) 25 26 //friends 27 friend std::istream & operator>>(std::istream & is, const Student & s); //输入一个单词 28 friend std::istream & getline(std::istream & is, const Student & s); //输入一行 29 friend std::ostream & operator<<(std::ostream & os, const Student & s); //输出 30 }; 31 32 #endif
1 /*私有继承Student.cpp*/ 2 #include <iostream> 3 #incldue "Student.h" 4 5 using std::istream; 6 using std::endl; 7 using std::ostream; 8 using std::string; 9 10 /* 11 01)返回ArrayDb中数据的平均值 12 02)私有继承访问基类中的方法 13 */ 14 double Student::Average() const 15 { 16 if(std::valarray<double>::size() > 0) //注意这里的std::valarray<double>也可以换成在h文件中声明的ArrayDb 17 return std::valarray<double>::sum() / std::valarray<double>::size(); //由于私有继承是没有变量名字的,所以只能是这样访问基类方法 18 else 19 return 0; 20 } 21 22 /*使用Student类对象访问ArrayDb中的数据*/ 23 double & Student::operator[](int i) 24 { 25 return ArrayDb::operator[](i); //公有继承中实现方法是直接返回scores[i],但是私有继承必须是调用valarray类中的operator[]()方法 26 } 27 28 /*使用Student类对象访问ArrayDb中的数据*/ 29 double Student::operator[](int i) const 30 { 31 return ArrayDb::operator[](i); 32 } 33 34 /* 35 01)返回数据std::string(类似返回公有继承中的name) 36 02)访问基类对象方法:使用强制转换 37 */ 38 const std::string & Student::Name() const 39 { 40 return (const std::string &) *this; //这里是要返回一个string对象,所以要进行强制转换 41 } 42 43 //友元函数定义 44 45 /*输入一个单词*/ 46 std::istream & Student::operator>>(std::istream & is, const Student & s) 47 { 48 is>>(string &)s; //还是要输入一个单词给string对象啊,所以这里还是要进行强制转换 49 return is; 50 } 51 52 /*输入一行*/ 53 std::istream & Student::getline(std::istream & is, const Student & s) 54 { 55 getline(is,(string &)s); //还是要输入一个单词给string对象啊,所以这里还是要进行强制转换 56 return is; 57 } 58 59 /*输出*/ 60 std::ostream & Student::operator<<(std::ostream & os, const Student & s) 61 { 62 os<<"Scores for "<<(string &)s<<":\n"; 63 arr_out(os); 64 return os; 65 } 66 67 /*定义私有方法*/ 68 std::ostream & Student::arr_out(std::ostream & os) const 69 { 70 int i; 71 int lim = ArrayDb::size(); 72 if(lim != 0) 73 { 74 for(i=0;i<lim;i++) 75 { 76 os << ArrayDb::operator[](i); 77 if(i%5 == 4) 78 os<<endl; //保证每四个数据输出一个换行 79 } 80 } 81 else 82 os << "Empty array" 83 }
1 /*私有继承user_main.cpp*/ 2 #include <iostream> 3 #include "Student.h" 4 5 using std::cin; 6 using std::cout; 7 using std::endl; 8 9 void set(const Student & s,int n); 10 11 const int pupils = 3; 12 const int quizzes = 5; 13 14 int main() 15 { 16 int i; 17 Student ada[pupils] = {Student(quizzes),Student(quizzes),Student(quizzes)}; 18 for(i=0;i<pupils;i++) 19 set(ada[i],quizzes); 20 cout<<"\nStudent List:\n"; 21 for(i=0;i<pupils;i++) 22 cout<<ada[i].Name()<<endl; 23 cout<<"Results:\n"; 24 for(i=0;i<quizzes;i++) 25 { 26 cout<<endl<<ada[i]; //这里的<<ada[i]将会首先调用std::ostream & operator<<(std::ostream & os,Student & stu)这个函数,在该函数内继续调用arr_out()函数 27 cout<<"average: "<<ada[i].Average()<<endl; 28 } 29 30 cout<<"Done.\n" 31 return 0; 32 } 33 34 void set(const Student & s,int n) 35 { 36 cout << "Please input student name"<<endl; 37 getline(cin,s); //调用对geline()的重载函数 38 cout << "Please input " << n << "quize scores" <<endl; 39 for(int i=0; i<n; i++) 40 cin >> s[i]; //s[i]将调用operator[]()函数 41 while(cin.get() != '\n') 42 continue; 43 }
5、使用using重新定义获得访问权限
01)问题的提出:使用保护继承或者是私有继承时,基类的公有方法将成为保护成员或私有成员,假设要让基类的方法在派生类的外面可用
以下是两种方法:
02)方法一:定义一个使用该基类方法的派生类方法,例如,假设希望Student类能够使用valarray类的sum()方法,可以在Student类中声明
一个sum()方法,然后像下面这样定义:
double Student::sum() const { return std::valarray<double>::sum(); //使用的是私有继承,所以这里要使用私有继承调用基类方法的方式 }
这样Student对象就可以调用Student::sum(),方法为ada.sum(),假如ada是一个Student对象
03)方法二:使用一个using声明来指出派生类可以使用特定的基类方法,可以在Student.h的公有部分加入如下using声明:
class Student : private std::string, private std::valarray { public: using std::valarray<double>::min(); using std::valarray<double>::max(); ... }
假如ada是一个Student对象,那么就可以直接调用min()和max()方法,即ada.min()和ada.max();需要注意的是using声明只适用于私有继承
并不适用于公有继承。
6、多重继承MI
6.1多重继承的引入
本版本重点介绍多重公有继承,其层次结构如下
class Waiter : public Worker {...};
class Singer : public Worker {...};
class SingingWaiter : public Waiter, public Singer {...};
其中Worker是一个基类
注意:本引入涉及到多态
1 #ifndef WORKER0_H_ 2 #define WORKER0_H_ 3 4 #include <string> 5 6 /*基类*/ 7 class Worker 8 { 9 private: 10 std::string fullname; 11 long id; 12 public: 13 Worker() : fullname("No one"), id(0L) {} //构造函数定义 14 Worker(const std::string & s, long n) : fullname(s),id(n) {} 15 ~Worker(); 16 virtual void Set(); //声明虚方法,在派生类中可不使用virtual,Set()也会自动成为虚方法 17 virtual void Show() const; 18 }; 19 /*派生类Waiter*/ 20 class Waiter : public Worker 21 { 22 private: 23 int panache; //在Worker类变量的基础上,添加新的变量 24 public: 25 Waiter() : Worker(),panache(0) {} //Worker()使用基类名字来初始化基类变量,将调用不带参数的基类构造函数 26 Waiter(std::string & s,long n, int p=0) : Worker(s,n),panache(p) {} //使用带两个参数的基类构造函数初始化基类变量 27 Waiter(const Worker &wk, int p=0) : Worker(wk),panache(p) {} //使用基类对象来初始化基类变量 28 29 void Set(); //由于在基类中Set()使用的是虚方法,所以在派生类中会自动成为虚方法 30 void Show() const; //同样是自动成为虚方法 31 }; 32 /*派生类Singer*/ 33 class Singer : public Worker 34 { 35 protected: 36 enum {other,alto,contralto,soprano,bass,baritone,tenor}; //定义枚举,other=0,alto=1... 37 enum {Vtype = 7}; 38 private: 39 static char *pv[Vtype]; //声明静态指针数组,并在cpp文件中进行初始化 40 int voice; 41 public: 42 Singer() : Worker(), voice(other) {} //使用不带参数的基类构造函数初始化基类变量 43 Singer(const std::string & s,long n,int v=other) : Worker(s,n),voice(v) {} 44 Singer(const Worker & wk, int v=other) : Worker(wk),voice(v) {} 45 46 void Set(); //由于在基类中Set()使用的是虚方法,所以在派生类中会自动成为虚方法 47 void Show() const; //同样是自动成为虚方法 48 }; 49 50 #endif
1 /*worker0.cpp*/ 2 #include <iostream> 3 #include "worker0.h" 4 5 using std::cin; 6 using std::cout; 7 using std::endl; 8 9 /*基类Worker中的方法实现*/ 10 Worker::~Worker() 11 { 12 13 } 14 15 /*基类输入*/ 16 void Worker::Set() 17 { 18 cout << "Enter woker's name: "; 19 getline(cin,fullname); 20 cout << "Enter worker's ID: "; 21 cin >> id; 22 while(cin.get() != '\n') //一直接收换行符,直到接收到了换行符 23 continue; 24 } 25 26 /*基类输出*/ 27 void Worker::Show() const 28 { 29 cout << "Name: " << fullname <<endl; 30 cout << "Employee ID: " << id <<endl; 31 } 32 33 /*派生类Waiter类方法实现*/ 34 /*派生类Waiter输入*/ 35 void Waiter::Set() 36 { 37 Worker::Set(); //调用基类中的Set()方法,输入基类中的数据 38 cout << "Enter waiter's panche rating: " << endl; 39 cin >> panche; 40 while(cin.get() != '\n') //一直接收换行符,直到接收到了换行符 41 continue; 42 } 43 /*派生类Waiter输出*/ 44 void Waiter::Show() const 45 { 46 cout<<"Category waiter"<<endl; 47 Worker::Show(); //调用基类中的Show()方法 48 cout<<"Panache rating: "<<panche<<endl; 49 } 50 51 /*派生类Singer类方法实现*/ 52 char* Singer::pv[] = {other,alto,contralto,soprano,bass,baritone,tenor};//初始化h文件中的pv指针 53 54 /*派生类Singer输入*/ 55 void Singer::Set() 56 { 57 Worker::Set(); //调用基类方法 58 cout << "Enter number for singer's voice: "; 59 int i; 60 for(i=0;i<Vtypes;i++) 61 { 62 cout<<i<<": "<<pv[i]<<" "; //由于没有换行符,此处用两个空格隔开两个voice类型 63 if(i&4==3) 64 cout<<endl; //每打印四个voice类型输出一个换行符 65 } 66 if(i%4!=0) 67 cout<<endl; 68 while(cin>>voice && (voice<0 || voice<=Vtypes)) //检查输入的是否为数字,且该数字是否在0和Vtypes之间 69 cout<<"Please enter a value >=0 and <"<<Vtypes<<endl; 70 while(cin.get() != '\n') //一直接收换行符,直到接收到了换行符 71 continue; 72 } 73 74 75 /*派生类Singer输出*/ 76 void Singer::Show() const 77 { 78 cout<<"Category: singer"<<endl; 79 Worker::Show(); 80 cout<<"Voice range: "<<voice<<endl; 81 }
1 /*worktest.cpp*/ 2 #include <iostream> 3 #include "worker0.h" 4 5 const int LIM = 4; 6 int main() 7 { 8 Waiter bob("Bob Apple",314L,5); //创建Waiter类对象fullname=Bob Apple,id=314,panache=5;只是初始化,后面输入会覆盖掉这些初始化内容 9 Singer bev("Beverlly Hills",522L.3); //创建Singer对象 10 Waiter w_temp; 11 Singer s_temp; 12 13 Woeker* pw[LIM] = {&bob,&bev,&w_temp,&s_temp}; //创建基类指针,分别指向Waiter对象和Singer对象 14 15 int i; 16 for(i=0.i<LIm;i++) 17 pw[i]->Set(); // 18 for(i=0;i<LIM;i++) 19 { 20 pw[i]->Show(); 21 std::cout<<std::endl; 22 } 23 return 0; 24 } 25 /* 26 需要注意的是: 27 01)由于Set()和Show()都是虚方法(在基类中定义的是虚方法,所以在派生类中自动会成为虚方法),所以 28 pw[i]->Set();将根据p[i]指向的对象来确定使用那个版本的Set()函数;例如pw[0]=&bob,而bob是Waiter类对象 29 所以pw[0]->Set()将调用Waiter类中的Set()函数;同理pw[1]->Set()将调用Singer类中的Set()方法 30 31 */
注意:
01)worker.h中包括了三个类Worker类(作为基类)、Waiter(派生自Worker类)、Singer类(派生自Worker类)
02)在workerTest.cpp中:由于Set()和Show()都是虚方法(在基类中定义的是虚方法,所以在派生类中自动会成为虚方法),所以
pw[i]->Set();将根据p[i]指向的对象来确定使用那个版本的Set()函数;例如pw[0]=&bob,而bob是Waiter类对象
所以pw[0]->Set()将调用Waiter类中的Set()函数;同理pw[1]->Set()将调用Singer类中的Set()方法
6.2多重继承的问题与改进
假如有如下继承方式:
1 class Worker {...} //作为基类 2 class Waiter : public Worker {...} //Waiter类继承自Worker类 3 class Singer : public Worker {...} //Singer类继承自Worker
如果定义一个类SingingWaiter类,继承Waiter和Singer,即:
class SingingWaiter : public Waiter, public Singer {...}
此时是有问题的:因为Singer和Waiter都继承了一个Worker,所以SingingWaiter中将含有两个Worker类重复的信息,
在使用多态时候,将会出现如下问题;
1 SingingWaiter ed; //声明一个SingingWaiter对象ed 2 Worker *pw = &ed; //基类指针指向SingingWaiter对象,此时会出现二义性
由于ed中包含两个Worker对象,所以上式会出现二义性,解决方法;
1 SingingWaiter ed; //声明一个SingingWaiter对象ed 2 Worker *pw1 = (Waiter *)&ed; 3 Worker *pw2 = (Singer *)&ed; //使用强制转换
但是这样会使多态复杂化,此时引入虚基类的概念
6.3虚基类
01)使Worker成为Waiter和Singer虚基类的方法:加关键字virtual
1 class Singer :virtual public Worker {...} 2 class Waiter :virtual public Worker {...} //其中virtual和public可以交换次序
然后可以将SingingWaiter定义为:
1 class SingingWaiter : public Singer, public Waiter {...}
此时SIngingWaiter对象只包含Worker对象的一个副本,继承的Singer和Waiter共享一个Worker对象
02)SingingWaiter构造函数的编写方法(主要是如何填充Worker中的数据):
以前的多继承构造函数编写方法:
1 class A //基类 2 { 3 int a; //省略了关键字private 4 public: 5 A(int n=0) : a(n) {} 6 ... 7 }; 8 class B : public A //B继承自A 9 { 10 int b; 11 public: 12 B(int m=0,int n=0) : A(n),b(m) {} //使用基类构造函数A()来填充基类数据 13 ... 14 }; 15 class C : public B //C继承自B 16 { 17 int c; 18 public: 19 C(int q=0,int m=0,int n=0) : B(m,n), c(q); //调用B(int m=0,int n=0)构造函数来填充B类和A类中的数据 20 ... 21 };
C类构造函数只能调用B类的构造函数,而B类构造函数只能调用A类构造函数。在C类构造函数中,将m和n的值传递给B类中的
构造函数,而B类中的构造函数又将n值传递给A类的构造函数,最后初始化自己的值c,这样是可行的。
03)但是如果A是虚基类,那么以上传递方法将不会起作用!!即:
SingingWaiter(const Worker &wk, int p=0,int v=Singer::other) : Waiter(wk,p), Singer(wk,v) {} //存在问题的
存在的问题是,自动传递信息时,将通过两种不同的途径(Singer和Waiter)将wk传递给Worker对象。为避免这种冲突,C++在
基类是虚的时候,禁止通过中间类(Singer和Waiter)将基类数据传递给基类。可以采用如下方式:
SingingWaiter(const Worker & wk, int p=0,int v=Singer::other) : Worker(wk),Waiter(wk,p),Singer(wk,v) {}
上述代码显式的调用虚基类构造函数Worker(const Worker &),对于虚基类,这样做事非法的。
那个方法?
01)问题的提出,假如在SingingWaiter中没有重新定义Show()方法,并试图使用SingingWaiter对象调用Show()方法,即:
1 SingingWaiter newhire("Elise Hawks",2005,6,soprano); 2 newhire.Show(); //会出现二义性,不知道使用那个祖先中的Show()方法
对于单继承来说,如果没有重新定义Show(),则使用最近祖先中的定义;但是对于多继承来说,则会出现二义性
02)解决方法一:可以使用作用域解析运算符,即:
1 SingingWaiter newhire("Elise Hawks",2005,6,soprano); 2 newhire.Singer::Show(); //合法,只有作用域解析运算符来说明要使用Singer类中的Show()f方法
03)解决方法二:最好的方法还是在SingingWaiter中重新定义Show()方法
void SingingWaiter::Show() { Singer::Show(); //但是这种递增方式对SingingWaiter无效,因为它忽略了Wiater类 }
可以使用如下方式进行补救:
void SingingWaiter::Show() { Singer::Show(); //调用了Worker::Show()另外显示自己的私有变量 Wiater::Show(); //调用了Worker::Show()另外显示自己的私有变量 }
然而这一同样会出现问题:这将显示姓名和ID两次,可以提供只显示Worker类变量方式
和只显示Singer类变量、Waiter类变量的三个方法,最后将他们进行组合,即:
1 /*在worker类中定义Data()方法*/ 2 void Worker::Data() const 3 { 4 cout << "Name: " << fullname <<endl; 5 cout << "ID: " << id << endl; 6 } 7 /*在Waiter类中定义Data()方法*/ 8 void Waiter::Data() const 9 { 10 cout << "Panache rating: " << panache <<endl; 11 } 12 /*在Singer类中定义Data()方法*/ 13 void Singer::Data() const 14 { 15 cout << "Vocal range: " << pv[voice] <<endl; 16 } 17 /*在SingingWaiter类中定义Data()方法*/ 18 void SingingWaiter::Data() const 19 { 20 Singer::Data(); 21 Waiter::Data(); 22 } 23 /*在SingingWaiter类中定义Show()方法*/ 24 void SingingWaiter::Show() const 25 { 26 Worker::Data(); //虚基类中的Data()方法 27 Data(); //SingingWaiter类中自己定义的Data()方法 28 }
由于保护成员只可以在本类内和子类内被访问,在其他文件中不可以被访问,所以可以将以上的Data()全部声明为类中的保护成员。