本小节回顾的知识点分别是类模板的概念、类模板的定义和使用。
今天总结的知识分为以下4个点:
(1)概述
(2)类模板定义
(3)类模板的成员函数
(4)非类型模板参数
(1)概述:
类模板:就是包含待指定数据类型的类,这个待指定的数据类型就是类模板的模板参数。
注意:我们必须为类模板提供<模板参数表>,这样才能够让编译器用其实例化(生成)成一个特定版本的类。(编译器不能够为类模板的模板参数do自动类型推断!)
为什么会提出类模板这种概念呢?答:类模板这种东西,实现了让不同数据类型都适用于同一套类的代码的目的!这样你就不用说为了某个特定的数据类型的兼容性,特意去写一套类的代码以适用于该数据类型的数据了。这样do的话你程序的可读性将大大增加,同时代码的维护难度也大大降低!
比如:vector<int>、vector<double>、vector<Person>等等。。。
(2)类模板定义:
声明格式:
template<typename T1, typename T2, ...typename Tn>
class ClassName {
public:
/*...*/
};
我在4.1小节就已经总结过:对于模板(函数/类)代码,除非编译器看到你使用了该模板,否则都是不生成对应特定版本的模板代码的!而当要生成模板代码时,就需要找到该类模板的类定义实现codes or 函数模板的定义实现codes,那么此时我们把模板的代码放在.h头文件中,就算非常合适不过的了!Especially,对于类模板而言,我们只有将类模板的all类成员声明以及类成员定义实现都放在一个头文件中时,才能在后续要用到该类模板时,能一次过让编译器知道该类模板的all信息,也只有这样才能让编译器为我们成功生成一个指定数据类型的特殊版本的类。因此在VS2022编译器下,建议,我们直接将包含上述两部分代码的XXX.h头文件直接改名为XXX.hpp头文件(.hpp 可理解为 .h + .cpp的结合)。(当然,你不改为.hpp,依然在一个.h头文件中去写一个类模板的all类成员声明以及类成员定义实现也是可以的,非强制性。)
请看以下代码:
myVector.h:
#ifndef __MYVECTOR_H__
#define __MYVECTOR_H__
#include<iostream>
using namespace std;
template<typename T>
class myVector {
private:
int m_Size;//当前数组元素个数
int m_Capacity;//数组总容量大小
T* my_Arr;
public:
typedef T* myiterator;//迭代器 vector iterator
myiterator mybegin() {//迭代器的起始位置my_Arr[0]
return &my_Arr[0];
}
myiterator myend() {//迭代器的最后一个元素my_Arr[n-1]的下一个位置
return &my_Arr[m_Size];
}
myVector() :m_Size(0), m_Capacity(0), my_Arr(nullptr) {}
myVector(int n) :m_Size(0), m_Capacity(n) {//指定数组大小为n
my_Arr = new T[n];//heap区开辟一个数组,包含n个类型为T的元素
}
//在类模板内部使用类名时是不需要指定模板参数的!
//==>myVector<T>& operator=(const myVector<T>& vec){...}
myVector& operator=(const myVector& vec) {
if (my_Arr != nullptr) {
delete[] my_Arr;//你new一个数组和delete一个数组要配对
my_Arr = nullptr;//防止my_Arr变成野指针乱指向
}
this->m_Size = vec.getSize();
this->m_Capacity = vec.getCapacity();
my_Arr = new T[m_Capacity];
int i = 0;
for (auto x : vec) {
my_Arr[i++] = x;
}
}
int getCapacity();
int getSize();
void Push_back(T nums);
void Pop_back();
T& operator[](int index);
~myVector();
};
#endif __MYVECTOR_H__
template<typename T>
int myVector<T>::getCapacity() {
return this->m_Capacity;
}
template<typename T>
int myVector<T>::getSize() {
return this->m_Size;
}
template<typename T>
void myVector<T>::Push_back(T nums) {
//未满时 push进来
if (m_Size < m_Capacity) {
//...
m_Size++;
}
//满了 动态扩容 再push进来
//...
}
template<typename T>
void myVector<T>::Pop_back() {
//未空时 pop掉my_Arr[n-1]这个数组元素!也即pop掉数组的last一个元素!
if (m_Size > 0) {
//...
m_Size--;
}
//空了 直接return;
cout << "myVector已空!无法从中pop元素!" << endl;
return;
}
template<typename T>
T& myVector<T>::operator[](int index) {
return my_Arr[index];
}
template<typename T>
myVector<T>::~myVector<T>() {
if (my_Arr != nullptr) {
delete[] my_Arr;
my_Arr = nullptr;//防止my_Arr变成野指针乱指向
}
}
main.cpp中:
#include<iostream>
#include"myVector.h"
using namespace std;
int main(void) {
myVector<int> vec;
//这个时候,编译器就会为我们生成一个特定版本的myVector类!
vec.Pop_back();
vec.Push_back(1);
vec.getSize();
vec.getCapacity();
return 0;
}
(3)类模板的成员函数:
在类模板的定义{内部}写类模板的成员函数时,和写普通的类的成员函数没区别。
在类模板的定义{}的外部写类模板的成员函数时,就必须像类似函数模板那样去写该类模板的成员函数。
注意:在类模板之外写其成员函数时,必须把模板参数列表<>里的东西写全写完整!!!
格式:
template<typename T1,typename T2,...,typename Tn>
retName templateClassName<T1,T2,...Tn>::memberFuncName(Params){
/*...*/
}
注意:类模板的成员函数只有当被调用时,才会给编译器实例化生成一份特定的成员函数的codes,供你的调用该成员函数的语句使用!你若不调用某个类模板的成员函数,编译器就不会为你生成特定版本的该成员函数的代码!一句话:对于一个实例化了的类模板,其成员函数只有被使用时才会给编译器实例化。
请看以下代码:
#ifndef __PERSON_H__
#define __PERSON_H__
#include<iostream>
#include<string>
using namespace std;
template<typename T>
class Person {
public:
int m_Age;
string m_Name;
T girlFriendNums;
Person(int age, string name) :m_Age(age), m_Name(name), girlFriendNums(0) {}
//在类模板定义之内定义其成员函数
void showInfo() {
cout << "Name: " << m_Name << ",Age:" << m_Age << endl;
cout << "girlFriendNums: " << girlFriendNums << endl;
}
int getAge();
string getName();
T getGrilFNums();
~Person() {}
};
//在类模板定义之外定义其成员函数
template<typename T>
int Person<T>::getAge() {
return this->m_Age;
}
template<typename T>
string Person<T>::getName() {
return this->m_Name;
}
template<typename T>
T Person<T>::getGrilFNums() {
return this->girlFriendNums;
}
#endif __PERSON_H__
(4)非类型模板参数:
非类型模板参数:当普通数据(int/double/self-defineType)类型作为<模板参数列表>中的参数时,它就可称之为非类型的模板参数了。
//myarr.h
#ifndef __MYARR_H__
#define __MYARR_H__
#include<iostream>
using namespace std;
//这里的Size为非模板参数类型
template<typename T,int Size=10>
class myArr {
private:
T arr[Size];
public:
//...
void myfunc();
};
template<typename T,int Size>//在类模板之外定义成员函数时,必须把模板参数列表写全!
void myArr<T,Size>::myfunc() {
cout << Size << endl;
}
#endif __MYARR_H__
//main.cpp
#include<iostream>
#include"myarr.h"
using namespace std;
int main(void) {
myArr<int,100> myarr;//一个int型且大小为100的数组
myarr.myfunc();//100
myArr<double> myarr2;//一个double型且大小为10的数组
myarr2.myfunc();//10
return 0;
}
注意①:在类模板外写成员函数时,不能给其模板参数列表中的参数指定默认值!
比如:![](https://img-blog.csdnimg.cn/51bd101922f6474b9659ce978e2a2fac.PNG)
注意②:浮点型(float/double)不能作为类模板的非类型模板的参数!
比如:![](https://img-blog.csdnimg.cn/eb6194252ebe4b07883ba114cd84b75b.PNG)
注意③:类 类型(内置标准库中的类/self-define类)都不能作为类模板的非类型模板的参数!
比如:
![](https://img-blog.csdnimg.cn/5d58e1ab2c8540e6abb26c6dd375165a.PNG?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBARmFuZmFuMjF5YQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
以上就是我总结的关于类模板的概念、类模板的定义和使用的笔记。希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~