第5.12章: 指针与 vector、array 和 string
概述
现代 C++ 容器在原始内存之上提供了安全、便利的抽象。
然而,三大连续容器——
::fast_io::vector、::fast_io::array 和
::fast_io::string——在需要时都会暴露原始指针。
在本章中,你将学到:
- 指针如何与
::fast_io::vector交互 - 指针如何与
::fast_io::array交互 - 指针如何与
::fast_io::string交互 .data()返回什么.c_str()返回什么- 为什么这些容器保证连续存储
- 指针算术如何作用于容器的存储
::std::to_address如何从迭代器中取出原始指针
1. ::fast_io::vector 与指针
::fast_io::vector<T> 把元素存储在一块连续的内存中。
这意味着:
- 元素的布局就像 C 风格数组
- 指针算术与 C 风格数组完全一致
v.data()返回指向首元素的指针v.index_unchecked(pos)提供非检查的元素访问v.front_unchecked()提供对首元素的非检查访问v.back_unchecked()提供对末元素的非检查访问
::fast_io::vector<int> v{1, 2, 3, 4};
int *p = v.data(); // pointer to v[0]
int *q = p + 2; // points to v[2]
int a = v.index_unchecked(1); // unchecked v[1]
int b = v.front_unchecked(); // unchecked v[0]
int c = v.back_unchecked(); // unchecked v[3]
⚠️ 非检查访问从不执行边界检查。
注意:fast_io 中的边界检查通常极其廉价。
当检测到越界访问时,fast_io 会立刻调用
fast_terminate(),既不打印诊断信息,也不
执行昂贵的错误处理。这让带检查的访问既快速又能提供安全性。
2. ::fast_io::array 与指针
::fast_io::array<T, N> 是固定大小的容器,行为
类似更安全的 C 风格数组。
它提供:
- 连续存储
- 带边界检查的
[]访问 - 用于有意非检查访问的
index_unchecked(pos) - 对首元素的非检查访问
front_unchecked() - 对末元素的非检查访问
back_unchecked() - 返回指向首元素指针的
.data()
::fast_io::array<int, 3> a{10, 20, 30};
int *p = a.data(); // pointer to a[0]
int *q = p + 1; // points to a[1]
int x = a.index_unchecked(2); // unchecked a[2]
int y = a.front_unchecked(); // unchecked a[0]
int z = a.back_unchecked(); // unchecked a[2]
⚠️ 非检查函数从不执行边界检查。
注意:fast_io 中的边界检查极其廉价,
因为失败时唯一采取的动作就是 fast_terminate()。
与 C 风格数组不同,::fast_io::array 允许大小为 0。
3. ::fast_io::string 与指针
::fast_io::string 把字符存储在一块连续的内存中。
这意味着:
s.data()返回指向首字符的指针s.c_str()返回以空字符结尾的指针- 指针算术与
char[]完全一致 s.index_unchecked(pos)提供非检查的字符访问s.front_unchecked()提供对首字符的非检查访问s.back_unchecked()提供对末字符的非检查访问
::fast_io::string s{"Hello"};
char *p = s.data(); // pointer to 'H'
char const *q = s.c_str(); // pointer to 'H', null-terminated
char a = s.index_unchecked(1); // unchecked 'e'
char b = s.front_unchecked(); // unchecked 'H'
char c = s.back_unchecked(); // unchecked 'o'
⚠️ 非检查访问不验证边界。
注意:fast_io 中的边界检查极其廉价,
因为失败时会立刻调用 fast_terminate()。
提醒:
s.data() 可能不是以空字符结尾的。
s.c_str() 始终以空字符结尾。
4. 连续存储的保证
这三种容器都保证其元素是连续存储的:
::fast_io::vector::fast_io::array::fast_io::string
这意味着只要你保持在边界之内,指针算术就是有效的。
int *p = v.data();
int *end = p + v.size(); // one past the end
这与 C 风格数组的行为完全相同。
5. 容器不会退化
C 风格数组在大多数表达式中会自动退化。 容器则不会。
int a[3]{1,2,3};
int *p = a; // decay
但是:
::fast_io::vector<int> v{1,2,3};
// int *p = v; // ❌ does not compile
你必须显式地用 .data() 请求一个指针。
6. 作用于容器存储的指针算术
一旦你从 .data() 获得了指针,指针算术的行为就
与 C 风格数组完全相同。
int *p = a.data();
int *q = p + 2; // points to a[2]
对 ::fast_io::string 同样适用:
char *p = s.data();
char *q = p + 4; // points to 'o'
一如既往,越界就是未定义行为。
7. ::std::to_address 与容器迭代器
::std::to_address 是从任何行为类似指针的东西
获取原始指针的通用方式。
- 裸指针
- 容器迭代器
- 类似指针的对象
这很重要,因为容器迭代器并不是裸指针, 尽管它们的行为像裸指针。
示例:::fast_io::vector
auto it = v.begin();
int *p = ::std::to_address(it);
示例:::fast_io::array
auto it = a.begin();
int *p = ::std::to_address(it);
示例:::fast_io::string
auto it = s.begin();
char *p = ::std::to_address(it);
::std::to_address 是从迭代器中取出原始指针的现代、
标准认可的方式。
8. 汇总表
| 容器 | 连续? | .data() 得到的指针 | 空字符结尾? | 退化? | 非检查访问? |
|---|---|---|---|---|---|
::fast_io::vector<T> |
✔ 是 | T* |
❌ 否 | ❌ 否 | ✔ index/front/back_unchecked() |
::fast_io::array<T,N> |
✔ 是 | T* |
❌ 否 | ❌ 否 | ✔ index/front/back_unchecked() |
::fast_io::string |
✔ 是 | char* |
✔ 通过 .c_str() |
❌ 否 | ✔ index/front/back_unchecked() |
要点
::fast_io::vector、::fast_io::array和::fast_io::string都提供连续存储。.data()返回指向首元素的指针。- 对
::fast_io::string,.c_str()返回以空字符结尾的指针。 - 容器不会退化;你必须调用
.data()。 - 作用于容器存储的指针算术与 C 风格数组完全一致。
index_unchecked()、front_unchecked()和back_unchecked()提供快速的非检查访问。fast_io中的边界检查极其廉价,因为它使用fast_terminate()。::std::to_address从迭代器中取出原始指针。- 越界的指针算术是未定义行为。