当前位置: 首页 > 工具软件 > bTemplate > 使用案例 >

template的用法(超详细)

喻昀
2023-12-01

首先总结一下,template其实是C++的一种语法糖,本意是去简化程序员的工作(但我认为语法实在有点繁琐,反而加大了开发成本)。

template的运用场景主要是以下部分,假设我们想写一个交换函数swap

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

那你这个交换函数只能用在int的数据类型吧,float呢?float就不用交换了吗?所以我们还得写以下代码
 

void Swap(float *a, float *b){
    float temp = *a;
    *a = *b;
    *b = temp;
}

对吧,然后还有char类型的swap,还有bool类型的类型的swap,全都要写.....此时你一拍桌子,不写了!什么垃圾玩意!

然后你百度一下,发现了有template这玩意。

那用template会不会更方便嘞,那必须的呀,只用写以下代码。

//声明时这样
template<typename T> void Swap(T *a, T *b){
    T temp = *a;
    *a = *b;
    *b = temp;
}

//调用时这样
int a=10,b=20;
swap(&a,&b);

此时,我们就不用针对每种类型都写一遍swap了(template会告诉编译器:这个a是int!这个b也是int!),这就是template的作用。


 

template其实比较关键的事函数模板和类模板,其他的都是在这些基础上延伸而来的。

1.函数模板声明

函数模板顾名思义,就是template用在函数上

定义的时候吧,咱就这样定义

template<typename T>
void swap(const T*a,const T*b)
{
    T temp = *a;
    *a = *b;
    *b = temp;
}

2.函数模板调用 

然后用起来吧,咱这样用,这时候你可以直接调用swap,这个咱叫隐式调用,就是你不用扯着嗓子喊:这个a是int!这个b也是int!的意思,稍后会讲到。

int main()
{
    int a=10,b=20;
    float c=30,d=40;

    swap(&a,&b);
    swap(&c,&d);

    return 0;
}

3.inline函数模板

如果你想把你的函数声明为inline提高效率,那么参考下面

inline关键字放在template之后,函数类型之前

//正确写法
template<typename T>inline T swap(const T*a,const T*b)

//错误写法
inline template<typename T>T swap(const T*a,const T*b)

2.类模板

类模板声明

template<class Type>class Queue{
public:
    Queue();
    Type &front();
    const Type &front()const;
    void push(const Type &);
    void pop();
    bool empty()const
private:
    ...
};

类模板定义

和咱的函数模板不同啊,我们的类模板需要加个括号(显式)告诉编译器老子是啥类型。

queue<int>qi;//什么是显式,在定义时加个括号告诉他老子是int就是显式
Queue<vector<double>>qc;
Queue<sting>qs;

3.非类型模板形参

非类型模板形参实际上传的就不是类型了,而是传一个具体的数字!

(待补充)

4.模板特化

有些时候,我们编写的模板定义并不总是适用于所有类型

template<typename T>
int compare(const T&v1,const T&v2)
{
    if(v1<v2)return -1;
    if(v1>v2)return 1;
    return 0;
}

当这个函数的参数类型是C风格字符串时,这个函数不能正常工作(这个函数将比较两个指针在内存中的相对位置,但没有比较指针指向对象的大小),这时候我们必须提供一个针对C风格字符串的特殊定义,这就是模板特化

1.函数模板的特化

特化的形式如下:

  • 关键字tempalte后面接一对空的尖括号(< >)
  • 函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
  • 函数形参表
  • 函数体
template<>
int compare<const char *>(const char *const &v1,const char *const &v2)
{
    return strcmp(v1,v2);
}

1.声明模板特化

与任意函数一样,函数模板特化可以声明而无须定义。

template<>
int compare<const char *>(const char *const &,const char *const &);

2.函数重载与模板特化

如果在特化中省略空的模板形参表template<>,那么结果是函数的重载而不是模板特化

int compare(const char *const&,const char *const&);
//声明了该函数的重载版本而不是模板版本

3.特化的作用域规则

在调用模板的这个特化版本之前,特化的声明必须在作用域中

template<class T>
int compare(const T&t1,const T& t2)
{
    ...
}

int main()
{
    int i=compare("hello","world");
    ...
}

template<>
int compare<const char *>(const char *const &s1,const char *const &s2)
{
    ...
}

这个程序有错误,这个函数将会调用未特化的原模板函数

特化出现在对该模板实例的调用之后是错误的

2.类模板的特化

1.类特化的定义

template<> class Queue<const char*>{
public:
    void push(const char *);
    void pop();{real_queue.pop();}
    bool empty()const{return real_queue.empty();}
    std::string front(){return real_queue.front();}
    const std::string &front()const{return real_queue.front();}
private:
    Queue<std::string>real_queue;
};

类模板特化应该与它所特化的模板定义相同的接口,否则当用户试图使用未定义的成员时会感到奇怪

2.在类特化外部定义成员

void Queue<const char *>::push(const char*val)
{
    return real_queue.push(val);
}

在类特化外部定义成员时,成员之前不能加template<>标记

3.特化成员而不特化类

template<>
void Queue<const char*>::push(const char *const &val)
{
    ...
}

template<>
void Queue<const char *>::pop()
{
    ...
}

现在,类类型Queue<const char *>将从通用类模板定义实例化而来,而push和pop函数例外。调用Queue<const char *>对象的push或pop函数时,将调用特化版本;调用其他任意成员时,将从类模板为const char*实例化一个通用版本。

4.类模板的部分特化

如果类模板有一个以上的模板形参,我们也许想要特化某些模板形参而非全部,使用类模板的部分特化可以做到这一点:

template<class T1,class T2>
class some_template{
    //...
};


//部分特化
template <class T1>
class some_template<T1,int>{
    //...
}

使用类模板的部分特化

some_template<int,string>foo;//使用类模板
some_template<string,int>bar;//使用部分特化

1.foo的实例化类型与提供的部分特化类型不匹配,因此,foo的类型必然从通用类模板实例化

2.bar选择部分特化模板来实例化。当声明了部分特化的时候,编译器将为实例化选择最特化的模板定义,当没有部分特化可以使用的时候,就使用通用模板定义。

ps:函数模板是没有部分特化的,如果声明了一个部分特化的函数模板,编译器会报错

 类似资料: