从放弃到重启 C++ 泛型编程篇(3)—函数模板

115 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

函数重载

从函数重载说起,然后引出我们今天主角函数模板。

swap(i,j)

用于将 i 和 j 进行交互的一个函数,swap 函数可以适合很多类型,也是我们在介绍函数重载时通常会提及到例子。

swap(int &a, int &b)
swap(string &a, string &b)

每调用函数时,编译器会根据调用函数参数的类型来选择对应的函数。

int i,j;
int s,t;
swap(i,j)

在调用 swap 使用整型参数 i,j 就会调用 swap(int&, int&) ,但是当 swap(s,j)没有根据参数匹配的函数就会抛出错误。 接下来看 swap 函数的实现

void swap(int &a,int &b)
{
    int temp{a};
    a = b;
    b = temp;
}
void swap(string &a,string &b)
{
    string temp{a};
    a = b;
    b = temp;
}
  • 函数重载可以让用户通过一个函数名可以使用多个函数
  • 这些函数执行的相同的业务逻辑
  • 重载可以产生更易于使用的库,也就是易于使用

那么函数重载是否也存在问题,当然了,接下来列出函数重载的问题

  • 然而,重载不可避免为每个几乎相同的函数编写代码,其实这可能存在潜在问题,因为一旦有更改就要进行多处修改。

函数模板

还我们再来看函数模板,函数模板是一个算法的泛化,并不是一个真正的函数。函数模板并非函数。 接下来我们可以定义函数模板来实现上面函数重载功能。其实函数模板可看做制作函数的配方。

using namespace std;
template<typename T>
void swap(T &a, T &b)
  • 这里声明了一个函数模板,在函数模板通过<> 在其中指定了类型变量,随后在函数中可以使用 T 来作为参数类型使用,也就是给编译器提供了调用函数时,如何生成函数的所需要信息。
template<typename T>
void swap2(T &a, T &b)
{
    T temp{a};
    a = b;
    b = temp;
}

int main()
{
    int a = 2;
    int b = 3;
    
    cout << "a:" << a << "b: " << b << endl;
    swap2(a,b);
    cout << "a:" << a << "b: " << b << endl;
    
    cout<<"Hello World";

    return 0;
}

STL 已经提供了 swap 方法,为了便于区别这里使用了 swap2 作为名称

模板函数参数列表

  • 模板参数列表template<typename T>
  • 函数参数列表void swap2(T &a, T &b)

这里 T 表示参数类型占位符,当在调用 swap 时通过为 T 指定一个类型来确定调用函数时参数的类型。根据类型参数确定参数类型是发生编译期间,并不是发生在运行时。

函数参数是发生编译时还是运行时,这个要具体情况具体分析,根其上下文有关,有关这些暂时还是不算了解,随后搜集资料再来进一步解释吧。

模板参数的作用域

template<typename T>//作用域开始
void swap2(T &a, T &b)
{
    T temp{a};
    a = b;
    b = a;
}//作用域结束

模板实例化

  • 函数模板提供一套图纸来用于生成函数
  • 从模板中生成一个函数定义的行为被称为模板实例化 一个从函数模板生成的函数定义是一个实例化的函数

函数模板调用

在调用时,程序可以使用实例化名来作为函数名来调用实例化的函数,如下

swap2<int>(a,b);

也可以省略<int> 让编译器根据传入参数来推断其类型,

void swap2<int>(int& a,int& b)
void swap2<string>(string& a,string& b)

注意在实例化函数多了<string><int> 这样做是便于区分这两个函数,是两个不同的函数

char c_a = 'a';
char c_b = 'b';
char const* a_ptr = &c_a;
char const* b_ptr = &c_b;

cout << "char a:" << *a_ptr << " char b: " << *b_ptr << endl;

swap2<char const*>(a_ptr,b_ptr);

cout << "char a:" << *a_ptr << " char b: " << *b_ptr << endl;
  • 对于上面相同类型调用模板只会实例化一次,例如

支持多个参数

template<typename U,typename V>
U find(U first, U second, V const &t)

typename 和 class

在模板定义时,typenameclass 作为类型参数的类型,并没有什么区别,可以通用。但是这并不表明在其他位置他们之间是通用的,在 swap2 我们选择 typename 来定义是因为这里 swap 接收的参数有 classnon-class 的类型