C++模板——初阶

fcf6292a67ec49bfb426abf42aa06b77.jpeg

目录

​一、认识模板

1.什么是模板

2.模板的分类

二、函数模板

1.泛型和函数模板

2.函数模板的格式

三、类模板

四、实例化

1.隐式实例化

2.显式实例化

3.隐式类型转换

4.模板参数的匹配原则


一、认识模板

1.什么是模板

模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

2.模板的分类

模板分为函数模板和类模板,函数模板针对参数类型不同的函数;类模板针对数据成员和成员函数类型不同的类。

模板支持与了类型无关的代码的编写。

二、函数模板

1.泛型和函数模板

编写与类型无关的通用代码,是代码复用的一种手段。

比如说,我们定义一个交换intl类型元素值的函数。

void swap(int& a, int& b)
{
    int temp = 0;
    temp = a;
    a = b;
    b = temp;
}

这个函数可以交换int类型的变量值,但是只能操作int类型。如果我们需要交换double类型的变量,按照C++的逻辑就需要再次定义一个新的重载函数。

void swap(double& a, double& b)
{
    double temp = 0;
    temp = a;
    a = b;
    b = temp;
}

实现所有类型变量的交换需要写太多逻辑相同的函数,过于冗余。那么,如果我们不指定这个参数的类型,用一个泛型去替代这些类型,让编译器去推导这个参数的具体类型,那么我们就只需要一个函数定义了。

这样的泛型函数就叫做模板,内部变量的类型叫做泛型。

2.函数模板的格式

template<typename T1, typename T2,......,typename Tn>//typename也可以换成class
返回值类型 函数名 ( 参数列表 )
{}
template<typename T1>//用template关键字定义泛型
void swap(T1& a, T1& b)//内部变量也用泛型
{
    T1 temp = 0;
    temp = a;
    a = b;
    b = temp;
}

可以定义多个泛型T1、T2、T3等等,一个泛型只能推导出一个具体类型。T1可以推导为int类型,但是不能又推导为int,又推导为其他类型。

三、类模板

我们定义一个没有完全写好的栈

#define TYPE int
class Stack
{
public:
    Stack()
        :_a((TYPE*)malloc(sizeof(TYPE)* 4))
        ,_size(0)
        , _volume(0)
    {}
    void push(TYPE data)
    {
        _a[_size++] = data;
    }
private:
    TYPE* _a;
    int _size;
    int _volume;
};

虽然通过改变TYPE对应的类型可以改变存储数据的类型,但是如果我们想同时使用一个储存int类型元素的栈和一个储存double类型元素的栈,那么还需要定义另一个栈的类,依旧冗余。所以类似于函数,类也可以使用泛型。这种使用泛型的类也叫做类模板,与函数模板相似。

template<typename T1>
class Stack
{
public:
    Stack()
        :_a((T1*)malloc(sizeof(T1)* 4))
        ,_size(0)
        , _volume(0)
    {}
    void push(T1 data)
    {
        _a[_size++] = data;
    }
private:
    T1* _a;
    int _size;
    int _volume;
};

四、实例化

1.隐式实例化

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数和类的声明,相当于建筑的图纸,只有使用到这个函数和类时才会定义。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

简单说就是本来我们应该多去写的Swap函数和Stack类的重复工作去给编译器做了。

这是我们之前的函数模板

template<typename T1>//用template关键字定义泛型
void swap(T1& a, T1& b)//内部变量也用泛型
{
    T1 temp = 0;
    temp = a;
    a = b;
    b = temp;
}

对于不同类型的变量进行swap,当编译器遇到符合模板但没有定义的函数,编译器就会自己生成一个对应的重载函数,如果这个函数之前定义过那就使用之前定义过的函数。

template<typename T1>
void swap(T1& a, T1& b)
{
    T1 temp = 0;
    temp = a;
    a = b;
    b = temp;
}
int main()
{
    int a = 1;
    int b = 2;
    swap(a,b);
    //交换两个int变量a和b,编译器会根据模板定义一个int类型的交换函数,如下:
    //void swap(int& a, int& b)
    double c = 1.0;
    double d = 2.0;
    swap(c,d);
    //交换两个double变量c和d,编译器会根据模板再次定义一个double类型的交换函数,如下:
    //void swap(double& a, double& b)
    int e = 1;
    int f = 2;
    swap(e,f);
    //再次交换两个int变量e和f,编译器会继续使用之前定义的int类型交换函数
    //void swap(int& a, int& b)
    return 0;
}

最后的结果还是定义了很多不同类型的重载函数,与我们自己定义许多不同类型的函数的结果是一样的。但是这时不再需要程序员去一个一个类型定义函数,大大简化了程序的代码量。

模板对于类也是一样的效果,也会自己定义许多不同类型的类去使用,但是类只支持显式实例化。

2.显式实例化

我们不光可以让计算机自己推断类型,而且也可以自己指定生成哪一种类型参数的函数和类。

template<typename T1>//定义泛型T1,在这里也表示下面的函数中会用到T1
void Swap(T1& a, T1& b)
{
    T1 temp = 0;
    temp = a;
    a = b;
    b = temp;
}
template<typename T1, typename T2>//定义泛型T2,T1在前面已经定义了表示会用到T1和T2
void print(T1& a, T2& b)//注意,这个T1经过推导只能对应一个类型,不能在上面的函数中是int下面的是double
{
    cout << a << ' ' << b << endl;
}
int main()
{
    int a = 1;
    int b = 2;
    double c = 1.0;
    Swap<int>(a, b);//在函数后面加<类型>就可以指定定义和使用对应类型的函数
    print<int, double>(a, c);//<>内部多个类型用,隔开对应上面的template
    cout << a << ' ' << b << endl;
    return 0;
}
//输出:
//2 1
//2 1

之前说过,类只支持显式实例化,不能通过编译器推断。类模板也是一个声明或者说是一个蓝图,只有使用了这个类型的函数或者类才会被定义。

template<typename T>
class Stack
{
public:
    Stack()
        :_a((T*)malloc(sizeof(T)* 4))
        , _size(0)
        , _volume(0)
    {}
    void push(T data)
    {
        _a[_size++] = data;
    }
private:
    T* _a;
    int _size;
    int _volume;
};

int main()
{
    Stack<int> s1;//定义一个储存int类型的类,类型为Stack<int>
    Stack<double> s2;//定义一个储存double类型的类,类型为Stack<double>
    s1.push(1);//int类型栈可以插入数据
    s2.push(1.0);//double类型栈也可以插入数据
    return 0;
}

3.隐式类型转换

我们可以清楚地看到T会推导出两个变量类型int和double,此时T还是会推导为int,但是b参数会隐式转化为int,我们知道这个转换的临时参数是有常性的,所以需要用const修饰。

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

int main()
{
    int a = 10;
    double b = 20.2;
    cout << Add<int>(a, b) << endl;//调用int类型Add函数,b生成一个临时int常量传递给函数
    return 0;
}

在不能完成隐式类型转换时,程序就会报错。

4.模板参数的匹配原则

  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  • 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

猜你喜欢

转载自blog.csdn.net/qq_65285898/article/details/127560510
今日推荐