C++ 模板 函数模板与普通函数的区别、调用规则以及模板的局限性

1 普通函数与函数模板的区别

普通函数与函数模板区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

场景描述: 普通函数和函数模板实现加法,测试两者区别

代码展示:

int myAdd1(int a, int b)
{
	return a + b;
}

template<typename T>
T myAdd2(T a, T b)
{
	return a + b;
}
void test1()
{
	int a = 10;
	int b = 20;
	char c = 'c';//a-97,c-99
	cout << "普通函数" << endl;
	cout << "a + b = " << myAdd1(a, b) << endl;
	cout << "a + c = " << myAdd1(a, c) << endl;//普通函数隐式转换,把c由char类型转成int类型

	cout << "函数模板-自动推导调用" << endl;
	cout << "a + b = ";
	cout << myAdd2(a, b) << endl;
	//cout << "a + c = " << myAdd2(a, c) << endl;//函数模板自动类型推导时,无法隐式转换,a是int类型,c是char类型

	cout << "函数模板-显示指定调用" << endl;
	cout << "a + b = ";
	cout << myAdd2<int>(a, b) << endl;
	cout << "a + c = ";
	cout << myAdd2<int>(a, c) << endl;
}

效果:
在这里插入图片描述

注意点:

  • 普通函数实现a+c时,由于myAdd1返回值是整型,再传入c时会把char类型的c隐式转为int类型,即字符c变成其ASCⅡ码99,再实现相加;
  • 在写函数模板myAdd2时,注意返回值类型是T;
  • 函数模板有两种调用方式,自动推导和显示指定;
  • 在使用自动推导方式调用函数模板时,是无法实现不同类型的数据相加的,如下图
    在这里插入图片描述
  • 要想在函数模板实现不同类型的数据相加时,只能使用显示指定方式调用,显示指定函数模板的返回值类型。

2 普通函数与函数模板的区别

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,但是会优先调用普通函数
  2. 可以通过空模板参数列表强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板

场景一描述及代码展示:

//留下普通函数的声明,没有实现;此时也会优先调用普通函数,但程序在连接阶段时,无法正常运行,因为找不到普通函数实现
void myprint(int a, int b);
//{
//	cout << "普通函数调用" << endl;
//}

template<class T>
void myprint(T a, T b)
{
	cout << "函数模板调用" << endl;
}

void test1()
{
	int a = 1;
	int b = 2;
	//1. 如果函数模板和普通函数都可以实现,优先调用普通函数
	//myprint(a, b);
	
	//2. 通过空模板参数列表来强制调用函数模板
	myprint<>(a, b);
}

效果:
在这里插入图片描述

笔记:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数。
  2. 如果普通函数有声明,没有实现,函数模板有声明时,也会优先找普通函数,但是程序挥发物正常运行,因为找不到普通函数的实现,如下图。
    在这里插入图片描述
  3. 为了应对2的情况,可以通过空模板参数列表来强制调用函数模板,用法:函数模板的函数名<>(参数列表)。即使,此时恢复普通函数的具体声明,也会优先调用函数模板了。
    在这里插入图片描述

场景二描述及代码:

//留下普通函数的声明,没有实现;此时也会优先调用普通函数,但程序在连接阶段时,无法正常运行,因为找不到普通函数实现
void myprint(int a, int b)
{
	cout << "普通函数调用" << endl;
}

template<class T>
void myprint(T a, T b)
{
	cout << "函数模板调用" << endl;
}

template<class T>
void myprint(T a, T b, T c)
{
	cout << "函数模板重载调用" << endl;
}

void test2()
{
	int a = 1;
	int b = 2;
	int c = 3;
	//3. 函数模板的重载
	恢复普通函数也不需要空模板参数列表,此时是函数模板的重载函数
	//myprint(a, b, c);

	//4. 函数模板可以产生更好的匹配,优先调用函数模板
	char d = 'd';//d-100
	myprint(a, d);
}
  1. 函数模板的重载
    在这里插入图片描述
  2. 如果有更好的匹配,优先调用函数模板。传入字符d时,会优先调用函数模板,此时参数是两个类型的数据,对于函数模板而言,不需要考虑参数的类型,直接传入;对普通函数而言,还要把d参数隐式转换数据类型。相比之下,会优先调用函数模板。
    在这里插入图片描述

3 模板的局限性

局限性: 模板的通用性并不是万能的

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

//对于内置的数据类型可以比较
template<class T>
bool mycompare(T& a, T& b)
{
	return a == b ? true : false;
}

//对于自定义的数据类型不能比较-解决办法
//1. 运算符== > < 重载,但是麻烦
//2. 函数模板重载,利用具体化Person版本实现,具体化优先调用
template<> bool mycompare(Person& p1, Person& p2)
{
	if (p1.mname == p2.mname && p1.mage == p2.mage)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void test1()
{
	int a = 10;
	int b = 20;
	bool result = mycompare(a, b);//对于内置的数据类型可以正常比较
	if (result == 1)
	{
		cout << "a = b" << endl;
	}
	else
	{
		cout << "a != b" << endl;
	}
}

void test2()
{
	Person p1("西施", 25);
	Person p2("貂蝉", 25);
	bool result = mycompare(p1, p2);//对于内置的数据类型可以比较
	if (result == 1)
	{
		cout << "p1 = p2" << endl;//对于自定义的数据类型不能比较
	}
	else
	{
		cout << "p1 != p2" << endl;
	}

对于内置的数据类型可以比较,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行。
有两个解决办法,一是重载运算符,比如==、<、>等,但是这样很麻烦;另外一个就是通过模板的重载,给特定的类型提供具体化的模板来实现比较,并且具体化会优先调用。

class Person
{
public:
	/*Person(string name, int age)
	{
		this->mname = name;
		this->mage = age;
	}*/
	Person(string name, int age):mname(name), mage(age) { }
	string mname;
	int mage;
};
//对于自定义的数据类型不能比较-解决办法
//1. 运算符== > < 重载,但是麻烦
//2. 函数模板重载,利用具体化Person版本实现,具体化优先调用
template<> bool mycompare(Person& p1, Person& p2)
{
	if (p1.mname == p2.mname && p1.mage == p2.mage)
	{
		return true;
	}
	else
	{
		return false;
	}
}

在这里插入图片描述
注意点:

  • 利用具体化的模板,可以解决自定义类型的通用化,会优先于常规模板
  • 具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板

4 总结

综合之前有关函数模板,有如下总结:

  1. 函数模板利用关键字template
  2. 使用函数模板有两种方式:自动类型推导、显示指定类型;
  3. 模板的目的是为了提高复用性,将类型参数化;
  4. 使用模板的自动类型推导,参数的数据类型必须一致,才可以使用;
  5. 使用模板的显示指定类型,必须要确定出T的数据类型,才可以使用;
  6. 对于没有使用T的函数模板,只能使用显示指定类型来调用函数模板;
  7. 使用自动类型推导时,不会发生隐式类型转换,用显示指定类型,可以发生隐式类型转换;
  8. 建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T;
  9. 普通函数调用时可以发生自动类型转换(隐式类型转换), 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;
  10. 既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性;
  11. 利用具体化的模板,可以解决自定义类型的通用化,具体化的模板会优先于常规模板;
  12. 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值