模板总结
模板:模板的使用是为了适应泛型编程。泛型编程就是编写与类型无关的逻辑代码,是代码复用的一种手段。模板就是实现泛型编程的基础。
1.模板函数:
模板函数代表了一个函数家族,该模板函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
模板函数的格式: class = typename != struct (尽量使用typename 更明显)
template<typename T1, typename T2,......,class Tn>
返回值类型 函数名(参数列表){}
template<class T>
bool IsEqual(const T& a, const T& b)
{
return a == b;
}
注意: 模板函数也可以被定义为内联函数,inline关键字必须放在模板形参表之后,返回值之前,不能放在
template之前。
template<class T>
inline T Add(const T& left, const T& right)
{
return left + right;
}
模板的本质相当于一张图纸,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
模板函数的编译分为两个阶段:
实例化之前检查模板的外壳。是否出现简单的语法错误,如: template <typename T 缺少了“>”。(这里不会对模板函数的函数体进行语法检测)。
实例化之后检查模板生成的代码,是否所有的调用都有效。 如:实例化后的类型不支持有的函数调用
模板函数的推演:从函数实参确定模板形参类型和值的过程称为模板实参推演,多个类型形参的实参必须完全匹配。
#include <iostream>
#include <stdlib.h>
using namespace std;
bool IsEqual(const int& a, const int& b)
{
return a == b;
}
template<class T>
bool IsEqual(const T& a, const T& b)
{
return a == b;
}
template<class T1, class T2>
bool IsEqual(const T1& a, const T2& b)
{
return a == b
}
int main()
{
int a = 10;
int b = 20;
char c = 'a';
char d = 'c';
//编译器调用模板时,编译器会根据传递的参数自动的推演出模板形参的类型,并自动生成对应的代码
cout << IsEqual(a, b) << endl; //int int //已经实现了的模板函数,自动调用,不会重复的推演
cout << IsEqual(c, d) << endl; //char char
cout << IsEqual(a, c) << endl; //int char
system("pause");
return 0;
}
对于已经实现了的模板函数,自动调用,不会重复的推演。
对于上面的代码还可以将模板函数的参数显示实例化
cout << IsEqual<int>(a, b) << endl; //int int //已经实现了的模板函树,自动调用,不会重复的推演
cout << IsEqual<int>(c, d) << endl; //char char
cout << IsEqual(a, c) << endl; //int char
模板函数的形参:
类型形参:
1. 模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
2.模板形参的名字在同一模板形参列表中只能使用一次
3. 所有模板形参前面必须加上class或者typename关键字修饰
模板形参表说明:
1.模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
2.模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
模板函数的重载:
int Max(const int& left, const int& right)
{
return(left>right) ? left : right;
}
template<class T>
T Max(const T& left, const T& right)
{
return (left>right) ? left : right;
}
template<class T>
T Max(const T& left, const T& mid, const T& right)
{
return Max(Max(left, mid), right);
}
int main()
{
int a = 10;
int b = 20;
char c = 'c';
char d = 'd';
char e = 'e';
Max(a, b);
Max(c, d);
Max(c, d, e);
system("pause");
return 0;
}
注意:
1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。不要特化函数模版:
1.函数模版的全特化版本不参与函数重载解析,并且优先级低于函数基础模版参与匹配的原因是:C++标准委员会认为如果因为程序员随意写了一个函数模版的全特化版本,而使得原先的重载函数模板匹配结果发生改变(也就是改变了约定的重载解析规则)是不能接受的。
2.函数模版的全特化到底是哪个函数基础模版的特化,需要参考可见原则,也就是说当特化版本声明时,它只可能特化的是当前编译单元已经定义的函数基础模版。
因为函数的全特化的版本和定义一个普通函数基本上一样,把模版声明去掉即可,而且普通函数的重载优先级最高,也就不会踩一些坑了。
模板类:
类名 : AA Vector
类型名 : AA Vector<T>类模板的格式:
template <class 形参名1, class 形参名2, 、、、class 形参名n>
class 类名
{
}
顺序表类:#pragma once
template <class T>
class SeqList
{
public:
SeqList()
: _size(0)
, _capacity(10)
, _arr(new T[_capacity])
{}
~SeqList()
{
delete []_arr;
_arr = NULL;
}
protected:
size_t _size;
size_t _capacity;
T* _arr;
};
void TestSeqList()
{
SeqList<int> s1;
SeqList<double> s2;
}
非类型形参:非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。如在静态顺序表中,需要自定义大小的顺序表。
注意:浮点数和类对象是不允许作为非类型模板参数的
//设置大小的数组类
const size_t N = 100; //这种方法将数组的大小固定在100,不灵活
template <class T>
//template <class T, size_t N = 100>
class Array
{
protected:
T _a[N];
size_t _size;
};
void TestArray()
{
Array<int> a1; //100
Array<int> a2; //10 //空间的浪费
//Array<int, 100> a3; //a3的大小为100
//Array<int, 1000> a4; //a4的大小为1000
}
模板实现容器适配器: (这里做简单介绍)
模板的参数、模板的模板参数:
#pragma once
template <class T>
class SeqList
{
public:
SeqList()
: _size(0)
, _capacity(10)
, _arr(new T[_capacity])
{}
~SeqList()
{
delete []_arr;
_arr = NULL;
}
protected:
size_t _size;
size_t _capacity;
T* _arr;
};
void TestSeqList()
{
SeqList<int> s1;
SeqList<double> s2;
}
template <class T, class Container>
//template <class T, class Container = SeqList<T>> //缺省的模板参数(模板类的类型)
class Stack
{
public:
void Push(const T& x);
void Pop();
const T & Top();
bool Empty();
private:
Container _con;
};
void Test()
{
Stack<int, SeqList<int>> s1;
//Stack<int>s2; //缺省的模板参数
}
如果遇到了下面这样的问题,代码就会出现越界的问题:
Stack<int, SeqList<char>>s; //---这里有问题--- 调用者误用 存入数据(>128)时可能溢出
为了避免这样的问题,采用模板的模板参数:
template<class T, template <class> class Container = SeqList> //模板的模板参数 (模板类的类名)
class Stack
{
Container<T> _con;
};
//Stack<int , SeqList>s; //调用
关于模板的分离编译:
main.c
int main()
{
SeqList<int> s;
system("pause");
return 0;
}
SeqList.h
template <class T>
class SeqList
{
public:
SeqList();
protected:
size_t _size;
size_t _capacity;
T* _arr;
};
SeqList.cpp
#include "SeqList.h"
template <class T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(10)
, _arr(new T(_capacity))
{}
编译时代买会提示以下错误: 在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就会出错,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器因为要提升性能而不会去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码。
模板的优缺点:
优点:
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2. 增强了代码的灵活性。
缺点:
1. 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。