函数模板
一、auto自动推导类型
-
auto声明的变量必须在定义时初始化。
-
初始化的右值可以是具体的数值,也可以是表达式和函数的返回值。
string func() { return("hhh"); } int main() { auto a = 3; cout << a << endl; auto b = 3 + 0.0f; cout << b << endl; auto c = "abc"; cout << c << endl; auto d = func(); cout << d << endl; return 0; } -
auto不能作为函数形参类型,因为函数形参的类型需要在编译时就已经确定。
void fun(auto a){} //参数不能为包含“auto”的类型 -
auto不能直接声明数组,数组的大小必须在编译时是已知的常量表达式。
int arr[] = {1, 2, 3, 4}; auto anotherArr = arr; // 这实际上是一个指向 int 的指针,而不是 int 数组 auto myArray[] = {5, 6, 7, 8}; // 错误 -
auto不能定义类的非静态成员变量。
auto 的设计初衷是为了在声明变量时自动推导其类型,而类的成员变量需要在类的定义中明确指出类型,以便编译器在编译时能够知道其确切的类型和大小。
class MyClass { public: auto myVar = 42; // 错误:auto不能用于类的成员变量 };
auto的真实用途:
- 代替冗余复杂的变量声明。
double func(int a, double b, const char *c, float d, short e, long f) { cout << a << " " << b << " " << c << " " << d << " " << e << " " << f << endl; } int main() { //声明函数指针 double(*p1)(int a, double b, const char *c, float d, short e, long f); p1 = func; p1(1, 2.2, "hhh", 0.0f, 3, 9); //引入auto auto p2 = func; p2(1, 2.2, "hhh", 0.0f, 3, 9); return 0; } - 在模版中,用于声明依赖模版参数的变量。
#include <iostream> #include <vector> // 模板函数,计算并返回容器中所有元素的和 template<typename T> T sum(const std::vector<T>& vec) { T total = 0; // 初始化总和为0,类型为T for (const auto& element : vec) { // 使用auto来迭代容器中的元素,但这里auto主要用于简化迭代器的使用 // 我们也可以声明一个auto类型的变量来存储中间结果 auto temp = element * 2; // 假设我们需要将每个元素乘以2后再加到总和中 // 注意:这里的temp变量类型实际上是T,因为element的类型是T,所以element * 2的类型也是T total += temp; } return total; } int main() { std::vector<int> intVec = {1, 2, 3, 4, 5}; std::vector<double> doubleVec = {1.1, 2.2, 3.3, 4.4, 5.5}; std::cout << "Sum of intVec elements doubled: " << sum(intVec) << std::endl; std::cout << "Sum of doubleVec elements doubled: " << sum(doubleVec) << std::endl; return 0; } - 函数模版依赖模版参数的返回值。
#include <iostream> // 模板函数,接受两个相同类型的参数并返回它们的和 template<typename T> auto add(T a, T b) { return a + b; } int main() { int x = 5, y = 10; double a = 2.5, b = 3.5; // 调用模板函数,编译器将根据传递的参数类型自动推导返回类型 std::cout << "Sum of integers: " << add(x, y) << std::endl; // 返回int类型 std::cout << "Sum of doubles: " << add(a, b) << std::endl; // 返回double类型 return 0; } - 用于lambda表达式中。
二、函数模版
C++98 添加关键字typename之前,C++使用class来创建模版。
考虑到向后兼容,应该使用typename,而不是class。
template<typename T>
void Swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
return 0;
}
在使用的时候,会根据参数的类型自动推导模版参数的类型。
在生成函数实例的时候,编译器会进行名称修饰,以区分不同类型的函数实例。
//类似于
void #DD%$Swap#$@@&(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 1;
int b = 2;
#DD%$Swap#$@&(a, b);
}
还可以手动指定模版参数的类型。
Swap<int>(a, b);
三、函数模版注意事项
-
可以为类的成员函数创建模版,但不能是虚函数和析构函数。
class MyClass { public: template <typename T> MyClass(T a) { cout << "a = " << a << endl; } template <typename T> void Show(T b) { cout << "b = " << b << endl; } //错误,虚函数不能使用函数模板 template <typename T> virtual void fun(T c) { cout << "c = " << c <<endl; } //析构函数没有参数,不需要模板 ~MyClass() { cout << "析构函数" << endl; } }; int main() { MyClass a(3); a.Show("hhh"); a.Show(3 + 3.1); //a.fun(3.1415); error return 0; } -
使用函数模版时,必须明确数据类型,确保实参与函数模版匹配上。
//错误,参数类型不一致 template <typename T> void Swap(T& a, T&b) { T temp = a; a = b; b = temp; } int main() { int a = 10; float b = 3.14f; Swap(a, b); }特殊情况:
template <typename T> void Swap() { cout << "调用Swap()函数 " << endl; } int main() { Swap<int>(); //此处<>什么类型都可以 } -
使用函数模版时,推导的数据类型必须适应函数模版中的代码。
这个例子中a + b是类类型的相加,没有重载+运算符,所以会报错。
template <typename T> class MyClass { public: MyClass(T a) { cout << "a = " << endl;} }; template <typename T> T add(T a, T b) { return a + b; } int main() { MyClass a(1); MyClass b(20); add(a, b); return 0; } -
使用函数模版时,如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模版的数据类型,可以发生隐式类型转换。
template <typename T> T Add(T a, T b) { return a + b; } int main() { int a = 10; char b = 20; //显式指定函数模板的类型,编译器可以对实参进行隐式的类型转换。 int c = Add<int>(a, b); //int c = Add(a, b); //报错,类型不一致。 cout << c << endl; return 0; } -
函数模板支持多个通用数据的类型。
template <typename T1, typename T2> void Show(T1 num, T2 name) { cout << "号码是:" << num << " 名字是:" << name << endl; } int main() { Show(10,"hhh"); return 0; }
四、函数模板的具体化
可以提供一个具体化的函数定义,当编译器找到函数调用匹配的具体化定义时,将使用该定义,不再寻找模板。
template<> void 函数模板名 <数据类型> (参数列表)
template<> void 函数模板名 (参数列表)
{
//函数体
}
下面这个例子,如果仅需要交换类中的排名rank,该如何实现,这里就要引入函数模板的具体化。
template <typename T>
void Swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
class MyClass
{
public:
int _rank;
string name;
};
引入具体化函数:
//两种方式相同
//template<> void Swap<MyClass>(MyClass &m1, MyClass &m2)
template<> void Swap(MyClass &m1, MyClass &m2)
{
int temp = m1._rank;
m1._rank = m2._rank;
m2._rank = temp;
cout << "调用了Swap(MyClass &m1, MyClass &m2)" << endl;
}
int main()
{
MyClass m1;
m1.name = "hh";
m1._rank = 10;
MyClass m2;
m2.name = "jj";
m2._rank = 3;
Swap(m1, m2);
//当编译器推导实参的数据类型,如果与具体化函数能匹配上,
//会优先使用具体化函数
cout << "hh :" << m1._rank << " jj :" << m2._rank << endl;
}
对于给定的函数名,可以有普通函数、模板函数和具体化的模板函数,以及它们重载的版本。
编译器调用规则:
- 普通函数 > 具体化的模板函数 > 模板函数
void Swap(int a, int b)
{
cout << "调用了普通函数" << endl;
}
template <typename T>
void Swap(T a, T b)
{
cout << "调用了函数模板" << endl;
}
template<>
void Swap(int a, int b)
{
cout << "调用了具体化的函数模板" << endl;
}
int main()
{
Swap(1,3); //调用普通函数
return 0;
}
template <typename T>
void Swap(T a, T b)
{
cout << "调用了函数模板" << endl;
}
template<>
void Swap(int a, int b)
{
cout << "调用了具体化的函数模板" << endl;
}
int main()
{
Swap(1,3); //调用具体化的函数模板
return 0;
}
- 如果希望使用函数模板,可以用空模板参数强制使用函数模板
template <typename T>
void Swap(T a, T b)
{
cout << "调用了函数模板" << endl;
}
template<>
void Swap(int a, int b)
{
cout << "调用了具体化的函数模板" << endl;
}
int main()
{
Swap<>(1,3); //空模板参数,调用具体化的函数模板
//如果注释掉具体化的函数模板,调用函数模板
return 0;
}
- 虽然普通函数 > 函数模板,但是如果函数模板能产生更好的匹配,将优先于 非模板函数。
void Swap(int a, int b)
{
cout << "调用了普通函数" << endl;
}
template <typename T>
void Swap(T a, T b)
{
cout << "调用了函数模板" << endl;
}
template<>
void Swap(int a, int b)
{
cout << "调用了具体化的函数模板" << endl;
}
int main()
{
Swap('a','b'); //此处是字符类型,字符类型可以隐式转换为int类型
//此处字符类型可以和函数模板完全匹配,不需要进行隐式转换。
//所以,调用函数模板
return 0;
}
五、函数模版分文件编写
函数模版只是函数的描述,没有实体,声明和定义函数模版的代码放在头文件中。
函数模版的具体化和普通函数,声明放在头文件,定义放在源文件。
public.h
#include <iostream>
using namespace std;
void Swap(int a, int b); //普通函数在.h中声明
template <typename T> //函数模版在.h中声明并定义
void Swap(T a, T b)
{
cout << " 使用了函数模版" << endl;
}
template <> //函数模版的具体化在.h中声明
void Swap(int a, int b);
public.cpp
#include "public.h"
void Swap(int a, int b) //普通函数在.cpp文件中定义
{
cout << "调用了普通函数" << endl;
}
template <>
void Swap(int a, int b) //具体化的函数模版在.cpp中定义
{
cout << "调用了具体化的函数模版" << endl;
}
main.cpp
#include "public.h"
int main()
{
Swap(1,2); //调用普通函数
Swap('a', 'b'); //调用函数模版
Swap<>(1, 2); //将使用函数模版的具体化版本
return 0;
}
六、函数模版高级技巧
1、引出问题
我们定义一个函数模版,使得T1和T2相加,那么他们相加的类型是什么?
我们可以使用auto解决这个问题。
template <typename T1, typename T2>
void func(T1 x, T2 y)
{
auto temp = x + y;
cout << " temp = " << temp << endl;
//return temp;
}
那如果需要他返回一个值呢,使用auto也同样可以。
auto func(T1 x, T2 y);
我们也可以用decltype来解决这个问题。
2、decltype关键字
在C++11中,用于查询表达式的数据类型。
语法: decltype (表达式) var;
decltype分析表达式并得到它的类型,不会计算执行表达式。如果表达式是函数调用的话,不用担心使用decltype时会执行函数。
总结:decltype要么是表达式的类型,要么是表达式的引用类型。
decltype推导规则:
-
如果
表达式是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const 等限定符。 -
如果
表达式是一个函数调用,则var的类型与函数的返回值类型相同(不能返回void ,但是可以返回void *) -
如果
表达式是一个左值(能取地址,要排除第一种情况),或用括号括起来的标识符,则var的类型是该表达式的类型的引用。 -
如果上面的条件都不满足,则var的类型与表达式的类型相同。
-
如果需要多次使用decltype,可以结合typedef 和 using
-
表达式是没有括号的标识符
int main()
{
short a = 5;
decltype(a) da;
//这里a是原本定义时的括号,不算,此处应遵循1,da的类型应该和a相同
//都是short类型。
short *b;
decltype(b) db; //db是 short*类型
const short *c;
decltype(c) dc; //dc是const short*类型
short &d = a;
decltype(d) dd = a; //dd是short &类型
return 0;
}
- 表达式是函数调用
int fun()
{
cout << "调用了fun函数" << endl;
return 1;
}
int main()
{
decltype(fun()) a;
//decltype(fun) a; //此时的a是一个函数类型
//decltype(fun) *a; //此时的a是一个函数指针
//decltype(fun) *a = fun;
//a(); //此时就调用了fun函数
//此处注意区分函数调用和函数名
//如果是函数名,得到的是函数类型,而不是函数返回值的类型。
//如果是函数调用,不会指向这个函数,得到的是函数返回值的类型。
cout << a << endl;
return 0;
}
- 表达式是一个左值,要与情况一区分
short a = 5;
decltype(++a) da = a;
//情况三:表达式是左值,da是short&类型
decltype(a) da;
//情况一:表达式是没有括号的标识符,应该与a的类型相同,da是short类型
decltype((a)) da = a;
//情况三:用括号括起来的标识符,da应该是表达式的引用类型。
decltype(fun) da;
//情况一:表达式是函数调用,da是函数类型
decltype((fun)) da = fun;
//情况三:用括号括起来的标识符,da是函数的引用类型。(注意:引用都需要初始化)
return 0;
- 结合typedef 和 using
#include <iostream>
#include <array>
int main() {
// 定义一个简单的整数数组
std::array<int, 5> myArray = {1, 2, 3, 4, 5};
// 使用decltype获取myArray的类型,并使用using定义一个别名
using MyArrayType = decltype(myArray);
// 现在可以使用MyArrayType来声明另一个相同类型的数组
MyArrayType anotherArray = {6, 7, 8, 9, 10};
// 输出anotherArray的内容
for (int value : anotherArray) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
3、函数后置返回类型
int func(int x, double y);
等同于:
auto func(int x, double y) -> int;
将返回类型移到了函数声明后面。
//以上面的相加为例
template <typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x + y)
{
decltype(x + y) temp = x + y;
cout << " temp = " << temp << endl;
return temp;
}
int main()
{
func(1, 2.5);
return 0;
}
C++14中对函数返回值类型推导规则进行了优化,函数的返回值可以用auto,不必尾随返回类型。
//意思是可以不要函数后面的-> decltype(x + y)
template <typename T1, typename T2>
auto func(T1 x, T2 y) //-> decltype(x + y)
{
decltype(x + y) temp = x + y;
cout << " temp = " << temp << endl;
return temp;
}
七、非模版类型参数
一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字class或typename来指定非类型参数。非类型模版参数的模版实参必须是常量表达式,在编译时就可以确定其值。
template <typename T, int N>
class MyArray
{
public:
MyArray()
{
cout << "调用了MyArray的构造函数" << endl;
}
private:
T arr[N];
};
int main()
{
MyArray<int, 10> arr;
return 0;
}

浙公网安备 33010602011771号