Skip to content

函数模板

假如我们定义一个交换两个变量值的函数Myswap(),要实现int、float、double等多种类型的变量进行交换,则要定义多个函数,这样显然很麻烦。

交换示例
cpp
#include <iostream>
using namespace std;

void swap_int(int &, int &);
void swap_float(float &, float &);

int main() {
    int a = 10;
    int b = 20;
    cout << "a=" << a << "\tb=" << b << endl;

    swap_int(a, b);
    cout << "a=" << a << "\tb=" << b << endl;

    float c = 12.2f;
    float d = 15.5f;
    cout << "\nc=" << c << "\td=" << d << endl;

    swap_float(c, d);
    cout << "c=" << c << "\td=" << d << endl;
    
    return 0;
}

void swap_int(int &lhs, int &rhs) {
    int temp = rhs;
    lhs = rhs;
    rhs = temp;
}

void swap_float(float &lhs, float &rhs) {
    float temp = lhs;
    lhs = rhs;
    rhs = temp;
}

程序输出:

bash
a=10	b=20
a=20	b=10

c=12.2	d=15.5
c=15.5	d=12.2

问题:如果我要交换double类型数据,那么还需要些一个double类型数据交换的函数。繁琐!写的函数越多,当交换逻辑发生变化的时候,所有的函数都需要修改,无形当中增加了代码的维护难度。

如果能把类型作为参数传递进来就好了,传递int就是int类型交换,传递char就是char类型交换。我们有一种技术,可以实现类型的参数化——函数模板,此时我们可以通过函数模板来解决。函数模板并不是一个可以直接使用的函数,它是可以产生多个函数的模板。

使用模板的目的就是要让这些程序的实现与类型无关,比如定义一个函数模板swap(),它既可以实现int类型数据相交换,又可以实现double类型数据相交换。

代码示例
cpp
#include <iostream>
using namespace std;

// class 和 typename都是一样的,用哪个都可以,这里使用class
template<class T>
void MySwap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main(int argc, char *argv[]) {
    int a = 10;
    int b = 20;
    cout << "a:" << a << " b:" << b << endl;
    //1. 这里有个需要注意点,函数模板可以自动推导参数的类型
    MySwap(a, b);
    cout << "a:" << a << " b:" << b << endl;

    char c1 = 'a';
    char c2 = 'b';
    cout << "c1:" << c1 << " c2:" << c2 << endl;
    //2. 函数模板可以自动类型推导,那么也可以显式指定类型
    MySwap<char>(c1, c2);
    cout << "c1:" << c1 << " c2:" << c2 << endl;

    return 0;
}

程序输出:

shell
a:10 b:20
a:20 b:10
c1:a c2:b
c1:b c2:a

用模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。

  • 使用自动类型推导,必须推导出一致的数据类型才能用
  • 模板不能单独使用,必须指定出T才可以使用

模板函数

函数模板并不是一个可以直接使用的函数,它是可以产生多个函数的模板。在调用函数模板时,编译器会根据实际的参数类型来推断模板参数的类型生成相应的函数,这个过程称为。被实例化出来的这个函数称为

cpp
#include <iostream>

// 函数模板
template <typename T>
bool compare(T a, T b) {
    return a > b;
}

int main() {
    // 指定类型调用函数模板
    compare<int>(1, 2);
    
    /*
     bool compare(int a, int b) {
        return a > b;
     }
     */

    return 0;
}

和普通函数区别

  • 函数模板不允许自动类型转化(使用函数模板不会发生自动类型转换,但是可以指定类型进行强制类型转换)
  • 普通函数能够自动进行类型转化
代码示例
cpp
#include <iostream>
using namespace std;

//函数模板
template<class T>
T MyPlus(T a, T b) {
    T ret = a + b;
    return ret;
}

//普通函数
int MyPlus(int a, char b) {
    int ret = a + b;
    return ret;
}

int main(int argc, char *argv[]) {
    int a = 10;
    char b = 'a';

    //调用函数模板,严格匹配类型
    MyPlus(a, a);
    MyPlus(b, b);

    //调用普通函数
    MyPlus(a, b);
    
    //调用普通函数  普通函数可以隐式类型转换
    MyPlus(b, a);

    return 0;
}

调用规则

  • 如果函数模板和普通函数都可以调用,C++编译器优先考虑普通函数
  • 如果想强制调用函数模板,可以使用空模板参数列表,语法为 myPrint<>(a, b);
  • 函数模板可以像普通函数那样可以被重载
  • 如果函数模板可以产生一个更好的匹配,那么选择模板
代码示例
cpp
#include <iostream>
using namespace std;

//函数模板
template<class T>
T MyPlus(T a, T b) {
    T ret = a + b;
    return ret;
}

//普通函数
int MyPlus(int a, int b) {
    int ret = a + b;
    return ret;
}

void test1() {
    int a = 10;
    int b = 20;
    char c = 'a';
    char d = 'b';
    //如果函数模板和普通函数都能匹配,C++编译器优先考虑普通函数
    cout << MyPlus(a, b) << endl;

    //如果我必须要调用函数模板,那么怎么办?
    cout << MyPlus<>(a, b) << endl;

    //此时普通函数也可以匹配,因为普通函数可以自动类型转换
    //但是此时函数模板能够有更好的匹配
    //如果函数模板可以产生一个更好的匹配,那么选择模板
    cout << MyPlus(c, d) << endl;
}

//函数模板重载
template<class T>
T MyPlus(T a, T b, T c) {
    T ret = a + b + c;
    return ret;
}

void test2() {
    int a = 10;
    int b = 20;
    int c = 30;
    cout << MyPlus(a, b, c) << endl;
    //如果函数模板和普通函数都能匹配,C++编译器优先考虑普通函数
}

int main(int argc, char *argv[]) {
    test1();
    test2();

    return 0;
}

程序输出:

shell
30
30

60

模板特化

提到特化就不得不提另一个与其完全相对的概念——泛化,泛化可以用于对象的类型不确定的情况,而特化只能用于对象的类型确定的情况。

  • 特化版本可以有任意多个,根据实际开发需求对相应的类型进行特化版本代码的编写即可。

函数模板全特化

先看一个函数模板没有进行特化的例子

点击查看代码
cpp
#include <iostream>
using namespace std;

template <typename T1, typename T2>
void func(T1& value1, T2& value2){
    cout << "泛化模板函数" << endl;
    cout << "value1 type:" << typeid(value1).name();
    cout << "   value2 type:" << typeid(value2).name() << endl;
}

int main(int argc, char *argv[]){
    int i = 10;
    const char *str = "Hello Cpp";
    func(i, str);

    return 0;
}

下面将函数模板进行特化操作

点击查看代码
cpp
#include <iostream>
using namespace std;

template <typename T1, typename T2>
void func(T1& value1, T2& value2){
    cout << "泛化模板函数" << endl;
    cout << "value1 type:" << typeid(value1).name();
    cout << "   value2 type:" << typeid(value2).name() << endl;
}

template<>
void func(int& value1, const char*& value2){
    cout << "int,const char*特化模板函数" << endl;
    cout << "value1 type:" << typeid(value1).name();
    cout << "   value2 type:" << typeid(value2).name() << endl;
}

int main(){
    int i = 10;
    int j = 20;
    const char *str = "Hello Cpp";
    func(i, str);
    func(i, j);

    return 0;
}

针对int,const char*进行特化之后,再进行传入,调用的就是特化版本的函数。

注意

  • 函数模板特化感觉上像是模板的实例化操作,并不是函数的重载
    cpp
    //全特化等价于实例化一个函数模板
    void func<int, const char*>(int& value1, const char*& value2){}
    //区别于函数重载
    void func(int& value1, const char*& value2){}
    两者之间虽然形式上相似,但是并不是同一种东西
  • 如果存在函数重载和函数模板特化相同的参数类型及个数,优先调用函数重载。
    • 编译器会按照最合适的进行选择使用,的选择顺序应该是:普通函数 → 特化版本函数模板 → 泛化版本函数模板。
    • 假如程序代码中存在一个数组类型的特化版本,一个指针类型的特化版本,此时传递一个字符串的参数,编译器肯定会选择数组类型的特化版本(因为在C语言中,字符串就是一个末尾带有\0的字符数组)。

函数模板偏特化

按照我们的理解函数模板偏特化应该和类模板偏特化一样,那么我进行下面的代码编写

cpp
#include <iostream>
using namespace std;

template <typename T1, typename T2>
void func(T1& value1, T2& value2){
    cout << "泛化模板函数" << endl;
    cout << "value1 type:" << typeid(value1).name();
    cout << "   value2 type:" << typeid(value2).name() << endl;
}

template<typename T2>
void func<int, T2>(int& value1, T2& value2) //error C2768: “func”: 非法使用显式模板参数
{
    cout << "int,const char*特化模板函数" << endl;
    cout << "value1 type:" << typeid(value1).name();
    cout << "   value2 type:" << typeid(value2).name() << endl;
}

int main(int argc, char *argv[]){

    return 0;
}

我们首先编写以上代码,并且main函数中没有进行任何调用,但是编译程序失败。由此我们可以发现

特化代码位置

模板的泛化版本应该放在.h文件的最前面,然后紧挨着后面就应该放模板的特化版本。这样是比较规矩的操作。

练习

使用函数模板实现对char和int类型数组进行排序。
cpp
#include <iostream>
using namespace std;

//模板打印函数
template<class T>
void PrintArray(T arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

//模板排序函数
template<class T>
void MySort(T arr[], int len) {

    for (int i = 0; i < len; i++) {
        for (int j = len - 1; j > i; j--) {
            if (arr[j] > arr[j - 1]) {
                T temp = arr[j - 1];
                arr[j - 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

void test() {
    //char数组
    char tempChar[] = "aojtifysn";
    int charLen = strlen(tempChar);

    //int数组
    int tempInt[] = { 7, 4, 2, 9, 8, 1 };
    int intLen = sizeof(tempInt) / sizeof(int);

    //排序前打印
    PrintArray(tempChar, charLen);
    PrintArray(tempInt, intLen);
    //排序
    MySort(tempChar, charLen);
    MySort(tempInt, intLen);
    //排序后打印
    PrintArray(tempChar, charLen);
    PrintArray(tempInt, intLen);
}

int main(int argc, char *argv[]) {
    test();

    return 0;
}

程序输出:

shell
a o j t i f y s n 
7 4 2 9 8 1 
y t s o n j i f a 
9 8 7 4 2 1