c++ 模板

c++    模板

在编程中  ,我们经常会遇到一些通用 函数 。。。。 要通过这个函数来实现不同的参数的函数的调用 。。。

之前在C++中我们学习了 函数的重载 来解决这个问题 。。。
但是函数重 载要对每一种情况都要进行编写 。。。。并且函数重载对返回值不同的两个函数不能构成重载。。

除了函数重载我们还可以使用公共基类来解决这个问题 。。。。 
如果 使用一个公共的基础类,,,系统将不会对函数进行类型检查 ,并且代码维护起来不是很方便。。。。
当然我们也可以使用宏函数来解决这个问题 。。。。
但是这毕竟不是函数 ,不会进行类型检测 ,而且安全性不高 ,不建议使用。。。。。。



既然这些解决方法都有缺陷,那我们该怎么办呢?????

所以在这里我们引进了模板这个概念 。。。。


模板又叫泛型编程   分为    函数模板 与类模板两部分 》》》》

我们先来说说函数模板 《《《《《

函数模板

说到模板 ,,必须要说一个关键字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两种类型区分开来  ,以免造成类型冲突。。。
当然你也可以这样的显示的定义  。。。
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;
	}
	
}
在这里  类型T就是类型形参 ,而int 就是非类型形参》》》》》

模板形参声明过程中可能会遇到的一些问题 》》》》

1、模板形参的名字,只能在模板声明到定义的末尾之间使用 ,,,,并 遵循名字屏蔽规则

 意思就是模板形参只能在对应的模板函数中使用,,,,
遵循名字屏蔽规则意思就是(举个例子来说)
typedef int T;
template<typename  T>
void funtest(T t)
{
	cout <<"T type="<< typeid(t).name() << endl;
}
在这里的T表示的就是int类型的

2、模板形参的名字在同一模板形参列表中只能使用一次

意思就会是在模板形参列表中相同的形参名字只能使用一次,,,,,代码示例一下、、、
template<typename T,typename T>
void fun(T t1,T t2)
{}
这就造成了错误,也可以说成是T重定义
projects\模板\模板\temlptae.cpp(39): error C2991: 重定义 模板 参数“T”

3、所有模板形参前面必须加上class或者typename关键字修饰。。。。。。。
(这个很容易理解))


下面是一些函数声明错误的示例::::
1111111、、、
template<class T, U, typename V>
void fun(T, U, V);
错误报告为::::
error C2061: 语法错误: 标识符“U”
error C2061: 语法错误: 标识符“U”
error C2780: “void fun(T)”: 应输入 1 个参数,却提供了 0 个

意思就是 每个模板参数之前都要class或者typename  并且模板函数fun没有提供形参。。。。
222222、、、
template<class T>
T fun(int &T);
错误报告
 error C2780: “T fun(int &)”: 应输入 1 个参数,却提供了 0 个
模板函数fun中的参数类型为  int&T   ,但并没有提供参数名称  

333333、、
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;
}
在这个模板函数中加入了一个隐藏的参数N用来表示数组的长度》》》》》
下面我们来看看编译器运行过程函数的调用(反汇编)
	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、模板形参表不能为空
4、在函数模板的内部不能指定缺省的模板实参。
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);
011E5101  call        Max<int> (011E14B5h)  
	Max<>(1, 2);
011E512B  call        Max<int> (011E14BFh)  
	Max(1, 2);
011E5155  call        Max (011E14BAh)  
	Max(1, 2.1);
011E517F  call        Max (011E14BAh)  
	Max<int>(1.0, 2.0);
011E51A9  call        Max<int> (011E14BFh)  
	Max(1.0, 2.0);
011E51DF  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);一般函数的函数参数类型不合适。。。
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【说明】
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;
}
const修饰的是left   ,如果传进来的是指针那么const 修饰的就是char  *const

最后要注意的一点是
注意:特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然
后使用该特化版本的每个源文件包含该头文件。


函数模板 到这介绍的就差不多了,下次就好好说类模板了吧!!!!!
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值