C++ 中的模板函数简介

文章目录

  • 前言
  • 一、函数模板(模板函数)是什么,有什么用,怎么用?
    • 声明和定义:
    • 函数模板的调用方式
  • 二、模板函数与普通函数的异同
    • 1.关于隐式类型转换:
    • 2.关于重载和调用优先级
  • 三、关于可能的问题和局限性
    • 1.问题
    • 2.解决方式:
  • 总结


前言

这一章主要讲模板函数和泛型的定义,主要用法和作用,简单写了一下和普通函数的异同。
本文主要为学习心得笔记,如有纰漏,欢迎指正

一、函数模板(模板函数)是什么,有什么用,怎么用?

当你写了一个交换函数,交换传入参数的值,例如:

void mySwap(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
}

你会发现两个问题:

  • 这个函数只适用与该指定数据类型的运算。
  • 无论是重新实现函数,或者重载函数,或者重载运算符,都需要大量的重复代码,且不易维护。
    比如你要实现double类型的比较,就需要重新使用double类型来重新实现一遍这个函数,很不方便。
    函数模板的作用:
  • 将数据类型抽象化<泛型>,可以传入多种不同的数据类型,并实现逻辑运算,提高代码的复用性。

声明和定义:

//声明创建模板 
//函数返回值 函数名(参数列表) { 函数主体 }

template<typename T>
void func()
{ // 代码块}

//或:
template<class T>
void func()
{ // 代码块}
  • 上面的代码中,大写字母 T 指代了一个数据类型,这个数据类型需要手动来指定,这个数据也被称为泛型

函数模板的调用方式

  1. 自动类型推导,可以直接传入指定参数,编译器会自动识别
  2. 指定数据类型调用,编译器会根据你给出的类型引用(或强制类型转换)
	//第一种方式:
	函数名(参数列表);

	//第二种方式:
	函数名<指定数据类型>(参数列表);
  • 在下面重新给交换函数做模板函数实现,这个时候就可以传入其他类型的数据了:
template<typename T>
void mySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test1()
{
	long a = 10;
	long b = 20;
	//两种方式使用函数模板
	//第一种方式:
	mySwap(a, b);

	//第二种方式:
	//mySwap(a, b);

	cout << "a = " << a << endl
		<< "b = " << b << endl;
}

注意:

  1. 自动类型推导,必须导出一致的数据类型
    上面的例字中,如果使用自动类型推导方式传参,是无法进行隐式类型转换的;如果传入参数不一致,则需要在调用时强制类型转换,或者使用指定数据类型的方式进行调用。
  2. 模板必须要有确定(推导)出的数据类型,才可以使用
    当你写了一个模板函数,但函数体内不含有任何泛型时,调用也必须指定泛型,否则就会报错,如下:
template<typename T>
void print()
{
	cout << "打印函数" << endl;
}

void test2()
{
	print<int>();	//如果模板函数无法判断泛型类型,则必须写上类型
}

二、模板函数与普通函数的异同

1.关于隐式类型转换:

  • 普通函数调用时可以发生隐式类型转换;
  • 函数模板在调用时,只有通过指定数据类型的方式调用才能进行隐式类型转换
  • 使用自动类型推导的方式调用无法发生隐式类型转换
template<typename T>
void myPrint(T a, T b)	//不要传引用
{
	cout << a << endl;
	cout << b << endl;
}

void test1()
{
	int a = 10;
	char b = 'a';
	// 这样写是错误的,编译器无法确定转化的数据类型
	//mySwap(a, b);

	// 这种写法可以使两个变量发生隐式类型转换
	myPrint<int>(a, b);
}

注:

  • 如需使用隐式类型转换,则不能传入地址(引用),不论任何函数都是一样的。

2.关于重载和调用优先级

  1. 函数模板可以重载
  2. 如果函数模板和普通函数都可以调用,则优先调用普通函数
  3. 如果函数模板可以产生更好的匹配,则优先调用函数模板(优先调用不需要类型转化的)
  4. 可以通过空模板参数列表,强制调用函数模板
void myPrint(int a, int b)
{
	cout << "普通函数" << endl;
}

template<typename T>
void myPrint(T a, T b)
{
	cout << "模板函数" << endl;
}

template<typename T>
void myPrint(T a, T b, T c)
{
	cout << "重载模板函数" << endl;
}

void test3()
{
	int a = 1;
	int b = 1; 
	
	// 优先调用普通函数
	myPrint(a, b);			//	=> 普通函数

	//强制调用模板函数,写不写数据类型都可以
	myPrint<int>(a, b);		//	=> 模板函数
	myPrint<>(a, b);		//	=> 模板函数

	//重载
	myPrint(a, b, 10);		//	=> 重载模板函数

	//发生更好的匹配时,优先调用不需要类型转化的
	myPrint('a', 'b');		//	=> 模板函数
}

注:

  • 如果提供函数模板,则最好不要提供普通函数重载,否则容易出现二义性

三、关于可能的问题和局限性

1.问题

例:当你想实现两个数值的交换,但是传进来一个数组(各种不符合这个函数功能的数据);
这个时候用原有的方式无法对这种情况做处理,很容易出问题。

2.解决方式:

  • 使用具体化的方式做特殊实现,使用具体化的模板,解决自定义类型的通用化。(类似重载,但不是重载)

class Person
{
public:
	string name;
	int age;
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
};

//模板函数
template<typename T>
bool myCmp(T& a, T& b)
{
	return a == b;
}

//Person版本具体化实现
template<>
bool myCmp(Person& a, Person& b)
{
	return (a.name == b.name && a.age == b.age);
}

void test4()
{
	Person p1("gzy", 10);
	Person p2(p1);
	Person p3(p1);
	p3.name = "张利伟";
	cout << (myCmp(p1, p2) ? "相等" : "不相等") << endl;	// => 相等
	cout << (myCmp(p1, p3) ? "相等" : "不相等") << endl;	// => 不相等
}

总结

函数模板的学习一个目的是学习模板的使用编写,更大的作用是理解stl库的容器使用,为后面的学习做铺垫。

你可能感兴趣的:(C++,c++,开发语言)