一、类模板基本概念
在C++中,模板类是一种用于创建通用数据结构或算法的强大工具。模板类允许您编写一次代码,然后在不同数据类型上重复使用它,以提高代码的可重用性和灵活性。
模板类允许您定义一种通用的类模板,其中某些成员或函数可以根据不同数据类型进行参数化。它们使用template关键字定义
template <class T>
class 类模板名
{
类的定义;
};
函数模板建议用 typename
描述通用数据类型,类模板建议用 class
。
1.1 类模板举例
template<class T1, class T2>
class MyClass {
public:
T1 a_;//通用类型用于成员变量
T2 b_;
MyClass() {}
//通用类型用于成员函数的参数
MyClass(T1 a, T2 b) : a_(a), b_(b) {
}
//通用类型用于成员函数返回值
T1 fun() {
T1 a = 2; //通用类型用于成员函数代码中
return a;
}
};
int main() {
MyClass<int, double> myClass;//用模板类AA创建对象myClass,其中的数据类型只能显示设置,不能隐形自动推导
myClass.a_=30;
myClass.b_=32.9;
cout<<myClass.fun();//2
}
注意:
-
在创建对象的时候,必须指明具体的数据类型。
MyClass<int, double> myClass;//用模板类AA创建对象myClass,其中的数据类型必须指明,不能隐形自动推导,
-
使用类模板时,数据类型必须适应类模板中的代码。
template<class T1, class T2> class MyClass { public: T1 a_;//通用类型用于成员变量 T2 b_; MyClass() {} //通用类型用于成员函数的参数 MyClass(T1 a, T2 b) : a_(a), b_(b) { } //通用类型用于成员函数返回值 T1 fun() { //假如T1的类型是string字符串,那么这句话就是错的 T1 a = 2; //通用类型用于成员函数代码中 return a; } };
-
类模板可以为通用数据类型指定缺省的数据类型(C++11 标准的函数模板也可以,但是对函数模板意义不大)。
template<class T1, class T2=double>//如果第二个缺失,默认使用double class MyClass { public: T1 a_;//通用类型用于成员变量 T2 b_; MyClass() {} //通用类型用于成员函数的参数 MyClass(T1 a, T2 b) : a_(a), b_(b) { } //通用类型用于成员函数返回值 T1 fun() { T1 a = 2; //通用类型用于成员函数代码中 return a; } }; int main() { MyClass<int> myClass;//缺少第二个参数,默认使用double myClass.a_=30; myClass.b_=32.9; cout<<myClass.fun();//2 }
-
模板类的成员函数可以在类外实现。
template<class T1, class T2> class MyClass { public: T1 a_;//通用类型用于成员变量 T2 b_; MyClass() {} //通用类型用于成员函数的参数 MyClass(T1 a, T2 b) : a_(a), b_(b) { } //通用类型用于成员函数返回值 T1 fun(); }; //在类外定义,但是类外不支持数据类型缺省,因为不同的编译器可能不支持 template<class T1, class T2> T1 MyClass<T1,T2>::fun() { T1 a = 2; //通用类型用于成员函数代码中 return a; } int main() { MyClass<int,double> myClass;//用模板类AA创建对象myClass,其中的数据类型只能显示设置,不能隐形自动推导 myClass.a_=30; myClass.b_=32.9; cout<<myClass.fun();//2 }
-
可以用 new 创建模板类对象。
template<class T1, class T2> class MyClass { public: T1 a_;//通用类型用于成员变量 T2 b_; MyClass() {} //通用类型用于成员函数的参数 MyClass(T1 a, T2 b) : a_(a), b_(b) { } //通用类型用于成员函数返回值 T1 fun() { T1 a = 2; //通用类型用于成员函数代码中 return a; } }; int main() { MyClass<int,double> *myClass = new MyClass<int,double>; MyClass<int,double> *myClass1 = new MyClass<int,double>(3,1.4); }
-
在程序中,模板类的成员函数使用了才会创建。
template<class T1, class T2> class MyClass { public: T1 a_;//通用类型用于成员变量 T2 b_; MyClass() { a_.ccc;//这行会报错 } }; int main() { MyClass<int,int> *m;//只声明了指针,但未创建类对象,不会调用构造函数,所以不会报错 }
template<class T1, class T2> class MyClass { public: T1 a_;//通用类型用于成员变量 T2 b_; MyClass() { } void fun(){ a_.da2342342(); } }; int main() { MyClass<int,int> *m=new MyClass<int,int>;//也不会报错,因为每调用fun函数 }
二、创建模板类的方法
- 先写一个普通类,用具体的数据类型
- 调试普通类
- 把模板类改为模板类
三、模板类示例——栈
-
普通类实现:以
int
类型为例class Stack{ private: int *items;//栈数组 int stacksize;//栈大小 int top;//栈顶指针 public: //构造函数 Stack(int size):stacksize(size),top(0){ items = new int[size]; } //析构函数 ~Stack(){ delete []items;//释放内存 items=nullptr; } //判断是否为空 bool isEmpty() const{ return top == 0; } //判断是否已满 bool isFull() const{ return top == stacksize; } //入栈 bool push(const int& item){ if(isFull()){ return false; } items[top++] = item; return true; } //出栈 bool pop(int &item){ if(isEmpty()){ return false; } item = items[--top]; return true; } }; int main(){ Stack stack(10); stack.push(1); stack.push(2); stack.push(3); int popitem; while (!stack.isEmpty()){ stack.pop(popitem); cout << popitem << endl; } }
-
使用typedef来自定义元素类型,实现类似于类模板功能
typedef int DataType;//定义数据类型 class Stack{ private: DataType *items;//栈数组 int stacksize;//栈大小 int top;//栈顶指针 public: //构造函数 Stack(int size):stacksize(size),top(0){ items = new DataType[size]; } //析构函数 ~Stack(){ delete []items;//释放内存 items=nullptr; } //判断是否为空 bool isEmpty() const{ return top == 0; } //判断是否已满 bool isFull() const{ return top == stacksize; } //入栈 bool push(const DataType& item){ if(isFull()){ return false; } items[top++] = item; return true; } //出栈 bool pop(DataType &item){ if(isEmpty()){ return false; } item = items[--top]; return true; } }; int main(){ Stack stack(10); stack.push(1); stack.push(2); stack.push(3); DataType popitem; while (!stack.isEmpty()){ stack.pop(popitem); cout << popitem << endl; } }
-
使用类模板实现
template<class DataType>//模板类 class Stack{ private: DataType *items;//栈数组 int stacksize;//栈大小 int top;//栈顶指针 public: //构造函数 Stack(int size):stacksize(size),top(0){ items = new DataType[size]; } //析构函数 ~Stack(){ delete []items;//释放内存 } //判断是否为空 bool isEmpty() const{ return top == 0; } //判断是否已满 bool isFull() const{ return top == stacksize; } //入栈 bool push(const DataType& item){ if(isFull()){ return false; } items[top++] = item; return true; } //出栈 bool pop(DataType &item){ if(isEmpty()){ return false; } item = items[--top]; return true; } }; int main(){ Stack<int> stack(10); stack.push(1); stack.push(2); stack.push(3); int popitem; while (!stack.isEmpty()){ stack.pop(popitem); cout << popitem << endl; } }
三、模板类示例——栈
3.1 定长数组Array
template <class DataType,int len>
class Array{
private:
DataType array[len];
public:
Array(){
memset(array,0,sizeof(array));
}
~Array(){}//由于没有堆空间,析构函数可以为空
//重写下标运算符,包含非const和const版本
//非const版本,可以直接修改数组元素
DataType& operator[](int index){
return array[index];
}
//const版本,不可以修改数组元素
const DataType& operator[](int index) const{
return array[index];
}
};
int main(){
Array<int,10> array;
array[0]=3;
array[1]=4;
for (int i = 0; i < 2; ++i) {
cout<<array[i]<<endl;
}
}
注意:
类模板可以有非通用类型参数:template <class DataType,int len>
- 通常是整型 (C++20标准可以用其它);
Array<int,10> array;
- 实例化模板时必须用常量表达式;
Array<int,10> array;
- 模板中不能修改参数的值;
- 可以为非通用类型参数提供默认值。
template <class DataType,int len=20>
Array<int> array;
优点:在栈上分配内存,易维护,执行速度快,合适小型数组。
缺点:在程序中,不同的非通用类型参数将导致编译器生成不同的类。
构造函数的方法更通用,因为数据的大小是类的成员(而不是硬编码),可以创建数组大小可变的类。
3.1 可变长度数组Vector
template<class DataType>
class Vector {
private:
DataType *vector;
int len;
public:
Vector(int size = 2) : len(size) {
vector = new DataType[size];
}
~Vector() {
delete[] vector;
vector = nullptr;
}
//调整数组大小函数
void resize() {
DataType *new_vector = new DataType[len * 2];//重新申请一个两倍len大小的数组
for (int i = 0; i < len; ++i) {//拷贝原始数据
new_vector[i] = vector[i];
}
delete[] vector;//释放原来的数组
vector = new_vector;//让数组指针指向新数组
len = len * 2;//更新数组长度
}
//获取数组长度
int getsize() {
return len;
}
//重写下标运算符,包含非const和const版本
//非const版本,可以直接修改数组元素
DataType &operator[](int index) {
if (index >= len) {
resize();//如果要插入或修改的元素大于等于原始数组大小,则扩充数组大小
}
return vector[index];
}
//const版本,不可以修改数组元素,这里不用修改,因为是只读的
const DataType &operator[](int index) const {
return vector[index];
}
//由于可变数组类里面包含堆元素,因此要实现深拷贝,重写赋值运算符
Vector<DataType>& operator=(const Vector<DataType> &vec) {
//释放原内存
delete[] vector;
len = vec.len;
vector = new DataType[len];//重新分配数组
for (int i = 0; i < len; ++i) {
vector[i] = vec.vector[i]; //拷贝数组中的元素
}
return *this;
}
};