C++模板

1.使用说明符修饰模板的时候,需要将说明符放在模板形参之后,返回值之前。例如:

template<class T> inline T min(const T&, const T&); // ok
inline template<class T> T min(const T&, const T&); // error

2.模板的声明必须指出函数或类是一个模板,同一模板的声明和定义中,模板形参的名字不必相同。例如:

template<class T> T calc(const T&, const T&);
template<class U> U calc(const U&, const U&);
template<class Type> Type calc(const Type& a, const Type& b) {
    // ...
}

3.不能省略关键字或类型说明符。例如:

template<typename T, U> T calc(const T& m, const U& n) { // error
    // ...
}


4.在形参列表中typename与class相同,typename是后来加入标准的,所以旧程序可能不认识。

5.在形如

template<class P, class U>
P fcn(P* array, U val) {
    P::size_type * p; // strange
    // ...
}

这段代码中的P::size_type * p可能会引发一些误会,如果size_type是一个类型,那么这将是一条声明;如果size_type是一个值那么这将是一个乘法表达式。在默认情况下编译器假定这样的名字指定数据成员,而不是类型。如果希望编译器将其作为类型,则必须使用typename显示的告诉编译器:

template<class P, class U>
P fcn(P* array, U val) {
    typename P::size_type * p;
    // ...
}

6.模板形参不必都是类型,同样可以是非类型形参。例如:

template<typename T, size_t N> size_t array_length(const T (&a)[N]) {
    return N; //返回数组长度
}

7.模板的实参转换是很有限的,接受const引用或指针的函数可以分别用const或者非const的对象的引用或指针;如果模板参数不是引用类型,则对数组或函数类型的实参应用常规指针转换。除了这两种情况以外其他的兼容类型都视为不同类型,例如:

template<class T> bool comp(const T& a, const T& b) { return a > b; }
int a = 1;
short b = 1;
comp(a, b); // error

要解决这类问题可以显示的使用强制转换,或者为模板提供两个不同的形参。例如:

template<class T> bool comp(const T& a, const T& b) { return a > b; }
int a = 1;
short b = 1;
comp(a, static_case<int>(b));
template<class T> bool comp(const T& a, const T& b) { return a > b; }
comp(a, b);

8.为模板显示指定类型形参。最典型的例子就是指定返回值。

template<class T1, class T2, class T3>
T1 cmp(const T2& a, const T3& b) { return a > b; }
cmp<long>(a, b);
在显示指定类型参数的时候,括号内的顺序是严格匹配的,也就是说,long与T1匹配,如果还有其他的参数则一次与T2、T3……匹配。

9.模板的编译分为包含编译模型和分别编译模型。在包含编译模型中我们可以使用include将实现模板的文件在最后包含进来就好了。例如:

// 头文件
#ifndef _U_H_
#define _U_H_

template<class T> int comp(const T&, const T&); // 声明

#include "U.cc"

#endif // _U_H_

// 实现文件
template<class T> int comp(const T& a, const T& b) {
    if (a < b) {
        return -1;
    }

    if (b < a) {
        return 1;
    }

    return 0;
}

对于分别编译模型介于我没有实现,还是自己下去看primer吧。

10.在类模板的作用域内部,可以用它的非限定名字引用该类。例如:

template<typename T> class Queue {
public :
    // ...
    Queue(const Queue& q): head(0), tail(0) { // ... }
    // ...
};

11.非类型形参的模板实参。

template<int hi, int wid>
class Screen {
public :
    Screen(): screen(hi * wid, '#'), cursor(0), height(hi), width(wid) {}
private:
    std::string screen;
    std::string::size_type cursor;
    std::string::size_type height, width;
};

Screen<24, 80> hp2621; // screen 24lines by 80 characters

非类型模板实参必须是编译时常量表达式。虽然这所有的所有我都理解,但是我不理解,为什么要用模板,书上说当用户定义对象时必须为每个形参提供常量表达式以供使用,但是这样做有什么意义吗,难道构造函数做不到吗?

12.模板的友元。

i)普通的函数与类可以是模板的友元:

template<class T> class Bar {
    friend class FooBar;
    friend void fcn();
// ...
};

ii)一般模板友元关系:

template<class T> class Bar {
    template<class U> friend class Foo;
    template<class U> friend void tem_fcn();
// ...
};

这种友元关系会形成一对多的关系,也就是说每一种Bar都会有不同类型的Foo和tem_fcn可以任意的访问Bar。

iii)特定的模板友元关系:

template<class T> class Foo2;
template<class T> void temp_func();
template<class T> class Bar {
    friend class Foo2<T>;
    friend void temp_func<char*>();
//...
};
这样就会形成与之前一对多不同的一对一了。当授予给定模板访问权限的时候,在作用域中不需要存在这个类或模板,因为编译器会将友元声明也当作是的类或函数的声明。这样就引发了一个问题,如果你需要声明模板的友元你就必须提前声明这是一个模板,不然编译器会默认的将其作为普通的函数或类。例如:

template<class T> class A;
template<class T> class B {
    friend class A<T>; // ok, 模板类
    friend  class C; // ok, 普通类
    template<class U> friend class D; // ok, 模板类
    friend class R<int>; // error
    friend class F<T>; // error
};

13.成员模板。顾名思义,成员是一个模板,成员模板不能是虚的。当类模板在作用域外定义模板成员的时候,需要两个模板形参表,第一个是类模板的,第二个是自身的:

template<typename T>
class B {
	template<typename Iter> void func( … ){ … }
};
template<typename T> template<typename Iter>
void fun( … ) { … }

14.模板的static类型的数据是属于每种类型的实例的,并不属于模板。例如:

template<class T> F {
public :
    static int count() { return c; }
private:
    static int c;
};

template<class T>
int F<T>::c = 0; // 定义并初始化

F<int> f1, f2;
int a = F<int>::count();
a = f1.count(); // ok,F<int>::count();
a = f2.count(); // ok,F<int>::count();
a = F::count() // error

15.虽然模板是泛型编程,但是模板并不是万能的,因此我们需要在特殊情况下对模板进行特殊处理。例如对C风格的字符串进行比较就不能直接使用模板:

template<>
bool comp<const char*>(const char* a, const char* b) {
    //...
}

这就是模板的特化。我们可以特化一个类,一个函数,或者是一个类的成员。当我们在类特化外部定义成员时,成员之前不能加template<>标记,类只是在外部定义了一个成员函数;成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参开头。

16.特化与重载。函数模板可以重载,可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。在同时包含有模板函数与重载的普通函数的情况下,确定函数调用步骤如下:

1)为这个函数名建立候选函数集合,包括:

    a)与被调用函数名字相同的任意普通函数

    b)任意可以与函数实参匹配的函数模板

2)确定普通函数是否可行的,然后确定模板是否可行。

3)如果需要转换来进行调用,根据转换的种类排列可行函数。记住,调用模板函数实例所允许的转换是有限的。

    a)如果只有一个函数可选,就调用这个函数。

    b)如果调用有二义性,从可行函数集合中去掉所有函数模板实例。

4)重新排列去掉函数模板实例的可行函数

    a)如果只有一个函数可选,就调用这个函数

    b)否则调用具有二义性。

其实只是两个原则:普通函数优先原则和完全匹配原则。习题16.63是一个很好的检测题。

设计既包含函数模板又包含非模板函数的重载函数集合是困难的,因为可能会使函数的用户感到奇怪,定义函数模板特化几乎总是比使用非模板版本更好。

 
 

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值