第十四章 C++中的代碼重用
本章内容包括:
- has-a 關系
- 包含對象成員的類
- 模闆類valarray
- 私有和保護繼承
- 多重繼承
- 虛基類
- 建立類模闆
- 使用類模闆
- 模闆的具體化
C++的一個主要目标是促進代碼重用。公有繼承是實作這種目标的機制之一,但并不是唯一的機制。
包含對象成員的類:
student.h
#ifndef D1_STUDENT_H
#define D1_STUDENT_H
#include <iostream>
#include <valarray>
#include <string>
using std::string;
class Student {
private:
typedef std::valarray<double> ArrayDb;
string name;
ArrayDb scores;
std::ostream & arr_out(std::ostream & os) const ;
public:
Student() :name("Null Student"),scores(){};
explicit Student(const string & s):name(s),scores(){};
explicit Student(int n) :name("Nully"),scores(n){};
Student(const string &s,int n):name(s),scores(n){};
Student(const string &s,ArrayDb &ad):name(s),scores(ad){};
Student(const char * str, const double * pd,int n):name(str),scores(pd,n){};
~Student(){};
double Average()const ;
const string & Name() const ;
double &operator[](int i);
double operator[](int i) const ;
friend std::istream &operator>>(std::istream & is,Student &stu);
friend std::ostream &getline(std::istream & is,Student &stu);
friend std::ostream &operator<<(std::ostream &os, const Student &stu);
};
student.cpp
#include "Student.h"
#include <iostream>
std::ostream &Student::arr_out(std::ostream &os) const {
int i;
int lim = scores.size();
if (lim > 0) {
for (i =0;i < lim;i++) {
os << scores[i] << " ";
if (i % 5 == 4) os << std::endl;
}
if (i % 5 != 0) os << std::endl;
} else {
os << "empty array";
}
return os;
}
double Student::Average() const {
if (scores.size() > 0) return scores.sum()/scores.size();
else return 0;
}
const string &Student::Name() const {
return name;
}
double &Student::operator[](int i) {
return scores[i];
}
double Student::operator[](int i) const {
return scores[i];
}
std::istream &operator>>(std::istream &is, Student &stu) {
is >> stu.name;
return is;
}
std::istream &getline(std::istream &is, Student &stu) {
getline(is,stu.name);
return is;
}
std::ostream &operator<<(std::ostream &os, const Student &stu) {
os << "Scores for " << stu.name << ":\n";
stu.arr_out(os);
return os;
}
main.cpp
#include <iostream>
#include "Student.h"
using std::cin;
using std::cout;
using std::endl;
void set(Student &stu,int n);
const int pupils = 3;
const int quizzes = 5;
int main(){
Student ada[pupils] ={Student(quizzes),Student(quizzes),Student(quizzes)};
int i;
for (i=0;i<pupils;i++) set(ada[i],quizzes);
cout << "\nStudent List:\n";
for (i=0;i<pupils;i++){
cout << endl << ada[i];
cout << "average: " << ada[i].Average() << endl;
}
cout << "Done.\n";
return 0;
}
void set(Student &stu,int n){
cout<< "Please enter the student's name:";
getline(cin,stu);
cout << "Please enter " << n << " quiz scores:\n";
for (int i= 0;i< n; i++){
cin >> stu[i];
}
while (cin.get() != '\n') continue;
}
私有繼承:
另一種實作has-a關系的途徑—私有繼承。使用私有繼承,基類的公有成員和保護成員都将稱為派生類的私有成員。這意味着基類方法将不會稱為派生對象公有接口的一部分,但可以在派生類的成員函數中使用它們。
使用公有繼承,基類的公有方法将成為派生類的公有方法,這是is-a關系。使用私有繼承,基類的公有方法将成為派生類的私有方法,這是has-a關系。
Student使用私有繼承:
Student_private.h
#ifndef D1_STUDENT_PRIVATE_H
#define D1_STUDENT_PRIVATE_H
#include <iostream>
#include <string>
#include <valarray>
using std::string;
class Student:private std::string,std::valarray<double >{
private:
typedef std::valarray<double> ArrayDb;
std::ostream &arry_out(std::ostream &os) const ;
public:
Student() : string("Null Student"),ArrayDb(){};
explicit Student(const string & s):string(s),ArrayDb(){};
explicit Student(int n) :string("Nully"),ArrayDb(n){};
Student(const string &s,int n):string(s),ArrayDb(n){};
Student(const string &s,ArrayDb &ad):string(s),ArrayDb(ad){};
Student(const char * str, const double * pd,int n):string(str),ArrayDb(pd,n){};
~Student(){};
double Average()const ;
const string & Name() const ;
double &operator[](int i);
double operator[](int i) const ;
friend std::istream &operator>>(std::istream & is,Student &stu);
friend std::istream &getline(std::istream & is,Student &stu);
friend std::ostream &operator<<(std::ostream &os, const Student &stu);
};
#endif //D1_STUDENT_PRIVATE_H
Student_private.cpp
#include "Student_private.h"
#include <iostream>
std::ostream &Student::arry_out(std::ostream &os) const {
int i;
int lim = ArrayDb::size();
if (lim > 0) {
for (i =0;i < lim;i++) {
os << ArrayDb::operator[](i) << " ";
if (i % 5 == 4) os << std::endl;
}
if (i % 5 != 0) os << std::endl;
} else {
os << "empty array";
}
return os;
}
double Student::Average() const {
if (ArrayDb::size() > 0) return ArrayDb::sum()/ArrayDb::size();
else return 0;
}
const string &Student::Name() const {
return (string &) *this;
}
double &Student::operator[](int i) {
return ArrayDb::operator[](i);
}
double Student::operator[](int i) const {
return ArrayDb::operator[](i);
}
std::istream &operator>>(std::istream &is, Student &stu) {
is >> (string &) stu;
return is;
}
std::istream &getline(std::istream &is, Student &stu) {
getline(is,(string &) stu);
return is;
}
std::ostream &operator<<(std::ostream &os, const Student &stu) {
os << "Scores for " <<(const string &) stu << ":\n";
stu.arry_out(os);
return os;
}
使用包含還是私有繼承:
由于既可以使用包含,也可以使用私有繼承來建立has-a關系,那麼應該使用哪種方式呢?多數C++程式員傾向于使用包含。首先,它易于了解。類聲明中包含表示被包含類的顯式命名對象,代碼可以通過名稱引用這些對象,而使用繼承将使關系更抽象,其次,繼承會引起 很多問題,尤其是從多個基類繼承時,可能必須處理很多問題,如包含同名方法的獨立基類或共享祖先的獨立基類。總之,使用包含不太可能遇到這樣的問題。另外,包含能夠包括多個同類的子對象。如果某個類包含三個string對象,可以使用包含3個獨立聲明的string成員。而繼承隻能使用一個這樣的對象。
通常,應使用包含來建立has-a關系;如果新類需要通路原有類的保護成員,或需要重新定義虛函數,則應使用私有繼承。
保護繼承:
使用保護繼承,基類的公有和保護成員都将稱為派生類的保護成員。和私有繼承一樣基類的接口在派生類中是可用的。與私有繼承的差别在于下一代能否通路這些成員。
使用using 重新定義通路權限:
使用保護派生或私有派生時,基類的公有成員将成為保護成員或私有成員。假設要讓基類的方法在派生類之外可用,比如在Student類能夠使用valarry類的sum()方法,可以這樣定義
double Student::sum() const {
return std::valarray<double>::sum();
}
另一種方法是使用using聲明來指定派生類可以使用的特定基類成員,即使采用的是私有派生,比如:
class Student:private std::string,std::valarray<double >{
... ...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
}
多重繼承:
MI描述的是多個直接類的基類,與單繼承一樣,公有MI表示的也是is-a關系。如,可以從Waiter類和singer類中派生出SingingWaiter類;
class SingingWaiter :public Waiter,public Singer {...};
要注意,需要使用關鍵字限定每一個基類,因為,除非特别指出,否則編譯器将認為是私有派生:
class SingingWaiter :public Waiter, Singer {...}; //Singer is private
MI可能跟給程式員帶來很多新問題。其中主要是兩個問題:
- 從兩個不同的基類繼承同名方法時
- 從兩個或更多個相關基類那裡繼承同一個類的多個執行個體
下面這個例子中 Worker是派生類,Singer與Waiter公有繼承Woker類,SingingWorker公有繼承Singer與Waiter類。
worker.h
#ifndef D1_WORKER_H
#define D1_WORKER_H
#include <string>
using std::string;
class Worker {
private:
string fullname;
long id;
public:
Worker() :fullname("null"),id(0L){};
Worker(const string &s,long n):fullname(s),id(n){};
virtual ~Worker() = 0;
virtual void Set();
virtual void Show() const ;
};
class Waiter:public Worker{
private:
int panache;
public:
Waiter() :Worker(),panache(0){};
Waiter(const string &s,long n, int p = 0) :Worker(s,n),panache(p){};
Waiter(const Worker & w,int p= 0) :Worker(w),panache(p){};
void Set();
void Show() const;
};
class Singer:public Worker{
protected:
enum {other,alto,contralto,soprano,bass,baritone,tenor};
enum {Vtpyes = 7};
private:
static char *pv[Vtpyes];
int voice;
public:
Singer() :Worker(),voice(other){};
Singer(const string &s,long n, int p = 0) :Worker(s,n),voice(other){};
Singer(const Worker & w,int v= other) :Worker(w),voice(v){};
void Set();
void Show() const;
};
#endif //D1_WORKER_H
worker.cpp
#include "Worker.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
Worker::~Worker() {}
void Worker::Set() {
cout << "Enter worker's name: ";
getline(cin,fullname);
cout << "Enter worker's ID: ";
cin >> id;
while (cin.get() != '\n') continue;
}
void Worker::Show() const {
cout << "Name: " << fullname << endl;
cout << "Employee ID: " << id << endl;
}
void Waiter::Set() {
Worker::Set();
cout << "Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '\n') continue;
}
void Waiter::Show() const {
Worker::Show();
cout << "Panache rating: " << panache << endl;
}
char * Singer::pv[] = {"other","alto","contralto","soprano","bass","baritone","tenor"};
void Singer::Set() {
Worker::Set();
cout << "Enter number for singer's voical range:\n";
int i;
for (i= 0; i< Vtpyes; i++){
cout << i << ": " << pv[i] << " ";
if (i % 4 == 3) cout << endl;
}
if (i % 4 != 0) cout << endl;
while (cin >> voice && ( voice < 0 || voice >= Vtpyes))
cout << "Please enter a value >=0 and < " << Vtpyes << endl;
while (cin.get() != '\n') continue;
}
void Singer::Show() const {
cout << "Category: singer\n";
Worker::Show();
cout << "Vocal range: " << pv[voice] << endl;
}
main.cpp
#include <iostream>
#include "Worker.h"
const int LIM = 4;
int main(){
Waiter bob("Bob",314L,5);
Singer bev("Beverly",522L,3);
Waiter w_temp;
Singer s_temp;
Worker *pw[LIM] = {&bob,&bev,&w_temp,&s_temp};
int i;
for (i=2;i<LIM;i++)
pw[i]->Set();
for (i=0;i<LIM;i++){
pw[i]->Show();
std::cout<<std::endl;
}
return 0;
}
這種設計看起來是可行的:使用Waiter指針來調用Waiter::Set(),Waiter::Show();使用Singer指針來調用Singer::Set(),Singer::Show()。然後,如果添加一個從Singer和Waiter類派生出的SingingWaiter類後,将會帶來一些問題。
- 有多少個Worker?
- 哪個方法?
有多少個Worker:
假設首先從Singer和Waiter公有派生出SingingWaiter:
class SingingWaiter :public Singer, public Waiter{...}
因為Singer和Waiter都繼承了一個Worker元件,是以SingingWaiter将包含兩個Worker元件。
正如預期,這将引發問題。例如,通常可以将派生類對象的位址指派給基類指針,但現在将出現二義性:
SingingWaiter ed;
Worker *pw = &ed; // error
通常,這種指派将把基類指針設定為派生類對象中的基類對象的位址。但ed中包含兩個Worker對象,有兩個位址可供選擇,是以應使用類型轉換來指定對象
Worker *pw1 = (Waiter *) &ed;
Worker *pw2 = (Singer *) &ed;
這将使得使用基類指針來引用不同的對象(多态性)複雜化;
包含兩個Worker對象拷貝還會導緻其他問題。然而,真正的問題是:為什麼需要Worker對象的兩個拷貝?唱歌的侍者和其他Worker對象一樣,也應該隻包含一個姓名和一個ID。C++引入多重繼承的同時,引入了一種新技術—虛基類,使MI稱為可能。
虛基類:
虛基類使得從多個類(它們的基類相同)派生出的對象隻繼承一個基類對象。例如,通過在類中聲明使用關鍵字virtual,可以使Worker被用作Singer和Waiter的虛基類
class Singer:virtual public Worker{...};
class Waiter:public virtual Worker{...};
然後
class SingingWaiter :public Singer, public Waiter{...}
現在StringWaiter對象隻包含Worker對象的一個副本。從本質上說,繼承的Singer和Waiter對象共享一個Worker對象,而不是各自引入自己的Worker對象副本。因為StringWaiter對象隻包含一個Worker子對象,是以可以使用多态。
新的構造函數規則:
使用虛基類時,需要對類構造函數采用一種新的方法。對于非虛基類,唯一可以出現在初始化清單中的構造函數是基類的構造函數。但這些構造函數可能需要傳遞資訊給其他基類
class A{
int a;
public:
A(int n = 0) :a(n){};
}
class B:public A{
int b;
public:
B(int m=0,int n = 0) :A(n),b(m){};
}
class C:public B{
int c;
public:
C(int m=0,int n=0,int k=0) :B(m,n),c(k){};
}
C(int m=0,int n=0,int k=0) :B(m,n),c(k){}; 這裡隻能出現B(m,n),而不會出現A(n)
C類的構造函數隻能調用B類的構造函數,而B類的構造函數隻能調用A類的構造函數。這裡C類将m,n傳遞給B,B又将n傳遞給A。
如果Worker是虛基類,則這種資訊自動傳遞将不起作用。例如:
SingingWaiter(const Worker &wk,int p = 0, int v = Singer::other)
: Waiter(wk,p),Singer(wk,v) {};// flawed
問題在于,自動傳遞資訊時,有兩條路可走(Waiter,Singer)。存在二義性。是以,必須顯式調用所需構造函數
SingingWaiter(const Worker &wk,int p = 0, int v = Singer::other)
:Waiter(wk), Waiter(wk,p),Singer(wk,v) {};
上面代碼将顯式調用函數worker(const Worker &)。對于虛函數,這樣是合法的,也是必須的。但是對于非虛函數,是非法的。
哪個方法:
多重繼承可能導緻函數調用的二義性,因為Singier與Waiter類中都有Show()方法
需要定義自己的Show()來指定或重新定義這個方法。
Workermi.h
#ifndef D1_WORKER_H
#define D1_WORKER_H
#include <string>
using std::string;
class Worker {
private:
string fullname;
long id;
protected:
virtual void Data() const;
virtual void Get();
public:
Worker() :fullname("null"),id(0L){};
Worker(const string &s,long n):fullname(s),id(n){};
virtual ~Worker() = 0;
virtual void Set() =0;
virtual void Show() const =0;
};
class Waiter:public virtual Worker{
private:
int panache;
protected:
void Data() const;
void Get();
public:
Waiter() :Worker(),panache(0){};
Waiter(const string &s,long n, int p = 0) :Worker(s,n),panache(p){};
Waiter(const Worker & w,int p= 0) :Worker(w),panache(p){};
void Set();
void Show() const;
};
class Singer:public virtual Worker{
protected:
enum {other,alto,contralto,soprano,bass,baritone,tenor};
enum {Vtpyes = 7};
void Data() const;
void Get();
private:
static char *pv[Vtpyes];
int voice;
public:
Singer() :Worker(),voice(other){};
Singer(const string &s,long n, int p = 0) :Worker(s,n),voice(other){};
Singer(const Worker & w,int v= other) :Worker(w),voice(v){};
void Set();
void Show() const;
};
class SingingWaiter:public Waiter,public Singer{
protected:
void Data() const;
void Get();
public:
SingingWaiter(){};
SingingWaiter(const string &s,long n, int p = 0,int v= other) :Worker(s,n),Waiter(s,n,p),Singer(s,n,v){};
SingingWaiter(const Worker & wk, int p = 0,int v= other) :Worker(wk),Waiter(wk,p),Singer(wk,v){};
SingingWaiter(const Waiter & wt,int v= other) :Worker(wt),Waiter(wt),Singer(wt,v){};
SingingWaiter(const Singer & s, int p = 0) :Worker(s),Waiter(s,p),Singer(s){};
void Set();
void Show() const;
};
#endif //D1_WORKER_H
Workermi.cpp
#include "Workermi.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
Worker::~Worker() {}
void Worker::Get(){
getline(cin,fullname);
cout << "Enter worker's ID: ";
cin >> id;
while (cin.get() != '\n') continue;
}
void Worker::Data() const {
cout << "Name: " << fullname << endl;
cout << "Employee ID: " << id << endl;
}
void Waiter::Get() {
cout << "Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '\n') continue;
}
void Waiter::Set() {
cout << "Enter waiter's name: ";
Worker::Get();
Get();
}
void Waiter::Data() const {
cout << "Panache rating: " << panache << endl;
}
void Waiter::Show() const {
cout << "Category: waiter\n";
Worker::Data();
Data();
}
char * Singer::pv[] = {"other","alto","contralto","soprano","bass","baritone","tenor"};
void Singer::Get() {
cout << "Enter number for singer's voical range:\n";
int i;
for (i= 0; i< Vtpyes; i++){
cout << i << ": " << pv[i] << " ";
if (i % 4 == 3) cout << endl;
}
if (i % 4 != 0) cout << endl;
while (cin >> voice && ( voice < 0 || voice >= Vtpyes))
cout << "Please enter a value >=0 and < " << Vtpyes << endl;
while (cin.get() != '\n') continue;
}
void Singer::Set() {
cout << "Enter singer's name: ";
Worker::Get();
Get();
}
void Singer::Data() const {
cout << "Vocal range: " << pv[voice] << endl;
}
void Singer::Show() const {
cout << "Category: singer\n";
Worker::Data();
Data();
}
void SingingWaiter::Data() const {
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Get() {
Waiter::Get();
Singer::Get();
}
void SingingWaiter::Set() {
cout << "Enter singing waiter's name: ";
Worker::Get();
Get();
}
void SingingWaiter::Show() const {
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
main.cpp
#include <iostream>
#include <cstring>
#include "Workermi.h"
const int SIZE = 5;
int main(){
using std::cout;
using std::cin;
using std::endl;
using std::strchr;
Worker *lolas[SIZE];
int ct;
for (ct=0;ct<SIZE;ct++){
char choice;
cout << "Enter the employee category:\n"
<< "w: waiter; s: singer; t:singing waiter; q: quit\n";
cin >> choice;
while (strchr("wstq",choice) == NULL){
cout << "Please enter a, w, s, t or q: ";
cin >> choice;
}
if (choice == 'q') break;
switch(choice){
case 'w': lolas[ct] = new Waiter;
break;
case 's': lolas[ct] = new Singer;
break;
case 't': lolas[ct] = new SingingWaiter;
break;
}
cin.get();
lolas[ct]->Set();
}
cout << "\nHere is your staff:\n";
int i;
for (i=0;i<ct;i++){
cout<<endl;
lolas[i]->Show();
}
for (i=0;i<ct;i++){
delete lolas[i];
}
cout << "Bye.\n";
return 0;
}
其他有關MI的問題:
-
混合使用虛類和非虛類
假設B被用作C類和D類的虛基類,同時被用作X和Y類的非虛基類。而M是從C,D,X,Y派生過來的,這種情況下,M将從C和D類那繼承一個B類子對象,再從X和Y那裡分别繼承一個。共有3個B類子對象。
-
虛基類和支配
使用虛基類将改變C++解析二義性的方式。使用非虛基類時,規則很簡單。如果從不同的類中繼承了兩個或更多的同名函數,則使用該方法時,如果沒有用類名進行限定,将導緻二義性。但如果使用的是虛基類,則這樣做不一定會導緻二義性。在這種情況選,如果某個名稱優先于其他所有名稱,則使用它時,即便不使用限定符,也不會導緻二義性。
那麼一個成員名如何優先于另一個成員名呢?派生類中的名稱優先于直接或間接祖先的相同名稱,例如:
class B { public: short q(); ...... } class C :virtual public B { public: long q(); int omg(); ...... } class D :public C { ...... } class E :virtual public B { private: int omg(); } class F :public D,public E { ...... }
類C中的q()定義優先與B中的q()定義,因為類C是從B類中派生而來的。是以,F中的方法可以使用q()來表示C::q()。另一方面任何一個omg()的定義都不優先于其他omg()定義,因為C和E都不是對方的基類。是以在F中通路omg()将導緻二義性。
虛二義性規則與通路規則無關。假如C類中的q()為私有函數,F中的q()意味着調用不可通路的C::q();
類模闆:
泛型程式設計
template<class Type,int n>
class A
{
private:
Type item[n];
}
stack.h
#ifndef D1_STACK_H
#define D1_STACK_H
template <class Type>
class stack {
enum { SIZE= 10 };
int stacksize;
Type * items;
int top;
public:
explicit stack(int ss= SIZE);
stack(const stack &st);
~stack(){ delete []items;};
bool isempty(){ return top == 0;};
bool isfull(){ return top == stacksize;};
bool push(const Type &item);
bool pop(Type &item);
stack &operator=(const stack &st);
};
template<class Type>
stack<Type>::stack(int ss) :stacksize(ss),top(0){
items = new Type[stacksize];
}
template<class Type>
stack<Type>::stack(const stack &st) {
stacksize = st.stacksize;
top = st.top;
items = new Type[stacksize];
for (int i =0;i<stacksize;i++){
items[i] = st.items[i];
}
}
template<class Type>
bool stack<Type>::push(const Type &item) {
if (isfull()) return false;
items[top++] = item;
return true;
}
template<class Type>
bool stack<Type>::pop(Type &item) {
if (isempty()) return false;
item = items[--top];
return true;
}
template<class Type>
stack<Type> &stack<Type>::operator=(const stack &st) {
if (this == &st) return *this;
stacksize = st.stacksize;
top = st.top;
delete []items;
items = new Type[stacksize];
for (int i =0;i<stacksize;i++){
items[i] = st.items[i];
}
return *this;
}
#endif //D1_STACK_H
main.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "stack.h"
using std::cin;
using std::cout;
using std::endl;
const int Num = 10;
int main(){
std::srand(std::time(nullptr));
cout << "Please enter stack size: ";
int stacksize;
cin >> stacksize;
stack<const char *> st(stacksize);
const char * in[Num] {
"1: Hank","2: KiKi","3: Betty","4: Ian","5: Wolfgang",
"6: Portia","7: Joy","8: Xaverie","9: Juan","10: Misha"
};
const char *out[Num];
int processed = 0;
int nextin = 0;
while (processed < Num)
{
if (st.isempty())
st.push(in[nextin++]);
else if (st.isfull())
st.pop(out[processed++]);
else if (std::rand() % 2 && nextin < Num) // 50% chance
st.push(in[nextin++]);
else
st.pop(out[processed++]);
}
for (int i = 0;i< Num;i++)
cout << out[i] << endl;
cout << "Bye.\n";
return 0;
}
數組模闆示例和非類型參數:
使用模闆參數來提供正常數組的大小。如C++11中新的模闆array。
array.h
#ifndef D1_ARRAY_H
#define D1_ARRAY_H
#include <iostream>
#include <cstdlib>
template <class T,int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP(){};
explicit ArrayTP(const T &v);
virtual T &operator[](int i);
virtual T operator[](int i) const ;
};
template<class T, int n>
ArrayTP<T, n>::ArrayTP(const T &v) {
for (int i=0;i<n;i++){
ar[i] = v;
}
}
template<class T, int n>
T &ArrayTP<T, n>::operator[](int i) {
if (i<0 || i >= n){
std::exit(EXIT_FAILURE);
}
return ar[i];
}
template<class T, int n>
T ArrayTP<T, n>::operator[](int i) const {
if (i<0 || i >= n){
std::exit(EXIT_FAILURE);
}
return ar[i];
}
#endif //D1_ARRAY_H
表達式參數有一些限制,表達式參數可以是整形,枚舉,引用或指針。是以 double m 是不合法的但是double *m或double &m是合法的。另外模闆代碼不能修改參數的值,也不能使用參數的位址。
與Stack中使用的構造函數相比,這種改變數組大小的方法有一個優點。構造函數的方法使用的是new和delete管理的堆記憶體,而表達式參數方法使用的是為自動變量維護的記憶體棧。這樣執行速度将更快,尤其在使用很多小型數組時。
表達式參數的方法的主要缺點是,每種數組大小都将生成自己的模闆。也就是說,下面的聲明将生成兩個獨立的類:
ArrayTP<double,12> eggweights;
ArrayTP<double,13> donuts;
另一個差別是,構造函數方法更通用,這是因為數組大小是作為類成員存儲在定義中的。這樣可以将一種尺寸的數組賦給另一種尺寸的數組,可以建立允許數組大小的可變量。
模闆多功能性:
可以将用于正常類的技術用于模闆類。模闆類可用作基類,也可用作元件類還可以作為其他模闆類型的參數
template<class TP>
class stack
{
ArrayTp<TP> arr;
}
- 遞歸使用模闆:
ArrayTP<ArrayTp<int,5>,10> twodee;
- 使用多個類型參數
template<class T1,class T2> class Pair { private: T1 a; T2 b; }
- 預設類型模闆參數
template<class T1,class T2=int> class Pair {...}
模闆具體化:
-
隐式執行個體化:
前面的例子都是隐式執行個體化:聲明一個或多個對象,指出所需要的類型,而編譯器使用通用模闆生成類定義。
編譯器在需要對象之前,不會生成類的隐式執行個體化。
ArrayTP<std::string,10> *pt; // a pointer, noobject needed yet pt = new ArrayTP<std::string,10>; // now an object is needed
-
顯式執行個體化
使用關鍵字template并指出需要的類型,編譯器将生成類聲明的顯式執行個體化。聲明需位于模闆定義所在的名稱空間中。
template class ArrayTP<std::string,10>;
-
顯示具體化
在模闆類型為特定的某種類型時,使用顯示具體化的類,而不是通用類
比如:SortedArray是一個表示排序後數組的類,其中元素大小比較使用了>運算符進行比較。對于數字或重載了>運算符的類管用,但是對于const char * 就不管用了
這時,可以定義一個專門用于const char * 類型使用的SortedArraytemplate <typename T> class SortedArray { ...... }
template <> class SortedArray<const char *> { ....... } template <> class stack<const char *> { ....... }
部分具體化:
C++還允許部分具體化,即部分限制模闆的通用性。。例如,部分具體化可以給類型之一指定具體的類型template<class T1,class T2> class Pair{...} template<class T1> class Pair<T1,int>{...}
成員模闆:
模闆可作為結構、類或模闆類的成員。要完全實作STL設計,必須使用這項特性:
tempmemb.cpp
#include <iostream>
using std::cout;
using std::endl;
template <class T>
class beta{
private:
template <class V>
class hold{
private:
V val;
public:
hold(V v=0):val(v){};
void show() const {cout<<val<<endl;};
V Value() const { return val;};
};
hold<T> q;
hold<int> n;
public:
beta(T t,int i):q(t),n(i){};
template <class U>
U blab(U u, T t){ return (n.Value() + q.Value()) * u / t;};
void Show() const {q.show();n.show();};
};
int main(){
beta<double> guy(3.5,3);
guy.Show();
cout << guy.blab(10,2.3) << endl;
cout << guy.blab(10.0,2.3) << endl;
return 0;
}
将模闆用作參數:
template<template<class T> class Thing>
class Crab{
.......
}
Crab<King> legs;
為了使上面聲明成立,King類:
template<class T>
class King{
......
}
tempparm.cpp
#include <iostream>
#include "stack/stack.h"
using std::cout;
using std::cin;
using std::endl;
template <template <typename T> class Thing,class Item>
class Crab{
private:
Thing<Item> items;
public:
Crab(int n):items(n){};
bool push(Item item){ return items.push(item);};
bool pop(Item & item){ return items.pop(item);};
};
int main(int argnum, char *args[]) {
Crab<stack,int> nebula(10);
for (int i =0;i<10;i++){
nebula.push(i);
}
int temp;
for (int i =0;i<10;i++){
nebula.pop(temp);
cout << temp << endl;
}
return 0;
}
模闆類和友元:
模闆類也可以有友元,模闆的友元分三類:
- 非模闆友元
- 限制模闆友元,即友元的類型取決于類被執行個體化時的類型
- 非限制模闆友元,即友元的所有具體化都是類的每一個具體化的友元。
模闆類中的非模闆友元函數:
friend2tmp.cpp
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
template <class T>
class HasFriend
{
private:
T item;
static int ct;
public:
HasFriend(const T &i) :item(i){ct++;}
~HasFriend(){ct--;}
friend void counts();
friend void reports(HasFriend<T> &hf);
};
template <class T>
int HasFriend<T>::ct = 0;
void counts() {
cout << "int cout: " << HasFriend<int>::ct << "; ";
cout << "double cout: " << HasFriend<double>::ct << endl;
}
void reports(HasFriend<int> &hf) {
cout << "HasFriend<int>: " << hf.item << endl;
}
void reports(HasFriend<double> &hf) {
cout << "HasFriend<double>: " << hf.item << endl;
}
int main(int argnum, char *args[]) {
cout << "No objects declared: ";
counts();
HasFriend<int> hfil(10);
cout << "After hfil declared: ";
counts();
HasFriend<int> hfil2(20);
cout << "After hfil2 declared: ";
counts();
HasFriend<double > hfildb(20.5);
cout << "After hfildb declared: ";
counts();
reports(hfil);
reports(hfil2);
reports(hfildb);
return 0;
}
模闆類中的限制模闆友元函數:
tmp2tmp.cpp
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
// 1 在類定義前面聲明每個模闆函數
template <typename T> void counts();
template <typename T> void report(T &);
template <class T>
class HasFriend
{
private:
T item;
static int ct;
public:
HasFriend(const T &i) :item(i){ct++;}
~HasFriend(){ct--;}
friend void counts<T>();
friend void report<>(HasFriend<T> &hf); // 聲明中的<>指名這是模闆的具現化
};
template <class T>
int HasFriend<T>::ct = 0;
template <class T>
void counts() {
cout << "template size: " << sizeof(HasFriend<T>) << "; ";
cout << "template couts: " << HasFriend<T>::ct << endl;
}
template <class T>
void report(T & hf) {
cout << "HasFriend<T>: " << hf.item << endl;
}
int main(int argnum, char *args[]) {
counts<int>();
HasFriend<int> hfil(10);
HasFriend<int> hfil2(20);
HasFriend<double > hfildb(20.5);
report(hfil);
report(hfil2);
report(hfildb);
cout << "counts<int>()output:\n";
counts<int>();
cout << "counts<double>()output:\n";
counts<double>();
return 0;
}
結果
template size: 4; template couts: 0
HasFriend<T>: 10
HasFriend<T>: 20
HasFriend<T>: 20.5
counts<int>()output:
template size: 4; template couts: 2
counts<double>()output:
template size: 8; template couts: 1
模闆類的非限制模闆友元:
manyfriend.cpp
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
template <class T>
class ManyFriend
{
private:
T item;
public:
ManyFriend(const T & i):item(i){};
template <class C,class D> friend void show(C &,D &);
};
template<class C, class D>
void show(C & c, D & d) {
cout << c.item << ", " << d.item << endl;
}
int main(int argnum, char *args[]) {
ManyFriend<int> hfil(10);
ManyFriend<int> hfil2(20);
ManyFriend<double> fhdb(10.5);
cout << "hfil, hfil2: ";
show(hfil,hfil2);
cout << "fhdb, hfil2: ";
show(fhdb,hfil2);
return 0;
}
結果:
hfil, hfil2: 10, 20
fhdb, hfil2: 10.5, 20
模闆别名:
如果能為類型指定别名,将很友善,在模闆設計中尤為重要。可使用typeof為模闆具體化指定别名:
type std::array<double,12> arrd;
type std::array<int,12> arri;
type std::array<string,12> arrst;
arrd gallons;
arri days;
arrst months;
新增的模闆别名:
template <class T>
using arrtype = std::array<T,12>;
arrtype <double> gallons;
arrtype <int> days;
arrtype <std::string> months;
C++ 11 允許将文法using=用于非模闆。用于非模闆時,這種文法與正常typeof等價:
typeof const char * pc1;
using pc2 = const char *;
typeof const int *(*pa1)[10];
using pa2 = const int *(*)[10];