C++系列-泛型编程概念及函数模板


白云泉
唐代:白居易
天平山上白云泉,云自无心水自闲。
何必奔冲山下去,更添波浪向人间。


泛型编程的引入

想要比较两个整形数据的大小,写一个形参数是整形的函数。想要比较两个浮点型数据的大小,写一个形参是浮点型的函数。想要比较两个字符的ascii码的大小,写一个形参是char的函数。实际上几个函数的代码都差不多,那你觉得啰里八嗦写了一堆代码,烦不烦?
所以,如果可以把数据类型也作为一种可以灵活传递的参数,就太人性了。这种编程就是泛型编程了。

泛型,广泛的类型,指的仍是一种数据类型,它是一种通用类型,它可以代替其它多种类型,这个方法可以大规模的减少程序代码的编写量和重复性。泛型编程利用的主要技术是模板。

模板

C++中有两种模板机制:函数模板,类模板

函数模板

  • 函数的返回类型和形参的类型可以不具体指定,用代号T来表示,T称为泛型(generic type)。使用这种类型占位符的编程方式就叫泛型编程。
  • 在函数调用的时候再去确认T的实际类型。
  • 语法: template, 在有了这个声明后,其下紧跟的函数就是一个函数模板。template:声明创建模板,typename:表明其后的符号代表一种数据类型,也可以用class代替。后面可以有多个typename。T:通用数据类型,也可以用其它符号代替。
  • 函数模板的两种实现方式
    – 自动类型转换
    – 显式指定类型,推荐。
code:
# include <iostream>
using namespace std;

template <typename T>	// 声明一个模板,告诉编译器后面代码中紧跟着的T是一个通用数据模型	
T max_2(T a, T b)		// T是抽象的一个类型,在使用的时候再确定具体类型
{
	T result = a > b ? a : b;
	cout << "the max value: " << result << endl;
	return result;
}

template <typename T>	// 声明一个模板,告诉编译器后面代码中紧跟着的T是一个通用数据模型
void swap_2(T &a, T &b)	// T是抽象的一个类型,在使用的时候再确定具体类型
{
	T temp = a;
	a = b;
	b = temp;
}

int main()
{
	max_2(8, 5);
	max_2(8.6, 9.5);
	int a = 666;
	int	b = 888;
	cout << "原始的a: " << a << ", 原始的b: " << b << endl;
	swap_2(a, b);		// 模板实现的方式是自动类型转换
	cout << "交换后的a: " << a << ", 交换后的b: " << b << endl;
	
	float c = 666.666;
	float d = 888.888;
	cout << "原始的c: " << c << ", 原始的d: " << d << endl;
	swap_2<float>(c, d);		// 模板实现的方式是显式指定类型
	cout << "交换后的c: " << c << ", 交换后的d: " << d << endl;
	system("pause");
	return 0;
}

result:
the max value: 8
the max value: 9.5
原始的a: 666, 原始的b: 888
交换后的a: 666, 交换后的b: 888
原始的c: 666.666, 原始的d: 888.888
交换后的c: 888.888, 交换后的d: 666.666

函数模板注意事项

  • 如果使用自动类型推导,如果只使用类型T,则必须要能推导出一致的数据类型T。
  • T必须要有确定的类型,可以使用显示指示类型方式直接指定。
code:
#include <iostream>
using namespace std;

template <typename T>
void swap_1(T& a, T& b)	// T是抽象的一个类型,在使用的时候再确定具体类型
{
	T temp = a;
	a = b;
	b = temp;
}

template <typename T>
void print_info()
{
	cout << "print_info函数" << endl;
}

int main()
{
	int a = 11;	
	int b = 22;
	char c = 'g';
	//swap_1(a, c);			// 错误,在模板中类型T,函数调用是有int,有char,无法推导出T的类型
	cout << "原始的a: " << a << ", 原始的b: " << b << endl;
	swap_1(a, b);			// 模板实现的方式是自动类型转换
	cout << "交换后的a: " << a << ", 交换后的b: " << b << endl;
	// print_info();		// 错误,没有确定的T,无法使用自动类型推导的方式
	print_info<int>();		// 使用显示指定类型,T有了确定的类型
	system("pause");
	return 0;
}

result:
原始的a: 11, 原始的b: 22
交换后的a: 22, 交换后的b: 11
print_info函数

函数模板举例

code:
#include <iostream>
using namespace std;
template <typename T>
void my_swap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

template <typename T>
void my_sort(T array[], int num)	// T是抽象的一个类型,在使用的时候再确定具体类型
{
	for(int i_loop=0; i_loop<num; i_loop++)
	{
		int max_idx = i_loop;
		for (int j_loop = i_loop + 1; j_loop < num; j_loop++)
		{
			if (array[j_loop] > array[max_idx])
			{
				my_swap(array[j_loop], array[max_idx]);
			}
		}
	}
}

template <typename T>
void print_info(T array[], int num)
{
	for (int i_loop = 0; i_loop < num; i_loop++)
	{
		cout << array[i_loop] << " ";
	}
	cout << endl;
}

template <typename T>
void test01(T array, int num)		// 这里的T是个指针类型的
{
	cout << "排序前的数据: ";
	print_info(array, num);
	my_sort(array, num);
	cout << "排序后的数据: ";
	print_info(array, num);
}

int main()
{
	int array1[] = { 1,0,-2,4,8,10,6,9,44 };
	int num1 = sizeof(array1) / sizeof(array1[0]);
	char array2[] = "afgbdozs";
	int num2 = sizeof(array1) / sizeof(array1[0]);
	test01(array1, num1);			// 自动推导
	test01<char*>(array2, num2);	// 显式指示T类型
	system("pause");
	return 0;
}

result:
排序前的数据: 1 0 -2 4 8 10 6 9 44
排序后的数据: 44 10 9 8 6 4 1 0 -2
排序前的数据: a f g b d o z s
排序后的数据: z s o g f d b a

普通函数和函数模板的区别

隐式类型转换上的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)。
  • 函数模板调用时,如果使用自动类型推导,不发生隐式类型转换。
  • 函数模板调用时,如果使用显式指定类型的方式,可以发生隐式类型转换。
code:
#include <iostream>
using namespace std;
template <typename T>
void my_swap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void my_add01(int a, int b)
{	
	cout << "add result: " << a + b << endl;
}

template <typename T>
void my_add02(T a, T b)	// T是抽象的一个类型,在使用的时候再确定具体类型
{
	cout << "add result: " << a + b << endl;
}

int main()
{
	int a = 68;
	int b = 22;
	char c = 'a';
	cout << "普通函数调用" << endl;
	my_add01(a, b);
	my_add01(a, c);			// 普通函数调用会自动实现隐式类型转换
	cout << "函数模板自动类型推导调用" << endl;
	my_add02(a, b);			// 函数模板自动类型推导为int	
	// my_add02(a, c);		// 错误,无法实现隐式类型转换,不知道替换成是int还是char
	cout << "函数模板显式指定类型调用" << endl;
	my_add02<int>(a, b);
	my_add02<int>(a, c);	// 函数模板采用显式指定类型的方式,可以发生隐式类型转换
	system("pause");
	return 0;
}

result:
普通函数调用
add result: 90
add result: 165
函数模板自动类型推导调用
add result: 90
函数模板显式指定类型调用
add result: 90
add result: 165

调用上的规则和区别

既然有函数模板,就不要再提供普通函数了,不明确。实际开发中最好不要同时出现二者

  • 如果普通函数和函数模板都可以调用,优先普通函数。
  • 可以使用显式指定类型的方式强制调用函数模板,也可以在类型指定中指定为空,强制调用。
  • 函数模板也可以实现重载。
  • 如果函数模板产生更好的匹配,则优先调用函数模板。
code:
// 如果普通函数和函数模板都可以调用,优先普通函数。
#include <iostream>
using namespace std;

void print_info(int a)
{
	cout << "调用的是普通函数" << endl;
}

template<typename T>
void print_info(T a)
{
	cout << "T的类型" << typeid(T).name() << endl;
}

template<typename T1, typename T2>
void print_info(T1 a, T2 b)
{
	cout << "T1的类型" << typeid(T1).name() << endl;
	cout << "T2的类型" << typeid(T2).name() << endl;
}

int main()
{
	cout << "---------- 1 -----------" << endl;
	print_info(3);			// 默认是调用普通函数的
	cout << "---------- 2 -----------" << endl;
	print_info<>(3);		// 通过空模板参数列表实现函数模板的调用,也可以显式指定类型法
	cout << "---------- 3 -----------" << endl;
	print_info<int, char>(33, 'a');		// 函数模板也可以发生重载
	cout << "---------- 4 -----------" << endl;
	print_info<int>(66);				// 函数模板也可以发生重载
	cout << "---------- 5 -----------" << endl;
	// 如果使用函数模板,则会直接将T指定为char,而如果使用普通函数,则需要进行隐式类型转换
	// 函数模板产生更好的匹配,则优先调用函数模板
	system("pause");
	print_info('c');		
	return 0;
}

result:
---------- 1 -----------
调用的是普通函数
---------- 2 -----------
T的类型int
---------- 3 -----------
T1的类型int
T2的类型char
---------- 4 -----------
T的类型int
---------- 5 -----------

模板的局限性

  • 模板并非是万能的,有些特定的数据类型,需要做特殊的实现。
    先看如下代码:
    如果func1中传的参数是数组,则在调用阶段会出错。
    如果func2中传的参数是类的对象,按照什么比较大小?
template<typename T>
void func1(T a, T b)
{
	a = b;
}

template<typename T>
T func2(T a, T b)
{
	return a > b ? a : b;
}

具体化的模板解决自定义类型的通用化

  • 下面代码中,在my_compare函数中,如果传递的参数是Person类型的,是无法比较大小的,所以单独对Person类型的实现具体化的代码,则当传递的参数是Person类型时,会走具体化的代码。
  • template<> bool my_compare(Person a, Person b), 前面要有template<> ,函数定义中的参数类型直接指明为Person。
code:
#include <iostream>
using namespace std;

class Person
{
public:
	Person(string name, int age)
	{
		m_name = name;
		m_age = age;
	}
	string m_name;
	int m_age;
};

template<typename T>
bool my_compare(T a, T b)
{
	cout << "T的类型: " << typeid(T).name() << endl;
	//return (a == b) ? true : false;		// 如果T是Person,这里的比较会报错,无法用==比较,除非==重载
	return true;
}

// 利用具体化的Person的代码实现,具体化调用,当传递T是Person时,走这段代码
template<>		
bool my_compare(Person a, Person b)
{
	if ((a.m_name == b.m_name) && (a.m_age == b.m_age))
	{
		return true;
	}
	else
	{
		return false;
	}
}

void test01()
{
	Person p1("Jerry", 2);
	Person p2("Tom", 5);
	cout << (my_compare(p1, p2) ? "a==b" : "a!=b") << endl;
	my_compare(3, 5);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

result:
a!=b
T的类型: int

学习模板的意义

  • 一般不是为了写模板,而是在STL能够运用系统提供的模板。

注意:本文部分内容来自黑马

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值