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;
}
未完待續