C++的探索路17泛型程序设计与模板之基本形式

Posted Guerrouj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++的探索路17泛型程序设计与模板之基本形式相关的知识,希望对你有一定的参考价值。

学习内容调整

按照书中的顺序应当是输入输出流以及文件操作两部分的内容,相对来说,这两部分对我目前用途不是太大,而泛型程序设计以及后续的STL部分内容有着更高的价值,所以先跳过I/O流以及文件操作,先进行模板方面的学习与总结,后续再对剩下的这些内容进行整理。

整体学习结束以后将进行一星期左右的C++习题课练习,下一步进行数据结构系列的了解与学习。

章节内容与分段

泛型程序设计

我们日常生活中接触最多的带“泛”的词语可能就是“广泛”,也就是说这是一种传播方面具备广度的行为。从字面意义上理解,泛型指的就是能广泛使用的数据类型。

泛型程序设计(generic programming)是一种算法在实现时不指定具体要操作的数据类型的程序设计方法。

这种程序设计思想运用最为深度的内容就是STL(标准模板库),该部分也会在下一部分进行介绍。

整体来说,泛型程序设计带来的好处与其所起的意义,丝毫不亚于面向对象的特性。因此需要好好对这一方面进行掌握。

模板

模板为泛型程序设计中的一个基本概念,C++中,模板分为函数模板和类模板两种。

熟练的C++程序员,在编写函数的时候都会考虑能否把函数编写为函数模板;在编写类的时候考虑将其编写为类模板,从而方便重用。


内容分段

书中的几个章节依据标题内容分为基本形式与细节两个部分,本篇文章对泛型程序设计与模板的基本形式内容进行学习与练习。该部分内容为函数模板以及类模板。


函数模板

函数模板分为四部分进行了解,依次涉及函数模板的作用、原理、例子以及调用细节。

函数模板的作用

我们知道C++是一种很注重数据类型的语言,编写任何函数时,都需要考虑到其参数类型的匹配。

虽然这样能够避免发生错误,但这也在一定程度上容易引起一些麻烦:比如我们要定义交换函数Swap对数字进行交换。

除了函数实现的主体外,还需考虑的事就是数据类型:是对double类型的数据进行交换还是int型进行交换?如果都有,很不幸,你要写两次交换函数:

#include<iostream>
using namespace std;
void Swap(int &a, int &b) 
	int tmp;
	tmp = a;
	a = b;
	b = tmp;
	cout << "int recall" << endl;

void Swap(double &a, double &b) 
	double tmp;
	tmp = a;
	a = b;
	b = tmp;
	cout << "double recall" << endl;

int main()

	int a = 3, b = 4;
	cout << "a= " << a << endl << "b= " << b << endl;
	Swap(a, b);
	cout << "a b after exchange" << endl;
	cout << "a= " << a << endl << "b= " << b << endl<<endl;


	double c = 3.0, d = 4.4;
	cout << "c= " << c << endl << "d= " << d << endl;
	Swap(c, d);
	cout << "c d after exchange" << endl;
	cout << "c= " << c << endl << "d= " << d << endl << endl;
    return 0;

这下你可能就会想:什么破玩意,这么搞人都会累死了;这时候你就需要模板了。

函数模板的原理

模板就是方便我们依样画葫芦,C++中分为函数模板以及类模板;这两货除了作用于不同的东西外,内容实际相同。

函数模板的作用为:由你提供一个整体框架(函数体),再由编译器去完善整个细节。

定义形式

template<class 类型参数1,class 类型参数2,....>

返回值类型 模板名(形参表)

  函数体

类型参数指的是数据类型,此外class关键字也可由typename替换。

实现

编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分原封不动的照抄。

template<class T>
void Swap(T &a, T &b) 
	T tmp;
	tmp = a;
	a = b;
	b = tmp;


int main()

	int a = 3, b = 4;
	cout << "a= " << a << endl << "b= " << b << endl;
	Swap(a, b);
	cout << "a b after exchange" << endl;
	cout << "a= " << a << endl << "b= " << b << endl << endl;


	double c = 3.0, d = 4.4;
	cout << "c= " << c << endl << "d= " << d << endl;
	Swap(c, d);
	cout << "c d after exchange" << endl;
	cout << "c= " << c << endl << "d= " << d << endl << endl;
	return 0;

这样,就可以避免写好几个Swap进行交换了。


编译器由模板自动生成函数的过程,称为模板的实例化,由实例化得到的函数,称为模板函数。


函数模板例题---求数组中最大元素的函数模板

设计一个分数类CFraction,再设计一个名为MaxElement的函数模板,能够求数组中最大的元素,并用该模板求一个CFraction数组中的最大元素

template<class T>
T MaxElement(T a[], int size) 
	T tmpMax = a[0];
	for (int i = 1; i < size; ++i)
		if (tmpMax < a[i])
			tmpMax = a[i];
	return tmpMax;

class CFraction 
	int numerator;
	int denominator;
public:
	CFraction(int n, int d) :numerator(n), denominator(d) ;
	bool operator<(const CFraction&f)const 
		if (denominator*f.denominator > 0)
			return numerator*f.denominator < denominator*f.numerator;
		else
			return numerator*f.denominator > denominator*f.numerator;
	
	bool operator==(const CFraction&f)const 
		return numerator*f.denominator == denominator*f.numerator;
	
	friend ostream&operator<<(ostream&o, const CFraction&f);
;
ostream&operator<<(ostream&o, const CFraction&f) 
	o << f.numerator << "/" << f.denominator;
	return o;

int main() 
	int a[5] =  1,5,3,2.4 ;
	CFraction f[4] =  CFraction(8,5),CFraction(-8,4),CFraction(3,2),CFraction(5,6) ;
	cout << MaxElement(a, 5) << endl;
	cout << MaxElement(f, 4) << endl;
	return 0;


这部分对为什么重载==运算符不是很清楚,先行跳过。

函数或函数模板调用语句的顺序

具体内容波澜不惊,扔一张图

程序

template<class T>
T Max(T a, T b) 
	cout << "Template Max 1" << endl;
	return 0;

template<class T,class T2>
T Max(T a, T2 b) 
	cout << "Template Max2" << endl;
	return 0;

double Max(double a, double b) 
	cout << "Function Max" << endl;
	return 0;

int main() 
	int i = 4, j = 5;
	Max(1.2, 3.4);
	Max(i, j);
	Max(1.2, 3);

程序一共定义三个Max函数,当然他们没有实现最大值返回的功能。

第一个Max函数为具备两个形参,且类型相同的函数模板。

第二个Max函数为具备两个不同类型的函数模板

第三个Max为我们常见的普通函数

在主函数中,首先定义了整型变量i,j。

第一个Max函数直接与普通函数的形参列表相同,依据首先调用普通函数原则,输出:Function Max

第二个Max为整形参数,依据模板匹配原则,输出Template Max 1

第三个Max一个参数相同,一个不同,则输出Template Max2

崩崩

知道你们喜欢看犯错,那就犯一个错吧,直接会崩溃的那种,是不是萌萌哒?

下面这个程序定义了两个string类型的变量,显然没有任何一个类型可以转换成功, 那么崩吧。

template<class T,class T2>
T Max(T a, T2 b) 
	cout << "Template Max2" << endl;
	return 0;

double Max(double a, double b) 
	cout << "Function Max" << endl;
	return 0;

int main() 
	int i = 4, j = 5;
	Max(1.2, 3.4);
	Max(i, j);
	Max(1.2, 3);
	string a="aa", b="bb";
	Max(a, b);
	return 0;

类模板

类模板的写法

template<类型参数表>
class 类模板名
 成员函数和成员变量

类模板的成员函数,在类模板定义外面编写时的语法如下:

template<类型参数表>
返回值类型 类模板名<类型参数表>::成员函数名(参数表)
   函数体

用类模板定义对象可以写做

类模板名<真实类型参数表>对象名(实际参数表);
如果类模板有无参构造函数,那么也可以只写:

类模板名<真实类型参数表>对象名;

Pair类模板例题

某项数据记录由两部分组成:关键字与值,其中关键字用来检索。比如,学生记录由学号和绩点注册成。程序如下:

#include<string>
template<class T1,class T2>
class Pair 
public:
	T1 key;
	T2 value;
	Pair(T1 k, T2 v) :key(k), value(v) ;
	bool operator<(const Pair<T1, T2>&p)const;
;
template<class T1,class T2>
bool Pair<T1, T2>::operator<(const Pair<T1, T2>&p)const 
	return key < p.key;

int main() 
	Pair<string, int>student("Tom", 19);
	cout << student.key << " " << student.value;
	return 0;

emmm,貌似有点复杂,逐条来看。

首先定义了一个类模板Pair,类模板内部包含成员变量key,value。构造函数Pair(T1 k,T2 v);以及重载了一个返回值为bool类型的<比较函数。

其内部需要输入一个同参数的对象,也就是Pair<T1,T2>,注意Pair<T1,T2>不是怪物而是p的类型。

在外部调用的时候也要同等对待。

主函数基本啥也没做,就起了个输出的作用。

函数模板作为类模板成员

类模板中的成员函数还可以是一个函数模板。成员函数模板只有在调用时,才会被实例化

template<class T>
class A 
public:
	template<class T2>
	void Func(T2 t) 
		cout << t;
	
;
int main() 
	A<int >a;
	a.Func('K');
	return 0;

总结

类模板与函数模板为泛型编程中最直接、最基本的体现,这两种技术均利用了代码的重用性,能够在一定程度上减少代码的使用量。

其基本形式都差不太多,比如函数模板的形式为:

template<class T1, class T2...>

T1 function(T2 a, T3 b...)

类模板的形式为

template<class T1, class T2,.....>

class CLASSName

public:

  T1 a;

  T2 b;

利用类型进行传参。


要记住,泛型编程是针对类型的编程,是为了减少写类型带来的麻烦现象。只要把它当做类型就好了










以上是关于C++的探索路17泛型程序设计与模板之基本形式的主要内容,如果未能解决你的问题,请参考以下文章

C++的探索路18泛型程序设计与模板之细节

C++的探索路19泛型程序设计与模板之练习题

C++的探索路19泛型程序设计与模板之练习题

C++的探索路20标准模板库STL之STL的基本概念与容器

C++的探索路20标准模板库STL之STL的基本概念与容器

C++的探索路11继承与派生之拓展篇--多形式派生以及派生类指针转换