C++第十四章_引入_包含(公有繼承)和私有繼承_is-a關係和has-a關係_私有繼承_使用using從新定義得到訪問權限_多重繼承的問題與改進_虛基類_類模板類模板類模板_棧指針

目錄

一、引入html

二、包含(公有繼承)和私有繼承ios

三、is-a關係和has-a關係(複習回顧)c++

四、私有繼承數組

五、使用using從新定義得到訪問權限app

六、多重繼承MIide

  6.1多重繼承的引入函數

  6.2多重繼承的問題與改進oop

  6.3虛基類ui

  6.4虛基類與二義性this

七、類模板

7.1類模板簡單實現

7.2棧指針

一、引入(使用公有繼承)

/*關閉隱式轉換的緣由*/
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 */
Student.h
 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 }  
Student.cpp
 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 }
user_main.cpp

注意:在主函數中,使用了sa[i],這個是使用了對[]的重載函數,返回的是scores[i],詳見user_main.cpp,剛開始的沒看明白

二、包含(公有繼承)和私有繼承

  

三、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類的數據成員,即私有繼承。

四、私有繼承

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
Student.h
 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 }
Student.cpp
 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 }
user_main.cpp

 五、使用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聲明只適用於私有繼承
並不適用於公有繼承。

六、多重繼承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
worker.h
 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 }
worker.cpp
 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 */
workTest.cpp

注意:

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 };
View Code

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 }
從新定義SingingWaiter類中的Show()方法

因爲保護成員只能夠在本類內和子類內被訪問,在其餘文件中不能夠被訪問,因此能夠將以上的Data()所有聲明爲類中的保護成員。

6.4虛基類與二義性

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 };
View Code

 七、類模板

7.1類模板簡單實現

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
stacktp.h

使用類模板:

 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 }
stcaktp.cpp

執行結果:

 7.2棧指針

須要參考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
stacktp1.h
 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 }
stacktp1.cpp

執行結果:

  

相關文章
相關標籤/搜索