模版部分是stl的基础,因此作为初学者花了很多时间整理,如果有错希望能过指出
函数模版转自:https://blog.youkuaiyun.com/WX_East/article/details/53012815 2018.4.4进行可读性修改
模板概念
编程中 ,需要通过这个函数实现不同的参数的调用 。
可以解决这个问题方案有:
1、函数重载,函数重载要对每一种情况都要进行编写,并且函数重载要求返回值相同。
2、公共基类,
如果使用一个公共的基础类,系统将不会对函数进行类型检查 ,并且代码维护起来不是很方便。
3、宏函数,使用宏函数来解决这个问题 ,但系统
不会进行类型检测 ,而且安全性不高 ,不建议使用。
在这里我们引进了模板这个概念 ,
模板又叫泛型编程分为
函数
模板与类模板两部分
函数模板
说到模板 ,必须要说一个关键字template 用来定义模板参数 。
函数模板的形式为

一般建议使用typename来定义模板参数类型。(注意不能使用struct 来代替typename)
并且模板函数也可以定义为内联函数 ,写段代码来看看
- template <typename T>
- inline T Max(T a, T b)
- {
- return (a > b) ? a : b;
- }
inline关键则必须放在template模板参数列表后面,函数返回值之前 。
编写一个模板 ,模板中的函数模板在代码运行时调用时编译编译了两次
编译器会对函数模板产生 函数的特定版本,这叫做是函数模 板的实例化。
函数模板被编译 的两次,
一次是在实例化之前 , 对于函数模板进行代码本身的检测,查找一些语法错误。
一次是在函数被调用后,实例化过程中,对于模板代码进行检测,查看是否所有的调用都有效。比如实例化不支持某些函数的调用 。
并且,函数模板被实例化之前会对函数的参数进行 推演 ,进而确定函数的参数类型 ,
如果出现冲突编译器就报出一个错误 ,来提醒你 。
下面是一个实参推演时,出现冲突造成的错误
- template <typename T>
- T Max(T a, T b)
- {
- return (a > b) ? a : b;
- }
- void fun1()
- {
- cout << Max(1, '1') << endl;
- }
这个模板函数在被调用时进行参数推演,,两个参数 整型 1和字符 1
这两参数都是T类型 这是编译器就会报一个错误
- temlptae.cpp(18): error C2782: “T Max(T,T)”: 模板 参数“T”不明确
模板参数不明确,造成冲突。
当然遇到这种问题的时候,可以声明两个模板参数,示例代码可以这样写
- template <typename T1 ,typename T2>
- T1 Max(T1 a, T2 b)
- {
- return (a > b) ? a : b;
这种情况的意思就是定义两种参数类型,将int 和char两种类型区分开来 ,以免造成类型冲突。
当然你也可以这样的显示的定义
[html]
view plain
copy
- template <typename T>
- T Max(T a, T b)
- {
- return (a > b) ? a : b;
- };
- void fun1()
- {
- cout << Max(1, (int)'1') << endl;
- }
将字符1强制转化为 int类型的 ,这样就会避免上面的问题 。
说到类型转化的话 ,在这里就要多说两句了 。
在函数模板中 ,模板形参的类型转换,有两种情况
1、const转换 简单说就是将 传进来的实际参数转化为const修饰的参数 。
- template <typename T>
- T Max(const T a, const T b)
- {
- return (a > b) ? a : b;
- };
- void fun1()
- {
- int a = 10;
- int b = 20;
- cout << Max(a,b) << endl;
- }
在这里就是就是将普通int属性的参数a 与 b附上const属性 。
2、 数组或函数到指针的转换 ,简单就是对于穿进来的参数为数组或者是函数 ,编译器就会自动将参数
类型转换为指针类型
用示例代码来说明一下吧!
- template <typename T>
- void Funtest(T arr)
- {
- cout << arr << endl;
- }
- void fun()
- {
- int a[] = {1,23,4,5,6};
- Funtest(a);
- }
- Funtest(a);
- 00AD4CEB lea eax,[a]
- 00AD4CEE push eax
- 00AD4CEF call Funtest<int *> (0AD1221h)
- 00AD4CF4 add esp,4
- }
通过这个函数我们可以清楚地看到编译器调用的模板函数参数为int *类型的。
见识到参数的变化了吧 ,那么下面我们就来看看函数模板的参数吧
模板参数
函数模板 有两种类型的参数
1、模板参数
2、调用参数
模板参数 有可以分为 类型形参 和 非类型形参
类型形参就指的是那些用class或者typename声明的形参 。。。。
比如:::::
- template <typename T,int >
- void Funtest(T arr,int size)
- {
- for (int i = 0; i < size; i++)
- {
- cout << arr[i] << endl;
- }
- }
模板形参声明过程中可能会遇到的一些问题
1、模板形参的名字,只能在模板声明到定义的末尾之间使用 ,并遵循名字屏蔽规则
意思就是模板形参只能在对应的模板函数中使用
遵循名字屏蔽规则意思就是(举个例子来说)
- typedef int T;
- template<typename T>
- void funtest(T t)
- {
- cout <<"T type="<< typeid(t).name() << endl;
- }
2、模板形参的名字在同一模板形参列表中只能使用一次
意思就会是在模板形参列表中相同的形参名字只能使用一次,代码示例
- template<typename T,typename T>
- void fun(T t1,T t2)
- {}
- projects\模板\模板\temlptae.cpp(39): error C2991: 重定义 模板 参数“T”
3、所有模板形参前面必须加上class或者typename关键字修饰。
(这个很容易理解))
下面是一些函数声明错误的示例
- template<class T, U, typename V>
- void fun(T, U, V);
- error C2061: 语法错误: 标识符“U”
- error C2061: 语法错误: 标识符“U”
- error C2780: “void fun(T)”: 应输入 1 个参数,却提供了 0 个
- template<class T>
- T fun(int &T);
- error C2780: “T fun(int &)”: 应输入 1 个参数,却提供了 0 个
- typedef int TYPENAME;
- template<typename TYPENAME>
- TYPENAME fun(TYPENAME);
在我们使用模板函数掉数组是就要使用到非模板类型形参》》》
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。
看这一段代码
- template<typename T,int N>
- void fun(T(&arr)[N])
- {
- for (int i = 0; i < N; ++i)
- {
- cout << arr[i] << endl;
- }
- }
- int main()
- {
- int a[] = { 1, 2, 34, 5, 6, 7 };
- fun(a);
- system("pasue");
- return 0;
- }
下面我们来看看编译器运行过程函数的调用(反汇编)
- fun(a);
- 011E45D2 lea eax,[a]
- 011E45D5 push eax
- 011E45D6 call fun<int,6> (011E14B5h)
- 011E45DB add esp,4
从这里 我们可以明确的看出fun的函数原型为 fun<int,6>
其中的6就是表示的N 为数组的长度。
通过中我们可以得到一个类型等价式
- fun(a); // fun<int, 6> 两个数组等价
关于模板形参这里有一些要注意的地方
1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、模板形参表不能为空
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、模板形参表不能为空
4、在函数模板的内部不能指定缺省的模板实参。
5、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
6、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
7、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
5、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
6、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
7、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
模板函数的重载
模板函数的重载和一般的函数重载规则是一样的都是三个要点
1、在同一作用域 ;
2、函数名相同 ;
3、函数参数列表不同 (顺序 、类型 、数量)
看下面一段代码
- int Max(const int& left, const int & right)
- {
- return left>right ? left : right;
- }
- template<typename T>
- T Max(const T& x1, const T& x2, const T& x3)
- {
- return Max(Max(x1, x2), x3);
- };
- template<typename T>
- T Max(const T& left, const T& right)
- {
- return left>right ? left : right;
- }
- int main()
- {
- Max(1, 2, 3);
- Max<>(1, 2);
- Max(1, 2);
- Max(1, 2.1);
- Max<int>(1.0, 2.0);
- Max(1.0, 2.0);
- return 0;
- }
在主函数中每个函数调用的是哪一个函数体呢?
我们来看看吧!
- Max(1, 2, 3);
- 11E5101 call Max<int> (011E14B5h)
- Max<>(1, 2);
- 11E512B call Max<int> (011E14BFh)
- Max(1, 2);
- 11E5155 call Max (011E14BAh)
- Max(1, 2.1);
- 11E517F call Max (011E14BAh)
- Max<int>(1.0, 2.0);
- 11E51A9 call Max<int> (011E14BFh)
- Max(1.0, 2.0);
- 11E51DF call Max<double> (011E14C4h)
- return 0;
通过这段反汇编我们清楚的知道每一个函数调用的是哪一个函数。。。。
为什么呢
Max(1, 2, 3);函数中没有三个参数的一般函数,只能调用模板函数
Max<>(1, 2);明确规定要使用模板函数
Max(1, 2);当遇到模板函数和一般函数都适合的时候优先选择一般的函数
Max(1, 2.1);模板函数遇到这种情况会造成冲突
Max<int>(1.0, 2.0);
Max(1.0, 2.0);一般函数的函数参数类型不合适。。。
Max<>(1, 2);明确规定要使用模板函数
Max(1, 2);当遇到模板函数和一般函数都适合的时候优先选择一般的函数
Max(1, 2.1);模板函数遇到这种情况会造成冲突
Max<int>(1.0, 2.0);
Max(1.0, 2.0);一般函数的函数参数类型不合适。。。
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【说明】
1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例
化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板
函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,
那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,
而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例
化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板
函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,
那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,
而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
但是要是比较两个字符串的大小,使用模板函数的话,出来的用是先定义的变量 。
为什么呢?
- template<typename T>
- T Max(const T& left, const T& right)
- {
- return left>right ? left : right;
- }
所以遇到这种情况我们就要对函数进行特特化
模板函数的特化
有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定
义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事。。。
义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事。。。
就像上面说的,那就要对模板函数进行特化
针对上述的问题来举例
- template<>
- char *Max<char *>(char *const & left, char * const& right)
- {
- if (strcmp(left, right) >= 0)
- return left;
- else
- return right;
- }
模板函数特化形式如下:
1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3、函数形参表
4、函数体
template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}
这里要时刻记住的一点是::
特化的声明必须与特定的模板相匹配,否则就会报错》》》
还有代码中
- template<typename T>
- T Max(const T& left, const T& right)
- {
- return left>right ? left : right;
- }
最后要注意的一点是
注意:特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然
后使用该特化版本的每个源文件包含该头文件。
后使用该特化版本的每个源文件包含该头文件。
函数重载
通常在C语言编程中,如果我们比较三个整数的大小,我们可以进行编写一个max()函数,而如果我们要继续比较三个浮点数的大小,就需要重新编写一个函数,函数体基本是完全相同的,就是返回值和参数类型是不同的,但是函数名称不能是一个了,所以要写第二个函数max1()。
在C++中就可以避免这个问题,允许用同一个函数名定义多个函数,但是一定要注意一点,重载函数的参数个数、参数类型、参数顺序、这三者中至少要有一个不同,才能进行函数重载,因为调用重载函数就是要根据这个来区分具体的调用哪个函数,而函数的返回值可以相同可以不同。
函数模板
函数重载虽然是方便了,但是你还是要定义多个函数,优点就是名字可以相同了,原来的C语言中是不允许两个比较大小函数名字相同的。而函数模板的好处更近一步了,就是你只是需要定义一个函数就可以了。函数模板的通用形式如下所示:
template<typename T>
通用函数定义
或
template<class T>
通用函数定义
但是用函数模板只能用于 函数的参数个数相同而类型不同的情况,如果参数个数不同,则不能使用函数模板。