ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【和春笋一起学C++】(七十二)类模板

【和春笋一起学C++】(七十二)类模板 目录0 前言1 类模板定义2 使用类模板3 使用非类型参数创建数组模板3.1 使用动态数组和构造函数提供大小3.2 使用模板参数来指定数组大小3.3 两种方法的比较0 前言继承和包含并不总是能够满足重用代码的需要。例如Stack类Stack类是容器类容器类被设计用来存储其他对象或数据类型。存储int类型的stack类与存储double类型的stack类除了存储的类型不同其他都相同。对于这种情况可以编写一个通用独立于类型的堆栈然后将具体的类型作为参数传递给这个类这样就可以通过通用的代码生成存储不同类型的堆栈。类模板就是为生成通用的类提供的解决方案。模板提供参数化类型即能够将类型名作为参数传递给接受方来建立类或函数。前面文章《包含对象成员的类》中设计Student类时就使用了模板类valarraydouble来存储学生的分数。1 类模板定义类模板的定义与函数模板一样以templateclass Type代码开头关键字template告诉编译器将要定义一个模板。尖括号中的内容相当于函数的参数列表可以把关键字class看作是变量的类型名该变量接受类型作为其值Type是该变量的名称。关键字class并不意味着Type必须是一个类而只是说明Type是一个通用的类型说明符。为避免混淆较新版本的C已使用关键字typename代替class。templatetypename Type可以使用其他的通用类型名代替Type命名规则与变量名的命名规则相同但通常使用Type或T。下面是Stack类模板的定义#pragma once template typename T class Stack { public: Stack(); bool isempty(); bool isfull(); bool push(const T item); bool pop(T item) private: enum { MAX 10; }; T item[MAX]; int top; }; template typename T StackT::Stack() { top 0; } template typename T bool StackT::isempty() { return top 0; } template typename T bool StackT::isfull() { return top MAX; } template typename T bool StackT::push(const T item) { ... }说明上述代码都放在一个头文件中。类模板和函数模板是C编译器指令说明了如何生成类和成员函数定义。模板的具体实现被称为实例化或具体化例如用来处理int类数据的堆栈类就是具体类。因为模板不是具体函数因此不能单独编译。将函数模板的实现放在一个单独的文件中是无法进行编译的模板必须与特定的模板实例化请求一起使用。为此最简单的方法是将所有模板信息放在一个头文件中并在要使用这些模板的文件中包含该头文件。2 使用类模板要使用类模板必须声明一个类型为模板类的对象即使用所需的具体类型替换通用类型名。例如下面的语句将创建两个具体的堆栈类对象。Stackint kernels; Stackstring kernels;编译器将根据上述语句生成两个独立的类声明和两组独立的类方法。Stackint类声明将使用int替换模板中所有的TStackstring类声明将使用string替换模板中所有的T。3 使用非类型参数创建数组模板模板常被用作容器类这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型为容器类提供可重用代码是引入模板的主要目的。使用非类型或表达式参数创建一个指定数组大小的数组模板。一种方法是在类中使用动态数组和构造函数参数来提供元素数目另一种方法是使用模板参数来提供数组的大小。3.1 使用动态数组和构造函数提供大小使用动态数组和构造函数提供数组大小来创建一个指针数组模板类实例代码如下#pragma once templateclass T class Stack { public: explicit Stack(int ss SIZE);///default Stack(const Stack st); ~Stack() { delete[] items; } bool isempty() { return top 0; } bool isfull() { return top stackSize; } bool push(const T item); bool pop(T item); Stack operator(const Stack st); private: enum { SIZE 10; }; int stackSize; T* items;///存放堆栈 int top; }; templateclass T StackT::Stack(int ss):stackSize(ss),top(0) { items new T[stackSize]; } templateclass T StackT::Stack(const Stack st) { stackSize st.stackSize; top st.top; items new T[stackSize]; for (int i 0; i top; i) { items[i] st.items[i]; } } templateclass T bool StackT::push(const T item) { if (top stackSize) { items[top] item; return true; } else { return false; } } templateclass T bool StackT::pop(T item) { if (top 0) { item items[--top]; return true; } else { return false; } } templateclass T StackT StackT::operator(const Stack st) { if (this st)return *this; delete[] items; stackSize st.stackSize; top st.top; items new T[stackSize]; for (int i 0; i top; i) { items[i] st.items[i]; } return *this; }代码说明该类模板创建的是一个可指定大小的指针数组。赋值操作符函数说明赋值操作符函数原型的返回类型为Stack引用而实际的模板函数定义中返回类型为StackT。前者是后者的缩写只能在类中使用即只能在模板声明中使用。在类外指定返回类型或使用作用域解析操作符时必须使用完整的StackT。Stack构造函数为能够接受一个可选大小的参数来指定数组的大小。3.2 使用模板参数来指定数组大小代码如下#pragma once #include iostream #include cstdlib templateclass T,int n class ArrayTP { public: ArrayTP(){}; explicit ArrayTP(const T v); virtual T operator[](int i); private: T ar[n]; }; templateclass T, int n ArrayTPT,n::ArrayTP(const T v) { for (int i 0; i n; i) { ar[i] v; } } templateclass T, int n T ArrayTPT, n::operator[](int i) { if (i 0 || i n) { std::cerr error in array limits: i is out of range\n; std::exit(EXIT_FAILURE); } return ar[i]; }代码说明模板头templateclass T, int n中关键字class指出T为类型参数int n表示n为非类型参数或表达式参数。假设有声明代码ArrayTPdouble, 12ar1则编译器将定义一个名为ArrayTPdouble, 12的类并创建一个类型为ArrayTPdouble, 12的ar1对象。定义类时编译器将使用double替换T使用12替换n。类模板中的非类型参数有一些限制参数只能是整型、枚举、引用或指针templateclass T, double n是不合法的。另外模板代码不能修改参数的值也不能使用参数的地址。所以在ArrayTP模板中不能使用n或n表示式。另外在实例化模板时非类型参数必须是常量表达式。3.3 两种方法的比较使用模板参数指定数组大小与使用构造函数相比这种改变数组大小的方法有一个优点。构造函数方法使用的是通过new和delete管理的堆内存而非类型参数方法使用的是自动变量维护的内存栈执行速度更快尤其是在使用许多小型数组时。使用模板参数指定数组大小的方式缺点是每种数组大小都需要生成自己的模板类。也就是说下面的声明编译器将生成两个独立的模板类类名分别是ArrayTPdouble, 12和ArrayTPdouble, 13。ArrayTPdouble, 12ar1; ArrayTPdouble, 13ar2;而下面的声明编译器只生成一个模板类。Stackint books(5); Stackint students(20);实际编程中使用构造函数的方法更通用这是因为数组大小是作为类成员而不是硬编码存储在定义中的。这样可以将一种尺寸的数组赋给另一种尺寸的数组。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进