什么是模板?
引用 Microsoft Docs:
模板是 c + + 中的泛型编程的基础。 作为强类型语言,c + + 要求所有变量都具有特定类型,由程序员显式声明或由编译器推断。
但是,许多数据结构和算法的外观都是相同的,无论它们的操作类型是什么。
利用模板,您可以定义类或函数的操作,并允许用户指定这些操作应使用的具体类型。
总结:模板是 C++ 当中支持参数类型与返回值动态化的工具,使开发人员可以动态自定义函数、类中参数与返回值类型。
模板又分为两种:函数模板 与 类模板。
====================================
1,函数模板
先从最简单的定义讲起:
template<classT>TAddNum(Ta, Tb) { returna+b; } intmain() { inta=1; intb=2; intc=AddNum(a, b); std::cout<<"Add Num: "<<c<<std::endl; returnEXIT_SUCCESS; }
可以看到,以上函数实现了最简单的任意同类型变量相加的一个功能。
定义模板的关键字就是 template,语法:
template<classT>ortemplate<typenameT>
template<> 对函数声明或定义进行修饰,其中 T 可以是任意名字(例如Object)。
进行在模板函数调用时,编译器会根据变量类型推断函数参数类型。
那么,函数模板是否可以支持多种类型呢?可以!
template<classI, classF>FGetProduct(Ia, Fb) { returna*b; } intmain() { inta_i=5; floatb_f=8.5; floatc_f=GetProduct(a_i, b_f); std::cout<<"Product: "<<c_f<<std::endl; returnEXIT_SUCCESS; }
以上函数最简单实现了求两个不同类型变量的乘积。当你的参数类型需要两种或以上的时候,就是在 template<> 当中增加声明,再对函数或类进行修饰:
template<classI, classF, classD>
那么,除了基本的数据类型,模板是否支持结构体(struct)或其他类型呢?可以!
但是,进行运算操作的时候,你要确保你的 struct 或 class 重载的相应的运算符!
typedefstructIntCell { inta; intb; intc; structIntCell(inti, intj, intk) : a(i), b(j), c(k) {}; }IntCell; typedefstructDoubleCell { doublea; doubleb; doublec; structDoubleCell(doublei, doublej, doublek) : a(i), b(j), c(k) {}; }DoubleCell; // ------------------------template<classstructT, classstructY>inlineboolCompareStructMemSize(structTa, structYb) { returnsizeof(a) >sizeof(b); }; // ------------------------intmain() { IntCellcell_a(1, 2, 3); DoubleCellcell_b(1.1, 2.2, 3.3); boolflag=CompareStructMemSize(cell_a, cell_b); std::cout<<"bool: "<<flag<<std::endl; returnEXIT_SUCCESS; }
可以看到,以上代码当中我定义了两个不同的结构体,只是单纯的去比较这两个结构体的大小。
====================================
2,类模板
函数模板很好理解,那么类模板是什么呢?
可以在类模板的内部或外部定义成员函数。 如果在类模板的外部定义成员函数,则会像定义函数模板一样定义它们。 --Microsoft Docs
以下演示了一个最简单的使用模板的类:
template<classObject>classVectorMod { public: VectorMod() { this->_vec.reserve(10); }; ~VectorMod() { this->Clear(); }; std::vector<Object>&GetVec() { returnthis->_vec; }; voidAddData(Objectin) { this->_vec.push_back(in); }; intGetSize() { returnthis->_vec.size(); }; voidClear() { this->_vec.clear(); std::vector<Object>().swap(this->_vec); } private: std::vector<Object>_vec; }; 这个类仅仅是简单将std::vector包装了一下而已。
于是我们可以总结出语法:
1,使用 template<> 对类声明和类定义进行修饰。
2,类内部需要使用模板类型时,直接使用相应的模板形参名。
template<classObject>classVectorMod { std::vector<Object>_vec; };
请注意,就像任何模板类成员函数一样,类的构造函数成员函数的定义包含模板参数列表两次。
成员函数可以是函数模板,并指定附加参数。 --Microsoft Docs
PS: 模板可以在模板类当中被定义并使用,这种情况下成为 “成员模板”,但是逻辑会过于复杂,实际开发不建议使用,了解成员模板。
当模板类需要被使用的时候,如何进行声明并创建呢?
VectorMod<int>v_mod; shared_ptr<VectorMod<int>>int_pool=make_shared<VectorMod<int>>();
以上演示了局部变量及智能指针的创建。然后,像平常那样调用即可:
VectorMod<int>v_mod; v_mod.AddData(971); v_mod.AddData(981); v_mod.AddData(991); for (autoi : v_mod.GetVec()) { std::cout<<i<<std::endl; }
好了,上面是最基本的类模板。
然后:类模板当中非类型形参
这是一个什么东西呢?
1,它是一个常量。
2,它的类型只能是 int 、指针、引用这三种内置类型。
3,调用它的只能是一个常量表达式。
它的使用场景?
1,你封装了一个可以容纳固定大小的容器。
2,可以预初始化固定资源。
样例:
usingstd::vector; template<classObject, intPREMEM>classDataPool{ public: DataPool(); ~DataPool(); vector<Object>&GetVec(); voidAddData(Objectin); intGetSize(); voidClear(); private: vector<Object>_vec; }; // ------------------------template<classObject, intPREMEM>DataPool<Object, PREMEM>::DataPool() { this->_vec.reserve(PREMEM); return; } template<classObject, intPREMEM>DataPool<Object, PREMEM>::~DataPool() { this->Clear(); return; } template<classObject, intPREMEM>vector<Object>&DataPool<Object, PREMEM>::GetVec() { returnthis->_vec; } template<classObject, intPREMEM>voidDataPool<Object, PREMEM>::AddData(Objectin) { this->_vec.push_back(in); return; } template<classObject, intPREMEM>intDataPool<Object, PREMEM>::GetSize() { returnthis->_vec; } template<classObject, intPREMEM>voidDataPool<Object, PREMEM>::Clear() { this->_vec.clear(); std::vector<Object>().swap(this->_vec); return; }
以上代码,同样是将 std::vector 简单无意义包装了一下,但是,却使用了非类型形参进行了内存预分配操作以提高性能。
所以模板非类型形参的语法是:
template<classObject, intPREMEM>ortemplate<classObject, type*per>ortemplate<classObject, type&ref>
即不使用 class 或者 typename,直接使用 INT or PTR or REF。
那么该如何使用呢?
intmain() { shared_ptr<DataPool<string, 10>>str_pool=make_shared<DataPool<string, 10>>(); str_pool->AddData("Hello Byte!"); str_pool->AddData("Hello Blu!"); str_pool->AddData("Hello Frog!"); for (autos : str_pool->GetVec()) { std::cout<<s<<std::endl; } returnEXIT_SUCCESS; }
可以看到,使用它的语法就是:
DataPool<string, 10>orDataPool<string, ptr>orDataPool<string, &ref>
====================================
3,模板与完整泛型编程的区别(编译避坑)
C++ 的模板类在没有被使用之前,编译器完全不知道它会占用多少空间!而 C++ 每一个变量及对象占用的空间在编译的时候就要被确定!
所以 C++ 当中没有绝对的泛型编程概念。
因此,模板类必须是声明与实现同源(不一定是文件不分离),最合适的写法也就是 hpp 文件。
简单化使用.h头文件和.cpp文件分类声明时,几乎确定会报链接错误。
解决方法可以简单粗暴的将 cpp 文件 include 到 h 文件当中,但这并非标准做法,MSC编译器也已经不支持,所以最合适的做法还是使用 hpp 文件。
====================================
芯片烤电池 C++ Example 2022-Spring Season Pass :
【Example】C++ 回调函数及 std::function 与 std::bind
【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr
【Example】C++ Template (模板)概念讲解及编译避坑
【Example】C++ 标准库 std::thread 与 std::mutex
【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)
【Example】C++ 标准库 std::condition_variable
【Example】C++ 用于编译时封装的 Pimpl 演示 (编译防火墙 Private-IMPL)
【Example】C++ 单例模式 演示代码 (被动模式、兼容VS2022编译)
====================================