has-a关系:通常,包含、私有继承和保护继承用于实现has-a关系,即新的类将包含另一个类的对象。
子对象(subobject)表示通过继承或包含添加的对象。
多重继承(multiple inheritance,MI)使用多个基类的继承。多重继承使得能够使用两个或更多的基类派生出新的类,将基类的功能组合在一起。
虚基类:使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。
对于一个学生各科考试分数的表示:
接口和实现:
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提 供实现)。获得接口是is-a关系的组成部分。
使用组合(包含),类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
构造函数的成员初始化列表:
如果不使用初始化列表语法,情况将如何呢?
C++要求在构建对象的其他部分之前,先构建继承对象的所有成员对象。因此,如果省略初始化列表,C++将使用成员对象所属类的默认构造函数。
模板特性意味着声明对象时,必须指定具体的数据类型。因此,使用valarray类来声明一个对象时,需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型:
valarray类是由头文件valarray支持的。
#include
double gpa[5] = {3.1, 3.5, 3.8, 2.9, 3.3};
valarray<double> v1; // an array of double, size 0
valarray<int> v2(8); // an array of 8 int elements
valarray<int> v3(10,8); // an array of 8 int elements, each set to 10
valarray<double> v4(gpa, 4); // an array of 4 elements, initialized to the first 4 elements of gpa
valarray<int> v5 = {20, 32, 17, 9}; // C++11
studentc.h
#ifndef PRIMERPLUS_STUDENTC_H
#define PRIMERPLUS_STUDENTC_H
#include
#include
#include
using namespace std;
class Student
{
private:
typedef valarray<double> ArrayDb;
string name;
ArrayDb scores;
ostream & arr_out(ostream & os) const;
public:
Student() : name("Null"), scores() {}
explicit Student(const string & s) : name(s), scores() {}
explicit Student(int n) : name("Null"), scores(n) {}
Student(const string & s, int n) : name(s), scores(n) {}
Student(const string & s, const ArrayDb & a) : name(s), scores(a) {}
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 istream & operator>>(istream & is, Student & stu);
friend istream & getline(istream & is, Student & stu);
friend ostream & operator<<(ostream & os, const Student & stu);
};
#endif //PRIMERPLUS_STUDENTC_H
studentc.cpp
#include "studentc.h"
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];
}
// private method
ostream & Student::arr_out(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 << endl;
}
}
else
os << "empty array.";
return os;
}
// friend method
// use string version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
is >> stu.name;
return is;
}
// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
getline(is, stu.name);
return is;
}
// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << stu.name << ":\n";
stu.arr_out(os);
return os;
}
usestudentc.cpp
#include "studentc.h"
void set(Student & sa, 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 << ada[i].Name() << endl;
cout << "\nResults:";
for (i = 0; i < pupils; ++i)
{
cout << endl << ada[i];
cout << "average: " << ada[i].Average() << endl;
}
cout << "Done.\n";
return 0;
}
void set(Student & sa, int n)
{
cout << "Please enter the student's name: ";
getline(cin, sa);
cout << "Please enter " << n << " quiz scores:\n";
for (int i = 0; i < n; i++)
cin >> sa[i];
while (cin.get() != '\n')
continue;
}
私有继承访问基类的友元函数时为什么要进行强制类型转换?
studenti.h
#ifndef PRIMERPLUS_STUDENTI_H
#define PRIMERPLUS_STUDENTI_H
#include
#include
#include
using namespace std;
class Student : private string, private valarray<double>
{
private:
typedef valarray<double> ArrayDb;
ostream & arr_out(ostream & os) const;
public:
Student() : string("Null"), ArrayDb() {} // 调用相应基类的构造函数
explicit Student(const string & s) : string(s), ArrayDb() {}
explicit Student(int n) : string("Null"), ArrayDb(n) {}
Student(const string & s, int n) : string(s), ArrayDb(n) {}
Student(const string & s, const ArrayDb & a) : string(s), ArrayDb(a) {}
Student(const char * str, const double * pd, int n) : string(str), ArrayDb(pd, n) {}
~Student() {}
double Average() const;
double & operator[](int i);
double operator[](int i) const;
const string & Name() const;
friend istream & operator>>(istream & is, Student & stu);
friend istream & getline(istream & is, Student & stu);
friend ostream & operator<<(ostream & os, const Student & stu); // 这里注意加const
};
#endif //PRIMERPLUS_STUDENTI_H
studenti.cpp
#include "studenti.h"
double Student::Average() const
{
if (ArrayDb::size() > 0) // 访问基类方法:使用类名和作用域解析符
return ArrayDb::sum() / ArrayDb::size();
else
return 0;
}
const string & Student::Name() const
{
return (const string &) *this; // 访问基类对象:使用强制类型转换
}
double & Student::operator[](int i)
{
return ArrayDb::operator[](i);
}
double Student::operator[](int i) const
{
return ArrayDb::operator[](i);
}
// private method
ostream & Student::arr_out(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 << endl;
}
}
else
os << "empty array.";
return os;
}
// friend method
// use string version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
is >> (string &) stu;
return is;
}
// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
getline(is, (string &) stu);
return is;
}
// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << (const string &) stu << ":\n";
stu.arr_out(os);
return os;
}
私有继承和保护继承之间的主要区别:(当从派生类派生出另一个类时)
使基类的方法在派生类外面可用:
// 定义一个使用该基类方法的派生类方法。
double Student::sum() const // public Student method
{
return std::valarray<double>::sum(); // use privately-inherited method
}
// 方法二
class Student : private std::string, private std::valarray<double>
{
...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
using std::valarray<double>::operator[];
...
};
MI可能会给程序员带来很多新问题。其中两个主要的问题是:
例:先定义一个抽象基类Worker,并使用它派生出Waiter类和Singer类。然后,便可以使用MI从Waiter类和Singer类派生出SingingWaiter类。
假设首先从Singer和Waiter公有派生出SingingWaiter:因为Singer和Waiter都继承了一个Worker组件,因此SingingWaiter 将包含两个Worker组件。
把基类指针设置为派生对象中的基类对象的地址。将派生类对象的地址赋给基类指针。使用类型转换来指定对象
Worker * pw1 = (Waiter *) &ed; // the Worker in Waiter
Worker * pw2 = (Singer *) &ed; // the Worker in Singer
C++引入多重继承的同时,引入了一种新技术——虚基类(virtual base class),使MI成为可能。
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。
通过在类声明中使用关键字virtual,可以使Worker被用作Singer和Waiter的虚基类(virtual和public的次序无关紧要)
class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};
class SingingWaiter: public Singer, public Waiter {...};
如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。
C++在基类是虚的时,禁止信息通过中间类自动传递给基类。然而,编译器必须在构造派生对象之前构造基类对象组件;在上述情况下,编译器将使用Worker的默认构造函数。
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk,p), Singer(wk,v) {}
多重继承调用哪个基类的方法:可以采用模块化的方式,并使用作用域解析运算符来澄清编程者的意图。
void Worker::Data() const
{
cout << "Name: " << fullname << "\n";
cout << "Employee ID: " << id << "\n";
}
void Waiter::Data() const
{
cout << "Panache rating: " << panache << "\n";
}
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << "\n";
}
void SingingWaiter::Data() const
{
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
最后的例程:
workermi.h
#ifndef _PRIMERPLUS_WORKERMI_H
#define _PRIMERPLUS_WORKERMI_H
#include
using namespace std;
class Worker // an abstract base class
{
private:
std::string fullname;
long id;
protected:
virtual void Data() const;
virtual void Get();
public:
Worker() : fullname("no one"), id(0L) {}
Worker(const std::string & s, long n)
: fullname(s), id(n) {}
virtual ~Worker() = 0; // pure virtual function
virtual void Set() = 0;
virtual void Show() const = 0;
};
class Waiter : virtual public Worker
{
private:
int panache;
protected:
void Data() const;
void Get();
public:
Waiter() : Worker(), panache(0) {}
Waiter(const std::string & s, long n, int p = 0)
: Worker(s, n), panache(p) {}
Waiter(const Worker & wk, int p = 0)
: Worker(wk), panache(p) {}
void Set();
void Show() const;
};
class Singer : virtual public Worker
{
protected:
enum {other, alto, contralto, soprano,
bass, baritone, tenor};
enum {Vtypes = 7};
void Data() const;
void Get();
private:
static char *pv[Vtypes]; // string equivs of voice types
int voice;
public:
Singer() : Worker(), voice(other) {}
Singer(const std::string & s, long n, int v = other)
: Worker(s, n), voice(v) {}
Singer(const Worker & wk, int v = other)
: Worker(wk), voice(v) {}
void Set();
void Show() const;
};
// multiple inheritance
class SingingWaiter : public Singer, public Waiter
{
protected:
void Data() const;
void Get();
public:
SingingWaiter() {}
SingingWaiter(const std::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 & wt, int p = 0)
: Worker(wt),Waiter(wt,p), Singer(wt) {}
void Set();
void Show() const;
};
#endif
workermi.cpp
// workermi.cpp -- working class methods with MI
#include
#include "workermi.h"
using namespace std;
// Worker methods
Worker::~Worker() { }
// protected methods
void Worker::Data() const
{
cout << "Name: " << fullname << endl;
cout << "Employee ID: " << id << endl;
}
void Worker::Get()
{
getline(cin, fullname);
cout << "Enter worker's ID: ";
cin >> id;
while (cin.get() != '\n')
continue;
}
// Waiter methods
void Waiter::Set()
{
cout << "Enter waiter's name: ";
Worker::Get();
Get();
}
void Waiter::Show() const
{
cout << "Category: waiter\n";
Worker::Data();
Data();
}
// protected methods
void Waiter::Data() const
{
cout << "Panache rating: " << panache << endl;
}
void Waiter::Get()
{
cout << "Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '\n')
continue;
}
// Singer methods
char * Singer::pv[Singer::Vtypes] = {"other", "alto", "contralto",
"soprano", "bass", "baritone", "tenor"};
void Singer::Set()
{
cout << "Enter singer's name: ";
Worker::Get();
Get();
}
void Singer::Show() const
{
cout << "Category: singer\n";
Worker::Data();
Data();
}
// protected methods
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << endl;
}
void Singer::Get()
{
cout << "Enter number for singer's vocal range:\n";
int i;
for (i = 0; i < Vtypes; i++)
{
cout << i << ": " << pv[i] << " ";
if ( i % 4 == 3)
cout << endl;
}
if (i % 4 != 0)
cout << '\n';
while (cin >> voice && (voice < 0 || voice >= Vtypes) )
cout << "Please enter a value >= 0 and < " << Vtypes << endl;
while (cin.get() != '\n')
continue;
}
// SingingWaiter methods
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();
}
workmi.cpp
// workmi.cpp -- multiple inheritance
// compile with workermi.cpp
#include
#include
#include "workermi.h"
const int SIZE = 5;
int main()
{
using std::cin;
using std::cout;
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";
// cin.get();
// cin.get();
return 0;
}
混合使用虚基类和非虚基类:
使用虚基类将改变C++解析二义性的方式:
不使用虚基类的MI:
使用虚基类的MI:
template
// type泛型标识符,称为类型参数Stack::
template <class Type> // or template
bool Stack<Type>::push(const Type & item)
{
...
}
// 类模板实例化
Stack<int> kernels; // create a stack of ints
Stack<string> colonels; // create a stack of string objects
stacktp.h
// stacktp.h -- a stack template
#ifndef PRIMERPLUS_SATCKTP_H
#define PRIMERPLUS_SATCKTP_H
template <class Type>
class Stack // 什么时候对类实例化,什么时候Type变成需要的
{
private:
enum {MAX = 10}; // constant specific to class
Type items[MAX]; // holds stack items
int top; // index for top stack item
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item); // add item to stack
bool pop(Type & item); // pop top into item
};
template <class Type>
Stack<Type>::Stack()
{
top = 0;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template <class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
bool Stack<Type>::pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif //PRIMERPLUS_STCKTP_H
stacktem.cpp
// stacktem.cpp -- testing the template stack class
#include
#include
#include
#include "stacktp.h"
using std::cin;
using std::cout;
int main()
{
Stack<std::string> st; // create an empty stack
char ch;
std::string po;
cout << "Please enter A to add a purchase order,\n"
<< "P to process a PO, or Q to quit.\n";
while (cin >> ch && std::toupper(ch) != 'Q')
{
while (cin.get() != '\n')
continue;
if (!std::isalpha(ch))
{
cout << '\a';
continue;
}
switch(ch)
{
case 'A':
case 'a': cout << "Enter a PO number to add: ";
cin >> po;
if (st.isfull())
cout << "stack already full\n";
else
st.push(po);
break;
case 'P':
case 'p': if (st.isempty())
cout << "stack already empty\n";
else {
st.pop(po);
cout << "PO #" << po << " popped\n";
break;
}
}
cout << "Please enter A to add a purchase order,\n"
<< "P to process a PO, or Q to quit.\n";
}
cout << "Bye\n";
// cin.get();
// cin.get();
return 0;
}
double m
是不合法的template <class T, int n> // T为类型参数,n为int类型(非类型/表达式参数)
class ArrayTP
{
...
};
// 定义一个名为ArrayTP的类,并创建一个类型为ArrayTP的eggweight对象。
ArrayTP<double, 12> eggweights;
构造函数方法和表达式参数方法的区别:
// 生成两个独立的类声明
ArrayTP<double, 12> eggweights;
ArrayTP<double, 13> donuts;
// 只生成一个类声明,并将数组大小信息传递给类的构造函数
Stack<int> eggs(12);
Stack<int> dunkers(13);
arraytp.h
//arraytp.h -- Array Template
#ifndef PRIMERPLUS_ARRAYTP_H
#define PRIMERPLUS_ARRAYTP_H
#include
#include
template <class T, int n> // T为类型参数,n为int类型(非类型/表达式参数)
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::cerr << "Error in array limits: " << i
<< " is out of range\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::cerr << "Error in array limits: " << i
<< " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
#endif //PRIMERPLUS_ARRAYTP_H
模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。
template <typename T> // or
class Array
{
private:
T entry;
...
};
template <typename Type>
class GrowArray : public Array<Type> {...}; // inheritance
template <typename Tp>
class Stack
{
Array<Tp> ar; // use an Array<> as a component
...
};
...
Array < Stack<int> > asi; // an array of stacks of int
// 上面一行代码C++98要求>>之间有空格,C++11不要求
在模板语法中,维的顺序与等价的二维数组相反。
// 以下两条命令等价
ArrayTP< ArrayTP<int,5>, 10> twodee;
int twodee[10][5];
twod.cpp
// twod.cpp -- making a 2-d array
#include
#include "arraytp.h"
int main(void)
{
using std::cout;
using std::endl;
ArrayTP<int, 10> sums;
ArrayTP<double, 10> aves;
ArrayTP< ArrayTP<int,5>, 10> twodee;
int i, j;
for (i = 0; i < 10; i++)
{
sums[i] = 0;
for (j = 0; j < 5; j++)
{
twodee[i][j] = (i + 1) * (j + 1);
sums[i] += twodee[i][j];
}
aves[i] = (double) sums[i] / 10;
}
for (i = 0; i < 10; i++)
{
for (j = 0; j < 5; j++)
{
cout.width(2);
cout << twodee[i][j] << ' ';
}
cout << ": sum = ";
cout.width(3);
cout << sums[i] << ", average = " << aves[i] << endl;
}
cout << "Done.\n";
// std::cin.get();
return 0;
}
out:
1 2 3 4 5 : sum = 15, average = 1.5
2 4 6 8 10 : sum = 30, average = 3
3 6 9 12 15 : sum = 45, average = 4.5
4 8 12 16 20 : sum = 60, average = 6
5 10 15 20 25 : sum = 75, average = 7.5
6 12 18 24 30 : sum = 90, average = 9
7 14 21 28 35 : sum = 105, average = 10.5
8 16 24 32 40 : sum = 120, average = 12
9 18 27 36 45 : sum = 135, average = 13.5
10 20 30 40 50 : sum = 150, average = 15
Done.
模板可以包含多个参数类型。假设希望类可以保存两种值,则可以创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名为pair)。
pairs.cpp
// 方法first( ) const和second( ) const报告存储的值,
// 由于这两个方法返回Pair数据成员的引用,因此让您能够通过赋值重新设置存储的值。
// pairs.cpp -- defining and using a Pair template
#include
#include
template <class T1, class T2>
class Pair
{
private:
T1 a;
T2 b;
public:
T1 & first();
T2 & second();
T1 first() const { return a; }
T2 second() const { return b; }
Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
Pair() {}
};
template<class T1, class T2>
T1 & Pair<T1,T2>::first()
{
return a;
}
template<class T1, class T2>
T2 & Pair<T1,T2>::second()
{
return b;
}
int main()
{
using std::cout;
using std::endl;
using std::string;
Pair<string, int> ratings[4] =
{
Pair<string, int>("The Purpled Duck", 5),
Pair<string, int>("Jaquie's Frisco Al Fresco", 4),
Pair<string, int>("Cafe Souffle", 5),
Pair<string, int>("Bertie's Eats", 3)
};
int joints = sizeof(ratings) / sizeof (Pair<string, int>);
cout << "Rating:\t Eatery\n";
for (int i = 0; i < joints; i++)
cout << ratings[i].second() << ":\t "
<< ratings[i].first() << endl;
cout << "Oops! Revised rating:\n";
ratings[3].first() = "Bertie's Fab Eats";
ratings[3].second() = 6;
cout << ratings[3].second() << ":\t "
<< ratings[3].first() << endl;
// std::cin.get();
return 0;
}
out:
Rating: Eatery
5: The Purpled Duck
4: Jaquie's Frisco Al Fresco
5: Cafe Souffle
3: Bertie's Eats
Oops! Revised rating:
6: Bertie's Fab Eats
template <class T1, class T2 = int> class Topo {...};
Topo<double, double> m1; // T1 is double, T2 is double
Topo<double> m2; // T1 is double, T2 is int
虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数提供默认值,这对于类模板和函数模板都是适用的。
模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。
通俗说法:模板类并没有生成类的定义,只是说了以下这个类的架子。模板是用于生成类的方式。具体化是用特定的类型对模板进行填充。
声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。
编译器在需要对象之前,不会生成类的隐式实例化:
ArrayTP<int, 100> stuff; // 隐式实例化
ArrayTP<double, 30> * pt; // 此时还没有使用创建对象,没有分配内存,只是创建了一个指针指向这样的对象
pt = new ArrayTP<double, 30>; // 该语句导致编译器生成类定义,并根据该定义创建一个对象。
template class ArrayTP
当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。
虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。
更快速,省去运行阶段创建这个类的时间。
template <> class Classname
已经有了类的定义,对其中的一些进行修改,重新进行类的实现。
有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。
关键字template后面的<>声明的是没有被具体化的类型参数。
#include
using namespace std;
template<class T1, class T2>
class A
{
public:
void show();
};
template<class T1, class T2>
void A<T1, T2>::show()
{
cout << "this is a general definition." << endl;
}
// 显式实例化,A这个类在内存中已经存在
template class A<double, double>;
// 显示具体化,不再使用通用的方法,所有类型都指定为某一特定的类型
template<>
class A<int, int>
{
public:
void show();
};
// template<> 不用这一行语句
void A<int, int>::show()
{
cout << "explicit specialization." << endl;
}
// 部分具体化
template<class T1> // <>中放没有具体化的,T1泛型,T2指定为某种特殊的类型
class A<T1, int>
{
public:
void show();
};
template<class T1> // 注意这里
void A<T1, int>::show()
{
cout << "partial specialization." << endl;
}
int main(void)
{
A<char, char> a1; // 隐式实例化
a1.show();
A<int, int> a2; // 显示具体化过
a2.show();
A<double, int> a3; // 调用部分具体化
a3.show();
return 0;
}
模板可用作结构、类或模板类的成员。要完全实现STL的设计,必须使用这项特性。
// 模板类和模板函数做成员变量
#include
using namespace std;
template<class T>
class beta
{
private:
template<class V> // beta模板类里面的成员变量是一个hold模板类
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; // 基于int类型的hold对象
public:
beta(T t, int i):q(t), n(i) {}
void Show() const { q.show(); n.show(); }
template<class U> // 定义模板函数
U blab(U u, T t) {return (q.Value() + n.Value()) * u / t;}
// 注意q.Value()后面加括号,因为调用的是一个函数。
};
int main(void)
{
beta<double> guy(1.2, 3);
guy.Show();
cout << guy.blab(10, 6.1) << endl; // (f+int)*int/f,返回int类型
cout << guy.blab(10.0, 6.1) << endl;// (f+int)*f/f,返回f类型
return 0;
}
模板可以包含类型参数(如typename T)和非类型参数(如int n)。模板还可以包含本身就是模板的参数,这种参数是模板新增的特性,用于实现STL。
template <typename T>
class King {...};
template <template <typename T> class Thing>
class Crab{...};
Crab<King> legs;
// 模板类做参数
#include
#include "stacktp.h"
using namespace std;
template <template <class T> class Thing> // 参数Thing是一个模板类型
class Crab
{
private:
Thing<int> s1; // Ting类的对象
Thing<double> s2; // Ting类的对象
public:
Crab() {}
bool push(int a, double x) {return s1.push(a) && s2.push(x);}
bool pop(int & a, double & x) {return s1.pop(a) && s2.pop(x);}
};
int main(void)
{
Crab<Stack> nebula; // 包含两个对象Stack和Stack,包含两个栈
int ni;
double nb;
cout << "Enter int double pairs, such as 4 3.5 (0 to end):\n";
while (cin >> ni && ni > 0 && cin >> nb && nb > 0)
{
if (!nebula.push(ni, nb))
break;
}
while (nebula.pop(ni, nb))
cout << ni << ", " << nb << endl;
cout << "Done.\n";
return 0;
}
非模板友元;将一个常规的函数声明为友元函数。
友元函数可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象;可以访问独立于对象的模板类的静态数据成员。
// 模板类中使用非模板友元函数
template <class T>
class HasFriend
{
public:
friend void counts(); // friend to all HasFriend instantiations
friend void report(HasFriend<T> &); // 要提供模板类参数,必须指明具体化。
...
};
// 友元声明的格式如下:
// 带HasFriend参数的report( )将成为HasFriend类的友元。
class HasFriend<int>
{
friend void report(HasFriend<int> &); // bound template friend
...
};
// 友元函数的定义:
// report( )本身并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显式具体化:
void report(HasFriend<int> & hf) {...}; // explicit specialization for int
void report(HasFriend<double> & hf) {...};
// 模板类中使用非模板友元函数
#include
using namespace std;
template <typename T>
class HasFriend
{
private:
T item;
static int ct; // HasFriend a1, a2; a1和a2公用一个ct变量
public:
HasFriend(const T & i):item(i) {ct++;}
~HasFriend() {ct--;}
friend void counts(); // display ct
friend void reports(HasFriend<T> &); // display item
};
template <typename T> // 这里要注意
int HasFriend<T>::ct = 0;
void counts()
{
cout << "int count: " << HasFriend<int>::ct << ";";
cout << " double count: " << HasFriend<double>::ct << ".\n";
}
void reports(HasFriend<int> & hf)
{
cout << "HasFriend: " << hf.item << endl;
}
void reports(HasFriend<double> & hf)
{
cout << "HasFriend: " << hf.item << endl;
}
int main(void)
{
counts();
HasFriend<int> hfi1(10);
counts();
HasFriend<int> hfi2(20);
counts();
HasFriend<double> hfi3(30.5);
counts();
reports(hfi1);
reports(hfi2);
reports(hfi3);
return 0;
}
约束(bound)模板友元,即友元的类型取决于类被实例化时的类型;
约束模板友元函数是在类外面声明的模板的具体化。
// 首先,在类定义的前面声明每个模板函数。
template <typename T> void counts();
template <typename T> void report(T &);
// 然后,在函数中再次将模板声明为友元。
template <typename TT>
class HasFriendT
{
...
friend void counts<TT>(); // counts( )函数没有参数,因此必须使用模板参数语法()来指明其具体化。
friend void report<>(HasFriendT<TT> &); // report<>可以为空,可以从函数参数推断出模板类型参数
};
// 然后,函数定义
template <typename T>
void counts() {...}
template <typename T>
void report(T & hf) {...}
// 最后,在main()中调用
counts<int>();
非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元。
通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。
友元模板类型参数与模板类类型参数是不同的。
// 非约束模板友元
// manyfrnd.cpp -- unbound template friend to a template class
#include
using std::cout;
using std::endl;
template <typename T>
class ManyFriend
{
private:
T item;
public:
ManyFriend(const T & i) : item(i) {}
template <typename C, typename D> friend void show2(C &, D &);
// 在类内部声明,非约束友元,友元模板类型参数与模板类类型参数是不同的
};
template <typename C, typename D> void show2(C & c, D & d)
{
cout << c.item << ", " << d.item << endl;
}
int main()
{
ManyFriend<int> hfi1(10);
ManyFriend<int> hfi2(20);
ManyFriend<double> hfdb(10.5);
cout << "hfi1, hfi2: ";
show2(hfi1, hfi2); // hfi1, hfi2: 10, 20
// 具体化是void show2 &, ManyFriend &>(ManyFriend & c, ManyFriend & d);
cout << "hfdb, hfi2: ";
show2(hfdb, hfi2); // hfdb, hfi2: 10.5, 20
// std::cin.get();
return 0;
}
// 可使用typedef为模板具体化指定别名:
typedef std::array<std::string, 12> arrst;
arrst months; // months is type std::array
// 使用模板提供一系列别名:
template<typename T>
using arrtype = std::array<T,12>; // template to create multiple aliases
arrtype<double> gallons; // gallons is type std::array
// arrtype表示类型std::array
// C++11允许将语法using =用于非模板。用于非模板时,这种语法与常规typedef等价:
typedef const char * pc1; // typedef syntax
using pc2 = const char *; // using = syntax
typedef const int *(*pa1)[10]; // typedef syntax
using pa2 = const int *(*)[10]; // using = syntax
template <class T>
class Ic
{
T v;
...
public:
Ic(const T & val) : v(val) { }
...
};
其中,T是类型参数,用作以后将指定的实际类型的占位符(这个参数可以是任意有效的C++名称,但通常使用T和Type)。在这种环境下,也可以使用typename代替class:
template <typename T> // same as template
class Rev {...} ;
class Ic sic; _// implicit instantiation_
template class IC; _// explicit instantiation_
Ic
,虽然尚未请求这个类的对象。可以提供显式具体化——覆盖模板定义的具体类声明。方法是以template<>打头,然后是模板类名称,再加上尖括号(其中包含要具体化的类型)。例如,为字符指针提供专用Ic类的代码如下:
template <> class Ic<char *>.
{
char * str;
...
public:
Ic(const char * s) : str(s) { }
...
};
这样,下面这样的声明将为chic使用专用定义,而不是通用模板:
class Ic
template <class T, class TT, int n>
class Pals {...};
下面的声明将生成一个隐式实例化,用double代替T,用string代替TT,用6代替n:
Pals
template < template <typename T> class CL, typename U, int z>
class Trophy {...};
其中z是一个int值,U为类型名,CL为一个使用template
template <class T> Pals<T, T, 10> {...};
template <class T, class TT> Pals<T, TT, 100> {...};
template <class T, int n> Pals <T, T*, n> {...};