C++ Primer Plus-代码重用-note3

C++ txt文档转存
迭代器分类
分配器allocator
容器对元素要求
哈希函数
以下Cpp重点

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

法一,类成员是另一个对象的类—包含、组合、层次化。例如在自己写的类中包含vector之类的
法二,使用私有或保护继承
用以实现has-a关系,新的类包含另一个类的对象
法三,第十章 函数模板,本章,类模板
使用通用术语定义类,然后使用模板创建针对特定类型顶一个的特殊类。

14.1包含对象成员的类

valarray类由头文件valarray支持。处理数值。支持诸如数组中的一系列操作-----是一个模板类 。std::valarray 是表示并操作值数组的类。它支持对元素逐个进行数学运算,并且支持多种形式的广义下标运算符、切片及间接访问。
初始化方法类似vector
valarray< type_name> value_name( number/array/… , array_size);
valarray< int> v5 = {22,32}; //c++11初始化列表
方法:
operator; 访问各个元素
size();元素个数
sum() 总和
max(); /min()
e.g.
class Student
{
private:
string name; //string类
valarray< double> scores; //valarray 类
}
student类的成员函数可以使用string类的公共接口访问其内部值,但是student类外部不可以这么做。只能通过student类的公有接口访问string类的值。student类获得了其成员对象的实现,没有继承接口。
使用公有继承,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口时is-a关系的组成部分。使用组合,类可以获得实现,但不获得接口。不继承接口时has-a关系的组成部分

对于has-a不继承接口是个好事,例如,string类将+运算符重载为将两个字符串连接起来。但是对于两个student类对象串接起来没意义。
另一方面,被包含的类的接口部分对于新类来说可能是有意义的,例如,可能希望使用string类接口中的
operator<()方法将student对象按姓名排序,可以定义student::operator<()成员函数,在内部使用string::operator<()。

explicit Student(int n) : name("Nully"), scores(n) {}
    一个参数调用的构造函数,可以用作从参数类型->类类型的隐式转换函数。通常会导致问题(没有按照你设计的来),加上explict,关掉隐式转换,按需手动转换P538

1.初始化被包含的对象
成员初始化列表
Queue::Queue(int qs) : qsize(qs) {…}
使用数据成员qsize的名称,初始化它
hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {…}
使用类名称baseDMA调用特定的基类构造函数,初始化派生类的基类部分
Student(const char * str, const double * pd, int n) : name(str), scores(pd, n) {}
对于成员对象,构造函数使用成员名(私有数据名称)
对于继承的对象,构造函数在成员初始化列表中使用类名调用特定的基类构造函数

初始化列表中的每一项都调用与之匹配的构造函数,即name(str)嗲用构造函数string(const char *)
scores(pd, n)调用构造函数Array Db(const double *, int)
此时的成员对象的背后是一个类。当不使用成员初始化列表,C++将使用成员对下个所属类的默认构造函数
初始化顺序
当初始化列表包含多个项目,初始化顺序为他们被声明的顺序,而不是他们在初始化列表中的顺序。一般来说顺序不太重要,但是当一个成员的值是另外一个成员的一部分的时候,很重要

2.使用被包含对象的接口
被包含对象的接口不是公有的,但是可以在类方法中使用它
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum() / scores.size();
else
return 0;
}
Student调用自己定义的average,在其内部,使用了valarray的方法size(),sum()。

当类方法没有提供时,可以自行提供私有辅助方法

14.2 私有继承

另一种实现has-a关系的途径。
此时,基类的公有成员和包含成员都将成为派生类的私有成员基类方法也不会成为派生对象公有接口的一部分,但是可以在派生类成员函数中使用他们

公有继承,基类的的公有方 将成为派生类的公有方法,派生类将继承基类的接口,–is-a关系的一部分
私有继承,基类的公有方法->派生类的私有方法派生类自己的方法可以调用来实现对父类的操作,但派生类的对象不能直接调用父类方法派生类不继承基类接口–不完全继承-has-a关系的一部分
例如String类->Student类

包含,将具体的对象作为一个命名的成员对象添加到类中
私有继承,将对象作为一个未被命名的继承对象添加到类中—子对象,表示通过继承或包含添加的对象
获得实现,不提供接口

14.2.1 示例
private默认值,省略也导致私有继承
class Student : private std::string, private std::valarray< double>
{
}
此student类从多个类继承而来,多重继承(multiple inheritance)。公有多重继承将导致很多问题。

1.初始化
包含,它的构造函数 使用对象名
Student (const char * str, const double * pd, int n) : name(str), scores(pd, n) {}

私有继承,使用类名
Student (const char * str, const double * pd, int n) :
std::string(str), ArrayDb(pd, n) {} // ArrayDb <–std::valarray< double>

2.访问基类方法
使用私有继承,只能在派生类的方法面里面使用基类方法。有时候希望能用到公有的,
则可以在公有函数中使用继承过来的基类方法
包含的方法:
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum() / scores.size();
else
return 0;
}
私有继承使得能够使用类名称和作用域解析运算符来调用基类方法
double Student::Average() const
{
if (ArrayDb :: size() > 0)
return ArrayDb.sum() / ArrayDb.size();
else
return 0;
}
当然,如果你想使用派生类对象直接调用私有继承来的基类方法,在public部分使用
using base :: func(); //14.2.4
这样在main中可就可以deried :: fun();

3.访问基类对象
使用作用域解析运算符可以访问基类的方法,如果使用基类本身,如Student类的包含版本实现了Name()方法,返回string对象成员name,私有继承时,string对象没有名称,无法返回name。
此时,强制类型转换
因为student是string类派生而来,因此可以通过强制类型转换,将Student类转换为string类对象,结果就成了继承而来的string对象。 this指向用来调用方法的对象, *this为用来调用方法的对象。
const string & Student::Name() const
{
return (const string &) *this;
}
返回一个引用,指向用于调用该方法的student对象中继承而来的string对象

    翻译:
    student由string类私有继承而来,因此它要访问string对象的私有成员,只能先将自己强制类型转换为string类型的公有继承对象

4.访问基类的友元函数
用类名显式地限定函数名不合适,友元函数不属于类,可以通过显式的转换为基类来调用它
ostream & operator<<(ostream & os, const Student & stu)
{
os <<(const String &) stu;
}
cout << plato;
plato是一个student对象,则上面代码可用。stu将是指向plato的引用,os指向cout的引用
引用stu不会自动转换为string引用,是因为在不进行显示类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针

在本例,即使公有继承,不强制转换也不行。会变成这样os < 另外,student例子是多重继承,编译器无法确定应转换成哪个基类。

14.2.2 使用包含还是继承

包含还是私有继承???
大多数倾向于包含!!!,
首先,容易理解,类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象。 而私有继承是关系更抽象
其次,继承会引起很多问题,尤其从多个基类继承。
另外,包含能够包含多个同类的对象,如三个string对象,继承只能使用一个这样的秀昂,对象呢没名称,那一区分
私有继承提供的特性比包含多
如果类包含了保护乘员(可以是数据,也可以是函数)。则这样的成员在派生类中是可用的,在继承层级结构之外不可用原因:如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是位于继承层次结构之外,因此不能访问保护乘员,(多重包含组合) 但可以通过继承得到的是派生类,可以访问保护成员
另外,需要重新定义虚函数时候,只能使用私有继承,派生类可以重定义虚函数,但是包含类不能。私有继承
重定义的函数将只能在类中使用,而不是公有的。

tip:通常,应该使用包含建立has-a关系,如果需要访问原有类的保护乘员,或重定义虚函数,则私有继承

14.2.3 保护继承

私有继承的变体,关键字:protected
class Student : protected std::string, protected std::valarray
{…}
保护继承,基类的公有成员和保护成员将成为派生类的保护成员,和私有继承一样,基类的接口在派生类中也可以使用,但是继承层次结构之外的不可用。
使用私有继承,派生出的第三代(基-派1-派2)类将不能使用基类的接口,因为基类的公有方法在派生类中变成私有方法
使用保护继承。基类的公有方法在第二代将变成受保护的,因此第三地派生类仍然可以使用他们
P550 表14.1

14.2.4使用using重新定义访问权限

使用保护派生或私有派生,要让基类的方法在派生类外可用
法一,定义一个使用该基类方法的派生类方法,如希望Student类能使用valarray的sum()方法
double Student::sum() const //公有的派生类方法
{
return std::valarray::sum(); //使用私有继承的方法
}
法二,将函数调用包装在另一个函数调用中,即用using声明(像名称空间那样)
class Studen : private std::string, private std::valarray< double>
{
public:
using std::valarray< double>::min;
using std::valarray< double>::max;
}

    上述using声明使得valarray::min 可用,就好似他们是student的公有方法一样
    cout << ada[i].max();

using声明只是用成员名,没有圆括号!! 函数特征标是返回类型。 using只适用于继承,不适用于包含
e.g. 为使student类可以使用valarray的operator方法,只需在student类声明的公有部分报下一下
using std::valarray< double>::operator[]

14.3 多重继承

MI描述的是有多个直接基类的类。公有MI表示的也是is-a关系。
class SingingWater : public Water, public Singer {…}
每一个基类都需要public,不然,编译器默认是私有派生

会碰到的主要问题:从两个基类继承而来的同名方法,从多个相关基类那里继承同一个类的多个实例。
慎用!!!

14.3.1 同一基类被继承多次
e.g.
     worker
     |   |
    |      |
singer    waiter 
    |      |
     |    |
  singingwaiter    

singingwaiter ed;
worker * pw = &ed; //二义性
通常,应该使用类型转换来制定对象
worker * pw1 = (waiter *) &ed; //the worker is waiter
P556
但是,即使这样,其中一份worker基类部分也是多余的,不需要的
在此,用到了虚基类。
1.虚基类
虚基类使得从多个类(具有相同的基类)派生出的对象只继承一个基类对象。 又是virtual
virtual 与public顺序没关系
class singer : virtual public worker {…}
class waiter : public virtual worker {…}
class singingwaiter : public singer, public waiter {…}
现在,singingwaiter对象只包含worker对象的一个副本。本质上来说,singer和waiter对象共享一个
worker对象,也因此,可以使用多态。

小问题解答:
1.虚函数和虚基类之间并不存在明显的联系,但是不能增加新关键字了。
2.为啥还使用将基类声明为虚的方式?第一,某些情况下,可能需要基类的多个拷贝;第二个,将基类作为虚的要求程序完成额外的计算,为不需要的工具付出代价是不应当的。第三,有缺点
        ----这啥垃圾问题,重难点
3.为了使得虚基类能工作,C++需要调整。另外,虚基类还可能需要修改已有的代码。如增加virtual关键字

2.新的构造函数规则
对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。但这些构造函数可能需要将信息传递给其基类。
e.g.
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 q = 0, int m = 0, int n = 0) : B(m,n), c(q) } {}
};
如果 worker是虚基类,以上自动传递信息给各自基类将失效。C++在基类是虚的时候,禁止信息通过中间类自动传递给基类。
singingwaiter(const worker & wk, int p = 0, int v = singer::other):
waiter(wk,p), singer(wk,v) {} //failed
因此,这种wk被通过两条路径传递,行不通,这种情况下使用worker的默认构造函数
不然,只能显式调用所需的基类构造函数
singingwaiter(const worker & wk, int p = 0, int v = singer::other):
worker(wk), waiter(wk,p),singer(wk,v) {}
waiter 和singer中wk基类部分数据不会被传递,只能通过worker被传递
上述代码将显式调用worker(const worker &).对于虚基类,必须,对于非虚基类,非法

如果类有间接虚基类,除非只使用虚基类的默认构造函数,否则必须显式调用该虚基类的某个构造函数

14.3.2继承了多个同名方法

对于单继承,如果没有重新定义方法,则将使用最近祖先中的定义而在多重继承中,每个直接祖先都有一个show()函数,因此二义性。
可以使用作用域解析运算符来表明意图
singingwaiter newhire(“aaa”, 2,2,soprano);
newhire singer::show(); //使用singer version

更好的方法是在singingwaiter中重新定义show,并指出使用哪个show
对于单继承来说,让派生方法调用基类的方法是可以的。还可以无线套娃。但是对于多重继承无效
如worker->waiter->singer ,单例继承ok,多重继承中,singer将忽略waiter组件
同样
void singingwaiter::show()
{
singer::show();
}
无效
补救措施:
void singingwaiter::show()
{
singer::show();
waiter::show();
}
但是这样会重复显示worker最终基类的内容多次

解决措施:模块化方式,而不是递增方式。
公有worker部分单独显示,个性化部分合并在一起显示,最后在singingwaiter:show()中组合
void worker::Data() const //protected 公共部分
{
cout <<…;
}
void waiter::Data() const //protected单独部分
{
cout <<…;
}
void singer::Data() const //protected单独部分
{
cout <<…;
}
void singingwaiter:Data() ocnst //protected单独部分
[
singer::Data();
waiter::Data();
}
void singingwaiter:Show() const //public最终合并
{
worker::Data();
Data();
}
MMP,不就是相当于把公有的基类单独下是剩余的各自单独显示嘛

这种方式,对象仍然可以沿用show()方法,
但是Data()方法只能在类内部使用,作为协助公有接口的辅助函数。然而,Data()方法成为私有的将组织waiter中的代码使用worker::Data(),这正是保护访问类的用武之地。
如果Data()方法是保护的,则只能在继承层次结构中的类使用它,其他地方不能使用

另外一种方法:将所有的数据组件都设置为保护的,而不是私有的。不过保护方法(而不是保护数据)将可以更严格地控制对数据的访问。
P560

有关MI的一些问题
1.混合使用虚基类和非虚基类
通过多种途径继承了一个基类的派生类。如果基类是虚基类,派生类将 包含 基类的一个子对象。
如果基类不是虚基类,派生类将包含多个子对象。
混合使用的话,虚基类的子对象,和非虚基类的多个子对象……
那个SB会这么用
2.虚基类和支配
使用虚基类不一定会导致二义性。如果某个名称优先于其他所有名称,则使用它时,不用限定符,也无所谓
优先级:派生类中的名称优先于直接或间接祖先类中的相同名称。
虚二义性规则与访问规则无关 只要在派生链中存在同名,但是你没有限定是谁,则会存在二义性P567

14.4类模板

通常情况,类定义放在一个头文件中,方法定义放在一个源代码文件中,使用类对象的代码会通过#include 来包含对应头文件,通过链接器访问这些代码。
模板不是。编译器需要通过模板为实例化类型生成实际的方法代码,因此需同时访问类定义和方法定义。解决办法:
1.方法定义和类定义直接放在同一个头文件中。也可以将模板方法定义放在另一个头文件,然后在类定义中包含这个头文件
2.方法定义在一个源代码文件中,然后在模板定义头文件中包含方法实现的源文件。
template… 下方包含实现文件
#include “haha.cpp”
这种方法不能把haha.cpp文件添加到项目中,且这个方法实现文件可以随便命名如haha.inl

除了对象类型不同之外,代码相同。可以使用泛型–独立于类型。然后再将具体的类型作为参数传递给这个类。这样就可以使用通用的代码生成存储不同类型的类

14.4.1 定义类模板

	和模板函数一样,模板类开头如下:
 	template 
    template--定义一个模板
    <>--参数列表 
    class--变量的类型名
    Type--变量的名称,不一定必须是一个类,知识说Type是一个通用的类型说明符。

新版C++:
template < typename Type>
typename: 表示模板参数是一个类型的占位符
可以使用自己的泛型名代替Type,其命名规则与其他标志符相同。被调用时,Type将被具体的类型值(如int)取代。

使用模板成员函数替换原有类的类方法,每个函数头都将以相同的模板声打头:
template < class Type>
同样应使用泛型名Type替换typedef的标志符,还需将Stack::改为Stack< Type>::
e.g.
bool stack::push(const Item & item){}
----->>>>>
template < typename Type>
bool stack< Type>::push(const Type & itme) {}

如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。

模板,不是具体的定义。他们是C++编译指令,说明如何生成类和成员函数定义,
具体实现—实例化或者具体化
不能将模板成员函数放在独立的实现文件中。模板必须与特定的模板实例化请求一起编译使用。 因此,模板信息放在一个头文件中。
限制模板实例化
头文件提供类模板,没有方法定义 末尾也不包含方法定义源码文件
这里在项目添加真正的.cpp文件,包含方法定义(包含模板头文件)并在该.cpp文件末尾使用如template class Grid< int>; 类似的语句,这就对int类型显示实例化,并禁止其他类型实例化

14.4.2使用模板类
通过模板生成具体可用的类——实例化,具体类型替换泛型名
e.g.
Stack< int> kernels; //int Stack
Stack< string> colonels; //object Stack
使用的算法必须与类型一致,如stack类假设可以将一个项目赋给另一个项目。这种假设对于基本类型,结构和类来说是成立的(除非将赋值运算符设置为私有),但是对于数组是不成立的。

泛型标志符–例如这里的Type–被称为:类型参数,类似于变量,但只能将类型赋给他们。
必须显式地提供所需类型,与常规的函数模板不同,函数模板可以根据参数类型确定要生成哪种函数

14.4.3 模板类-指针栈

可以将内置类型或类对象用作类模板stack< type>的类型(stack< int>),指针也可以,如指针栈,但是要修改代码
2.正确的使用指针栈

让调用程序提供一个指针数组,每个指针指向不同的字符串。这样的指针栈,实际上还相当于是指针数组。负责管理指针,而不是创建指针。

bool pop(Type & item);
template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

Stack & operator=(const Stack & st);
template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
    if (this == &st)
        return *this;
    delete [] items;
    stacksize = st.stacksize;
    top = st.top;
    items = new Type [stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
    return *this; 
}

原型将赋值运算符函数的返回类型声明为stack引用,是stack的缩写,只能在类中这么使用。
即可以在模板声明或模板函数定义内使用stack(而不是stack),在类外面,即指定返回类型或使用作用域解析运算符时,必须完整的使用stack

初始化一组字符串常量,P577 类型可以为const char*
析构函数对于保存了指针的栈来说,只是山粗了构造函数中new出来的数组,对于指向的字符串无影响
14.4.4数组模板示例和非类型参数

模板常用作容器类,因为类型参数的概念适合将相同从存储方案应用于不同的类型。
允许指定数组大小的简单数组模板,14.3法一中的构造函数接收数组大小的参数。另外就是使用模板参数来提供常规数组的大小。C++ array模板就是这么干

template
关键字class指出T为类型参数,int 指出n的类型为int—指定特殊的类型而不是用作泛型名,称为非类型或表达式参数
表达式参数可以是整型,枚举,引用或指针。因此,double m是不合法的,但double * pm是合法
模板的非类型形参也就是内置类型形参
非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。模板代码不能修改非类型参数的值,也不能使用参数的地址,因此n++ &n无效。实例化时,表达式参数的值必须是常量表达式。类模板类型形参不存在实参推演的问题,必须明确指定,且必须有

对比:1
与法一使用构造函数方法相比,当前方法使用表达式参数方法是用到了为自动变量维护的内存栈,速度块,尤其小型数组
构造函数用的是new和delete管理的堆内存
表达式参数方法的主要缺点是,不同大小的数组将生成自己的模板,也就是一下是俩个独立的类声明:
ArrayTP egg;
ArrayTP dcounts;
但是,
Stack egg[12];
Stack dunker[13];
只生成了一个类声明,并将数组大小信息传递给类的构造函数
对比:2
另一个却别是,构造函数方法更通用,因为数组大小是作为类成员,而不是硬编码存储在定义中的,这样可以将一种大小的数组赋给另一种大小的数组,也可以创建允许数组大小可变的类。表达式参数的大小是写死的。 可以返回类型T,也可以返回类对象Sack
https://www.runoob.com/w3cnote/c-templates-detail.html

1、类模板的格式为:
template
class 类名
{ … };

4、在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
template void A::h(){}。

14.4.5 模板的多功能性

**模板类可以用作基类,也可以用作组件类,还可以用作其他模板的类型参数。**如,可以使用数组模板实现栈模板,也可以使用数组模板来构造数组–数组元素是基于栈模板的栈。
e.g.
继承
template < typebname T>
class Array
{
privat:
T entry;
};
template < typebname Type>
class GrowArray : public Array< Type> {…};
包含
tmeplate < tmeplate Tp>
class Stack
{
Array< Tp> ar; //使用Array<> 作为元素
}
Array> asi; //an Array of stacks of int C++11

1.递归使用模板–套娃
ArrayTP< ArrayTP, 10> twodee;
teodee是一个包含了10个元素的数组,其中每个元素都是一个包含5个int元素的数组。等价于int twodee[ 10] [5];
参数顺序正好与二维数组相反。
2.使用多个类型参数
如希望类可以保存多种值
tmeplate
class Pair
{
private:
T1 a;
T2 b;

}
3.默认类型模板参数
可以为类型参数提供默认值。没有提供T2的值,则编译器默认使用int
template class Tp {…}
template
class CeilDemo
{
public:
int ceil(T1,T2);
};
类模板类型参数可以这么干,但是函数模板参数不能这么干。然而,可以为非类型参数提供默认值,类模板和函数模板都可以

14.4.6 模板的具体化

类模板与函数模板类似,因为可以有隐式实例化,显式实例化和显式具体化。统称具体化
模板以泛型的方式描述类。据具体化用具体的类型生成类声明。
1.隐式实例化
声明一个或多个对象,指出所需类型。编译器使用通用模板提供的处方生成具体的的定义
Array stuff; //隐式实例化,Array
编译器在需要对象之前,不会生成类的隐式实例化:
Array * pt; //指针,无需对象,没有实例化
pt = new Array; //现在需要对象了,此时需要编译器生成类定义,并根据定义创建要给对象,实例化

pt(1,2)这个过程就是一个隐式实例化的过程,它实例化生成了一个T为int的函数。

因此我们得知,隐式实例化实际上就是使用模板时,模板根据传入的实参类型实例化一个函数的过程。

2.显式实例化
当使用template,并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中,如:
定义了Array<>模板后后写了如下语句,
template class Array; //生成类了 类模板 Array

这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明,包括方法定义。和隐式实例化一样,也嫁给你根据通用模板生成具体化。
对,你猜的没错,实际上就是我们显式地写明了是何种类型

3.显式具体化
所谓具体化,是指我对此函数做出的具体的定义。注意:具体化需要给出函数的具体实现。通过以下例子将说明。
**针对特殊情况,更改具体的实现,而不是使用泛型定义的模板得到的方法。**也就是进一步在显式实例化的基础上,自己给出具体代码定义,而不是让编译器帮你补全。
当具体化模板和通用模板都和实例化请求匹配时,具体化更优先

注意:
对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。
显示具体化的原型和定义应以template<>打头,并通过名称支出类型
具体化优先于常规模板,而非模板函数优先于具体化和常规模板

4.部分具体化
即,部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型
//general template
template class Pair {…}
//specialization with T2 set to int
template < class T1> class Pair {…} //这是一个模板T1不确定的模板,类Pair使用T1,int传入
< class T1>是没有被具体化的类型参数
当T1也指定类型 ,<>内为空, tmeplate <> class Pair成了显式具体化
在有多种选择的情况下,编译器偏向于具体化程度最高的。
也可以通过指针提供特殊版本来部分具体化现有的模板

14.4.7成员模板

模板套模板
模板可用于结构,类或模板类的成员

模板嵌套
template< typename T>
{
template< typename V> //嵌套模板类成员
{
}
template< typename U> U fun(U u, T t) {…}; //模板方法
}

14.4.8 模板用作参数

模板套模板,融入
模板可以包含类型参数,非类型参数和模板作为参数,用于实现STL
template