第 04 章
第 4 章:布尔、字符与 auto
本章对应长视频 4:16:37–4:45:33。补齐剩下三种基本类型:bool 只有两个状态并占 1 字节,char 用 ASCII 编码把字符映射成 0–255,auto 让编译器从初始化式推导类型;最后演示初始化之后重新赋值,以及 auto 配合无符号类型时的静默溢出。
Chapter 4: Booleans, Characters and auto
Covers 4:16:37–4:45:33 of the long video. It completes the fundamental types: bool holds two states in one byte, char maps characters to 0–255 through ASCII, and auto lets the compiler deduce a type from the initialiser. It closes with reassignment after initialisation and the silent overflow that auto plus unsigned produces.
篇内目录
这一章在讲什么
第 4 章覆盖 4:16:37–4:45:33,把基本类型补全。三件事:
- bool:只存
true或false,是程序里做判断的基础。 - char:占 1 字节,通过 ASCII 编码把字符映射成整数。
- auto:不写类型,让编译器从初始化式推导。
章末再讲"初始化"与"赋值"的区别,以及 auto 推导出无符号类型后,再赋负值会发生什么。
时间轴
| 时间 | 内容 |
|---|---|
| 4:16:37 | bool:两个状态与条件判断 |
| 4:18:11 | 打印布尔值:默认 1/0,boolalpha 显示 true/false |
| 4:18:38 | bool 占 1 字节 |
| 4:24:25 | char 与单引号;ASCII 编码 |
| 4:26:38 | 用字符赋整数值;static_cast 转回整数 |
| 4:32:01 | auto:由初始化式推导类型 |
| 4:38:12 | 初始化之后重新赋值;auto 与无符号的陷阱 |
bool:只有两个状态
bool red_light {true};
bool green_light {false};
if (red_light) {
std::cout << "Stop" << std::endl;
} else {
std::cout << "Go through" << std::endl;
}
bool 只能取 true 或 false。if (red_light) 直接判断这个值本身,不需要写 red_light == true——两种写法等价,前者更简洁。
打印布尔值时有一个默认行为容易让人困惑:
bool condition {true};
std::cout << condition << std::endl; // 1
std::cout << std::boolalpha << condition << std::endl; // true
默认输出 1/0;加上 std::boolalpha 之后输出 true/false。这是第 6 章要系统讲的流操纵器的第一个例子。
bool 占 1 个字节(8 位),但只需要 1 位就能表达两个状态。这确实浪费,不过现代机器上不值得为此优化;内存极其紧张的设备才需要考虑位压缩。
char:字符就是小整数
字符用单引号表示,且只能用单引号:
char letter {'A'};
std::cout << letter << std::endl; // A
双引号 "A" 是字符串字面量,类型是 const char[2](含结尾的 \0),赋给 char 会编译失败。这是初学者最常见的错误之一。
ASCII 编码
char 占 1 字节,能表示 0–255。ASCII 规定其中一部分数值对应哪个字符:
| 数值 | 字符 |
|---|---|
| 33 | ! |
| 48 | 0 |
| 49 | 1 |
| 65 | A |
| 70 | F |
| 97 | a |
| 112 | p |
所以字符在内存里本来就是数字,只是 char 类型告诉编译器"请把它当字符显示":
char value {65}; // 存的是 65
std::cout << value << std::endl; // 按字符解释,输出 A
std::cout << static_cast<int>(value) // 按整数解释,输出 65
<< std::endl;
static_cast<int> 是显式类型转换:告诉编译器"把这个值当 int 看"。不加它,cout 会按 char 输出字符。
ASCII 只覆盖 128 个基本字符,处理中文、日文、阿拉伯文需要 Unicode(如 UTF-8),那已经超出本课范围。这里只需建立"字符 = 编码后的整数"这个认识。
auto:让编译器推导类型
auto 不是类型,而是让编译器根据初始化式决定类型的关键字:
auto var1 {12}; // int
auto var2 {13.0}; // double
auto var3 {14.0f}; // float
auto var4 {15.0l}; // long double
auto var5 {'e'}; // char
规则很直接:字面量本身是什么类型,auto 就推导成什么类型。后缀在这里起决定作用——14.0 是 double,14.0f 才是 float。
整数也可以用后缀控制推导结果:
auto v1 {12u}; // unsigned int
auto v2 {12ul}; // unsigned long
auto v3 {12ll}; // long long
VS Code 会把推导结果直接显示出来:把鼠标悬停在变量上,就能看到编译器认定的类型。也可以用 sizeof 验证:
std::cout << sizeof(var1) << std::endl; // 4
std::cout << sizeof(var2) << std::endl; // 8
std::cout << sizeof(var5) << std::endl; // 1
auto 的价值在于类型名字很长时省事(后面讲模板、迭代器时会非常明显)。代价是类型不再一眼可见,所以在关键变量上最好别滥用。
初始化与赋值
初始化是变量诞生时给值;赋值是之后改值:
int var1 {123}; // 初始化
std::cout << var1; // 123
var1 = 55; // 赋值
std::cout << var1; // 55
这对 double、bool 同样成立:
double var2 {44.50};
var2 = 99.99;
bool state {false};
state = true;
auto 与无符号的陷阱
auto 推导出的类型可能和你以为的不一样,赋值时就会出问题:
auto var3 {12u}; // 推导为 unsigned int
var3 = -22; // 不报错!
var3 是无符号类型,装不下负数。编译器不会报错,而是按模 回绕,-22 变成约 42.9 亿。程序照常编译、照常运行,只是结果完全不对。
这是本章最需要记住的教训:使用 auto 时,赋值前要确认推导出的类型是什么。悬停看一下,或者显式写 unsigned int var3,都比事后调试便宜。
本章结论
bool只有true/false,占 1 字节;boolalpha控制打印成true/false还是1/0。char占 1 字节,字符用单引号;内存里存的是 ASCII 码,static_cast<int>可以读出数值。auto由初始化式推导类型,后缀(u、l、f、ll)直接决定结果。- 初始化发生在变量创建时,赋值发生在之后。
auto推导为无符号类型后赋负值,不会报错,会静默回绕成大正数。
理解检验
1. std::cout << true; 输出什么?怎样才能输出 true?
参考答案:默认输出 1,因为 bool 的底层值就是 1/0,cout 按整数格式打印。加上 std::boolalpha 后输出 true,例如 std::cout << std::boolalpha << true;。这是流的格式化状态,会一直生效,直到用 std::noboolalpha 关闭。
2. char c = "A"; 为什么编译失败?正确的写法是什么?
参考答案:双引号 "A" 是字符串字面量,类型是 const char[2](A 加结尾的 \0),本质是一个数组,不能赋给单个 char。正确写法是单引号 char c = 'A';,它表示一个字符常量。同理,比较时 if (c == "A") 也是错的,应写成 if (c == 'A')。
3. char value {65}; 打印出来为什么是 A 而不是 65?怎样让它打印 65?
参考答案:char 类型告诉 cout 把这块内存按字符解释,65 在 ASCII 表中对应 A。要打印数值,需要显式转换:std::cout << static_cast<int>(value);。static_cast 不改变内存内容,只改变编译器解释这个值的方式。
4. 写出四个 auto 变量分别推导为 int、double、float、unsigned long 的初始化式。
参考答案:
auto a {12}; // int
auto b {12.0}; // double
auto c {12.0f}; // float
auto d {12ul}; // unsigned long
推导完全由初始化式决定:整数默认 int,带小数点的默认 double,后缀 f 变 float,u 表示无符号、l 表示 long,组合 ul 就是 unsigned long。
5. auto x {12u}; x = -22; 会发生什么?为什么不报错?
参考答案:12u 推导出 unsigned int,装不下负数。赋值时编译器把 -22 按模 回绕成 ,x 变成一个接近 43 亿的大正数。C++ 对无符号整数的溢出有明确定义(回绕),因此不属于错误,编译器不报错也不警告,问题被静默地带进运行结果。
6. 初始化与赋值有什么区别?为什么 auto 让这个区别变得更需要注意?
参考答案:初始化发生在变量创建时,决定它的初始值和类型;赋值发生在之后,只改值、不改类型。用显式类型时,类型在声明处一眼可见;用 auto 时,类型藏在初始化式里,后面赋值是否合法取决于那个看不见的类型。因此 auto 变量在赋值前应先确认推导出的类型,否则容易出现上面那种静默回绕。
与前后章的关系
本章把基本类型补齐:数值(第 3 章)、布尔、字符,以及写法上的 auto。第 5 章开始对它们做运算——算术与取模、优先级与结合性、自增自减、复合赋值、比较与逻辑运算。第 6 章再回到本章提到的 boolalpha,系统讲解输出格式化的整套操纵器。
What this chapter covers
Chapter 4 spans 4:16:37–4:45:33 and completes the fundamental types. Three pieces:
- bool: stores only
trueorfalse, the basis of every decision a program makes. - char: one byte, mapping characters to integers through ASCII.
- auto: omit the type and let the compiler deduce it from the initialiser.
It finishes with the difference between initialisation and assignment, and with what happens when an auto variable deduces an unsigned type and is then given a negative number.
Timeline
| Time | Topic |
|---|---|
| 4:16:37 | bool: two states, decisions |
| 4:18:11 | Printing booleans: 1/0 by default, true/false with boolalpha |
| 4:18:38 | bool occupies one byte |
| 4:24:25 | char and single quotes; ASCII |
| 4:26:38 | Assigning a numeric value to a char; static_cast back to int |
| 4:32:01 | auto: deducing the type from the initialiser |
| 4:38:12 | Reassigning after initialisation; the auto/unsigned trap |
bool: exactly two states
bool red_light {true};
bool green_light {false};
if (red_light) {
std::cout << "Stop" << std::endl;
} else {
std::cout << "Go through" << std::endl;
}
A bool holds true or false. if (red_light) tests the value itself; writing red_light == true is equivalent but noisier.
Printing a boolean has a default that surprises people:
bool condition {true};
std::cout << condition << std::endl; // 1
std::cout << std::boolalpha << condition << std::endl; // true
By default you get 1/0; std::boolalpha switches to true/false. This is the first example of the stream manipulators that chapter 6 covers systematically.
A bool occupies one byte (eight bits) although a single bit would do. That is wasteful, but on modern machines not worth optimising; only extremely memory-constrained devices bother packing bits.
char: a character is a small integer
Characters use single quotes, and only single quotes:
char letter {'A'};
std::cout << letter << std::endl; // A
"A" with double quotes is a string literal of type const char[2] (the character plus the terminating \0) and cannot initialise a char. This is one of the most common beginner errors.
ASCII
A char is one byte and covers 0–255. ASCII assigns characters to some of those values:
| Value | Character |
|---|---|
| 33 | ! |
| 48 | 0 |
| 49 | 1 |
| 65 | A |
| 70 | F |
| 97 | a |
| 112 | p |
So a character already is a number in memory; the char type merely tells the compiler to display it as a character:
char value {65}; // stores 65
std::cout << value << std::endl; // interpreted as a char: A
std::cout << static_cast<int>(value) // interpreted as an int: 65
<< std::endl;
static_cast<int> is an explicit conversion: it tells the compiler to treat the bits as an int. Without it, cout prints the character.
ASCII covers only the 128 basic characters; Chinese, Japanese or Arabic need Unicode (for example UTF-8), which is beyond this course. The idea to keep is simple: a character is an encoded integer.
auto: let the compiler deduce the type
auto is not a type but a keyword that makes the compiler derive the type from the initialiser:
auto var1 {12}; // int
auto var2 {13.0}; // double
auto var3 {14.0f}; // float
auto var4 {15.0l}; // long double
auto var5 {'e'}; // char
The rule is direct: the literal's own type becomes the deduced type. The suffix decides — 14.0 is a double, only 14.0f is a float.
Integer suffixes work the same way:
auto v1 {12u}; // unsigned int
auto v2 {12ul}; // unsigned long
auto v3 {12ll}; // long long
VS Code shows the result directly: hover over the variable and the deduced type appears. sizeof confirms it:
std::cout << sizeof(var1) << std::endl; // 4
std::cout << sizeof(var2) << std::endl; // 8
std::cout << sizeof(var5) << std::endl; // 1
auto pays off when type names become long (templates and iterators later in the course). The cost is that the type is no longer visible at the declaration, so it should be used with care on important variables.
Initialisation versus assignment
Initialisation gives a value as the variable is created; assignment changes it afterwards:
int var1 {123}; // initialisation
std::cout << var1; // 123
var1 = 55; // assignment
std::cout << var1; // 55
The same holds for double and bool:
double var2 {44.50};
var2 = 99.99;
bool state {false};
state = true;
The auto and unsigned trap
A deduced type may not be what you expect, and assignment then goes wrong:
auto var3 {12u}; // deduced as unsigned int
var3 = -22; // no error!
var3 is unsigned and cannot hold a negative value. The compiler does not complain; it wraps modulo , so -22 becomes about 4.29 billion. The program compiles and runs, with a completely wrong number.
This is the lesson of the chapter: with auto, check the deduced type before assigning. Hover to inspect it, or write unsigned int var3 explicitly; both are cheaper than debugging later.
Chapter summary
boolholdstrue/falseand takes one byte;boolalphacontrols whether it prints astrue/falseor1/0.chartakes one byte; characters use single quotes; memory holds the ASCII code, whichstatic_cast<int>reveals.autodeduces from the initialiser, and suffixes (u,l,f,ll) decide the result.- Initialisation happens at creation; assignment happens afterwards.
- Assigning a negative value to an
auto-deduced unsigned type compiles silently and wraps to a large positive number.
Comprehension check
1. What does std::cout << true; print, and how do you print true instead?
Reference answer: It prints 1, because a bool's underlying value is 1/0 and cout prints it as an integer. Add std::boolalpha: std::cout << std::boolalpha << true; prints true. This is a formatting state on the stream and stays active until std::noboolalpha turns it off.
2. Why does char c = "A"; fail to compile, and what is the correct form?
Reference answer: "A" is a string literal of type const char[2] — the character plus a terminating \0 — which is an array and cannot initialise a single char. The correct form is char c = 'A';, a character literal. For the same reason if (c == "A") is wrong and should be if (c == 'A').
3. Why does char value {65}; print A rather than 65, and how do you print 65?
Reference answer: The char type tells cout to interpret the byte as a character, and 65 is A in ASCII. To print the number, convert explicitly: std::cout << static_cast<int>(value);. static_cast does not change the bits, only how the compiler interprets them.
4. Write four auto initialisers that deduce to int, double, float and unsigned long.
Reference answer:
auto a {12}; // int
auto b {12.0}; // double
auto c {12.0f}; // float
auto d {12ul}; // unsigned long
Deduction follows the initialiser exactly: an integer literal is int, a decimal literal without a suffix is double, f makes it float, u means unsigned and l means long, so ul is unsigned long.
5. What happens with auto x {12u}; x = -22;, and why is there no error?
Reference answer: 12u deduces unsigned int, which cannot hold a negative number. The assignment wraps modulo , so -22 becomes , a number close to 4.3 billion. Unsigned overflow is defined by the standard (wraparound), so it is not an error and the compiler neither complains nor warns; the wrong value enters the program silently.
6. What is the difference between initialisation and assignment, and why does auto make it more delicate?
Reference answer: Initialisation happens when the variable is created and fixes both its value and its type; assignment changes only the value later. With an explicit type the type is visible at the declaration; with auto it is hidden inside the initialiser, and whether a later assignment is legal depends on that invisible type. Hence an auto variable should have its deduced type checked before assignment, or the wraparound above will happen quietly.
How it connects
This chapter completes the fundamental types: numbers (chapter 3), booleans and characters, plus the auto notation. Chapter 5 starts operating on them — arithmetic and modulus, precedence and associativity, increment and decrement, compound assignment, comparison and logic. Chapter 6 returns to the boolalpha seen here and treats the whole family of output manipulators systematically.