第4.2章: ::fast_io::char_category
概述
::fast_io::char_category 模块提供了一组
constexpr 字符分类与变换函数。
这些函数判断一个字符是小写、大写、数字、
空白、标点,等等。
所有函数都遵循程序的执行字符集(execution charset)。 这确保在基于 ASCII 和基于 EBCDIC 的系统上行为都正确。
1. 源字符集 vs 执行字符集
C++ 区分:
- 源字符集 —— 源文件中的字符是如何编码的
- 执行字符集 —— 字符在运行时是如何表示的
::fast_io::char_category 始终使用执行字符集。
这意味着:
- 如果
'A'的值是0x41,则适用 ASCII 规则。 - 如果
'A'具有 EBCDIC 值(不是0x41),则适用 EBCDIC 规则。 - 其他所有字符集(UTF-8、GB18030、Shift-JIS 等)对码点
[0, 127]都使用 ASCII 规则。
2. ::fast_io::char_literal_v 与 char_literal
创建遵循执行字符集的字符常量,主要方式是
::fast_io::char_literal_v。它在编译期取一个 char8_t
值,并产出执行字符集中的一个字符。
char c = ::fast_io::char_literal_v<u8'a', char>;
只要你手上有编译期的字符字面量,就用这种形式。
还有一个函数形式 ::fast_io::char_literal,它在运行时
取一个 char8_t 值:
char8_t runtime_ch{/* ... */};
char c = ::fast_io::char_literal<char>(runtime_ch);
这个版本更灵活,但可能更慢,因为它必须在运行时执行。
当字面量在编译期已知时,优先使用 char_literal_v。
3. ::fast_io::arithmetic_char_literal_v 与 arithmetic_char_literal
::fast_io::arithmetic_char_literal 的行为类似
char_literal,但它是为算术表达式设计的,
尤其是当 wchar_t 使用非 UTF 的执行字符集时。
char8_t runtime_ch{/* ... */};
char c = ::fast_io::arithmetic_char_literal<char>(runtime_ch);
简写形式 arithmetic_char_literal_v 是
编译期版本:
wchar_t w = ::fast_io::arithmetic_char_literal_v<u8'b', wchar_t>;
当你手上是编译期 char8_t 字面量,并且需要执行字符集中
适合算术运算的值时,请使用 arithmetic_char_literal_v。
4. 基本分类
每个分类函数接受单个字符,返回
true 或 false。
bool b1 = ::fast_io::char_category::is_c_lower('a');
bool b2 = ::fast_io::char_category::is_c_upper('A');
bool b3 = ::fast_io::char_category::is_c_digit('7');
bool b4 = ::fast_io::char_category::is_c_space(' ');
5. 配合 ::fast_io::string 使用 char_category
::fast_io::string 连续存储字符,因此你可以
遍历它并应用任何分类函数。
::fast_io::string s{"Hello World"};
for(char ch : s)
{
if(::fast_io::char_category::is_c_lower(ch))
{
// lowercase letter
}
}
6. 示例:统计小写字母
::fast_io::string s{"Hello fast_io!"};
std::size_t count{};
for(char ch : s)
{
if(::fast_io::char_category::is_c_lower(ch))
{
++count;
}
}
7. 为什么不直接比较范围?
你可能会想通过与某个范围比较来检测小写字母:
if(ch >= ::fast_io::char_literal_v<u8'a', char> &&
ch <= ::fast_io::char_literal_v<u8'z', char>)
{
++count;
}
即使这样使用了 char_literal_v,并让端点遵循执行
字符集,它仍然假定所有小写字母构成一个单一的连续区间。这
对 ASCII 成立,但并非对所有执行字符集(例如 EBCDIC)都保证成立。
始终优先使用:
if(::fast_io::char_category::is_c_lower(ch))
{
++count;
}
is_c_lower 的实现具备关于执行字符集的正确知识,
不依赖天真的区间假设。
8. 示例:筛选字母字符
::fast_io::string input{"Hello 123 World!"};
::fast_io::string letters{};
for(char ch : input)
{
if(::fast_io::char_category::is_c_alpha(ch))
{
letters.push_back(ch);
}
}
9. 变换整个字符串
::fast_io::char_category::ranges 命名空间提供了
一次作用于整个区间的函数。
::fast_io::string s{"Hello FAST_IO!"};
::fast_io::char_category::ranges::to_c_lower(s);
调用之后,s 变成 "hello fast_io!"。
::fast_io::char_category::ranges::to_c_upper(s);
::fast_io::char_category::ranges::to_c_halfwidth(s);
10. 字符分类函数
| 函数 | 说明 |
|---|---|
is_c_alnum | 字母或数字 |
is_c_alpha | 字母 |
is_c_blank | 空格或制表符 |
is_c_cntrl | 控制字符 |
is_c_digit | 数字 0–9 |
is_c_fullwidth | 全角字符 |
is_c_graph | 可见(非空格)字符 |
is_c_halfwidth | 半角字符 |
is_c_lower | 小写字母 |
is_c_print | 可打印字符 |
is_c_punct | 标点符号 |
is_c_space | 空白 |
is_c_upper | 大写字母 |
is_c_xdigit | 十六进制数字 |
is_html_whitespace | HTML 空白 |
is_dos_file_invalid_character | DOS 文件名中的非法字符 |
11. 字符变换函数
| 函数 | 说明 |
|---|---|
to_c_lower | 把单个字符转换为小写 |
to_c_upper | 把单个字符转换为大写 |
to_c_halfwidth | 把单个字符转换为半角 |
ranges::to_c_lower | 把整个区间转换为小写 |
ranges::to_c_upper | 把整个区间转换为大写 |
ranges::to_c_halfwidth | 把整个区间转换为半角 |
12. 使用 char_category_family 与 char_category_traits
12.1 char_category_family 定义
enum class char_category_family : ::std::uint_least32_t
{
c_alnum, // Alphanumeric characters (letters and digits)
c_alpha, // Alphabetic Character
c_blank, // Space or tab
c_cntrl, // Control characters (ASCII 0x00-0x1F, 0x7F or EBCDIC equivalents)
c_digit, // Numeric digits (0-9)
c_fullwidth, // Full-width character
c_graph, // Graphical characters (alphanumeric + punctuation)
c_halfwidth, // Half-width character
c_lower, // Lowercase alphabetic characters
c_print, // Printable characters (includes space)
c_punct, // Punctuation characters
c_space, // Whitespace characters (space, tab, newline, etc.)
c_upper, // Uppercase alphabetic characters
c_xdigit, // Hexadecimal digits (0-9, A-F, a-f)
dos_file_invalid_character, // DOS Path invalid character
html_whitespace // HTML whitespace
};
12.2 创建分类器
using lower_pred =
::fast_io::char_category::char_category_traits<
::fast_io::char_category::char_category_family::c_lower,
false
>;
lower_pred pred{};
bool a = pred('a'); // true
bool b = pred('Z'); // false
12.3 取反的分类器
using not_lower_pred =
::fast_io::char_category::char_category_traits<
::fast_io::char_category::char_category_family::c_lower,
true
>;
not_lower_pred pred{};
bool a = pred('A'); // true (because 'A' is NOT lowercase)
12.4 配合 ::fast_io::string 使用 traits
::fast_io::string s{"Hello 123 World"};
using digit_pred =
::fast_io::char_category::char_category_traits<
::fast_io::char_category::char_category_family::c_digit,
false
>;
auto it = digit_pred::find(s.begin(), s.end());
if(it != s.end())
{
// *it is the first digit in the string
}
这为你提供了一种灵活、通用的方式,在遵循执行字符集的前提下 对文本进行分类和搜索。
要点
::fast_io::char_category始终遵循执行字符集。- EBCDIC 系统使用 EBCDIC 规则;其他所有系统对码点
[0,127]使用 ASCII 规则。 char_literal_v是从char8_t创建执行字符集字面量的主要方式。char_literal与arithmetic_char_literal用于运行时的char8_t值。arithmetic_char_literal_v对宽字符的算术运算很有用。- 分类与变换函数都是 constexpr 且可预测的。
- 基于区间的函数可以就地变换整个字符串。
- 避免手工的范围检查;改用
is_c_lower及相关函数。 char_category_family与char_category_traits让你可以构建通用的、感知执行字符集的谓词。