c++模板

1.模板参数

(1).模板参数的内容

模板参数完整描绘了模板的特性,每个模板参数描述了下面内容之一

a).类型 c++自带的类型,或者是自定义类型

b).编译时常量

c).其他模板


(2).无类型模板参数

这是指的是类型是未知的,在实例化的时候才产生实例类

比如:创建一个Stack类

template<class T, size_t N>
class Stack{
T data[N];
size_t count;
};

在创建一个Stack实例的时候,就必须指明N的大小,比如

Stack<int, 100> mySatck;

(3)默认模板参数

类模板可以设定一个默认的参数,但是函数模板不可以。比如:

template<class T, size_t N = 100>
class Stack{
T data[N];
size_t count;
public:
void push(const T& t);
//......
};

也可以给类型指定默认值

template<class T = int, size_t N = 100>
class Stack{
T data[N];
size_t count;
public:
void push(const T& t);
//......
};

如果不指定类型和数值,则使用默认值。对于没有默认指定的部分,则需要指定。比如对于第一个模板类

实例化则需要 Stack<int> mystack; 或者更改默认值 Stack<double, 999> mystack;

第二个实例化,则可以 Stack<char> mystack或者 Stack<> mySatck; 或者 Stack<int, 188> mystack;

(4).模板类型的模板参数

比如:先创建一个模板类

template<class T>
    class Array{
        T data[N];
        
    }

然后以创建的模板类当作模板的参数

template<class T, template<class> class Seq>
class Container{
private:
Seq<T> seq;//这里会把Seq解释为模板
}

模板也可以写为:template<class T1, template<class T2> class Seq>这里T2并不是必须的

可以理解为模板参数只需要有提供的即可

    //比如: 
    //1.模板数组类
    template<class T, size_t N>
    class Array{
        T data[N];
        int count;
    };
    
    //2,包含模板数组的模板类,这个类含有一个常量:
    template<class T, size_t N, template<class, size_t>class Seq>
    class container{
        Seq<T,N> seq;
        
    };
  

这里的模板参数中,模板的类型和值都没有给出,这就如同函数的定义一样,是一个形式参数

为了实现常量,需要在模板中给出N,以便在类中初始化

(4.1) 模板类型的模板参数中含有默认值

//1.一个模板数组的声名,其中含有默认值
template<class T, size_t N = 10>
class Array{
T data[N];
int count;
};
//2.含有上面模板数组的模板类
template<class T, template<class, size_t = 10> class Seq>
class Container{
Seq<T> seq;//这里使用了默认值
};

当然也可以声名为

template<class T, size_t N, template<class, size_t = 10> class Seq>
class Container{
Seq<T,N> seq;
};

这里的默认值大小10是必须的.

模板作为参数的时候,有时候需要指定类型,比如分配器:

template<class T, template<class U, classs = allocate<U> >class Seq >
class Container{
}

这里指定了U类型,这是因为分配器需要保持分配的类型与模板提供的类型相匹配


2. typename 的解释

#include <iostream>
using namespace std;
template<class T>
class X{
typename T::id i;
public:
void f(){
cout << "class X" << endl;
i.g();
}
}; 
class Y{
public:
class id{
public:
void g(){
cout << "class Y" << endl;
}
};
};
int main(int argc, char** argv) {
X<Y> xy;
xy.f();
return 0;
}

这里的 typename T::id 是类型T中是否有一个叫做id的标识符,然后创建同类型的i对象。

如果id是一个静态成员,则可以对它直接进行操作,但是不能创建同类型的i

这个规则是:如果一个模板代码内部的某个类型被模板类型参数所限定,则必须使用关键字typename作为前缀进行说明,除非他已经出现在

基类的规格说明中,或者它出现在同一作用域范围内的初始化列表中。

案例如下:

//创建一个模板类-模板参数是T,以及模板类Seq,模板参数为类型U和默认类型allocator<U> 
template<class T, template<class U, class = allocator<U> >class Seq>
class Container{
private:
Seq<T> seq;
public:
void push_back(const T& t){
seq.push_back(t);
}
typename Seq<T>::iterator begin(){
return seq.begin();
}
typename Seq<T>::iterator end()
{
return seq.end(); 
 } 
 
};
int main(int argc, char** argv) {
Container<int, vector> vContainer;
for(int i = 0; i < 10; i++)
vContainer.push_back(i);
cout << *vContainer.begin() << endl;
cout << *(vContainer.end()-1)<< endl;
return 0;
}

2.1

标准序列容器都有一个默认的分配器参数,传递这些序列容器中的一个作为模板参数,创建一个新类。

按照之前的解释,这里的Seq被模板参数所限定,所以在引用他的迭代器的时候,需要指定typename

typename并不是创建了一个新的类型,而是通知编译器,被限定的表示符表示一个类型

typename Seq<T>::iterator it;

代表it的类型是 Seq<T>::iterator

2.2 typename也可以用来代替模板的class

2.3 template关键字可以作为提示

template<class charT, size_t N>
basic_string<charT> bitsetToString(const bitset<N>& bs)
{
return bs. template to_string<charT, char_traits<charT>, allocator> >();
}

这里的template代表to_string是一个函数模板,它的<>不是大于或者小于符号

2.4 成员模板

bitset::to_string()函数模板是一个成员模板的例子,在一个类或者类模板中声名一个模板。它允许一些独立的模板参数结合,以便组合使用

比如: complex类定义

template<typename T>
class complex{
public:
template<class X> complex(const complex<X>&);
}

complex类中的拷贝构造函数使用了其他的类型,比如:

complex<float> z(1.2);
complex(double) w(z);

z是float类,w是double类,这种方法使得类型转换更加灵活

但是在类外也需要反应这种嵌套的时候,就必须把类外的定义放在前面,函数模板的定义放在后面:

template<class T>
template<class X>
complex(const complex<X>& c)

成员函数模板在标准库中也有这样的:

template<class InputIterator>
vector(InputIterator first, InputIterator last, const Allocator& = Allocator());

2.5 函数模板的几个问题

(1).类型推断

下面这个函数返回a,b中小的那个

template<typename T>
T min(T a, T b);

a)调用方式: int c = min(1,2); T被推断为整型;

也可以这么调用 int c = min<int>(1,2);这种是显式调用,模板把类型推断为int,如果传入的参数是double,也会被转换为int

b)int c = min(1, 3.14)这种调用方式就是错误的,因为参数的类型不一致,我们可以使用显式调用

c)对于只有返回值类型是模板的,需要显式调用。

(2)函数模板重载

可以命名同名的函数模板,但是使用的时候编译器会尽量避免二义性,调用参数最接近的函数,如果无法避免,则会报错

(3).以一个以生成的函数模板地址作为参数

3.模板特化

1.显式特化

这个前面已经说过了。但是对于类的特化,应用会更加灵活,比如对vector有一个vector<bool>的特化

首先声名的是vector类

template<class T, class Allocator = allocator<T> >
class vector{
......
};

为上面的模板进行bool的特化

template<> class vector<bool, allocator<bool> >{
......
}

2.半特化

类模板也可半特化,比如bool类型的vector可以定义为:

template<class Allocator> 
class vector<bool, Allocator>;

这样后面一部分Allocator还是灵活的,这就是半特化,选择一种合适的模板实例化也遵循"特化程度最高"


4.模板和友元

#include <iostream>
using namespace std;
template<class T> class Friendly;//提前声名模板类 
template<class T> void f(const Friendly<T>&);//提前声名函数模板
template<class T>
class Friendly{
T t;
public:
Friendly(const T& theT) : t(theT){
}
friend void f<>(const Friendly<T>&);//这里必须使用<> 表示要用模板函数
void g(){
f(*this);
}
};
void h(){
f(Friendly<int>(1));
}
//实现友元函数
template<class T>
void f(const Friendly<T>& fo){
cout << fo.t << endl;
}
int main(int argc, char** argv) {
h(); 
Friendly<int>(2).g();
return 0;
}

//除了提前声名,还可以在类中声名友元函数,但是如此声名就不再是模板函数,而是一个普通函数

friend void f(const Friendly<T>& fo);

//案例2

#include <iostream>
using namespace std;
template<class T> class Box;//声名一个模板类 
template<class T> //重载+运算符友元函数 
Box<T> operator+(const Box<T>&, const Box<T>&);
template<class T>//重载输出 
ostream& operator<<(ostream& out, const Box<T>&);
template<class T>
class Box{
private:
T t;
public:
Box(const T& theT){
t = theT;
}
friend Box<T> operator+ <> (const Box<T>&, const Box<T>&);//这里 
friend ostream& operator<< <> (ostream& out, const Box<T>& obj);
}; 
template<class T>
Box<T> operator+(const Box<T>& b1, const Box<T>& b2)
{
return Box<T>(b1.t + b2.t);
}
template<class T>
ostream& operator<<(ostream& out, const Box<T>& obj){
return out << obj.t;
}
int main()
{
Box<int> b1(10);
Box<int> b2(20);
cout << b1 + b2 << endl;//成立,可以调用重载+
cout << b1 + 2 << endl;//错误,因为模板没有提供这样的隐式转换方法 
}

而使用内部类,则可以进行转换,

友元模板:在程序中可以使用更精确的模板,如果使用一个精确的类型,则可以认为这个函数是这个类所有实例的友元函数

5.模板的特征

这是一种将与某种类型相关联的所有声明绑定在一起的实现方式,实现了类型和值的灵活匹配

例子:

#include <iostream>
using namespace std;
//设置不同的食物
class Milk{
public:
friend ostream& operator<< (ostream& out, const Milk& obj){
return out << "Milk";
}
}; 
class CondenseMilk{
public:
friend ostream& operator<< (ostream& out, const CondenseMilk& obj){
return out << "CondenseMilk";
}
};
class Honey{
public:
friend ostream& operator<< (ostream& out, const Honey& obj){
return out << "Honey";
}
};
class Cookies{
public:
friend ostream& operator<< (ostream& out, const Cookies& obj){
return out << "Cookies";
}
};
//不同的人物类
class Bear{
public:
friend ostream& operator<< (ostream& out, const Bear& obj){
return out << "Bear";
}
}; 
class Boy{
public:
friend ostream& operator<< (ostream& out, const Boy& obj){
return out << "Boy";
}
};
//创建一个空的特性类-作为模板
template<class Guest>
class GuestTraits;
//创建一个Bear的特化模板 -- 这是将Beaer类与两种食物类型结合在一起 
template<> class GuestTraits<Bear>{
public:
typedef CondenseMilk beverage_type;
typedef Honey snack_type;
};
//创建一个Boy类的特化模板 
template<> class GuestTraits<Boy>{
public:
typedef Milk beverage_type;
typedef Cookies snack_type;
};
//创建一个顾客类,里面绑定两种食物类,如果有需要,可以修改 
class MixedUpTraits{
public:
typedef Milk beverage_type;
typedef Honey snack_type;
}; 
//创建一个模板类-将顾客与顾客特性类绑定 
template<class Guest, class traits = GuestTraits<Guest> >
class BearCorner{
Guest theGuest;//顾客 
typedef typename traits::beverage_type beverage_type;//特性1类型 
typedef typename traits::snack_type snack_type;//特性2类型 
beverage_type bev;//特性1 
snack_type snack;//特性2 
public:
BearCorner(const Guest& g):theGuest(g){
}
void entertain(){
cout << "Entertain " << theGuest << " "
<< "serving " << bev << " "
<< "and " << snack
<< endl;
}
};
int main(){
//前面使用默认的模板,分别给Bear和Boy创建模板 
Boy cr;
BearCorner<Boy> pc1(cr);
pc1.entertain();
Bear pb;
BearCorner<Bear> pc2(pb);
pc2.entertain();
//修改特性模板 -- 没有使用默认的特性模板 
BearCorner<Bear, MixedUpTraits> pc3(pb);
pc3.entertain(); 
return 0;
}

(5.2)策略

实际上,特征类是为特征而设计的,在相关的特征类之间的改变,通常就是类型和常数值,或者是相关类型的模板参数的固定算法。通常特征类本身就是模板,在

上个例子中,如果加入新的函数,比如点餐,然后封装在特征类之中,这种方法叫做策略

(5.3)模板递归

案例如下,构造一个能够跟踪对象数量的类

#include <iostream>
using namespace std;
template<class T>
class Count{
private:
static int count;
public:
Count(){
count++;
}
~Count(){
count--;
}
const static int getCount(){
return count;
} 
}; 
template<class T>
int Count<T>::count = 0;
//继承计数器类
class CountedClass1 : public Count<CountedClass1>{
}; 
class CountedClass2 : public Count<CountedClass2>{
}; 
//继承类以自己作为模板参数,看起来就如同模板递归一样
//实际上,用不同的类型作为模板的参数,初始的count都等于0
//也就是不同类型的模板 
int main(){
CountedClass1 a;
CountedClass2 a2;
cout << a.getCount() << endl;
CountedClass2 b; 
cout << a.getCount() << endl;
cout << b.getCount() << endl;
return 0;
}

6.模板元编程

如下面的程序,是一个递归的模板程序,这个程序的结果实际上在编译期间就已经计算出来了

#include <iostream>
using namespace std;
template<int n>
struct Factorial{
enum { val = Factorial<n-1>::val * n};
}; 
template<> struct Factorial<0>{
enum { val = 1};
};
int main()
{
cout << Factorial<12>::val << endl; 
return 0;
}

模板元编程就是利用编译期间生成的代码进行编程的技术

比如上面的程序,可以用半特化来写终止递归的条件

6.1 编译时编程

利用模板的递归可以实现循环,比如求一个正数的n次方

#include <iostream>
using namespace std;
int power(int n, int p){
int sum = 1;
while(p > 0){
sum *= n;
p--;
}
return sum;
}
template<int N, int P>
class Power{
public:
enum { val  = N * Power<N, P-1>::val}; 
};
//半特化设定终止条件
template<int N>
class Power<N, 0>{
public:
enum { val = 1};
};
int main(){
cout << power(10,3) << endl;
cout << Power<10,3>::val << endl;
}
把模板当作函数一样进行传递的例子:
#include <iostream>
using namespace std;
//实现的表达式是:
//f(n) + f(n-1) + f(n-2) + ... + f(0) 
template<int n, template<int> class F>
struct Accumulate{
enum{val = Accumulate<n - 1, F>::val + F<n>::val};
};
template<template<int> class F> 
struct Accumulate<0, F>{
enum {val = F<0>::val};
};
template<int n>
struct f1{
enum{ val = n};
};
int main(){
cout << Accumulate<5,f1>::val << endl; 
}

6.2 循环分解

可以减少循环的次数

6.3 编译时选择

6.4 编译时断言

如下面的例子所言:

#include <iostream>
using namespace std;
template<bool>
struct StaticCheck{
StaticCheck(...);//构造函数,默认生成true的模板
};
template<>
struct StaticCheck<false>{
};
#define STATIC_CHECK(expr, msg){\
 class Error_##msg{ };\
 sizeof((StaticCheck<expr>(Error_##msg())));\
 }
//函数模板 
template<class To, class From> 
To safe_cast(From from)
{
STATIC_CHECK(sizeof(From) <= sizeof(To), NarrowingConversion);
return reinterpret_cast<To>(from);
}
int main(){
void* p = 0;
int i = safe_cast<int>(p);
cout << "int cast okay" << endl;
return 0;
}

函数模板safe_cast<>()用来检查两个对象的长度,检查from对象是否大于to的长度,如果小于to,则会在编译期间报错:ERROR_NarrowingConversion

错误会在编译期间出现,并且显示一个有效的信息。

StaticCheck类是一个特殊的类,在条件是真的时候,则生成true的模板,当条件不通过的时候,则生成false的模板

7 表达式模板

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstddef>
using namespace std;
template<class, size_t>
class MyVectorSum;
//向量类
template<class T, size_t N> class MyVector{
T data[N];
public:
MyVector<T, N>& operator=(const MyVector<T, N>& right)
{
for(size_t i = 0; i < N; i++)
{
data[i] = right.data[i];
}
}
//用来处理两个向量相加
MyVector<T, N>& operator=(const MyVectorSum<T, N>& right);
const T& operator[](size_t i) const{
return data[i];
}
T& operator[](size_t i){
return data[i];
}
};
template<class T, size_t N> class MyVectorSum{
private:
const MyVector<T, N>& left;
const MyVector<T, N>& right;
public:
MyVectorSum(const MyVector<T, N>& lhs, const MyVector<T, N>& rhs) : left(lhs), right(rhs){
}
T operator[](size_t i) const{
return left[i] + right[i];
}
}; 
//支持 V1 = V2 + V3 
template<class T, size_t N>
MyVector<T, N>& MyVector<T, N>::operator=(const MyVectorSum<T, N>& right){
for(size_t i = 0; i < N; i++)
data[i] = right[i];
return *this; 
}
//仅仅是用来储存 
template<class T, size_t N>
MyVectorSum<T, N> operator+(const MyVector<T,N>& left, const MyVector<T,N>& right){
return MyVectorSum<T, N>(left, right);
}
//对Myvector初始化
template<class T, size_t N>
void init(MyVector<T,N>& v)
{
for(size_t i = 0; i < N; i++)
v[i] = rand() % 100;
 } 
template<class T, size_t N>
void print(const MyVector<T, N>& v){
for(size_t i = 0; i < N; i++)
cout << v[i] << " ";
cout << endl;
}
int main(){
srand(time(0));
MyVector<int, 5> V1;
init(V1);
print(V1); 
MyVector<int, 5> V2;
init(V2);
print(V2);
MyVector<int, 5> V3;
init(V3);
print(V3);
V3 = V1 + V2;
print(V3);
//V3 = V1 + V2 + V3;
return 0;
}

MyVector是一个模板向量类,当它执行V1 + V2 的时候 会调用MyVectorSum<T, N> operator+(const MyVector<T,N>& left, const MyVector<T,N>& right)

这时,它不会真的计算两个向量的和,而是生成一个新的对象MyVectorSum,它只是引用了两个向量,这个过程并没有产生新的对象,对内存的使用也没有增加多少

只有在使用 = 的时候,才会进行计算。

但是,这个程序无法计算 V1 + V2 + V3 ,这是因为 V1 + V2 生成了 MyVectorSum, 然后 V3 是MyVector无法进行相加

所以需要中间层,能使MyVectorSum与MyVector相加,可以使用模板类作为中间层

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstddef>
using namespace std;
template<class, size_t, class, class>
class MyVectorSum;
template<class T, size_t N> class MyVector{
T data[N];
public:
MyVector<T, N>& operator=(const MyVector<T, N>& right)
{
for(size_t i = 0; i < N; i++)
{
data[i] = right.data[i];
}
}
template<class Left, class Right>
MyVector<T, N>& operator=(const MyVectorSum<T, N, Left, Right>& right);
const T& operator[](size_t i) const{
return data[i];
}
T& operator[](size_t i){
return data[i];
}
};
//抽象储存类 
template<class T, size_t N, class Left, class Right> class MyVectorSum{
private:
const Left& left;
const Right& right;
public:
MyVectorSum(const Left& lhs, const Right& rhs) : left(lhs), right(rhs){
}
T operator[](size_t i) const{
return left[i] + right[i];
}
}; 
//支持 V1 = V2 + V3 
template<class T, size_t N>
template<class Left, class Right> 
MyVector<T, N>& MyVector<T, N>::operator=(const MyVectorSum<T, N, Left, Right>& right){
for(size_t i = 0; i < N; i++)
data[i] = right[i];
return *this; 
}
//仅仅是用来储存 
template<class T, size_t N>
MyVectorSum<T, N, MyVector<T,N>, MyVector<T,N> > operator+(const MyVector<T,N>& left, const MyVector<T,N>& right){
return MyVectorSum<T, N,MyVector<T,N>, MyVector<T,N> >(left, right);
}
template<class T, size_t N, class Left, class Right>
MyVectorSum<T, N, MyVectorSum<T,N,Left,Right>, MyVector<T,N> > operator+(const MyVectorSum<T,N, Left, Right>& left, const MyVector<T,N>& right){
return MyVectorSum<T, N, MyVectorSum<T,N, Left, Right>, MyVector<T,N> >(left, right);
}
//对Myvector初始化
template<class T, size_t N>
void init(MyVector<T,N>& v)
{
for(size_t i = 0; i < N; i++)
v[i] = rand() % 100;
 } 
template<class T, size_t N>
void print(const MyVector<T, N>& v){
for(size_t i = 0; i < N; i++)
cout << v[i] << " ";
cout << endl;
}
int main(){
srand(time(0));
MyVector<int, 5> V1;
init(V1);
print(V1); 
MyVector<int, 5> V2;
init(V2);
print(V2);
MyVector<int, 5> V3;
init(V3);
print(V3);
V3 = V1 + V2;
print(V3);
V3 = V1 + V2 + V3;
print(V3);
return 0;
}

8.模板编译模型

模板和普通多文件编译不一样,由于泛型模板,会造成编译失败,所以一般是把定义和实现放在一起,这样的话,会暴露接口。

如果想要分开定义和实现,则需要使用包含模型或者分离模型

1.包含模型

定义模板之后,使用各种特化模型

2.分离模型

使用export关键字定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值