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 普通函数与函数模板的区别
调用规则如下:
- 如果函数模板和普通函数都可以实现,但是会优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
场景一描述及代码展示:
//留下普通函数的声明,没有实现;此时也会优先调用普通函数,但程序在连接阶段时,无法正常运行,因为找不到普通函数实现
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);
}
效果:
笔记:
- 如果函数模板和普通函数都可以实现,优先调用普通函数。
- 如果普通函数有声明,没有实现,函数模板有声明时,也会优先找普通函数,但是程序挥发物正常运行,因为找不到普通函数的实现,如下图。
- 为了应对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);
}
- 函数模板的重载
- 如果有更好的匹配,优先调用函数模板。传入字符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 总结
综合之前有关函数模板,有如下总结:
- 函数模板利用关键字template;
- 使用函数模板有两种方式:自动类型推导、显示指定类型;
- 模板的目的是为了提高复用性,将类型参数化;
- 使用模板的自动类型推导,参数的数据类型必须一致,才可以使用;
- 使用模板的显示指定类型,必须要确定出T的数据类型,才可以使用;
- 对于没有使用T的函数模板,只能使用显示指定类型来调用函数模板;
- 使用自动类型推导时,不会发生隐式类型转换,用显示指定类型,可以发生隐式类型转换;
- 建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T;
- 普通函数调用时可以发生自动类型转换(隐式类型转换), 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;
- 既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性;
- 利用具体化的模板,可以解决自定义类型的通用化,具体化的模板会优先于常规模板;
- 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。