一、函数模板(提高复用性,类型参数化)
建立通用函数,其函数返回值类型和参数类型可以不具体指定,用虚拟类型代表; 模板声明或定义只能在全局或类范围内进行,不能在局部范围(如函数)内进行; 在定义函数模板后,当编译系统发现有对应的函数调用时将根据实参中的类型确认是否匹配函数模板中对应的类型形参,然后生成一个重载函数。 |
1、语法
template<typename T> template:声明创建模板,位置在函数模板紧跟上边位置; typename:一种数据类型,可以用class代替(类模板); T:通用数据类型,名称可以替换,通常为大写字母; |
函数模板分类: 自动类型推导(编译器推导); 显示指定类型(由程序员实现); |
交换函数为例: #include <iostream> #include <string> #include <typeinfo> using namespace std; // ----------------函数模板 template<typename T> void Swap(T &a,T &b) { T temp = a; a = b; b = temp; } // ----------------主函数 int main() { // 1.自动函数推导:T->int int a = 100; int b = 99; cout<<"自动函数推导:"<<endl; Swap(a,b); cout <<"a"<<a<<"b"<<b<<endl; // 2.自动函数推导:T->double double c = 100.11; double d = 99.99; Swap(c,d); cout <<"c"<<c<<"d"<<d<<endl; // 3.显示指定类型 cout<<"显示指定类型:"<<endl; Swap<int>(a,b); cout <<"a"<<a<<"b"<<b<<endl; return 0; } |
2、普通函数/函数模板区别
普通函数调用时可以发生自动类型转换(隐式类型转换); 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换; 如果利用显示指定类型的方式,可以发生隐式类型转换。 |
int a = 100; int b = 99; char c = 'a'; cout << Swap<int>(a,c) << endl; |
3、普通函数/函数模板调用规则
如果函数模板和普通函数都可以实现,优先调用普通函数; 如果函数模板可以有更好的匹配优先调用函数模板; 可以通过空模板参数列表来强制调用函数模板; 函数模板可以进行重载。 | ||
如果函数模板和普通函数都可以实现,优先调用普通函数: | 可以通过空模板参数列表来强制调用函数模板: | 函数模板可以进行重载: |
#include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------函数模板 template<typename T> T ADD(T a,T b) { cout<<"函数模板"<<endl; return a+b; } // -----------------普通函数 int ADD(int a,int b) { cout<<"普通函数"<<endl; return a+b; } int main() { int a = 100, b = 99; ADD(a,b); return 0; } | #include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------函数模板 template<typename T> T ADD(T a,T b) { cout<<"函数模板"<<endl; return a+b; } // -----------------普通函数 int ADD(int a,int b) { cout<<"普通函数"<<endl; return a+b; } int main() { int a = 100, b = 99; // 强制调用函数模板 ADD<>(a,b); return 0; } | #include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------函数模板 template<typename T> T ADD(T a,T b) { cout<<"函数模板"<<endl; return a+b; } // -----------------函数模板重载 template<typename T> T ADD(T a,T b,T c) { cout<<"函数模板重载"<<endl; return a+b+c; } int main() { int a = 100, b = 99; ADD(a,b,100); return 0; } |
如果函数模板可以有更好的匹配优先调用函数模板: | ||
#include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------函数模板 template<typename T> T ADD(T a,T b) { cout<<"函数模板"<<endl; return a+b; } // -----------------普通函数 int ADD(int a,int b) { cout<<"普通函数"<<endl; return a+b; } int main() { char a = 100,b = 99; ADD(a,b); return 0; } |
二、类模板(成员类型通用化)
1、语法
template<class T> |
类模板没有自动推导类型,只能使用显式指定类型: |
#include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------类模板 template <class Nametype,class Agetype> class Person { public: // 构造函数 Person(Nametype name,Agetype age) { this->N_name = name; this->Age = age; } // 成员变量 Nametype N_name; Agetype Age; }; // -----------------主函数 int main() { // 类模板只能使用显示指定类型 Person<string,int> a("张三",99); cout<<a.N_name<<a.Age<<endl; Person<string> a("张三",99); cout<<a.N_name<<a.Age<<endl; return 0; } |
#include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------类模板 template <class Nametype,class Agetype = int> class Person { public: // 构造函数 Person(Nametype name,Agetype age) { this->N_name = name; this->Age = age; } // 成员变量 Nametype N_name; Agetype Age; }; int main() { // 类模板只能使用显示指定类型 Person<string> a("张三",99); cout<<a.N_name<<a.Age<<endl; return 0; } |
2、类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机区别: 普通类中成员函数一开始就可以创建; 类模板中成员函数在调用时才创建; |
3、类模板对象做函数参数
// -----------------类模板 template <class Nametype,class Agetype = int> class Person { public: // 构造函数 Person(Nametype name,Agetype age) { this->N_name = name; this->Age = age; } // 成员变量 Nametype N_name; Agetype Age; }; |
-
指定传入类型
直接显示对象的数据类型: |
// -----------------指定传入的类型 - 直接显示对象的数据类型 void Test_1(Person<string> &p) { cout<<p.N_name<<p.Age<<endl; } int main() { Person<string> a("张三",99); Test_1(a); return 0; } |
-
参数模块化
将对象中的参数变为模板进行传递: |
// -----------------参数模块化 - 将对象中的参数变为模板进行传递 // 自动推导形式(函数模板+类模板) template <class T1,class T2> void Test_2(Person<T1,T2> &p) { cout<<p.N_name<<p.Age<<endl; } int main() { Person<string> a("张三",99); Test_2(a); return 0; } |
-
整个类模板
将这个对象类型模块化进行传递: |
// -----------------使用类模板 - 将对象类型模块化进行传递 // 自动推导形式(函数模板+类模板) template <class T> void Test_3(T &p) { cout<<p.N_name<<p.Age<<endl; } int main() { Person<string> a("张三",99); Test_3(a); return 0; } |
4、类模板与继承
当子类继承的父类为类模板,子类在声明时,要指定父类中T的类型(如果不指定[类型不确定],编译器无法给子类分配内存): |
#include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------基类模板 template<class T> class Person { T m; }; // -----------------派生类(继承)- 指定父类中T的类型 class Son:public Person<int> { }; int main() { Son a; return 0; } |
如果想灵活指定出父类中的T类型,将子类也变成类模板: |
#include <iostream> #include <string> #include <typeinfo> using namespace std; // -----------------基类模板 template<class T> class Person { T m; }; // -----------------派生类(继承)- 子类也变成类模板 template<class T1,class T2> class Son:public Person<T1> { T2 n; }; int main() { Son<char,char> a; return 0; } |
三、完美转发
在 C++ 中,(F&& f, Args&&... args) 是完美转发函数的常见参数声明形式,通常用于模板函数中,结合了右值引用和可变参数模板,用来转发任意类型的参数,std::forward 是 C++11 引入的一个标准库函数模板,用于实现完美转发(perfect forwarding)。 |
示例代码: |
#include <iostream> #include <utility> // for std::forward // 目标函数,处理一个任意类型的参数 template <typename F, typename... Args> void wrapper(F&& f, Args&&... args) { // 将参数完美转发给其他函数 some_function(std::forward<F>(f), std::forward<Args>(args)...); } // 处理函数,接收一个整数和一个字符串 void some_function(int&& x, const std::string& str) { std::cout << "Int: " << x << ", String: " << str << std::endl; } int main() { int a = 42; std::string s = "Hello"; // 使用完美转发,传递右值和左值 wrapper(100, s); // 右值int 和 左值string wrapper(a, "World"); // 左值int 和 字面量string return 0; } |
四、尾返回类型
在C++11之前,函数的返回类型通常是在函数名之前声明的,但这种方式在某些复杂场景下显得力不从心,尤其是在模板编程和类型推导中,传统的返回类型声明方式很容易导致代码变得冗长和难以理解,这就是尾返回类型(Trailing Return Type)auto func() -> ReturnType出现的背景。 |
// 传统的返回类型声明 int add(int a, int b) { return a + b; } // 使用尾返回类型 auto add(int a, int b) -> int { return a + b; } |
五、类型萃取
#include <type_traits> |
1、std::result_of
用于推导出某个可调用对象(函数、lambda 表达式、函数对象等)在给定参数类型下的返回值类型; std::result_of<T(Args...)>::type |
#include <type_traits> #include <functional> int add(int x, double y) { return x + static_cast<int>(y); } int main() { std::result_of<std::function<int(int, double)>(int, double)>::type result = 0; static_assert(std::is_same<decltype(result), int>::value, "result type should be int"); return 0; } |
六、函数封装器
std::function 是 C++11 标准中引入的一个模板类,它是一个通用的函数封装器,可以用来封装任何可以调用的目标,包括普通函数、函数指针、成员函数、仿函数以及 lambda 表达式等。 |
template <class R, class... Args> class function<R(Args...)>; R 是返回类型,Args... 是参数列表 |
1、封装任何可调用的对象
std::function 可以用来封装各种可调用的对象,包括函数指针、成员函数、仿函数以及 lambda 表达式等。 |
#include <iostream> #include <functional> void normalFunction(int x) { std::cout << "Normal function called with " << x << std::endl; } class Functor { public: void operator()(int x) { std::cout << "Functor called with " << x << std::endl; } }; int main() { std::function<void(int)> func1 = normalFunction; // 函数指针 std::function<void(int)> func2 = Functor(); // 仿函数 std::function<void(int)> func3 = [](int x) { // Lambda 表达式 std::cout << "Lambda called with " << x << std::endl; }; func1(1); // 调用函数指针 func2(2); // 调用仿函数 func3(3); // 调用 Lambda 表达式 return 0; } |
2、函数参数传递
std::function 可以作为函数参数传递,从而实现回调函数的功能。 |
#include <iostream> #include <functional> void callFunction(std::function<void(int)> func, int x) { func(x); } void normalFunction(int x) { std::cout << "Normal function called with " << x << std::endl; } int main() { callFunction(normalFunction, 42); // 将普通函数作为参数传递 return 0; } |
3、空对象
如果 std::function 未与任何可调用的对象绑定,那么它将表示一个空对象,调用它将导致未定义的行为。 |
#include <iostream> #include <functional> int main() { std::function<void(int)> func; // 这里调用 func 会导致未定义的行为 func(42); return 0; } |