4.11 — 字符

到目前为止,我们学过的基本数据类型都用于存储数字(整数和浮点数)或真/假值(布尔值)。但是,如果我们想存储字母或标点符号怎么办?

#include <iostream>

int main()
{
    std::cout << "Would you like a burrito? (y/n)";

    // We want the user to enter a 'y' or 'n' character
    // How do we do this?

    return 0;
}

char 数据类型被设计用于存储单个字符字符可以是单个字母、数字、符号或空白。

char 数据类型是一种整型,这意味着底层值以整数形式存储。类似于布尔值 `0` 被解释为 `false`,非零被解释为 `true`,char 变量存储的整数被解释为 `ASCII 字符`。

ASCII 代表美国信息交换标准代码,它定义了一种特定的方式,将英文字符(以及一些其他符号)表示为 0 到 127 之间的数字(称为 ASCII 码代码点)。例如,ASCII 码 97 被解释为字符 'a'。

字符字面量总是放在单引号之间(例如 'g', '1', ' ')。

以下是完整的 ASCII 字符表

代码符号代码符号代码符号代码符号
0NUL (空)32(空格)64@96`
1SOH (标题开始)33!65A97a
2STX (文本开始)34"66B98b
3ETX (文本结束)35#67C99c
4EOT (传输结束)36$68D100d
5ENQ (询问)37%69E101e
6ACK (确认)38&70F102f
7BEL (响铃)39'71G103g
8BS (退格)40(72H104h
9HT (水平制表符)41)73I105i
10LF (换行/新行)42*74J106j
11VT (垂直制表符)43+75K107k
12FF (换页符 / 新页)44,76L108l
13CR (回车)45-77M109m
14SO (移出)46.78N110n
15SI (移入)47/79O111o
16DLE (数据链路转义)48080P112p
17DC1 (数据控制 1)49181Q113q
18DC2 (数据控制 2)50282R114r
19DC3 (数据控制 3)51383S115s
20DC4 (数据控制 4)52484T116t
21NAK (否定确认)53585U117u
22SYN (同步空闲)54686V118v
23ETB (传输块结束)55787W119w
24CAN (取消)56888X120x
25EM (介质结束)57989Y121y
26SUB (替换)58:90Z122z
27ESC (转义)59;91[123{
28FS (文件分隔符)60<92\124|
29GS (组分隔符)61=93]125}
30RS (记录分隔符)62>94^126~
31US (单元分隔符)63?95_127DEL (删除)

代码 0-31 和 127 被称为不可打印字符。这些代码旨在控制打印机等外围设备(例如,通过指示打印机如何移动打印头)。现在大多数已过时。如果您尝试打印这些字符,结果取决于您的操作系统(您可能会得到一些类似表情符号的字符)。

代码 32-126 被称为可打印字符,它们代表大多数计算机用于显示基本英文字符的字母、数字字符和标点符号。

如果您尝试打印超出 ASCII 范围的字符,结果也取决于您的操作系统。

初始化字符

您可以使用字符字面量初始化 char 变量

char ch2{ 'a' }; // initialize with code point for 'a' (stored as integer 97) (preferred)

您也可以用整数初始化 char,但应尽可能避免这样做

char ch1{ 97 }; // initialize with integer 97 ('a') (not preferred)

警告

注意不要混淆字符数字和整数数字。以下两种初始化方式不同

char ch{5}; // initialize with integer 5 (stored as integer 5)
char ch{'5'}; // initialize with code point for '5' (stored as integer 53)

字符数字旨在当我们想将数字表示为文本,而不是作为用于数学运算的数字时使用。

打印字符

当使用 std::cout 打印 char 时,std::cout 将 char 变量输出为 ASCII 字符

#include <iostream>

int main()
{
    char ch1{ 'a' }; // (preferred)
    std::cout << ch1; // cout prints character 'a'

    char ch2{ 98 }; // code point for 'b' (not preferred)
    std::cout << ch2; // cout prints a character ('b')


    return 0;
}

这会产生结果

ab

我们也可以直接输出字符字面量

std::cout << 'c';

这会产生结果

c

输入字符

以下程序要求用户输入一个字符,然后打印该字符

#include <iostream>

int main()
{
    std::cout << "Input a keyboard character: ";

    char ch{};
    std::cin >> ch;
    std::cout << "You entered: " << ch << '\n';

    return 0;
}

以下是运行结果

Input a keyboard character: q
You entered: q

请注意,std::cin 允许您输入多个字符。但是,变量 ch 只能容纳 1 个字符。因此,只有第一个输入字符被提取到变量 ch 中。用户输入的其余部分留在 std::cin 使用的输入缓冲区中,可以通过后续对 std::cin 的调用提取。

您可以在以下示例中看到此行为

#include <iostream>

int main()
{
    std::cout << "Input a keyboard character: "; // assume the user enters "abcd" (without quotes)

    char ch{};
    std::cin >> ch; // ch = 'a', "bcd" is left queued.
    std::cout << "You entered: " << ch << '\n';

    // Note: The following cin doesn't ask the user for input, it grabs queued input!
    std::cin >> ch; // ch = 'b', "cd" is left queued.
    std::cout << "You entered: " << ch << '\n';
    
    return 0;
}
Input a keyboard character: abcd
You entered: a
You entered: b

如果您想一次读取多个字符(例如,读取姓名、单词或句子),您将需要使用字符串而不是字符。字符串是字符的集合(因此,字符串可以包含多个符号)。我们将在接下来的课程中讨论这个问题 (5.7 -- std::string 简介)。

提取空白字符

由于提取输入会忽略前导空白,这在尝试将空白字符提取到 char 变量时可能会导致意外结果

#include <iostream>

int main()
{
    std::cout << "Input a keyboard character: "; // assume the user enters "a b" (without quotes)

    char ch{};
    std::cin >> ch; // extracts a, leaves " b\n" in stream
    std::cout << "You entered: " << ch << '\n';

    std::cin >> ch; // skips leading whitespace (the space), extracts b, leaves "\n" in stream
    std::cout << "You entered: " << ch << '\n';

    return 0;
}
Input a keyboard character: a b
You entered: a
You entered: b

在上面的例子中,我们可能期望提取空格,但由于前导空白被跳过,我们反而提取了字符“b”。

解决此问题的一种简单方法是使用 `std::cin.get()` 函数进行提取,因为此函数不忽略前导空格

#include <iostream>

int main()
{
    std::cout << "Input a keyboard character: "; // assume the user enters "a b" (without quotes)

    char ch{};
    std::cin.get(ch); // extracts a, leaves " b\n" in stream
    std::cout << "You entered: " << ch << '\n';

    std::cin.get(ch); // extracts space, leaves "b\n" in stream
    std::cout << "You entered: " << ch << '\n';

    return 0;
}
Input a keyboard character: a b
You entered: a
You entered:  

字符大小、范围和默认符号

C++ 将 char 定义为始终为 1 字节大小。默认情况下,char 可以是有符号的或无符号的(尽管通常是有符号的)。如果您使用 char 来保存 ASCII 字符,则无需指定符号(因为有符号和无符号 char 都可以保存 0 到 127 之间的值)。

如果您使用 char 存储小整数(除非您明确优化空间,否则不应这样做),您应该始终指定它是有符号还是无符号。有符号 char 可以存储 -128 到 127 之间的数字。无符号 char 可以存储 0 到 255 之间的数字。

转义序列

C++ 中有一些字符序列具有特殊含义。这些字符被称为转义序列。转义序列以反斜杠“\”字符开头,然后是后续的字母或数字。

您已经见过最常见的转义序列:`'\n'`,它可以用于打印换行符

#include <iostream>

int main()
{
    int x { 5 };
    std::cout << "The value of x is: " << x << '\n'; // standalone \n goes in single quotes
    std::cout << "First line\nSecond line\n";        // \n can be embedded in double quotes
    return 0;
}

这输出

The value of x is: 5
First line
Second line

另一个常用转义序列是 `'\t'`,它嵌入一个水平制表符

#include <iostream>

int main()
{
    std::cout << "First part\tSecond part";
    return 0;
}

输出

First part	Second part

其他三个值得注意的转义序列是
\' 打印单引号
\" 打印双引号
\\ 打印反斜杠

这是所有转义序列的表格

名称符号含义
警报\a发出警报,例如蜂鸣声
退格\b将光标后退一个空格
换页\f将光标移动到下一逻辑页
换行\n将光标移动到下一行
回车\r将光标移动到行首
水平制表符\t打印水平制表符
垂直制表符\v打印垂直制表符
单引号\'打印单引号
双引号\"打印双引号
反斜杠\\打印一个反斜杠。
问号\?打印一个问号。
不再相关。您可以使用未转义的问号。
八进制数\(数字)转换为由八进制表示的字符

十六进制数\x(数字)转换为由十六进制数表示的字符

以下是一些示例

#include <iostream>

int main()
{
    std::cout << "\"This is quoted text\"\n";
    std::cout << "This string contains a single backslash \\\n";
    std::cout << "6F in hex is char '\x6F'\n";
    return 0;
}

打印

"This is quoted text"
This string contains a single backslash \
6F in hex is char 'o'

警告

转义序列以反斜杠(\)开头,而不是正斜杠(/)。如果您不小心使用了正斜杠,它可能仍然会编译,但不会产生所需的结果。

换行符 (\n) 与 std::endl

我们在课程 1.5 — iostream 简介:cout、cin 和 endl 中讨论了此主题。

将符号放在单引号和双引号之间的区别是什么?

单引号之间的文本被视为 `char` 字面量,表示单个字符。例如,`'a'` 表示字符 `a`,`'+'` 表示加号字符,`'5'` 表示字符 `5` (不是数字 5),`'\n'` 表示换行符。

双引号之间的文本(例如“Hello, world!”)被视为 C 风格字符串字面量,可以包含多个字符。我们将在课程 5.2 — 字面量 中讨论字符串。

最佳实践

单个字符通常应使用单引号(例如 `‘t’` 或 `‘\n’`),而不是双引号(例如 `“t”` 或 `“\n”`)。一个可能的例外是在输出时,为了保持一致性,最好对所有内容使用双引号(请参阅课程 1.5 — iostream 简介:cout、cin 和 endl)。

避免多字符字面量

由于向后兼容性原因,许多 C++ 编译器支持**多字符字面量**,即包含多个字符的 char 字面量(例如 `‘56’`)。如果支持,这些字面量具有实现定义的值(意味着它因编译器而异)。由于它们不属于 C++ 标准,并且其值未严格定义,因此应避免使用多字符字面量。

最佳实践

避免多字符字面量(例如 `‘56’`)。

多字符字面量支持通常会给新程序员带来问题,当他们忘记转义序列使用正斜杠还是反斜杠时

#include <iostream>

int add(int x, int y)
{
	return x + y;
}

int main()
{
	std::cout << add(1, 2) << '/n'; // we used a forward slash instead of a backslash here

	return 0;
}

程序员期望此程序打印值 `3` 和一个换行符。但相反,在作者的机器上,它输出以下内容

312142

问题在于程序员不小心使用了 `'/n'` (一个由正斜杠和字符 `‘n’` 组成的多字符字面量),而不是 `'\n'` (换行符的转义序列)。程序首先正确地打印了 `3` ( `add(1, 2)` 的结果)。但随后它打印了多字符字面量 `'/n'` 的值,在作者的机器上,该值是实现定义的 `12142`。

警告

确保您的换行符使用的是转义序列 `'\n'`,而不是多字符字面量 `'/n'`。

关键见解

请注意,如果我们对输出 `"/n"` 使用了双引号,程序将打印 `3/n`,这仍然是错误的,但混淆程度会大大降低。

这里是另一个例子。我们从以下内容开始

#include <iostream>

int main()
{
    int x { 5 };
    std::cout << "The value of x is " << x << '\n'; 

    return 0;
}

此程序输出结果与您预期完全一致

The value of x is 5

但是这个输出不够激动人心,所以我们决定在换行符之前添加一个感叹号

#include <iostream>

int main()
{
    int x { 5 };
    std::cout << "The value of x is " << x << '!\n'; // added exclamation point

    return 0;
}

虽然我们期望输出如下

The value of x is 5!

因为 `‘!\n’` 是一个多字符字面量,所以在作者的机器上,它实际打印了

The value of x is 58458

这不仅不正确,而且很难调试,因为您很可能会假设 `x` 的值是错误的。

在输出字符字面量时使用双引号(而不是单引号)可以更容易地发现或完全避免此类问题。

那么其他字符类型呢,`wchar_t`、`char8_t`、`char16_t` 和 `char32_t`?

就像 ASCII 将整数 0-127 映射到美国英语字符一样,其他字符编码标准也存在,用于将整数(不同大小)映射到其他语言中的字符。除了 ASCII 之外,最著名的映射是 Unicode 标准,它将超过 144,000 个整数映射到许多不同语言中的字符。因为 Unicode 包含如此多的代码点,一个 Unicode 代码点需要 32 位来表示一个字符(称为 UTF-32)。然而,Unicode 字符也可以使用多个 16 位或 8 位字符进行编码(分别称为 UTF-16 和 UTF-8)。

C++11 添加了 `char16_t` 和 `char32_t`,以提供对 16 位和 32 位 Unicode 字符的显式支持。这些字符类型的大小与 `std::uint_least16_t` 和 `std::uint_least32_t` 分别相同(但它们是不同的类型)。C++20 添加了 `char8_t`,以提供对 8 位 Unicode (UTF-8) 的支持。它是一个不同的类型,使用与 `unsigned char` 相同的表示。

您不需要使用 `char8_t`、`char16_t` 或 `char32_t`,除非您计划使您的程序与 Unicode 兼容。`wchar_t` 在几乎所有情况下都应避免使用(除非与 Windows API 接口),因为其大小是实现定义的。

Unicode 和本地化通常超出这些教程的范围,因此我们不再深入讨论。在此期间,您在处理字符(和字符串)时应仅使用 ASCII 字符。使用其他字符集中的字符可能会导致您的字符显示不正确。

guest
您的电子邮箱地址将不会被显示
发现错误?请在上方留言!
与勘误相关的评论在处理后将被删除,以帮助减少混乱。感谢您帮助使网站对每个人都更好!
来自 https://gravatar.com/ 的头像与您提供的电子邮箱地址相关联。
有回复时通知我:  
453 条评论
最新
最早 最多投票
内联反馈
查看所有评论