模板知识
模板,学习c++,只要深入一点,我们都会解接触到的,然而,就简单的函数模板来说,理解起来并不困难,但是随着深入的学习,我们会发现,c++就开始变成”天书”,各种组合让人难以理解,或者说,得花一些时间去分析才知道它到底是什么?刚开始学习c++的时候,当时因为学的比较浅,感觉和c的区别不是很大,就是多了一个面向对象而已,不过如此。随着深入的学习,恨不得扇当时自己两巴掌,越深入学习,便越感觉c++精妙与强大,当然每个语言都有其优缺点,c++做为一门古老的c语言发展而来的语言,难免相对来说有一些遗留的“不好”的操作,这个每个语言都会存在的,这个学多了我们自然会知道各种特点。
我们为什么需要模板?
对于为什么需要模板?我们在网上应该可以看到许多文章都是用的一个很经典的例子。
这里我们也举例这个例子
我们在编程中会遇到这样一个场景,我们写一个交换函数
1 2 3 4 5 6 7 8
| void swap(int &a,int &b) { int temp; temp = a; a = b; b = temp; }
|
像这样,我们写出了一个对于int类型数据的交换,但是,下一次我需要对两个float类型的数据进行交换,我们就又需要写一个函数来进行接收,下次如果需要double,需要char,需要long……….,如果这样代码就太过于冗杂的,这个时候,模板就产生了。
我们写一个模板函数
1 2 3 4 5 6 7 8
| template<typename T> void swap2(T &a, T &b) { T temp; temp = a; a = b; b = temp; }
|
T相当于我们不知道的类型的代替,T当我们传入什么类型,我们的编译器就行帮我们推导出它的类型,然后T就变成了那个类型
比如我们传入
1 2
| float a = 123.222,b = 3123.22; swap2(a,b)
|
这里的T就会推导成float.
模板的使用
我们写模板主要写的是两个方向,一个是写函数模板,一个是写结构体模板
函数模板
对于函数模板我们前面就已经看到了
1 2 3 4 5 6 7 8
| template<typename T> void swap2(T &a, T &b) { T temp; temp = a; a = b; b = temp; }
|
template
是模板关键字,其表示后面即将定义一个模板。后面尖括号中的就是「模板参数」,这里的T 就是一个占位符号,也就是对应推导出的类型,如果传入int类型,那么T 就相当于int. 而且这里的T 是我们给它的名字,其实可以写成T1 , T2 ,T3,t123,都行。
这里我们再声明一个不一样的a,b类型都行,然后函数里面对其进行操作
1 2 3 4 5
| template<typename T,typename T2> void swap2(T a, T2 b) { std::cout<<a<<b<<std::endl; }
|
对于这里的调用,有两种方式,一种是显式推导,一种是编译器进行隐式推导
1 2 3 4 5 6 7
| void text() { swap2<int>(a,b); swap2<int>(a,b);
}
|
模板类
对于模板类,我们有两种写法
一种是用class来进行声明
1 2 3 4 5
| template <typename T1, typename T2> class Test { T1 t1; T2 t2; };
|
一种是用struct来进行声明
1 2 3 4 5
| template <typename T1, typename T2> struct Test { T1 t1; T2 t2; };
|
这两种方法声明的类是等价的
在模板类中,对于两种关键词的声明,如果是用于「生成对象」的模板类,我们一般习惯用class
进行声明,而对于「模板元编程」的静态语言描述中,我们更习惯用struct
进行声明。
对于c++中来说,struct与class其实是很相似的,在某些时候甚至可以等价,struct内部也可以声明一个函数啥的。class与struct的最大的区别主要在于默认权限上,class的默认权限是private,而struct的默认权限是public。
类模板的实例化
类模板的实例化也有两种方式
显式的定义类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> template <typename T> class MyContainer { public: MyContainer(const T& value) : data(value) {} void print() { std::cout << data << std::endl; } private: T data; };
int main() { MyContainer<int> container1(5); container1.print();
MyContainer<double> container2(3.14); container2.print();
MyContainer<std::string> container3("Hello"); container3.print();
return 0; }
|
隐式的自动推导
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> template <typename T> class MyContainer { public: MyContainer(const T& value) : data(value) {} void print() { std::cout << data << std::endl; } private: T data; }; int main() { MyContainer<int> container1(5); container1.print(); MyContainer<double> container2(3.14); container2.print(); MyContainer<std::string> container3("Hello"); container3.print(); return 0; }
|
对于类模板的实例化,我们使用时,如果类型知道,防止错误,最好是我们进行指定的显式推导
对于类模板的重命名
在学c编程的时候,我们接触的很多都是使用typedef
C++中的类型重命名主要有两种方法,一种是使用typedef
,另一种是使用using
,它们都支持模板生成,并且效果是相同的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| template <typename T, size_t size> class Array { int a; };
template <typename T> using DefaultArray = Array<T, 16>;
template <typename T> typedef DefaultArray<T *> DefaultPtrArray;
void Demo() { DefaultArray<int> arr1; DefaultPtrArray<char> arr2; }
|
关于模板需要注意的地方
我们实例化模板必须要传入,常量,常量表达式,由于模板是编译期语法,因此,这里的我们的数据也必须是编译期能确定的,比如说常数、常量表达式等,而不可以是动态的数据。就算是传入的是指针,这个地址也需要一直只表示确定的数据,不会改变(即为不会被释放,然后重新分配给其他数据)下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream> #include <array> template <int N> class MyArray { public: MyArray() { std::cout << "MyArray size: " << N << std::endl; }
private: std::array<int, N> data; };
int main() { MyArray<5> arr1;
constexpr int a = 5; MyArray<a> arr2;
const int b = 6; MyArray<b> arr3;
MyArray<a * 3> arr4;
std::array<int, 3> arr5 {1, 2, 3}; MyArray<arr5.size()> arr6;
int arr7[] {1, 2, 3}; MyArray<arr7[0]> arr8;
constexpr int arr9[] {2, 4, 6}; MyArray<arr9[1]> arr10;
return 0; }
|
初学模板让人迷糊的一些写法
在我们看别人的代码的时候,模板这里我们会看到一些我们不知道的声明
比如
其实下面的这两个在声明上都是等价的,都是声明一个模板,因为在以前写模板声明参数类型的时候都是用的class,但是class会与我们声明一个类写时容易搞混,所以转而使用typename来代替了class来声明模板,但是class的写法一直兼容,所以出现了这两种写法
1 2
| template <class T> template <typename T>
|
使用模板来对stl进行扩展
这里实现了一个对stl中vector模板的内容进行复制,这个是一个例子,我们可以写很多关于如此的对stl扩展的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <vector> #include <cstdlib> #include <cstddef>
template <typename T> void CopyVec2Buf(const std::vector<T> &ve, void *buf, size_t buf_size) { if (buf_size < ve.size() * sizeof(T)) { return; } std::memcpy(buf, ve.data(), ve.size() * sizeof(T)); }
void Demo() { std::vector<int> ve{1, 2, 3, 4}; std::byte buf[64]; CopyVec2Buf(ve, buf, 64); }
|