第十四章 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];