C++ primer Plus 第十四章 C++中的代碼重用

 

14.1包含對象成員的類

當我們在描述has-a問題時,我們可以使用類裏包含一個類的方法來描述這種問題。

 

例如,student類中有學生的姓名和分數,

我們可以在student類創建一個string類的name對象,和valarray類(這個類類似於數組,但是功能比數組強大)的scores對象

這種方法比較簡單,可以直接在student類的成員函數裏使用string類和valarray的方法(valarray用法)來實現相應的操作,但是這種方法也有其缺點,無法使用虛函數

14.2私有繼承

另一種方法是使用私有繼承。

使用私有繼承,基類的公有成員和保護成員將稱爲派生類的私有成員,這意味着基類方法不會稱爲派生類對象的公有接口的一部分,但是派生類的成員函數裏可以使用它

下面給出使用私有繼承來實現student類的代碼

1.基本框架

class student:private string,private valarray<double>
{
private:
    typedef valarray<double> arrayDb;//這裏用typedef簡化一下
public:
    
};

基本框架如上,使用private關鍵字,由於成績是浮點數,所以繼承的是valarray<double>類,<double>指的是存儲double類型數據。新的student類不需要提供私有數據,因爲私有繼承已經提供了兩個無名稱的子對象成員,這是與第一個方法的第一個主要區別

2.初始化基類組件

如果我們是students裏包含着一個 stirng類的name對象,和valarray的scores對象

我們完全可以這樣來初始化

student(const char *str,const double *pd,int n):name(str),scores(pd,n){}

 由於我們是私有繼承,並沒有提供名稱,所以我們不能用name 和scores來初始化,這也是第二個主要區別

student(const char *str,const double *pd,int n):string(str),arrayDb(pd,n){}

 

3.訪問基類的方法

私有繼承只能在派生類的成員函數中使用基類的方法,由於沒有名稱,所以要用類名和作用域解析運算符來調用基類的方法

double student::Average()const
{
    if(arrayDb::size()>0)
    {
        return arrayDb::sum()/arrayDb::size();
    }
    else
    {
        return 0;
    }
}

4.訪問基類的對象

使用私有繼承時,string對象沒有名稱,那麼student類的代碼如何訪問內部的string對象呢?

答案是使用強制類型轉換。

const string& student::Name()const
{
    return (const string &)*this;
}

可以使用這個返回的引用來訪問基類的對象

5.訪問基類的友元函數

通過顯式地轉換位基類來調用正確的函數

ostream &operator <<(ostream &os,const student &stu)
{
    os<<(const string &)stu<<endl;
}

顯式地將stu轉換爲string對象引用,進而調用函數operator<<(ostream&os,const stirng&)

引用stu不會隱式的轉換爲string引用,因爲私有繼承中,未進行顯式類型轉換的派生類引用或指針,無法賦值給基類的引用或指針。

14.2.2使用包含還是私有繼承

通常使用包含來建立has-a關係,如果新類需要訪問原有類的保護成員,或需要重新定義虛函數,則應使用私有繼承

14.2.3保護繼承

 

使用保護繼承時,基類的公有成員和保護成員都將成爲派生類的保護成員,保護成員在類外無法使用。

 

14.2.4使用using重新定義訪問權限

我們希望在派生類的成員函數中像基類一樣訪問成員函數,我們可以使用using聲明

class student:private string,private valarray<double>
{
private:
    typedef valarray<double> arrayDb;
    using valarray<double>::min;
    using valarray<double>::max;
public:
    student(const char *str,const double *pd,int n):string(str),arrayDb(pd,n) {}
};

這樣可以使min和max像student的公有方法一樣,可以直接調用。

注意事項:using聲明只適用於繼承,並不適用於包含

 

14.3多重繼承

 

多重繼承意味着我們可以繼承多次,只需要將繼承的類用逗號分隔開即可

 

14.3.1有多少父類

但是可能存在下面這種情況

這種情況子類C中就會有兩部分父類中的成員,這將引發一個問題,如下

D x;
A *p = &x;
//這時候會發生二義性問題,因爲有兩部分A的成員,p不知道該指向哪一個了

我們可以通過顯式類型轉換解決這個問題

D x;
A *p = (B*)&x;
A *q = (C*)&x;

虛基類

虛基類可以使得從多個類中派生出的對象只繼承一個基類對象,例如,如果A是虛基類,那麼D中只會有一部分A中的成員。

基類是虛基類的時候,C++禁止信息通過中間類自動傳遞給基類,並且虛基類的構造函數先於派生類的構造函數執行。

所以我們在最終派生類的構造函數中,可以直接在初始化列表裏給虛基類的構造函數傳值,這一點對於非虛基類是非法的

#include <iostream>

using namespace std;
class A
{
    int a;
public:
    A();
    A(int aa):a(aa){}
    void display()
    {
        cout<<a<<endl;
    }
};
class B:virtual public A
{
    int b;
public:
    B();
    B(int aa = 0,int bb = 0):A(aa),b(bb){}

};
class C:virtual public A
{
    int c;
public:
    C();
    C(int aa = 0,int cc = 0):A(aa),c(cc){}
};
class D:public B,public C
{
public:
    D();
    D(int aa = 0,int bb = 0,int cc = 0):A(aa),B(aa,bb),C(aa,cc){}

};
int main()
{
    D x(1,2,3);
    x.display();
    return 0;
}

其他的注意事項

當B類被用作C和D的虛基類,同時被用做X和Y的非虛基類,此時M類是從C、D、X和Y那派生來的,那麼M類中有3個B部分成員

 

14.4類模板

類模板與函數模板類似,對於處理不同數據類型的類,我們可以給它編寫一個泛型的棧,然後將具體類型作爲參數傳遞給這個類。

模板提供了參數化類型,可以將類型名作爲參數傳遞給接收方建立類或者函數,例如我們將int傳遞給Queue模板,Queue模板將創建一個對int類型進行排隊的Queue類。

14.4.1定義類模板

使用template<class Type>來定義一個類模板

具體語法如下

template<class T>
class Stack
{
private:
    const static int MAX = 10;//或 enum{MAX = 10}
    T items[MAX];
    int top;
public:
    Stack();
};

template<class T>
Stack<T>::Stack()
{

......

}

需要在類的聲明前和類的成員函數前加上template<class T>,作用域解析運算符前需要使用Stack<T>的形式

 

14.4.2使用模板類

僅在程序包含模板並不能生成沒模板類,必須請求實例化

例如: Stack<int> fun  Stack<long long> happy

看到聲明後,編譯器將按照Stack<T>模板來生成獨立的類聲明和兩組獨立的類方法,屆時將用int來替換類裏所有的T

14.4.5模板的多功能性

模板類可以作爲基類,也可以作爲組件類,還可以作爲其他模板的類型參數。

1.遞歸使用模板

Array<Array<int,5>10> a;

就相當於 

int a[10][5];

2.使用多個類型參數

可以使用多個類型參數,例如

template<class T,class V>

3.默認類型模板參數

template <class T = int>

class Stack

則可以這樣做: Stack<>  a;//創建一個int類型的棧

14.4.6模板的具體化

1.隱式實例化

模板類默認就是進行的隱式實例化,即編譯器碰到創建具體類型對象時,才進行創建相應類型的類聲明

Stack<int> a;//會創建int類型的棧的類
Stack<double> *p;//不會創建
p = new Stack<double> //會創建int類型的棧的類

2.顯式實例化

使用關鍵字指出所需類型來聲明類的時候,編譯器將生成類聲明的顯式實例化,這種情況下,雖然沒有創建和提及類對象,但是編譯器也將生成類聲明

template<class T= int>
class Stack
{
private:
    const static int MAX = 10;
    T items[MAX];
    int top;
public:
    Stack();
};

template class Stack<double>;//顯式實例化

3.顯式具體化

對於特定類型,模板中的某些地方需要修改,我們可以使用顯式具體化,例如,比較字符串不能通過簡單的重載運算符比較,而是要使用strcmp函數,這時候需要用到顯示具體化

template<> 
class Stack<const char* >
{
    ......
}

當編譯器遇到char類型時,將使用這個具體化的模板,而不是泛式的模板

4.部分具體化

部分具體化即部分限制模板的通用性

1.給類型參數指以指定具體的類型

//隱式實例化
template<class T1,class T2> 
class car
{


}
//部分顯示具體化
template<class T1> 
class car<T1,int>
{


}

這裏注意,<>裏放的是沒有被具體化的參數,如果<>裏爲空,部分顯式具體化就變成顯式具體化了。

如果有多個模板可供選擇,編譯器將選擇具體化程度最高的模板。

2.部分具體化的其他操作

template<class T1,class T2> car<T1,T2,T2>
{

........

}
template<class T1> car<T1,T1*,T1*>
{

........

}

 

14.4.7成員模板

模板可用做結構 類或模板類的成員。

template<class T>
class fun
{
private:
    template<class V>
    class happy
    {
    private:
        V a;
    public:
    }
public:
    fun();
};

很老的編譯器不接受模板成員,而另一些編譯器接受模板成員,但是不接受類外面的定義,如果你的編譯器接受類外面的定義,則可以使用下列語法來在類外使用模板成員

#include <iostream>

using namespace std;

template<class T>
class fun
{
private:
    template<class V>
    class happy;

public:
    fun();
};

template<class T>
template<class V>
class fun<T>::happy
{
private:
    V a;
public:
    happy(V aa);
};


template<class T>
template<class V>
class fun<T>::happy(V aa)
{
    a = aa;
};

 

14.4.8將模板作爲參數

 

模板可以包含類型參數(typename T)和非類型參數(int n 用於傳數組元素個數),還可以包含模板的參數

如 

#include <iostream>

using namespace std;
template <class T>
class king//一個模板類
{

};

template<template<class T> class Ting>
class crab
{
private:
    Ting<int> s1;
    Ting<double> s2;
public:
    carb();
};
int main()
{
    crab<king> s;
    //king會通過Ting傳進類內,於是 Ting<int> s1 -->King<int> s1;
    //Ting<double> s2 -->King<double> s2
    crab<stack> s2;
    return 0;
}

未完待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章