文章目录
- 1. 类模板参数
- 代码解释:
- 2.函数模板参数
- 3. 模板参数的限制
- 3.1 避免无效的表达式
- 4. 模板参数类型 `auto`
对于函数和类模板来说,模板参数可以是类型,也可以是普通值。与使用类型参数的模板一样, 定义在使用之前。使用这样的模板时,必须显式地指定值。然后,实例化生成的代码。本章演示了 新版栈类模板的特性。此外,还会展示非类型函数模板参数的示例,并讨论了这种技术的限制。
1. 类模板参数
与前几章的栈实现不同,这里可以通过使用固定大小的元素数组来实现栈。这种方法的优点是 避免了内存管理开销,但这为堆栈确定最佳容量带来了困难。指定的容量越小,堆栈越有可能快速 填满。指定的容量越大,有可能会浪费内存。好的解决方案是让堆栈的用户指定数组的大小,作为 堆栈的最大容量。
为此,可以将 size 定义为模板参数:
这里是你提供的代码及其中文注释和解释:
#include <array>
#include <cassert>
#include <string>
#include <iostream>// 模板类定义:实现一个栈,最大容量由模板参数指定
template<typename T, std::size_t MaxSize>
class Stack {
private:std::array<T, MaxSize> elems; // 用std::array保存栈中的元素std::size_t numElems; // 当前栈中元素的数量public:Stack(); // 构造函数void push(T const& elem); // 压入元素到栈void pop(); // 弹出栈顶元素T const& top() const; // 返回栈顶元素bool empty() const { // 判断栈是否为空return numElems == 0;}std::size_t size() const { // 返回当前栈中元素的数量return numElems;}
};// 构造函数初始化
template<typename T, std::size_t MaxSize>
Stack<T, MaxSize>::Stack(): numElems(0) // 初始时元素数量为0
{// 无需其他初始化
}// 压入元素到栈
template<typename T, std::size_t MaxSize>
void Stack<T, MaxSize>::push(T const& elem)
{assert(numElems < MaxSize); // 确保栈没有溢出elems[numElems] = elem; // 添加元素到栈顶++numElems; // 更新元素数量
}// 从栈顶弹出元素
template<typename T, std::size_t MaxSize>
void Stack<T, MaxSize>::pop()
{assert(!empty()); // 确保栈不为空--numElems; // 减少元素数量
}// 返回栈顶元素
template<typename T, std::size_t MaxSize>
T const& Stack<T, MaxSize>::top() const
{assert(!empty()); // 确保栈不为空return elems[numElems - 1]; // 返回栈顶元素
}int main() {Stack<int, 20> int20Stack; // 定义一个可存储20个整数的栈Stack<int, 40> int40Stack; // 定义一个可存储40个整数的栈Stack<std::string, 40> stringStack; // 定义一个可存储40个字符串的栈// 操作20个整数的栈int20Stack.push(7); // 压入整数7std::cout << int20Stack.top() << '\n'; // 输出栈顶元素int20Stack.pop(); // 弹出栈顶元素// 操作40个字符串的栈stringStack.push("hello"); // 压入字符串"hello"std::cout << stringStack.top() << '\n'; // 输出栈顶元素stringStack.pop(); // 弹出栈顶元素
}
代码解释:
-
模板类 Stack:
- 使用 C++ 模板定义,一个类型参数
T
用来指定栈中元素的类型,一个无符号整数MaxSize
用来指定栈的最大容量。 - 内部使用
std::array
存储元素,这样可以在编译时确定栈的最大容量。 - 提供了基本的栈操作:
push
(压入),pop
(弹出),top
(获取栈顶元素),以及检查栈是否为空的empty
方法。
- 使用 C++ 模板定义,一个类型参数
-
构造函数:
- 初始化
numElems
为0,表示栈开始时没有元素。
- 初始化
-
成员函数:
push
方法在向栈中压入新元素之前使用assert
确保栈未溢出。pop
和top
方法则使用assert
确保操作前栈不为空。
-
main
函数:- 创建了用于测试的多个
Stack
实例。 - 对整数栈和字符串栈都进行了基本操作测试。
- 创建了用于测试的多个
这个类提供了一个简单而有效的方式去实现一个带有固定最大容量的栈,充分使用了 C++ 的模板和断言特性来进行类型安全的编程以及运行时检查。
第二个模板形参 Maxsize 是 std::size_t 类型,指定了堆栈元素内部数组的容量.
template<typename T, std::size_t Maxsize> class Stack { private: std::array<T, Maxsize> elems; // elements
}
另外,在 push() 中,可以使用它来检查堆栈是否已满:
void push(T const& elem) { assert(numElems < Maxsize); // check if the stack is full elems[numElems] = elem; // append element ++numElems; // increment number of elements }
每个模板实例化都是独立的类型。因此,int20Stack 和 int40Stack 是两种不同的类型。不能使用 其中一个来代替另一个,也不能将其中一个分配给另一个。
同样,可以指定模板参数的默认值:
template<typename T = int , std::size_t MaxSize=100>
class Stack {
private:
}
2.函数模板参数
// 文件: basics/addvalue.hpp
template<int Val, typename T>
T addValue(T x) {return x + Val;
}// 使用示例
#include <algorithm>
#include <vector>
#include <iostream>int main() {std::vector<int> source = {1, 2, 3, 4, 5};std::vector<int> dest(source.size());// 使用 std::transform 为 source 每个元素加 5,然后存入 deststd::transform(source.begin(), source.end(), dest.begin(), addValue<5, int>);// 打印结果for (int value : dest) {std::cout << value << " ";}return 0;
}
在这个例子中,我们定义了一个函数模板 addValue
,它接受一个整型非类型参数 Val
和一个类型参数 T
。我们使用 std::transform
将 source
中的元素加上 5
,并存入 dest
向量中。
接下来是用于后面说明的两个模板定义示例:
template<auto Val, typename T = decltype(Val)>
T foo();// 确保传递的值与传递的类型相同
template<typename T, T Val = T{}>
T bar();
这些模板展示了更高级的C++模板编程技巧,如通过 decltype
推导返回类型以及确保值和类型参数的一致性。.
以下是重新排版后的内容,包含模板参数限制的相关说明和示例代码:
3. 模板参数的限制
非类型模板参数有一些限制,它们只能是以下类型的值:
- 整型常量值(包括枚举)
- 指向对象/函数/成员的指针
- 指向对象或函数的左值引用
std::nullptr_t
(即nullptr
的类型)
浮点数和类型对象不允许作为非类型模板参数:
template<double VAT> // 错误:浮点数不能作为模板参数
double process(double v) {return v * VAT;
}template<std::string name> // 错误:类类型对象不能作为模板参数
class MyClass {// ...
};
当为指针或引用传递模板参数时,指向的对象不能是字符串字面值、临时对象或数据成员等。在 C++17 前,这些限制更严格:
- C++11:对象必须具有外部链接。
- C++14:对象必须具有外部或内部链接。
以下示例演示这些限制:
template<char const* name>
class Message {// ...
};// 错误:字符串字面值 "hello" 不被允许
Message<"hello"> x;
但是,可以通过以下方式解决问题(取决于 C++ 的版本):
extern char const s03[] = "hi"; // 外部链接
char const s11[] = "hi"; // 内部链接int main() {Message<s03> m03; // 在所有版本中都可以Message<s11> m11; // 从C++11起有效static char const s17[] = "hi"; // 无链接Message<s17> m17; // 从C++17起有效
}
在这三种情况下,常量字符数组都由 "hi"
初始化,该对象作为 char const*
类型的模板参数。对象具有外部链接(s03
)在所有C++版本中有效;对象具有内部链接(s11
)在C++11和C++14中有效;对象没有链接则需要C++17的支持。
请参阅第 12.3.3 节和第 17.2 节,了解该领域在未来可能的变化。
以下是关于避免无效表达式的重新排版内容,其中包含非类型模板参数和正确使用运算符的示例:
3.1 避免无效的表达式
非类型模板参数可以是编译时的表达式。例如:
template<int I, bool B>
class C;// 使用编译时表达式赋值给模板参数
C<sizeof(int) + 4, sizeof(int) == 4> c;
在表达式中使用 >
时,必须注意将整个表达式放入圆括号中,让编译器正确判断 >
的结束位置:
C<42, sizeof(int) > 4> c; // 错误:第一个 `>` 结束了模板参数列表C<42, (sizeof(int) > 4)> c; // 正确:使用圆括号明确表达式范围
这个示例说明了当在模板参数表达式中使用大于运算符 >
时,如何避免语法错误。通过将表达式用括号括起,可以确保编译器正确理解参数列表的边界。
以下是关于使用 auto
作为模板参数类型的内容重新排版,其中示例展示了如何定义一个确定大小的堆栈类:
4. 模板参数类型 auto
在 C++17 中,可以使用 auto
来定义非类型模板参数。这个特性允许我们创建一个确定大小的堆栈类,例如:
#include <array>
#include <cassert>// 使用类型 T 和非类型模板参数 Maxsize 定义 Stack 类
template<typename T, auto Maxsize>
class Stack {
public:using size_type = decltype(Maxsize); // Maxsize 的类型private:std::array<T, Maxsize> elems; // 存储元素的数组size_type numElems; // 当前元素数量public:Stack(); // 构造函数void push(T const& elem); // 压入元素void pop(); // 弹出元素T const& top() const; // 返回栈顶元素bool empty() const { // 判断栈是否为空return numElems == 0;}size_type size() const { // 返回栈的当前元素数量return numElems;}
};// 构造函数实现
template<typename T, auto Maxsize>
Stack<T, Maxsize>::Stack()
: numElems(0) // 初始化元素数量为 0
{// 无需其他操作
}// push 函数实现
template<typename T, auto Maxsize>
void Stack<T, Maxsize>::push(T const& elem)
{assert(numElems < Maxsize); // 检查栈是否已满elems[numElems] = elem; // 添加元素++numElems; // 增加元素数量
}// pop 函数实现
template<typename T, auto Maxsize>
void Stack<T, Maxsize>::pop()
{assert(!empty()); // 检查栈是否为空--numElems; // 减少元素数量
}// top 函数实现
template<typename T, auto Maxsize>
T const& Stack<T, Maxsize>::top() const
{assert(!empty()); // 检查栈是否为空return elems[numElems - 1]; // 返回栈顶元素
}
通过使用 auto
作为占位符类型,Maxsize
可以是任何允许的非类型模板参数类型。内部实现可以同时使用这两个值:
std::array<T, Maxsize> elems; // 元素存储
using size_type = decltype(Maxsize); // Maxsize 的类型定义
这个设计允许 Stack
类灵活地处理不同类型和大小的堆栈。
以下是关于使用 auto
确定返回类型的内容重新排版,其中展示了如何在 C++14 后使用 auto
作为返回类型,以及如何基于模板参数类型进行不同的操作:
在 C++14 之后,可以在类的方法中使用 auto
作为返回类型,编译器将自动确定返回类型。以下是一个例子:
// 使用 auto 作为 size() 的返回类型
auto size() const {return numElems; // 返回当前元素数量
}
通过这个类的声明,当我们在使用堆栈时,元素数量的类型取决于模板参数的类型定义。例如:
// 文件: basics/stackauto.cpp#include <iostream>
#include <string>
#include "stackauto.hpp"int main() {Stack<int, 20u> int20Stack; // 最多可容纳 20 个 int 的栈Stack<std::string, 40> stringStack; // 最多可容纳 40 个字符串的栈// 操作 int 类型栈(最多 20 个元素)int20Stack.push(7);std::cout << int20Stack.top() << '\n';auto size1 = int20Stack.size();// 操作 string 类型栈(最多 40 个元素)stringStack.push("hello");std::cout << stringStack.top() << '\n';auto size2 = stringStack.size();// 检查 int20Stack 和 stringStack 的 size() 返回类型是否相同if (!std::is_same_v<decltype(size1), decltype(size2)>) {std::cout << "size types differ" << '\n';}
}
在这个例子中:
Stack<int, 20u> int20Stack; // 最多可容纳 20 个 int 的栈
由于传递了 20u
,因此内部的大小类型为 unsigned int
。
Stack<std::string, 40> stringStack; // 最多可容纳 40 个字符串的栈
由于传递了 40
,因此内部的大小类型为 int
。
因此,两个堆栈的 size()
返回的类型不同:
auto size1 = int20Stack.size();
auto size2 = stringStack.size();
size1
和 size2
的类型会有所不同。我们可以使用标准类型特征 std::is_same
和 decltype
进行检查:
if (!std::is_same_v<decltype(size1), decltype(size2)>) {std::cout << "size types differ" << '\n';
}
因此,程序的输出将是:
size types differ
通过这种方式,我们可以灵活地在模板中处理不同类型参数和非类型参数组合。
以下是关于 C++17 后的一些新特性重新排版的内容,展示了如何简化类型特征的使用和更灵活的模板参数使用:
在 C++17 之后,可以使用 _v
后缀来代替使用 ::value
,这使代码更简洁。以下是一个示例:
// 比较 size1 和 size2 的类型是否相同
if (!std::is_same_v<decltype(size1), decltype(size2)>) {std::cout << "size types differ" << '\n';
}
对非类型模板参数的限制仍然适用。尤其是,非类型模板参数不能是浮点数类型。例如:
Stack<int, 3.14> sd; // 错误:浮点数类型的非类型参数
在 C++17 中,可以通过 auto
接受任意类型的非类型参数,还可以传递字符串作为常量数组(甚至可以是静态的局部声明)。例如:
// 文件: basics/message.cpp#include <iostream>// 接受任意可能的非类型参数值(自 C++17 开始)
template<auto T>
class Message {
public:void print() {std::cout << T << '\n';}
};int main() {Message<42> msg1;msg1.print(); // 使用 int 42 进行初始化并打印此值static char const s[] = "hello";Message<s> msg2; // 使用 char const[6] "hello" 进行初始化msg2.print(); // 打印该值
}
此外,使用 template<decltype(auto) N>
可以支持更复杂的类型推导,这也允许将模板参数实例化为引用。例如:
template<decltype(auto) N>
class C {// ...
};int i;
C<(i)> x; // N 是 int&
关于更多细节可以参考第 15.10.1 节。这种方式允许模板参数拥有更多的灵活性和应用场景。