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;
}

这产生了以下结果:

image

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

std::cout << 'c';

这产生了以下结果:

image


输入字符

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

#include <iostream>

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

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

    return 0;
}

以下是一次运行的输出结果:

image

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

image

若需一次读取多个字符(例如读取姓名、单词或句子),则应使用字符串而非单个字符。字符串是由连续字符组成的集合(因此字符串可存储多个符号)。我们将在后续课程中讨论此内容(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;
}

image

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

image


字符大小、取值范围与默认符号

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;
}

这输出:

image

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

#include <iostream>

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

输出结果:

image

另外三个值得注意的转义序列是:

\’ 打印单引号
\” 打印双引号
\ 打印反斜杠

以下是所有转义序列的对照表:

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;
}

输出:

image

警告
转义序列以反斜杠 () 开头,而非正斜杠 (/). 若误用正斜杠,程序虽仍可编译,但无法产生预期结果。


换行符 (\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和换行符。但在作者的机器上,它却输出以下内容:

image

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

image

问题在于程序员误用了'/n'(由正斜杠和字符'n'组成的多字符字面量)而非'\n'(换行符的转义序列)。程序首先正确输出3(即add(1, 2)的结果)。但随后它输出的是多字符字面量'/n'的值——在作者的机器上,该字符串被实现定义为12142。

警告
请确保换行符使用转义序列 ‘\n’,而非多字符字面量 ‘/n’。

关键要点
请注意,若将输出 “/n” 用双引号包裹,程序将输出 3/n,虽然结果仍错误,但会减少混淆。
image

以下是另一个示例。我们从以下代码开始:

#include <iostream>

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

    return 0;
}

该程序的输出结果完全符合预期:

image

但这样的输出效果实在不够惊艳,于是我们决定在换行符前添加一个感叹号:

#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’ 是多字符字面量,在作者的机器上,这实际上输出:

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

这不仅是错误的,而且可能难以调试,因为你很可能会误以为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字符集,使用其他字符集可能导致显示异常。

posted @ 2026-02-14 15:24  游翔  阅读(1)  评论(0)    收藏  举报