C++高阶-STL-用模板实现通用算法

函数模板

函数模板,它以一个或者多个数据类型作为参数,因此它可以用来处理各种类型的数据。当编译器发现一个函数模板的调用后,它将根据函数模板的类型参数自动生成一个此种类型的重载函数,称该重载函数为模板函数。根据参数类型的不同,一个函数模板可以随时变成各种不同的重载函数,从而实现对各种数据类型的处理,达到一个函数模板应对万千数据类型的效果。

函数模板可以用来创建一个通用功能的函数,以支持各种不同的数据类型,简化重载函数的设计。函数模板的定义非常简单,其语法格式如下:

template <typename 标识符 1typename 标识符 2>
返回值类型 函数名(形参表)
{ 
	// 函数体…
}

在定义函数模板时,需要用template关键字表示这是一个函数模板,然后在“<>”中用 typename关键字来定义一个或者多个标识符,这就是函数模板中抽象的数据类型,它们被称为函数模板的类型参数。在函数模板(包括返回值类型和参数类型)中,可以使用这些类型参数当作数据类型来使用,而在模板函数被实际调用的时候,它们会被实际的数据类型所代替,从而成为某个特定类型的重载函数。在定义函数模板时,这些标识符只是起一个占位的作用而已。

// 获取两个数中的较大值
// T 就是函数模板的类型参数
// 为了与标准库中的 max()函数相区别,用 mymax 作函数名
// 为了防止数据被修改,使用 const 对参数和返回值进行修饰
template <typename T>
const T& mymax(const T& a, const T& b )
{
	return a > b ? a : b ;
}

int main()
{
	// 两个 int 类型数据
	int a = 4;
	int b = 5;
	cout<<a<<"和"<<b<<"之间较大的是"
	<<mymax<int>(a,b)<<endl;// 调用 int 版本的 mymax()处理 int 类型数据
	// 两个 string 类型数据
	string strA = "good";
	string strB = "afternoon";
	cout<<strA<<"和"<<strB<<"之间较大的是"
	// 调用 string 版本的 mymax()处理 string 类型数据
	<<mymax<string>(strA,strB)<<endl;
	return 0;
}

编译器在编译 mymax()函数调用时,会以函数名之后“<>”内的数据类型作为函数模板定义时模板类型参数的实际类型,然后编译器会以函数模板中的定义为样板,用实际的数据类型替换函数模板中的类型参数,从而形成针对特定类型的重载函数。例如,在编译“mymax<int>(a,b)”时,编译器会用实际的类型 int 替换掉函数模板中的类型参数 T,自动为 mymax()函数调用生成一个整型数的版本:

// 整型数版本的 mymax()模板函数
const int& mymax( const int& a, const int& b )
{
	return a > b ? a : b ;
}

当参数是 string 类型时,它会逐个比较字符串中字符的 ASCII值来决定两个 string 类型参数的大小,而这并不是我们所期望的结果。在这种情况下,就需要对函数模板进行特化,实现特定类型的模板函数。通过这种方式,可以使得函数模板及能够适应大多数的情况,同时也能满足个性化的特殊需求。例如:

// 利用模板特化,实现特定的 string 类型的模板函数
template <> // 类型参数留空
// 使用实际类型 string 代替类型参数 T
string mymax<string>( const string a, const string b )
{
	// 通过长度比较决定字符串大小
	return a.length() > b.length() ? a : b ;
}

类模板

跟函数模板相同,在 C++中也可以将类模板化,通过为类提供类型参数来实现对不同类型数据的统一处理。

template <typename 标识符>
class 类名
{
	// 类的定义
}

typename 所定义的标识符实际上就是类模板的类型参数,可以是一个,也可以是多个typename 关键字也可以使用 class 关键字代替。因为类模板所定义的只是一个模板,所以类型参数只是代表一种抽象的数据类型,我们可以把它当作数据类型在类的定义中使用,比如可以用它定义成员变量,也可以用作函数返回值或者参数的数据类型。

// 定义一个用于比较两个数据大小的类模板
template <typename T> // T 是类模板的类型参数
class compare
{
	public:
		// 构造函数,使用 T 做参数类型,实际上它相当于一个函数模板
		compare(T a, T b):m_a(a),m_b(b)
		{}
	// 比较类的接口函数
	public:
		// 返回两个数中的较小值,使用 T 作返回值类型
		T min()
		{
			return m_a > m_b ? m_b : m_a;
		}
		// 返回两个数中的较大值
		T max()
		{
			return m_a > m_b ? m_a : m_b;
		}
	// 类模板的成员变量,使用 T 作为其数据类型
	private:
		T m_a;
		T m_b;
};

// 要比较两个整型数的大小,
// 所以使用 int 作为类模板的实际类型参数
compare<int> cmpint(2,3); // 定义用于比较两个 int 数据的 compare 对象
// 使用 compare<T>的成员函数进行比较
cout<<cmpint.max()<<"大于"<<cmpint.min()<<endl;
// 要比较两个字符串的大小,
// 所以使用 string 作为类模板的实际类型参数
compare<string> cmpstr("good","afternoon");
out<<cmpstr.max()<<"大于"<<cmpstr.min()<<endl;

无论是函数模板还是类模板,它们的意义都是将算法和具体的数据类型相分离,在定义的时候使用类型参数代表数据类型,而在使用的时候用实际的数据类型取代类型参数,以此使得算法适用于不同的数据类型,实现算法的通用性。这使得程序员可以用一个模板通吃各种数据类型,再也不用为了处理多种数据类型而去创建同一个函数或者类的多个版本,一劳永逸地解决了算法适应不同数据类型的问题。 而 STL 也正是基于 C++的模板机制构建起来的,名字当中就含有一个 T(template),其通用性自然是不言自明。

补充-泛型编程

泛型编程(generic programming)就是一种大量应用模板来实现更好的代码重用性的编程方式。一般而言,我们编写的算法都是针对某个特定数据类型的,一个算法无法同时应用于多种数据类型,而泛型编程通过使用模板,可以使算法具有更好的代码重用性,一个算法可以适应于多种数据类型,从而避免重复劳动提高开发效率。

跟面向对象编程不同,泛型编程并不要求我们通过额外的间接层来调用函数,它可以编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。所谓泛型(genericity),是指具有在多种数据类型上皆可操作的含义。它允许程序员在编写代码时定义一些可变的数据类型参数,而在使用时指定这个类型参数就可以得到针对特定的数据类型的算法,以实现代码的大量复用。程序员在设计算法的时候,不再是针对特定的数据类型来设计算法,而是直接针对最核心的‚操作‛来设计算法。

泛型编程的一个典型应用就是 STL,它提供了许多与具体数据类型无关的容器,同一种容器就可以用于保存多种不同类型的数据。同时其中的算法通过迭代器间接地操作容器中的数据,从而也使得算法与具体的数据类型不相关。这样,整个 STL 都与具体的数据类型无关,而这也就正是‚泛型‛的意义。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值