C++模板语法和原理

为什么要有模板

模板(Template)指的是C++程序设计语言中采用类型作为参数的程序设计,支持通用程序设计。 C++的标准库的函数大多结合了模板的概念,如STL以及IO Stream。模板是C++支持参数化多态的工具,使用模板可以使编译器自动推导内部参数的类型,具有提高代码复用性提高开发效率的特点。 模板通常有两种形式:函数模板类模板 函数模板针对仅此参数类型不同的函数 如 swap 类模板针对仅数据成员和成员函数类型不同的类 如 stack

上面是模板的定义,那么为什么模板可以做到呢,总不能是你说有他就有吧,证据呢?

我们先不急得在这里体现为什么需要有类模板,我相信大家看完本文后,自然会明白为何要有模板,定义中的特点体现在哪里

函数模板

函数模板语法

以swap函数为例

template<class T>
void swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = a;
}

其中template和class是关键字,class可以用typename关键字代替,在这里class和typename没有任何区别,<>括号中的参数叫做模板形参,此处一般用 T 或 Ty,用户可以自己起名,模板形参不能为空,并且可以有多个形参。

在调用swap函数时,如果用户没有指定,系统会根据使用自动推导T的类型,而不会发生隐士类型转换。

例如以下代码

#include <iostream>
using std::cout;
using std::endl;
​
template<class T>
void swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = a;
    if (typeid(T) == typeid(int))
    {
        cout << "T is int" << endl;
    }
    else if(typeid(T) == typeid(double))
    {
        cout << "T is double" << endl;
    }
    else
    {
        cout << "T is not int or double" << endl;
    }
}
​
int main()
{
    double d1 = 11.1;
    double d2 = 22.2;
    int i1 = 10;
    int i2 = 20;
    swap(d1, d2);
​
    swap(i1, i2);
​
    return 0;
}

 

此处d1,d2都是double类型,所以T是double,i1,i2都是int类型所以T是int

如果此时对d1和i1进行交换则会报错

 

编译器报错:没有与参数列表匹配的函数模板实例,因为他的参数一个是int,一个是double,编译器无法根据参数的类型推导出T的类型,但我们可以给T一个类型具体指定我们需要调用的函数

例如:

 

而当我们指定T为double时,编译器依然报错,无法将参数2从int转换位T&,此时我们知道这个T就是double

所以编译器报的错误应当是无法将int转换位double&,当然这是肯定的,因为int在向double转换时会进行数据的提升,也就是int转换的double是一个临时变量,是一个常量,必须用const进行修饰,我们对其进行以下修改

 

此时编译器报错无法给a,b常量赋值,这是很正常的,因为const修饰的变量不能修改其存储的值,但我们看到函数调用时,已经可以匹配到具体的函数了,这就说明我们可以显示的指定具体调用的函数。

多模板形参的函数模板

上面我们说了函数模板可以有多个参数,那么我们可否通过多个参数来实现上面的代码呢?

答案是可以的,上面swap不能执行的原因就是因为T无法被正确推导,或指定后进行转换的参数变为常量无法被修改,那么我们就可以使用两个模板参数来解决这个问题

#include <iostream>
using std::cout;
using std::endl;
​
template<class T1, class T2>
void swap(T1& a, T2& b)
{
    T1 tmp = a;
    a = b;
    b = tmp;
}
​
int main()
{
    double d1 = 11.1;
    double d2 = 22.2;
    int i1 = 10;
    int i2 = 20;
    swap(d1, i1);
    cout << i1 << " " << d1;
    return 0;
}

我们可以看到此处的程序运行出了我们想看到的结果,将11.1赋值给int类型的变量由于数据截断,导致小数位丢失。

 

虽然这个可以解决我们所遇到的问题,但我们最好还是不要这样写代码,因为将两个不同类型的数据进行交换本身就是不合理的

类模板

为什么要有类模板

上面我们说完了函数模板,接下来我们来说说类模板

看完了上面的函数模板,我相信大家也知道如何去写类模板了

我们以一个链表为例

template<class T>
class List
{
  T _data;
  List* _next;
  //.....
};

可以看到该类我只写了它的一些属性,我将其数据域部分指定为T类型,这样就可以使我们的链表存储应用的更广泛,并且不用进行修改,为了方便理解,下面给出不使用类模板的两种写法

typename int DataType;
class List
{
    DataType _data;
    List* _next;
    //...
};

class List
{
    void* _data;
    List* _next;
}

我们可以看到如果我们不是用类模板可以通过typedef来给变量重命名来间接的达到复用的特征,但在每次的DataType不同时,我们还需要修改DataType的类型,不方便,而且如果在一个代码中出现了一个字符链表和一个整数链表,我们就没有任何办法,只能将原链表拷一份,修改名字才能使用。

而第二种办法虽然不用修改类型,在面对不同数据时也不需要多拷几份修改,但我们却需要根据不同的类型写出对应的数据处理的回调函数,否则这个链表将无法解析我们的数据。

使用类模板

大家看完上面之后应该对于类模板的定义有了了解,但是他是如何使用的呢?是跟我们的模板函数的用法相同还是有所区别呢?

接下来我就带大家看一下类模板是如何使用的

#include <iostream>
using std::cout;
using std::endl;
​
template<class T>
class List
{
private:
    T _data;
    List* _next;
public:
    List(const T& data = { 0 }, List* next = nullptr) :_data(data), _next(next)
    {
    }
    void push_back(const T& val)
    {
        List* tmp = this;
        while (tmp->_next != nullptr)
        {
            tmp = tmp->_next;
        }
        tmp->_next = new List(val);
    }
    void push_front(const T& val)
    {
        List* tmp = _next;
        _next = new List(val);
        _next->_next = tmp;
    }
    void show()
    {
        List* tmp = _next;
        while (tmp != nullptr)
        {
            cout << tmp->_data << " ";
            tmp = tmp->_next;
        }
        cout << endl;
    }
};
​
int main()
{
    List<int> l1;
    l1.push_back(1);
    l1.push_front(2);
    l1.show();
    return 0;
}

 

我们可以看到,我们在创建对应的对象时需要显示的指定其中T的类型,而如果我们不指定T的类型,则会报以下错误

 

因为当我们构造类的时候需要根据T类型来给这个对象分配空间,那么我们可不可以传进去一个具体的值,让编译器根据这个值来确定T的类型呢?

 

我们可以看到很明显不行,但我们可以看到编译器已经可以确定类的大小,但是需要有模板参数列表才可以使用,形成对应的类

在指定了T之后,其对象对应类的所有成员函数也将确定,T 就是你给定的类型,这也导致了接下来在进行push操作时,你必须给T类型的参数或者能转换为T类型的参数,否则编译器报错。

总结:

我相信大家此时已经知道了为何需要有模板了,模板的出现是我们不用再像C语言那样需要给每一个类型写一个交互函数。

它是我们的开发效率大大提高,我们不需要再为了一些特定的需求而进行重复的工作,不需要像C语言那样用void*代替泛型时提供大量的回调函数,只为了解析数据。

这也正是STL在C++迅速扎根的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值