【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;

}

利用类型进行传参。


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










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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值