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];

猜你喜欢

转载自blog.csdn.net/luslin1711/article/details/101387964
今日推荐