标准库特殊设施

总览

这一部分常见的标准库“特殊设施”包括:

  • tuple:把多个不同类型的值打包成一个对象
  • bitset:按位存储和操作固定大小的二进制位集合
  • 正则表达式:进行字符串模式匹配
  • 随机数库:生成随机数与随机分布
  • IO 其他功能:格式控制、未格式化 IO、随机访问

可以把它们理解为:

不是容器/算法主线,但非常常用的标准库工具

tuple 类型

什么是 tuple

tuple 是一个固定大小、可包含不同类型元素的对象。

它和 pair 很像,但比 pair 更一般:

  • pair 只能放 2 个值
  • tuple 可以放任意多个值

例如:

1
tuple<int, string, double> t(1, "Tom", 95.5);

头文件

1
#include <tuple>

基本定义

1
2
tuple<int, string, double> t1;
tuple<int, string, double> t2(1, "Alice", 88.5);

也可用 make_tuple 创建:

1
auto t3 = make_tuple(1, "Bob", 90.0);

make_tuple 会自动推断类型。

访问元素:get

tuple 里的元素不能像数组那样用下标访问,
而是用:

1
get<下标>(对象)

例如:

1
2
3
cout << get<0>(t3) << endl;
cout << get<1>(t3) << endl;
cout << get<2>(t3) << endl;

注意:

下标必须是编译期常量

所以不能写:

1
2
int i = 1;
get<i>(t3); // 错误

获取元素个数:tuple_size

1
2
tuple<int, string, double> t;
cout << tuple_size<decltype(t)>::value << endl; // 3

获取某个元素类型:tuple_element

1
2
tuple<int, string, double> t;
tuple_element<1, decltype(t)>::type s = "hello"; // string

tuple 的比较

如果两个 tuple 类型相同,且其中元素类型都支持比较,则可以比较:

1
2
tuple<int, int> a(1, 2), b(1, 3);
cout << (a < b) << endl;

比较规则是:

按字典序逐个元素比较

先比第 0 个,若相等再比第 1 个,依此类推。

tuple 的常见用途

函数返回多个值

1
2
3
tuple<string, int, double> get_student() {
return make_tuple("Tom", 18, 92.5);
}

把一组异构数据打包

配合泛型编程使用

C++17 补充:结构化绑定

如果课程允许写到 C++17,可以记这个很实用:

1
auto [name, age, score] = get_student();

它能直接把 tuple 拆开。

如果考试主要按 C++11/14,可知道即可。

小结

tuple = 可存多个不同类型元素的固定大小对象,常用 make_tuple 创建,用 get<>() 访问。

bitset 类型

什么是 bitset

bitset 表示:

固定大小的位序列

可以把它理解成“长度固定的二进制集合”。

例如:

1
bitset<8> b;

表示一个 8 位的二进制集合。

头文件

1
#include <bitset>

定义与初始化

默认初始化

1
bitset<8> b1;   // 00000000

用整数初始化

1
bitset<8> b2(13);   // 00001101

用字符串初始化

1
bitset<8> b3(string("1011"));   // 00001011

也可以指定从字符串的哪一段构造。

常用操作

测试某一位:test

1
2
3
bitset<8> b(13);   // 00001101
cout << b.test(0) << endl; // 1
cout << b.test(1) << endl; // 0

设置某一位:set

1
2
b.set(1);      // 把第 1 位设为 1
b.set(2, 0); // 把第 2 位设为 0

复位某一位:reset

1
2
b.reset(0);    // 第 0 位设为 0
b.reset(); // 全部设为 0

翻转:flip

1
2
b.flip(0);     // 翻转某一位
b.flip(); // 全部翻转

统计 1 的个数:count

1
cout << b.count() << endl;

判断是否有位为 1

1
2
3
b.any();   // 是否至少有一个 1
b.none(); // 是否全是 0
b.all(); // 是否全是 1

获取长度:size

1
cout << b.size() << endl;

位运算

bitset 支持常见按位运算:

  • &
  • |
  • ^
  • ~
  • <<
  • >>

例如:

1
2
3
bitset<8> a(12);  // 00001100
bitset<8> c(10); // 00001010
cout << (a & c) << endl;

下标访问

1
2
cout << b[0] << endl;
b[1] = 1;

但注意:

  • 下标 0 通常表示最低位
  • 和字符串中最左边字符的方向容易混

这是常见易错点。

转换

可以把 bitset 转成:

  • unsigned long
  • unsigned long long
  • 字符串

例如:

1
2
3
bitset<8> b(13);
cout << b.to_ulong() << endl; // 13
cout << b.to_string() << endl; // 00001101

常见用途

  • 二进制状态压缩
  • 标志位管理
  • 位运算题
  • 模拟固定宽度二进制表示

复习小结

bitset<N> 表示固定大小的二进制位集合,常用于位操作、状态表示、标志位管理。

正则表达式

C++ 正则表达式库定义在:

1
#include <regex>

基本概念

正则表达式用来描述一个字符串模式,然后用它去做:

  • 匹配
  • 搜索
  • 替换
  • 提取子串

主要类型

复习时重点记这几个:

  • regex:表示一个正则表达式
  • smatch:保存 string 匹配结果
  • cmatch:保存 C 风格字符串匹配结果
  • sregex_iterator:在字符串中迭代查找所有匹配项

创建正则对象

1
regex r("[a-z]+");

表示匹配一个或多个小写字母。

三个常见函数

regex_match

表示:

整个字符串必须完全匹配

例如:

1
2
3
regex r("[a-z]+");
cout << regex_match("abc", r) << endl; // true
cout << regex_match("abc123", r) << endl; // false

因为 abc123 不是“整个字符串都由小写字母组成”。

表示:

字符串中只要有一部分匹配即可

1
cout << regex_search("abc123", r) << endl;  // true

因为里面有 abc 匹配。

regex_replace

表示:

把匹配到的部分替换掉

1
2
3
string s = "abc 123 def 456";
regex r("\\d+");
cout << regex_replace(s, r, "#") << endl;

输出:

1
abc # def #

匹配结果对象

smatch

如果操作 string,通常用:

1
2
3
smatch m;
string s = "abc123";
regex r("([a-z]+)(\\d+)");

然后:

1
2
3
4
5
if (regex_search(s, m, r)) {
cout << m[0] << endl; // 整个匹配结果 abc123
cout << m[1] << endl; // 第一组 [a-z]+ -> abc
cout << m[2] << endl; // 第二组 \d+ -> 123
}

子表达式

圆括号 () 表示分组。
m[^0] 是整体匹配,m[^1]m[^2]… 是各组匹配结果。

这是非常常考的点。

常见正则符号速记

复习时不需要背太多,先记最基本的:

表达式 含义
. 任意单个字符
* 前一个字符出现 0 次或多次
+ 前一个字符出现 1 次或多次
? 前一个字符出现 0 次或 1 次
[abc] a/b/c 中任一个
[a-z] 任一小写字母
[^a-z] 非小写字母
\\d 数字
\\w 单词字符
\\s 空白字符
^ 行首
$ 行尾
() 分组

注意字符串中的转义

在 C++ 字符串里写正则时,经常要写双反斜杠:

1
regex r("\\d+");

因为:

  • 正则里想写 \d
  • C++ 字符串本身又要转义 \

所以要写成 "\\d+"

这是最常见的坑之一。

原始字符串字面值

为了避免太多转义,也可写成:

1
regex r(R"(\d+)");

这往往更清晰。

小结

regex_match 要求整个串匹配,regex_search 只要求找到一部分匹配,regex_replace 用于替换。


随机数

随机数库定义在:

1
#include <random>

这一部分非常容易混,复习时要抓住核心模型:

随机数生成 = 随机数引擎 + 分布对象

为什么不用 rand()

C++11 以后更推荐 <random>,因为它:

  • 质量更好
  • 更灵活
  • 分布更丰富
  • 接口更规范

所以现代 C++ 中应优先使用 <random>

随机数引擎

随机数引擎负责:

生成原始随机序列

例如:

1
default_random_engine e;

常见引擎:

  • default_random_engine
  • mt19937
  • mt19937_64

其中 mt19937 很常用。

直接调用引擎

1
2
3
default_random_engine e;
cout << e() << endl;
cout << e() << endl;

每次调用 e() 产生一个伪随机数。

固定种子

如果给相同种子,产生的序列相同:

1
2
default_random_engine e1(42);
default_random_engine e2(42);

这样 e1e2 生成的随机序列一致。

使用时间作为种子

常见写法:

1
default_random_engine e(time(0));

这样每次程序运行的种子通常不同。

分布对象

引擎给出的是“原始随机数”,
分布对象负责把这些随机数映射为某种分布。

常见的有:

  • uniform_int_distribution
  • uniform_real_distribution
  • normal_distribution
  • bernoulli_distribution

均匀整数分布

1
2
3
4
default_random_engine e;
uniform_int_distribution<unsigned> u(0, 9);

cout << u(e) << endl; // 产生 0~9 的整数

注意:

u(e) 才是最终随机数
u 是分布,e 是引擎

均匀实数分布

1
2
uniform_real_distribution<double> u(0.0, 1.0);
cout << u(e) << endl;

产生 [0.0, 1.0) 范围内的随机实数。

正态分布

1
2
normal_distribution<double> n(4.0, 1.5);
cout << n(e) << endl;

表示均值 4.0、标准差 1.5 的正态分布。

理解

随机数库分成两层:

引擎

负责“产生随机序列”

分布

负责“决定这些随机数长什么样”

所以:

引擎 + 分布 = 真正常用的随机数生成方式

常见写法模板

产生 [a, b] 的随机整数

1
2
3
mt19937 gen(time(0));
uniform_int_distribution<int> dist(a, b);
int x = dist(gen);

产生 [0,1) 的随机实数

1
2
3
mt19937 gen(time(0));
uniform_real_distribution<double> dist(0.0, 1.0);
double x = dist(gen);

IO 库的其他功能

这一部分主要分三类:

  • 格式控制
  • 未格式化 IO
  • 随机访问

格式控制

C++ IO 流支持控制输出/输入格式。

头文件常见有:

1
#include <iomanip>

常见格式控制项


控制布尔值输出

1
2
3
cout << true << " " << false << endl;   // 1 0
cout << boolalpha << true << " " << false << endl; // true false
cout << noboolalpha << true << endl; // 1

控制进制

1
2
3
cout << dec << 20 << endl;   // 十进制
cout << hex << 20 << endl; // 十六进制
cout << oct << 20 << endl; // 八进制

还可配合:

1
2
cout << showbase << hex << 20 << endl;  // 0x14
cout << noshowbase;

控制浮点数形式

1
2
cout << fixed << 3.1415926 << endl;
cout << scientific << 3.1415926 << endl;
  • fixed:定点表示
  • scientific:科学计数法

控制精度

1
cout << setprecision(4) << 3.1415926 << endl;

这个很常用。

控制宽度和填充

1
2
cout << setw(10) << 123 << endl;
cout << setfill('*') << setw(10) << 123 << endl;

输出宽度不足时用指定字符填充。

对齐方式

1
2
3
cout << left << setw(10) << 123 << endl;
cout << right << setw(10) << 123 << endl;
cout << internal << setw(10) << -123 << endl;

常见操纵符

来自 <iostream> 的:

  • boolalpha / noboolalpha
  • showbase / noshowbase
  • showpoint / noshowpoint
  • showpos / noshowpos
  • uppercase / nouppercase
  • hex / dec / oct
  • left / right / internal
  • fixed / scientific
  • endl / ends / flush

来自 <iomanip> 的:

  • setw
  • setprecision
  • setfill
  • setbase
  • setiosflags
  • resetiosflags

小结

格式控制就是通过流操纵符改变输出/输入的显示方式。

未格式化 IO

未格式化 IO 指不按通常的“按类型解析”方式,而是直接按字符/字节操作流。

常用在:

  • 逐字符读取
  • 读取原始数据
  • 跳过格式解析

常见函数

get()

读取一个字符:

1
2
char ch;
cin.get(ch);

也可:

1
int c = cin.get();

getline()

读取一整行:

1
2
string line;
getline(cin, line);

注意这是非常常用的行读取方式。

put()

输出一个字符:

1
cout.put('A');

read()write()

按字节块读写,常用于二进制 IO:

1
2
3
char buf[100];
cin.read(buf, 100);
cout.write(buf, cin.gcount());

gcount()

返回上一次未格式化输入操作读取的字符数。

ignore()

跳过若干字符:

1
cin.ignore(100, '\n');

常用于清理输入缓冲区。

peek()

查看下一个字符但不提取:

1
int c = cin.peek();

unget() / putback()

把字符放回输入流。

格式化输入与未格式化输入的区别

例如:

1
2
int x;
cin >> x;

这是格式化输入:会跳过空白并按整数解析。

而:

1
2
char ch;
cin.get(ch);

是未格式化输入:直接读字符,连空格和换行也可能读进来。

随机访问文件

随机访问主要针对文件流。

头文件:

1
#include <fstream>

两组位置指针

文件流通常有两个位置:

  • get 位置:读位置
  • put 位置:写位置

tellg / tellp

返回当前读/写位置:

1
2
ifstream in("file.txt");
cout << in.tellg() << endl;

seekg / seekp

移动读/写位置:

1
2
in.seekg(0);                 // 回到文件开头
out.seekp(0, ios::end); // 移到文件末尾

常见形式:

1
2
seekg(pos)
seekg(offset, dir)

其中 dir 可以是:

  • ios::beg
  • ios::cur
  • ios::end

典型用途

  • 跳到文件某个位置重新读取
  • 获取文件长度
  • 覆盖某个位置的数据
  • 实现简单二进制文件结构操作

易错点总结

tupleget<>() 访问,不是 []

get<>() 的下标必须是编译期常量

bitset 的大小是编译期固定的

bitset<8> 里的 8 不是运行时变量。

bitset 下标方向容易混

通常 b[^0] 是最低位。

正则表达式里常有双重转义

1
"\\d+"

不要忘。

regex_match 是整串匹配,regex_search 是局部匹配

这个极易考。

随机数不是只靠引擎,还要配合分布

1
dist(engine)

才是常用写法。

rand() 不如 <random> 推荐

现代 C++ 优先用随机数库。

getline>> 混用时要小心换行残留

必要时配合:

1
cin.ignore(...)

随机访问主要用于文件流,不是普通 cin/cout

结论速记

tuple

多个不同类型值的固定大小组合

bitset

固定长度位集合,支持按位操作

正则表达式

用模式匹配字符串,match 看整串,search 看部分

随机数

引擎产生随机序列,分布决定随机数形式

IO 其他功能

包括格式控制、未格式化 IO、文件随机访问

总结

tuple 负责异构数据打包,bitset 负责位集合操作,正则表达式负责模式匹配,随机数库负责“引擎 + 分布”的随机生成,IO 扩展功能负责格式控制、原始读写和文件随机访问。