0


C++模板初阶

模板初阶

认识函数模板

我们已经知道C++里面支持函数重载,我们现在利用函数重载来写一个交换函数

void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}

void Swap(double& left, double& right)
{
    double temp = left;
    left = right;
    right = temp;
}

int main()
{
    //我们现在想对整型使用交换函数
    int a = 1, b = 2;
    Swap(a, b);
    
    //这时候我们又想对浮点型使用交换函数
    double c = 1.1, d = 2.2;
    Swap(c, d);
    return 0;
}

函数重载使得函数可以同名,对于不同的数据类型感觉像在使用同一个函数,用起来感觉还不错,但是我们还要实现针对不同类型的交换函数,终归是有不小代价的。就针对这种情况而言有没有一种办法能写一种通用的函数,能够让它“自动识别”类型呢?

接下来引入模板

C++中也存在这样一种模子,只要为其填充材料(模板代码),它就能够生成适应具体类型(char、int、double....)的代码。这是一种泛型编程理念,何为泛型编程?——编写与类型无关的通用代码,是代码复用的一种手段,而模板正是泛型编程的基础。

//接下来介绍函数模板:
//格式:template<typename/class T>
template<typename T>
void Swap(T& left, T& right)
{
    T temp = left;
    left = right;
    right = temp;
}

int main()
{
    int a = 1, b = 2;
    Swap(a, b);

    double c = 1.1, d = 2.2;
    Swap(c, d);
    return 0;
}

值得注意的是:这里调用的并不是模板函数,而是模板函数推演生成出来的具体类型函数,从反汇编的角度:调用的两个函数地址不同,所以证明调用的不是同一个函数,而是由编译器根据实际调用,利用模板推演生成的不同函数。

换句话说:函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器


隐式实例化

***类型的推演就是函数模板实例化的过程:在实际调用中,编译器将模板中的T替换成对应的类型,从而生成对应类型的函数,这就叫做函数模板的隐式实例化(后面称为推演实例化)。 ***

//还有一个简单的问题:
int main()
{
    int a = 1, b = 2;
    Swap(a, b);
    
    int x = 1, y = 2;
    Swap(x, y);
    
    //这里调用的Swap是同一个函数吗?
    /*解答:由于第一次根据具体类型调用的时候推演出是int类型,编译器根据该类型生成
    对应函数,函数在编译过程中生成指令,之后若是再使用int类型Swap函数,再去调用该
    指令即可,所以在这里是同一个函数*/
}

显示实例化

//实际当中还可能存在如下调用
int main()
{
    int a1 = 1, a2 = 2;
    double d1 = 1.1, d2 = 2.2;
    //我们将int类型和double类型进行交换:
    Swap(a1, d1);
    return 0;
}

//这是不可行的,报错如下图所示:

报错原因通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错

可能会有这样的提问:不能在传参的过程当中发生隐式类型转换吗,将类型转换为一致吗?

解答:这里有先后顺序的概念,类型的转换在传参或者赋值的时候才会发生,而这里的报错其实并没有走到传参赋值这一步,而是在推演实例化的时候(即在语法层面)就发生了错误。一般地,在模板中,编译器不会进行类型转换操作,因为一旦转化出问题,就是编译器的责任

但是实际上,就算是能够走到传参这一步,编译器依然会报错:

//隐式类型转换会发生临时拷贝,即从int->double要发生临时拷贝,生成的临时变量具有常性,应该用const接收
void Swap(const double& left, const double& right);
//但是用const修饰,就不允许被更改,就不能完成交换,所以传参这一步编译器仍然会报错
//再举一个例子:
template<class T>
T Add(const T& left, const T& right) //这里不改变参数,使用const修饰
{
    return left + right;
}

int main2()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.1, d2 = 20.2;
    //这里正常调用,编译器正常推演实例化出对应函数
    cout << Add(a1, a2) << endl;
    cout << Add(d1, d2) << endl;
    
    /*这里用int和double传参,在模板函数上已经使用const修饰过形参(即可以发生隐式类型转化),如果说编译器能够推演实例化成功,那么如下的这种语法也能够编译通过:*/
    cout << Add(a1, d2) << endl;
    /*那么有没有什么办法能让这种语法在推演实例化的过程中不报错?*/
    // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    //1.
    cout << Add((double)a1, d2) << endl;
    cout << Add(a1, (int)d2) << endl;

    //2.
    //前面的方式目的在于让其能够正常的推演实例化,接下来引入显示实例化
    //显示实例化:在函数名和参数列表之间加<类型>
    cout << Add<int>(a1, d2) << endl; 
    cout << Add<double>(a1, d2) << endl;
  
    return 0;
}

显示实例化和隐式实例化的区别:后者在于让编译器根据参数调用的情况来推演出具体的函数,前者直接让编译器不再推演,直接使用调用者给定的<类型>来实例化函数


函数模板的匹配原则

int Add(int left, int right)
{
    return left + right;
}

template<class T>
T Add(T& left, T& right)
{
    return left + right;
}

//具体的函数和模板函数能够同时存在吗? ——可以
//那么如何调用?

int main()
{
    int a = 1, b = 2;
    Add(a, b); //调用谁?
    //调用已经写好的,也就是第一个
    //我们也可以显示调用模板:
    Add<int>(a, b);
}

Add(a, b) 和 Add<int>(a, b)同时存在,可以反过来印证模板的函数名修饰规则和普通函数名修饰规则不同


认识类模板

以栈(Stack)为例,存在需要我们存储不同类型数据的场景(int, char, double...),这也是一种泛型的应用场景,所以对于Stack类,我们也需要一种模板

//有这样的可能性:在同一份代码中,我们需要两个不同类型的栈
int main
{
    Stack st1; //存double
    st1.push(1.1);
    
    Stack st2; //存int
    st2.push(1);
}
/*如果没有类模板,那么我们需要实现两种Stack类来分别存放int和double的数据类型,这样的成本是很高的
所以我们需要一种模板来"自动识别"类型*/
//类模板的实现:
template<class T>
class Stack
{
public:
    Stack(int capacity = 4)
    {
        _a = (T*)malloc(sizeof(T) * capacity);
        _capacity = capacity;
        _top = 0;
    }
    Stack(Stack& d)
    {
        _a = (T*)malloc(sizeof(T) * d._capacity);
        memcpy(_a, d._a, sizeof(T) * d._capacity);
        _top = d._top;
        _capacity = d._capacity;
    }
    void Push(const T& x);
    void Pop();
    T Top();  
    bool Empty();
    int Size();
    void Print();
    ~Stack()
    {
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
private:
    T* _a;
    int _top;
    int _capacity;
};

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

//Stack类名, Stack<int>才是类型
Stack<int> st1;
Stack<double> st2;

好了,这篇文章就先到这里了,还有关于模板一些高阶的内容之后的文章会来介绍,例如:非类型模板参数、类模板的特化、模板的分离编译问题等等......

标签: c++ 开发语言

本文转载自: https://blog.csdn.net/Lin_zhiyouyu/article/details/127445354
版权归原作者 林知有雨 所有, 如有侵权,请联系我们删除。

“C++模板初阶”的评论:

还没有评论