frankfan的胡思乱想

学海无涯,回头是岸

模板

模板 函数模板 类模板 模板特例化

本章讲授的主要内容是『模板』,围绕模板进行展开。包含模板函数与模板类,此外还有模板的特例化。

模板编程是C++面向对象编程的特色之一,是代码复用的另一种手段,模板编程语法繁杂,通常只需要掌握常用的模板知识点即可。

模板的本质是将数据类型进行动态化,将函数以及类的生成交由编译器,而非硬编码

模板的诞生场景

#include <iostream>
using namespace std;

int Add(int a,int b){
  return a + b;
}

float Add(float a,float b){
  return a + b;
}

int main(){
  
  //通过重载,我们可以将整型或者浮点型当做参数,但是可以看到Add的实现逻辑是相同的,唯一不同的是数据类型,这样显然是代码冗余重复的。
  Add(11,22);
  Add(11.1,22.2);
  return 0;
}

此时,模板编程便应运而生。将数据类型动态化交由编译器去处理,让编译器在编译期生成函数的实现,而非硬编码。

#include <iostream>
using namespace std;

template<typename T>
T Add(T a,T b){
  return a + b;
}

int main(){
  
  //只需要定义函数的逻辑实现而不管其数据类型,编译时编译器根据调用时的具体上下文生成具体的函数实现体代码。
  
  //编译器根据参数的类型推断出T的具体类型
  int r1 = Add(11,22);
  float r2 = Add(11.1,22.2);
  
  //当然,也可以在调用时指定T的具体类型
  int r3 = Add<int>(55,66);
  return 0;
}

可以发现,模板函数是在编译期根据具体的函数调用生成具体的函数实现体代码。

若无具体函数调用发生,则不会生成具体的函数体实现代码。

调用模板函数时,既可以直接填写参数,编译期根据参数类型推断T类型。也可以手动直接指定T类型。

//Util.h
template<T>
T Add(T a,T b);
//Util.cpp
#include "Util.h"
T Add(T a,T b){
  return a + b;
}

在编译阶段是正确的,完全符合语法要求。

但是在链接阶段是错误的,因为并没有生成具体的函数实现体代码。并不能找到Add这个符号。

因为C++的编译是每个Cpp文件独立编译的。在编译util.cpp文件时,因为并没有产生函数调用所以并不会生成具体的函数体实现代码。

因此模板函数通常是接口与实现定义在同一个文件中。该文件通常被称为xxx.hpp

#include <iostream>
using namespace std;
#include "Util.h"

int main(){
  int r = Add(11,22)
  return 0;
}

模板函数通常这样定义(定义在头文件中)

//Util.hpp
template<typename T>
T Add(T a,T b){
	return a + b;
}
#include <iostream>
using namespace std;
#include "Util.hpp"

int main(){
  int r = Add(11,22)
  return 0;
}

模板特例化

设计模板函数时通常考虑的是符合大部分场景的逻辑,当设计的模板函数不能满足某一特定调用时,可以通过设计一个特殊的模板函数来满足要求,这个特殊的模板函数就是所谓的模板特例化

#include <iostream>
using namespace std;
template<typename T>
T Add(T a,T b){
  return a + b;
}
int main(){
  //这个模板函数符号常规的Add逻辑
  int a = Add(11,22);
  float b = Add(11.1,22.2);
  
  //但是,当参数为字符串类型时,Add的逻辑就不对了,这时就需要我们为模板函数Add特例化
  auto r = Add<const char*>("hello","world");
  return 0;
}

模板函数Add并不能满足字符串的要求,因此我们需要特例化一下,使得传入字符串时也能够满足要求,这个重新设计的Add就叫模板函数特例化

#include <iostream>
#include <stdlib.h>
using namespace std;

template<typename T>
T Add(T a,T b){
  return a + b;
}

//模板特化
template<>
char *Add<char*>(char * a,char *b){
  char *str = new char[strlen(a)+strlen(b)+1];
  strcpy(str,a);
  strcat(str,b);
  return str;
}


int main(){
  int a = Add(11,22);
	float b = Add(11.1,22.2);
  char *s = Add<char*>("hello","world");
  return 0;
}

模板类

C++中模板的运用不只是局限在函数的运用上,在类的定义中也可以运用模板,这样定义的类被称为模板类。

与模板函数相同的是,只有在使用模板类时编译器才会生成对应的类代码。

与模板函数不同的是,类模板使用时必须指定模板的具体类型,而不能靠编译器推断。

#include <iostream>
using namespace std;

class Node{
public:
  //在类内部定义模板成员函数
  template<typename T>
  void showinfo(T info){
    cout<<info<<endl
  }
private:
  //在类内部定义的模板不能用于成员变量
  int value;
};

int main(){
  Node node;
  node.showinfo("hello");//hello
  node.showinfo(11);//11
  return 0;
}

除了在类内部定义模板成员函数外,也能在类外部定义模板(而这也是模板类的常规用法)

#include <iostream>
using namespace std;

template<typename T>
class Node{
public:
  Node(T t):value(t){}
  void showinfo(T info){
    cout<<info<<endl;
  }
  void showA();
private:
  T value;
};

template<typename T>
void Node<T>::showA(){
  cout<<value<<endl;
}

int main(){
  
  Node<int> node(11);
  node.showA();//1
  
  Node<const char *> node2("hello");
  node2.showA();//hello
  
  ///当定义模板类以后,并不存在单纯的以类名为类的存在,而是必须加上模板参数
  return 0;
}

模板类与普通类一样,也是可以继承的,不过要注意要带上模板参数

#include <iostream>
using namespace std;
template <typename T>
class Node{
public:
  Node(T v):value(v){}
private:
 	T value;
};

template<typename T>
//继承模板类时,注意模板类父类必须带模板参数
class TTNode:public Node<T>{
  
};

int main(){
  return 0;
}

也就是说,模板类A与普通类A不是同一个东西(但是不能定义同名类)

posted on 2021-12-28 00:17  shadow_fan  阅读(112)  评论(0编辑  收藏  举报

导航