c++函数模板

一、 函数模板基本概念

C++函数模板是一种可以在代码中定义通用函数的方式,其可以用于定义可以接受任意类型的参数的函数。通过使用函数模板,程序员可以避免重复编写相似的代码,提高代码的可重用性和可维护性。

1.1 定义函数模板的语法

template <typename T>
T func_name(T arg1, T arg2, ...)
{
    // 函数体
}
  • template关键字表示接下来的代码是一个函数模板的定义
  • typename表示模板参数的类型
  • T是一种类型参数的通用名字
  • func_name即函数模板的名称。

在 C++98 添加关键字 typename 之前,C++使用关键字 class 来创建模板。
函数模板中的参数列表可以是任意数量的、任意类型的参数。在函数模板中,可以使用模板参数来表示参数的类型、函数返回值的类型等。

1.2 实现函数模板

函数模板的实现可以在代码文件中或头文件中完成。建议将函数模板的实现放在头文件中,以便其他代码可以直接使用。

template <typename T>
T max(T a, T b)
{
    return a > b ? a : b;
}

上述代码实现了一个可以接受任意类型的函数模板max,其返回两个参数中的最大值。

使用函数模板时,用指定的类型替换函数模板中的模板参数即可。例如,以下代码使用函数模板max来查找两个整数中的最大值:

int x = 10, y = 20;
int max_val = max(x, y);  // 使用函数模板max查找两个整数中的最大值

double a=10.0,b=20.0;
double max_val1 = max(a,b);

函数模板实例化可以让编译器自动推导,也可以在调用的代码中显式的指定。

int x = 10, y = 20;
int max_val<int> = max(x, y);  // 使用函数模板max查找两个整数中的最大值

double a=10.0,b=20.0;
double max_val1<double> = max(a,b);

二、函数模板注意事项

  1. 可以为类的成员函数创建模板,但不能是虚函数和析构函数

    class MyClass{
    public:
        template<typename T>
        MyClass(T a){//构造函数可以使用函数模板
            cout<<a<<endl;
        }
        template<typename T>
        void fun(T b){//普通函数可以使用函数模板
            cout<<b<<endl;
        }
    //    template<typename T>虚函数不可以用于函数模板:virtual' cannot be specified on member function templates
    //    virtual void fun1(T c){
    //        cout<<c<<endl;
    //    }
    
    	//析构函数不用函数模板,因为析构函数没有参数
    };
    int main() {
        MyClass m(3);//3
        m.fun(4);//4
    }
    
  2. 在使用函数模板时,必须明确指定数据类型,以确保实参与函数模板参数匹配

    //比如,我们定义了一个函数模板,用于计算两个数的和。函数模板的定义如下:
    template <typename T>
    T add(T a, T b)
    {
        return a + b;
    }
    //如果我们想要计算两个整数的和,可以这样调用函数:
    int a = 1, b = 2;
    int c = add<int>(a, b);
    //或者int c = add(a, b);
    //这里,我们明确指定了数据类型为 int,保证了实参与函数模板能匹配上。如果我们这样调用函数:
    int a = 1;
    double b = 2.0;
    double c = add<double>(a, b);
    //这里,我们指定了数据类型为 double,但是实参 a 的数据类型为 int,与函数模板的数据类型不匹配,会出现编译错误。
    
  3. 使用函数模板时,推导的数据类型必须适应函数模板中的代码

    template <typename T>
    T fun(T a,T b) {//函数中的代码,不适用MyClass类型
        return a+b;
    }
    
    class MyClass{};
    
    int main() {
        int a=1,b=2;
        int c=fun(a,b);
    
        double x=1.0,y=2.0;
        double z= fun(x,y);
    //    MyClass aa,bb;
    //    MyClass cc = fun(aa,bb);
    }
    
  4. 在使用自动类型推导时,函数模板不会发生隐式类型转换

    template <typename T>
    T Add(T a, T b) {
        return a + b;
    }
    
    int main() {
        int sum = Add(5, 3.2); // 错误:隐式类型转换不适用于自动类型推导
        double total = Add<double>(5, 3.2); // 正确:显式指定数据类型进行隐式类型转换
        return 0;
    }
    
  5. 函数模板可以支持多个通用数据类型的参数

    template <typename T1,typename T2>
    void fun(T1 a, T2 b) {
        cout<<a<<endl;
        cout<<b<<endl;
    }
    
    int main() {
        fun(3,"123");
    }
    
  6. 函数模板支持重载,其中可以包含非通用数据类型的参数

    void fun(int a){
        cout<<"void fun(int a)"<<endl;
    }
    
    template <typename T>
    void fun(T a) {
        cout<<"fun(T a)"<<endl;
    }
    
    template <typename T1,typename T2>
    void fun(T1 a,T2 b) {
        cout<<"void fun(T1 a,T2 b)"<<endl;
    }
    
    template <typename T1,typename T2>
    void fun(T1 a,T2 b,int c) {
        cout<<"void fun(T1 a,T2 b,int c)"<<endl;
    }
    
    int main() {
        fun(3);//void fun(int a)
        fun(1.2);//fun(T a)
        fun(1.2,3);//void fun(T1 a,T2 b)
        fun(1.2,3,3);//void fun(T1 a,T2 b,int c)
    }
    

三、函数模板的具体化

函数模板是一种通用的函数定义,可以用来创建适用于多种数据类型的函数。然而,有时候我们需要针对特定的数据类型提供更特定的实现,这就引入了函数模板的具体化(也叫特化)概念。具体化允许我们为特定的数据类型提供专门的函数实现,以优先于通用的函数模板或普通函数。
下面是具体化的语法示例:

template<> 
void 函数模板名<数据类型>(参数列表)
{
    // 函数体。
}

或者简化写法:

template<> 
void 函数模板名 (参数列表)
{
    // 函数体。
}

对于给定的函数名,可以有普通函数、函数模板和具体化的函数模板,以及它们的重载版本。

3.1编译器使用各种函数的规则

  1. 具体化优先于常规模板,普通函数优先于具体化和常规模板。

    • 普通函数优先于具体化和常规模板

      void fun(int a){
          cout<<"普通函数:void fun(int a)"<<endl;
      }
      
      template <typename T>
      void fun(T a) {
          cout<<"函数模板:fun(T a)"<<endl;
      }
      template<>
      void fun<int>(int a){
          cout<<"函数模板的具体化:void fun<int>(int a)"<<endl;
      }
      
      int main() {
          fun(3);//普通函数:void fun(int a)
      }
      
    • 具体化优先于常规模板

      template <typename T>
      void fun(T a) {
          cout<<"函数模板:fun(T a)"<<endl;
      }
      template<>
      void fun<int>(int a){
          cout<<"函数的具体化:void fun<int>(int a)"<<endl;
      }
      
      int main() {
          fun(3);//函数模板的具体化:void fun<int>(int a)
      }
      
  2. 如果不希望使用普通函数,希望使用函数模板,可以用空模板参数强制使用函数模板。

    void fun(int a) {
        cout<<"普通函数:void fun(int a)"<<endl;
    }
    template <typename T>
    void fun(T a) {
        cout<<"函数模板:fun(T a)"<<endl;
    }
    
    int main() {
        fun<>(3);//函数模板:fun(T a)
    }
    
    void fun(int a) {
        cout<<"普通函数:void fun(int a)"<<endl;
    }
    template <typename T>
    void fun(T a) {
        cout<<"函数模板:fun(T a)"<<endl;
    }
    template<>
    void fun<int>(int a){
        cout<<"函数的具体化:void fun<int>(int a)"<<endl;
    }
    
    int main() {
        fun<>(3);//函数的具体化:void fun<int>(int a)
    }
    
  3. 如果函数模板能产生更好的匹配,将优先于普通函数

    void fun(int a) {
        cout<<"普通函数:void fun(int a)"<<endl;
    }
    template <typename T>
    void fun(T a) {
        cout<<"函数模板:fun(T a)"<<endl;
    }
    
    template <>
    void fun(int a) {
        cout<<"函数模板:fun(T a)"<<endl;
    }
    
    int main() {
        fun('a');//函数模板:fun(T a)
    }
    

四、普通函数、函数模板和函数模板具体化详解

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

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)

    void fun(int a, int b) {
        cout<<a+b<<endl;
    }
    
    int main() {
        int a=10;
        double b=20.0;
        char c='A';
        fun(a,a);
        fun(a,b);//将double隐式转换成int
        fun(a,c);//将char隐式转换成int
    }
    
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

    template<typename T>
    void fun(T a,T b){
        cout<<a+b<<endl;
    }
    
    int main() {
        int a=10;
        double b=20.0;
        char c='A';
        fun(a,a);
        fun(a,b);//报错:使用自动类型推导,不会发生隐式类型转换
        fun(a,c);//报错:使用自动类型推导,不会发生隐式类型转换
    }
    
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

    template<typename T>
    void fun(T a,T b){
        cout<<a+b<<endl;
    }
    
    int main() {
        int a=10;
        double b=20.0;
        char c='A';
        fun(a,a);
        fun<int>(a,b);//如果用显示指定类型,可以发生隐式类型转换
        fun<int>(a,c);//如果用显示指定类型,可以发生隐式类型转换
    }
    

总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

4.2普通函数与函数模板的调用规则

  • 如果函数模板和普通函数都可以实现,优先调用普通函数

    void fun(int a,int b){
        cout<<"普通函数:void fun(int a,int b)"<<endl;
    }
    
    template<typename T>
    void fun(T a,T b){
        cout<<"函数模板:void fun(T a,T b)"<<endl;
    }
    
    int main() {
        fun(1,2);//普通函数:void fun(int a,int b)
    }
    
  • 可以通过空模板参数列表来强制调用函数模板

    void fun(int a,int b){
        cout<<"普通函数:void fun(int a,int b)"<<endl;
    }
    
    template<typename T>
    void fun(T a,T b){
        cout<<"函数模板:void fun(T a,T b)"<<endl;
    }
    
    int main() {
    	//可以通过空模板参数列表来强制调用函数模板
        fun<>(1,2);//函数模板:void fun(T a,T b)
    }
    
  • 函数模板也可以发生重载

    template<typename T>
    void fun(T a,T b){
        cout<<"函数模板:void fun(T a,T b)"<<endl;
    }
    template<typename T>
    void fun(T a,T b,T c){
        cout<<"函数模板:void fun(T a,T b,T c)"<<endl;
    }
    
    int main() {
        fun<>(1,2);//函数模板:void fun(T a,T b)
        fun<>(1,2,3);//函数模板:void fun(T a,T b,T c)
    }
    
  • 如果函数模板可以产生更好的匹配,优先调用函数模板(和第三节一样的)

总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

4.3 函数模板的局限性

模板的通用性并不是万能的
例如:

template<class T>
void f(T a, T b)
{ 
    a = b;
}

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了
再例如:

template<class T>
void f(T a, T b)
{ 
    if(a > b) { ... }
}

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

class Dog{
public:
    Dog(string name,int age):name_(name),age_(age){
    }
    string name_;
    int age_;
};

//普通函数模板
template<typename T>
bool compare(T &a,T &b){
    if(a==b)return true;
    return false;
}

//如果要比较两个对象是否相等,就不能只想上面直接判断传入的两个对象相等来判断了
//第一种解决方案:重载Dog类里面的==运算符,稍微有点麻烦
//第二种解决方案:使用函数模板的具体化
template<>
bool compare(Dog &a,Dog &b){
    if((a.age_==b.age_)&&(a.name_==b.name_))return true;
    return false;
}

int main(){
    int a=1,b=2;
    bool result1 = compare(a,b);//调用通用函数模板
    cout<<result1<<endl;//false

    Dog dog1("哈士奇",3);
    Dog dog2("哈士奇",3);
    bool result2= compare(dog1,dog2);//调用函数模板具体化
    cout<<result2<<endl;//true
}

五、函数模板分文件编写

5.1 实体是什么

在函数模板的上下文中,"实体"指的是根据函数模板的描述生成的实际函数代码,也就是特定类型或参数的具体函数。
举例来说,如果有一个函数模板用于交换两个值的函数:

template <typename T>
void swapValues(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

在这个例子中,swapValues 函数模板的实体是在实际调用时根据提供的类型(例如 int、double、string 等)生成的函数。所以当你在代码中调用 swapValues 函数模板时,编译器会根据提供的类型生成对应的实体函数
实体函数是编译器最终生成的、可以被机器执行的代码。在 C++ 中,函数模板的实体是根据模板参数具体化生成的。实体函数才是真正被程序调用和执行的部分。

5.2 函数模板份文件编写原则:

  • 函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中。
  • 函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中。
  1. 普通函数:
    • test.h
      #include<iostream>
      using namespace std;
      void fun(int a);//普通函数声明在头文件
      
    • test.cpp
      include"test.h"
      void fun(int a){//普通用函数定义在源文件
          cout<<a<<endl;
      }
      
    • main.cpp
      #include "test.h"
      int main(){
          fun(3);//3
      }
      
  2. 函数模板和函数模板具体化
    • test.h
      #include<iostream>
      using namespace std;
      
      template<typename T>//函数模板的创建写在头文件中
      void fun(T a){
          cout<<a<<endl;
      }
      
      template<>//函数模板的具体化的声明写在头文件中
      void fun(int a);
      
    • test.cpp
      #include "test.h"
      
      template<>//函数模板的具体化的定义写在头文件中
      void fun(int a){
          cout<<a<<endl;
      }
      
    • main.cpp
      #include "test.h"
      int main(){
          fun(3);//3
      }
      
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值