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数据类型设计用于存储单个字符。字符character可以是单个字母、数字、符号或空格。
char数据类型属于整数的类型,意味着其底层值以整数形式存储。类似于布尔值0被解释为false、非零值被解释为true,char变量存储的整数会被解释为ASCII字符。
ASCII即美国信息交换标准代码American Standard Code for Information Interchange,它定义了一种将英文字符(及少量其他符号)表示为0至127之间数字(称为ASCII码ASCII code或码点code point)的特定方式。例如,ASCII码97被解释为字符'a'。
字符常量始终用单引号括起(如‘g’、‘1’、‘ ’)。
以下是完整的ASCII字符表:
| Code | Symbol | Code | Symbol | Code | Symbol | Code | Symbol |
|---|---|---|---|---|---|---|---|
| 0 | NUL (null) | 32 | (space) | 64 | @ | 96 | ` |
| 1 | SOH (start of header) | 33 | ! | 65 | A | 97 | a |
| 2 | STX (start of text) | 34 | ” | 66 | B | 98 | b |
| 3 | ETX (end of text) | 35 | # | 67 | C | 99 | c |
| 4 | EOT (end of transmission) | 36 | $ | 68 | D | 100 | d |
| 5 | ENQ (enquiry) | 37 | % | 69 | E | 101 | e |
| 6 | ACK (acknowledge) | 38 | & | 70 | F | 102 | f |
| 7 | BEL (bell) | 39 | ’ | 71 | G | 103 | g |
| 8 | BS (backspace) | 40 | ( | 72 | H | 104 | h |
| 9 | HT (horizontal tab) | 41 | ) | 73 | I | 105 | i |
| 10 | LF (line feed/new line) | 42 | * | 74 | J | 106 | j |
| 11 | VT (vertical tab) | 43 | + | 75 | K | 107 | k |
| 12 | FF (form feed / new page) | 44 | , | 76 | L | 108 | l |
| 13 | CR (carriage return) | 45 | - | 77 | M | 109 | m |
| 14 | SO (shift out) | 46 | . | 78 | N | 110 | n |
| 15 | SI (shift in) | 47 | / | 79 | O | 111 | o |
| 16 | DLE (data link escape) | 48 | 0 | 80 | P | 112 | p |
| 17 | DC1 (data control 1) | 49 | 1 | 81 | Q | 113 | q |
| 18 | DC2 (data control 2) | 50 | 2 | 82 | R | 114 | r |
| 19 | DC3 (data control 3) | 51 | 3 | 83 | S | 115 | s |
| 20 | DC4 (data control 4) | 52 | 4 | 84 | T | 116 | t |
| 21 | NAK (negative acknowledge) | 53 | 5 | 85 | U | 117 | u |
| 22 | SYN (synchronous idle) | 54 | 6 | 86 | V | 118 | v |
| 23 | ETB (end of transmission block) | 55 | 7 | 87 | W | 119 | w |
| 24 | CAN (cancel) | 56 | 8 | 88 | X | 120 | x |
| 25 | EM (end of medium) | 57 | 9 | 89 | Y | 121 | y |
| 26 | SUB (substitute) | 58 | : | 90 | Z | 122 | z |
| 27 | ESC (escape) | 59 | ; | 91 | [ | 123 | { |
| 28 | FS (file separator) | 60 | < | 92 | \ | 124 | |
| 29 | GS (group separator) | 61 | = | 93 | ] | 125 | } |
| 30 | RS (record separator) | 62 | > | 94 | ^ | 126 | ~ |
| 31 | US (unit separator) | 63 | ? | 95 | _ | 127 | DEL (delete) |
代码0-31和127被称为不可打印字符。这些代码原本用于控制打印机等外围设备(例如指导打印机如何移动打印头)。如今其中大部分已过时。若尝试打印这些字符,结果取决于操作系统(可能显示类似表情符号的字符)。
代码32-126称为可打印字符,代表字母、数字及标点符号,构成计算机显示基础英文文本的核心元素。
若尝试打印超出ASCII范围的字符,其显示效果同样取决于操作系统。
初始化字符
您可以使用字符字面量初始化字符变量:
char ch2{ 'a' }; // initialize with code point for 'a' (stored as integer 97) (preferred)
字符也可以用整数初始化,但应尽量避免这样做。
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 打印字符时,std::cout 会将字符变量作为 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;
}
这产生了以下结果:

我们也可以直接输出字符常量:
std::cout << 'c';
这产生了以下结果:

输入字符
以下程序要求用户输入一个字符,然后打印该字符:
#include <iostream>
int main()
{
std::cout << "Input a keyboard character: ";
char ch{};
std::cin >> ch;
std::cout << "You entered: " << ch << '\n';
return 0;
}
以下是一次运行的输出结果:

请注意,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;
}

若需一次读取多个字符(例如读取姓名、单词或句子),则应使用字符串而非单个字符。字符串是由连续字符组成的集合(因此字符串可存储多个符号)。我们将在后续课程中讨论此内容(5.7节——std::string简介)。
提取空格字符
由于提取输入时会忽略开头空格,因此在尝试将空格字符提取到字符变量时可能导致意外结果:
#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;
}

在上例中,我们本期望提取空格,但由于开头空格被忽略,实际提取的是字符 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;
}

字符大小、取值范围与默认符号
C++ 规定字符(char)始终为 1 字节大小。默认情况下,字符可以是带符号的或无符号的(尽管通常为带符号)。若使用字符存储 ASCII 字符,则无需指定符号(因为带符号和无符号字符均可存储 0 到 127 之间的值)。
若将 char 用作小型整数存储(除非明确进行空间优化,否则不应如此),则必须明确指定其有符号或无符号属性。有符号 char 可存储 -128 至 127 之间的数值,无符号 char 可存储 0 至 255 之间的数值。
转义序列
C++ 中存在一些具有特殊含义的字符序列,这些字符被称为转义序列escape sequences。转义序列以反斜杠(\)字符开头,后跟一个字母或数字。
你已经见过最常见的转义序列:‘\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;
}
这输出:

另一个常用的转义序列是'\t',它嵌入一个水平制表符:
#include <iostream>
int main()
{
std::cout << "First part\tSecond part";
return 0;
}
输出结果:

另外三个值得注意的转义序列是:
\’ 打印单引号
\” 打印双引号
\ 打印反斜杠
以下是所有转义序列的对照表:
| Name | Symbol | Meaning |
|---|---|---|
| Alert | \a |
Makes an alert, such as a beep |
| Backspace | \b |
Moves the cursor back one space |
| Formfeed | \f |
Moves the cursor to next logical page |
| Newline | \n |
Moves cursor to next line |
| Carriage return | \r |
Moves cursor to beginning of line |
| Horizontal tab | \t |
Prints a horizontal tab |
| Vertical tab | \v |
Prints a vertical tab |
| Single quote | \' |
Prints a single quote |
| Double quote | \" |
Prints a double quote |
| Backslash | \\ |
Prints a backslash |
| Question mark | \? |
Prints a question mark. No longer relevant. You can use question marks unescaped. |
| Octal number | \(number) |
Translates into char represented by octal |
| Hex number | \x(number) |
Translates into char represented by hex number |
以下是一些示例:
#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;
}
输出:

警告
转义序列以反斜杠 () 开头,而非正斜杠 (/). 若误用正斜杠,程序虽仍可编译,但无法产生预期结果。
换行符 (\n) 与 std::endl 的区别
我们在第 1.5 课——iostream 介绍:cout、cin 和 endl 中探讨了这个主题。
单引号和双引号之间的区别是什么?
单引号内的文本被视为字符常量,代表单个字符。例如,‘a’ 代表字符 a,‘+’ 代表加号字符,‘5’ 代表字符 5(而非数字 5),而 ‘\n’ 代表换行字符。
双引号内的文本(如“Hello, world!”)被视为C风格字符串字面量,可包含多个字符。字符串将在第5.2节——字面量中详细讨论。
最佳实践
单个字符通常应使用单引号而非双引号(例如 ‘t’ 或 ‘\n’,而非 “t” 或 “\n”)。唯一例外是输出操作时,为保持一致性可优先使用双引号(详见第 1.5 课——iostream 介绍:cout、cin 和 endl)。
避免使用多字符字面量
出于向后兼容性考虑,许多C++编译器支持多字符字面量,即包含多个字符的字符字面量(例如'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和换行符。但在作者的机器上,它却输出以下内容:

需得暂时去掉"将警告视为错误", 使用clang++ main.cpp -o main;

问题在于程序员误用了'/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;
}
该程序的输出结果完全符合预期:

但这样的输出效果实在不够惊艳,于是我们决定在换行符前添加一个感叹号:
#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’ 是多字符字面量,在作者的机器上,这实际上输出:

(处理效果如何:暂时取消"将错误视为警告")

这不仅是错误的,而且可能难以调试,因为你很可能会误以为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 相同的表示形式。
除非计划使程序兼容Unicode,否则无需使用char8_t、char16_t或char32_t。除与Windows API交互外,几乎所有情况下都应避免使用wchar_t,因其大小取决于具体实现。
Unicode与本地化通常超出本教程范围,故不再赘述。目前处理字符(及字符串)时请仅使用ASCII字符集,使用其他字符集可能导致显示异常。


浙公网安备 33010602011771号