从一个通用加法程序看模板

在以往如果我们要写一个通用的加法程序,以实现任意类型相加(比如:整形相加、字符型相加、双精度/单精度相加、整形加字符型、整形加双精度型......) 我们可有以下几种方法:
1、采用函数重载:针对每个所需相同行为的不同类型重新实现
例如:
int add(int left, int right)//整型相加
{
       return left + right;
}

double add(double left, double right)//双精度相加
{
       return left + right;
}
int add(char left, char right)//字符相加
{
       return left + right;
}
...
int main()
{
       cout << add(1, 2) << endl;
       cout << add(1.0, 2.0) << endl;
       cout<<add('1','5')<<endl;
       system("pause");
       return 0;
}


但是,很明显,想要用这种方法完成通用加法的程序所需要的代码, 代码是远远不够的,因为只要有新类型出现,就需要添加对应函数。
显然,函数重载具有以下缺点:
(1 )代码复用率不高,我们可以看到除类型外,所有函数的函数体都相同;
(2)如果函数仅仅返回值不同,函数重载不能解决;
(3)假如,我们的函数实现方法有问题,那将所有的方法都有问题,不好维护;
(4)麻烦,只要有新类型出现,就需要添加对应函数。

2、使用继承的思想:将通用的代码放在公共的基础类里面

缺点:
(1)借助公共基类来编写通用代码,将失去类型检查的优点;
(2)对于以后实现的许多类,都必须继承自某个特定的基类,代码维护更加困难。

3、使用宏函数
即例如 #define add(a , b) ((a)+(b))
这个即在预处理时,直接将宏替换到程序代码中;
缺点:不是函数,不进行类型检测,因此,安全性不高。

以上三种方法都不同程度的有自己的缺陷! 那么,又没有有一种函数与类型无关,我们只需要提供给它程序逻辑,他就能实现我们要的功能呢???

c++中提供模板来解决此类问题。
模板分为函数模板 和类模板
4.函数模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
在使用时添加关键字
模板函数的格式
template<typenameParam1, typename Param2,...,classParamn>
返回值类型 函数名(参数列表)
{
...
}
typename是用来定义模板参数关键字,也可以使用class。建议尽量使用typename。
每一个param均代表一个类型。
例如:同种类型通用加法可以这样写:
template <class T>
T Add(T left, T right)
{
	return left + right;
}
只需声明一个类型就可实现各种相同类型加法
又例如,要实现不同的两种类型的加法,可以声明两个类型:
template <class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}

class是用来定义模板参数关键字,也可以使用 typename。建议尽量使用typename,因为使用typename更加直观,而且,typename更清楚的指明类后面的名字是一个类型名,但是,关键字typename是作为标准c++d组成部分加到c++中的,因此旧的编译器可能只识别class。

注意:不能使用struct代替typename。
模板形参说明
(1)模板形参表使用<>括起来
(2)和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
(3)定义模板函数时模板形参表不能为空
(4)模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
(5)模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
(6)模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加
5、模板的作用域
模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。
模板形参遵行常规名字屏蔽规则,当与全局作用域中声明的对象、函数或类型同名的模板形参将会屏蔽全局名字。
例如:
int left = 10;
template <class T>
T fun(T left)
{
	return left;
}
void funtest()
{
	cout << fun(3);//输出3,屏蔽全局变量
}
又例如:
typedef double T;
template <class T>
T calc(T& a)
{
	T tmp = a;
	return tmp;
}
将T定义为double的全局类型别名将被名为T的参数所屏蔽,因此,tmp不是double型,而是,绑定到模板形参的任意类型。
6、使用模板形参的名字不能在模板内部重用
template <typename T>
T calc(T& a)
{
	typedef double T;//错误格式
	T tmp = a;
	return tmp;
}
template <typename T,typename T>//错误格式
即模板形参的名字只能在同一模板形参中使用一次
7、模板函数也可以定义为inline函数
template<typename T>
inline T Add(const T _left, const T _right)
{
	return (_left + _right);
}

需要注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前,并且,将其设置为内联函数,同时和普通函数一样,设置为内联函数只是给编译器一个建议,并非一定指定inline,编译器就必须那样做。
8、模板实例化
模板是一个蓝图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
模板实例化分为
(1)隐式实例化:
例如Add(1,3)//如果也有一个普通的Add()函数,将调用普通函数
       Add<>(1,3)//如果也有一个普通的Add()函数,将调用模板函数
(2)显示实例化:例如Add<int>(1,3)
例如:



模板被编译了两次:
实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号
在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用。
9、模板实参推演
要确定应该实例化那个函数,编译器会查看每个实参,如果相应形参为类型形参的类型,则编译器从实参的类型推断形参的类型。
例如:add(1,2) ,实参为int型
   add(1.0,2.0),实参为double型
从函数实参确定模板形参类型和值的过程称为模板实参推断
(1)、多个类型形参的实参必须完全匹配
如下是错误的:
template <typename T>
T Add(T left,T right)
{
	return left+right;
}
int main()
{
 	double a = 10;
	int b = 20;
	Add(a,b)
	return 0;
}


调用Add()是错误的,因为从第一个实参推断出double型,而从第二个实参则推断出int型,两个类型不匹配,所以模板推断失败。
(2)类型形参转换
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用,无需产生新的实例化。
template <typename T>
T fun(T& t)
{
	return t;
}
int main()
{
	int const a = 20;
	cout<<fun(a)<<endl;//正确
	return 0;
}

2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
template<typename T>
void fun(T t)
{
	cout << t << endl;
}
int main()
{
	int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	fun(array);//转化为指针
	return 0;
}
10、模板重载
int Max(const int& left, const int & right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& left, const T& right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b), c);
};
int main()
{
Max(10, 20, 30);
Max<>(10, 20);
Max(10, 20);
Max(10, 20.12);
Max<int>(10.0, 20.0);
Max(10.0, 20.0);
return 0;
}
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【说明】
(1)一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
(2)对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,
那么将选择模板。
(3)显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调
用,而且所有的模板参数都应该根据实参演绎出来。
(4)模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

11、模板函数特化
模板特化:就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本,当以特化定义时的形参使用模板时,将调用特化版本
例如一个比较大小的函数:
template <typename T>
int compare(T t1, T t2)
{
	if (t1>t2)
	{
		return 1;
	}
	return -1;
}
int main()
{
	char*p1 = "abcd";
	char*p2 = "higk";
	cout<<compare(p1, p2)<<endl;
	system("pause");
	return 0;
}




当时一个指向字符串的指针时,将有可能得不到正确结果,因此,需要对这种类型的特化出一个版本
模板函数特化形式如下:
(1)关键字template后面接一对空的尖括号<>
(2)函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
(3)函数形参表
(4)函数体
上面可用如下代码,就可为该模板特化
nt compare< char*>( char*  t1,  char*  t2)
{
	return strcmp(t1, t2);
}

当指针用const修饰时,模板中const相当于const int a = 10和int const int 10,与平时我们遇到的const 不同。假如,上面函数为:
int main()
{
	const char*p1 = "abcd";
	const char*p2 = "higk";
	cout<<compare(p1, p2)<<endl;
	system("pause");
	return 0;
}

在特化是,应该:
int compare<const char*>(const char* const t1, const char* const t2)
{
	return strcmp(t1, t2);
}
const char * 与特化版本完全匹配,将调用特化版本,const t1,预防函数体对指针进行修改,提高安全性能。
注意:模板特化,假如少了模板形参表,只是定义了一个普通函数,该函数含有返回类型和与模板实例化相匹配的形参表。在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
12、模板类
模板类和模板函数大致相同,类模板也是模板,因此,也必须以关键字template开头,后接模板形参表。
假如要用模板实现顺序表,如下例;
template<typename T>
class SeqList
{
public:

private:
	T* date;
	int capacity;
	int size;
};
在实现时,将对应的数据类型或值作为占位符,在使用时在提供哪些类型或值。
与调用函数模板相比,使用类模板必须显式的指定形参。。。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值