一、引入html
四、私有繼承數組
六、多重繼承MIide
6.3虛基類ui
6.4虛基類與二義性this
/*關閉隱式轉換的緣由*/
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,剛開始的沒看明白
01)is-a關係即派生類對象也是一個基類對象,能夠對基類對象執行任何操做;例若有一個Fruit類,保存水果的重量和熱量,
由於apple是一種水果,因此能夠從Fruit類中派生出apple類,apple類將繼承Fruit類全部的數據成員(重量和熱量),也能夠有屬於本身的
數據成員,可是屬於apple的數據成員不屬於Fruit類;公有繼承建立is-a關係,它是一種包含關係。
02)has-a關係:例如午飯可能包含水果(Fruit類),可是午飯並非水果,因此不能從Fruit類中派生出Lunch類來在午飯中添加水果;在午飯中
添加水果的正確方法是建立has-a關係,即午飯中有水果,最容易的方式是,將Fruit對象作爲Lunch類的數據成員,即私有繼承。
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 }
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聲明只適用於私有繼承
並不適用於公有繼承。
本版本重點介紹多重公有繼承,其層次結構以下
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()方法
假若有以下繼承方式:
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; //使用強制轉換
可是這樣會使多態複雜化,此時引入虛基類的概念
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()所有聲明爲類中的保護成員。
01)使用非虛基類時:若是一個類從不一樣的類中繼承了兩個或更多的同名函數(數據或方法),則使用該
成員名的時候,若是沒有用類名進行限定,將會致使二義性。
02)使用虛基類時:若是一個類從不一樣的類中繼承了兩個或更多的同名函數(數據或方法),則使用該
成員名的時候,若是沒有用類名進行限定,將不必定會致使二義性,若是某個名稱優先於其餘全部名稱
,則使用它時,即便不適用限定符,也不會致使二義性。
03)一個成員名如何優先於另外一個成員們呢?派生類中的名稱優先於直接或間接祖先類中相同名稱,例如:
1 class B //做爲基類 2 { 3 public: 4 short q(); 5 ... 6 }; 7 class C : virtual public B /使B成爲虛基類 8 { 9 public: 10 long q(); //因爲C繼承B,因此C中的q()優先於B中的q() 11 int omg(); 12 }; 13 class D : public C 14 { 15 ... 16 }; 17 class E :virtual public B 18 { 19 private: 20 int omg(); 21 ... 22 }; 23 class F : public D, public E 24 { 25 q(); //這裏使用的是C中的q()方法;由於只有B和C中有q()方法,而C中的優先於B中的q()方法 26 //而D繼承自C,C繼承自B 27 omg(); //因爲C中有omg()方法,E中也有;可是C不是E的基類,且E也不是C的基類,因此C和E中的omg()方法沒法比較有限性 28 //這裏調用omg()將會致使二義性 29 ... 30 };
01)因爲模板不是函數,它們不可單獨編譯,所以模板必須和實例化(即實現)一塊兒使用,爲此最簡單的方法就是將全部模板信息放入一個頭文件中
02)模板的具體實現成爲實例化或具體化,即將int、string、double等替換Type
03)每一個函數定義都須要使用項目的模板聲明template <class Type> 打頭
04)類限定符Stack::改成Stack<Type>::
05)若是在類聲明中定義了方法(內聯定義),則能夠省略模板前綴和類限定符。
因爲[]的優先級比--高,因此在執行item = items[--top]的時候,先將items[top]的值賦給item,後將top的值自減1
類聲明+類方法定義的h文件:
1 #ifndef STACKTP_H_ 2 #define STACKTP_H_ 3 4 /* 5 01)下面的type只說明Type是一個通用類型說明符,並非類,在使用時將用實際類型替換它 6 02)Type被稱爲泛型標識符 7 */ 8 template <class Type> //關鍵字template告訴編譯器將要建立一個模板,其中class可用typename替換 Tyoe爲任意取的名字,實際會被int、string等代替 9 class Stack 10 { 11 private: 12 enum {MAX = 10}; //使用枚舉建立常量 13 Type items[MAX]; //使用模板中的Type建立數組 14 int top; 15 public: 16 Stack(); //聲明構造函數 17 bool isempty(); 18 bool isfull(); 19 bool push(const Type & item); //入棧 20 bool pop(Type & item); //出棧,因爲是使用的引用,故在item的變化和傳入的實參一塊兒變化(即形參和實參一塊兒變化) 21 }; 22 23 /* 24 01)因爲模板不是函數,它們不可單獨編譯,所以模板必須和實例化(即實現)一塊兒使用,爲此最簡單的方法就是將全部模板信息放入一個頭文件中 25 02)模板的具體實現成爲實例化或具體化,即將int、string、double等替換Type 26 03)每一個函數定義都須要使用項目的模板聲明template <class Type> 打頭 27 04)類限定符Stack::改成Stack<Type>:: 28 05)若是在類聲明中定義了方法(內聯定義),則能夠省略模板前綴和類限定符。 29 */ 30 template <class Type> //模板前綴 31 Stack<Type>::Stack() 32 { 33 top = 0; 34 } 35 36 /*判斷棧是否爲空*/ 37 template <class Type> 38 bool Stack<Type>::isempty() 39 { 40 return top == 0; //若top=0則返回true 41 } 42 43 /*判斷棧是否已滿*/ 44 template <class Type> 45 bool Stack<Type>::isfull() 46 { 47 return top == MAX; //若top=MAX則返回true 48 } 49 50 /*入棧*/ 51 template <class Type> 52 bool Stack<Type>::push(const Type & item) 53 { 54 if (top < MAX) 55 { 56 items[top++] = item; //將傳入的參數傳入items數組(棧),並將top自加1 57 return true; 58 } 59 else 60 return false; 61 } 62 63 /* 64 01)出棧 65 02)因爲[]的優先級比--高,因此在執行item = items[--top]的時候,先將items[top]的值賦給item,後將top的值自減1 66 02)因爲--top是先修改後使用,故pop(po)中 67 */ 68 template <class Type> 69 bool Stack<Type>::pop(Type & item) 70 { 71 if (top > 0) 72 { 73 item = items[--top]; //將數組items(棧)中的參數傳給item,同時主函數中的實參也會更新 74 return true; 75 } 76 else 77 return false; 78 } 79 80 #endif
使用類模板:
1 #include <iostream> 2 #include <string> 3 #include <cctype> 4 #include "stacktp.h" 5 6 using std::string; 7 using std::cin; 8 using std::cout; 9 using std::endl; 10 11 /* 12 01)模板類對象的建立方法: 13 Stack<int> st1; //使用int替換模板中全部的Type 14 Stack<string> st2; 15 */ 16 int main() 17 { 18 Stack<string> st; //將Stack類中的Type使用string替換,並建立Stack對象st,st爲模板類對象 19 char ch; 20 string po; 21 cout << "please enter A to add a purchase order,\n" 22 << "P to process a puurchase order, or Q to quit." << endl; 23 while (cin >> ch && std::toupper(ch) != 'Q') //注:toupper(ch)將ch字符轉換爲大寫 24 { 25 while (cin.get() != '\n') 26 continue; //一直接收換行符 27 if (!std::isalpha(ch)) //isalpha(ch)判斷ch是否爲英文字母,如果則返回非0 28 { 29 cout << '\a'; //發出蜂鳴聲 30 continue; //返回while循環 31 } 32 switch (ch) 33 { 34 case 'A': 35 case 'a': 36 cout << "Enter a purchase order to add: "; 37 cin >> po; 38 if (st.isfull()) 39 cout << "棧已滿!" << endl; 40 else 41 st.push(po); //將po入棧 42 break; 43 case 'p': 44 case 'P': 45 if (st.isempty()) 46 cout << "棧已空!" << endl; 47 else 48 { 49 st.pop(po); //將po出棧,並更新po;由於po爲被指向的引用,會隨着形參的改變而改變 50 cout << "purchase order #" << po << " popped" << endl; 51 break; 52 } 53 } 54 cout << "please enter A to add a purchase order,\n" 55 << "P to process a puurchase order, or Q to quit." << endl; 56 } 57 cout << "Bye!" << endl; 58 system("pause"); 59 return 0; 60 }
執行結果:
須要參考int**表示一個二維數組方法:http://www.javashuo.com/article/p-zuogcnyp-dg.html
另外此程序也展現了類對象能夠直接用直接訪問運算符(點)來訪問本身的私有數據
棧中存儲的是指針(字符串的首地址)
1 #ifndef STACKTP1_H_ 2 #define STACKTP1_H_ 3 4 template <class Type> 5 class Stack 6 { 7 private: 8 enum {SIZE = 10}; //默認棧大小 9 int stacksize; 10 Type * items; //棧 Type用char*替換後,即char** items;建立指向指針的指針,可是一旦用new給items分配空間以後,就items就能夠指代一個二維數組 11 int top; 12 public: 13 explicit Stack(int ss = SIZE); //構造函數,由於該構造函數只含有一個參數,須要使用explicit關閉隱式轉換 14 Stack(const Stack & st); //聲明覆制構造函數 15 ~Stack() { delete[] items; } //因爲是要將Type聲明爲char *,因此須要加上[] 16 bool isempty() { return top == 0; } 17 bool isfull() { return top == stacksize; } 18 bool push(const Type & item); 19 bool pop(Type & item); 20 Stack & operator=(const Stack & st); 21 }; 22 23 /*含有一個int型參數的構造函數*/ 24 template <class Type> 25 Stack<Type>::Stack(int ss) : stacksize(ss), top(0) 26 { 27 items = new Type[stacksize]; //給items分配空間,當Type=char*時,items = new char*[stacksize]即建立一個數組,數組內全是指針 28 } 29 30 /* 31 01)含有一個Stack類對象引用參數的構造函數 32 02)items = new Type[stacksize];給items分配空間,當Type=char*時,items = new char*[stacksize]即建立一個數組,數組內全是指針 33 */ 34 template <class Type> 35 Stack<Type>::Stack(const Stack & st) 36 { 37 stacksize = st.stacksize; //使用類對象直接訪問本身自己的私有數據 38 top = st.top; //使用類對象直接訪問本身自己的私有數據 39 items = new Type[stacksize]; //Type用char*替換後,即items = new char*[stacksize]建立一個數組,內全爲指針 40 for (int i = 0; i < top; i++) 41 { 42 items[i] = st.items[i]; 43 } 44 } 45 46 /*入棧*/ 47 template <class Type> 48 bool Stack<Type>::push(const Type & item) 49 { 50 if (top < stacksize) 51 { 52 items[top++] = item; 53 return true; 54 } 55 else 56 return false; 57 } 58 59 /*出棧*/ 60 template <class Type> 61 bool Stack<Type>::pop(Type & item) 62 { 63 if (top > 0) 64 { 65 item = items[--top]; 66 return true; 67 } 68 else 69 return false; 70 } 71 72 /* 73 01)對=的重載函數定義 74 02)注意:函數返回類型爲Stack類引用時,返回類型在聲明中能夠直接寫Stack & ,可是在定義中必須使用Stack<Type> & 75 03)返回類型爲類引用,若是在類中可使用Stack,可是在類外必須使用Stack<Type> 76 04)一下代碼使用了深複製 77 */ 78 template <class Type> 79 Stack<Type> & Stack<Type>::operator=(const Stack & st) 80 { 81 if (this == &st) 82 return *this; 83 delete[] items; 84 stacksize = st.stacksize; 85 top = st.top; 86 items = new Type[stacksize]; 87 for (int i = 0; i < top; i++) 88 items[i] = st.items[i]; 89 return *this; 90 } 91 92 #endif
1 #include <iostream> 2 #include <cstdlib> 3 #include <ctime> 4 #include "stacktp1.h" 5 6 using std::cin; 7 using std::cout; 8 using std::endl; 9 const int Num = 10; 10 11 int main() 12 { 13 std::srand(std::time(0)); 14 cout << "please enter atck size: "; 15 int stacksize; 16 cin >> stacksize; 17 18 Stack<const char*> st(stacksize); //隱式的調用含一個int行參數的Stack類構造函數建立對象st,其中h文件中全部Type用char*代替 19 20 //建立指針數組 21 const char* in[Num] = { 22 "1: Hank Gilgaeh", "2: Kiki Ishtar", 23 "3: Wolfang Kibble", "4: Ian Flagranti", 24 "5: Betty Rocker", "6: Ports Koop", 25 "7: Joy Almondo", "8: Xavertie Paprika", 26 "9: Juan Moora", "10: Misha Mache" 27 }; 28 const char* out[Num]; //編譯打印輸出 29 30 int processed = 0; 31 int nextin = 0; 32 while (processed < Num) 33 { 34 if (st.isempty()) //棧只有第一次執行的時候是空的,以後就是有內容在裏面,不是空也不是滿的狀態 35 st.push(in[nextin++]); //以指針的形式入棧,即棧中存儲的是一個字符串的地址 36 else if (st.isfull()) //當達到主程序中輸入的stacksize以後,棧就達到了滿的狀態,而後執行第一個else if,以後就有達到了不滿要也不空的狀態 37 st.pop(out[processed++]); 38 else if (std::rand() % 2 && nextin < Num) //執行下面兩句的概率各爲50% 39 st.push(in[nextin++]); 40 else 41 st.pop(out[processed++]); 42 } 43 for (int i = 0; i < Num; i++) 44 cout << out[i] << endl; 45 system("pause"); 46 return 0; 47 48 }
執行結果: