天天看點

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

第十四章 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 * 就不管用了

    template <typename T>
    class SortedArray
    {
    	......
    }
               
    這時,可以定義一個專門用于const char * 類型使用的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];