第5章 循环和关系表达式
cout布尔值输出:直接用 cout << boolalpha << 变量;。这是现代 C++ 官方标准支持的通用写法。
#include <iostream>
int main() {
using namespace std;
bool is_happy = true;
bool is_tired = false;
// ====================================================
// 1. 默认状态:输出数字 1 或 0
// ====================================================
cout << "--- 默认状态 ---" << endl;
cout << "is_happy: " << is_happy << endl; // 输出: 1
cout << "is_tired: " << is_tired << endl; // 输出: 0
cout << endl;
// ====================================================
// 2. 开启开关:使用现代最通用的 boolalpha
// ====================================================
cout << "--- 开启 boolalpha 开关 ---" << endl;
// 这一行执行后,cout 会被全局切换为字母输出模式
cout << boolalpha;
cout << "is_happy: " << is_happy << endl; // 输出: true
cout << "is_tired: " << is_tired << endl; // 输出: false
// 也可以直接在输出变量时顺手带上它,效果完全一样:
cout << "直接连写输出: " << boolalpha << is_happy << endl;
cout << endl;
// ====================================================
// 3. 关闭开关:使用 noboolalpha 切回数字模式
// ====================================================
cout << "--- 关闭开关(切回数字) ---" << endl;
cout << noboolalpha;
cout << "is_happy: " << is_happy << endl; // 输出: 1
cout << "is_tired: " << is_tired << endl; // 输出: 0
return 0;
}
/*
--- 默认状态 ---
is_happy: 1
is_tired: 0
--- 开启 boolalpha 开关 ---
is_happy: true
is_tired: false
直接连写输出: true
--- 关闭开关(切回数字) ---
is_happy: 1
is_tired: 0
*/
16的阶乘demo:
#include "ayzw.top/init.h"
//输出前16个阶乘
int64 jc(int64 input) {
if (input == 1) {
return 1;
}
int64 result = 1;
for (int64 i = 1; i <= input; i++) {
result *= i;
}
return result;
}
int main() {
for (int64 i = 0; i < 16; i++) {
print("{}! = {}\n", i, jc(i));
}
return 0;
}
/*
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
*/
延时demo:
#include <iostream>
#include <ctime> // 1. 必须包含头文件,声明了 clock() 函数和 clock_t 类型
int main() {
using namespace std;
//1.需要延迟的秒数:
int secs = 5;
// 2. 核心转换:将用户输入的“秒数”转换为计算机底层的“时钟周期数(Ticks)”
// CLOCKS_PER_SEC 是 ctime 头文件中定义的宏,表示每秒包含的时钟周期数
clock_t delay = secs * CLOCKS_PER_SEC;
cout << "计时开始!"<<endl; // 这里用endl保证立刻输出,不要用\n
// 3. 获取当前的起始时钟周期数
// clock_t 存的是时钟周期(一个抽象的硬件单位),如果你想把它打印成人类能看懂的“秒数”,就必须除以常量 CLOCKS_PER_SEC(每秒时钟周期数)
// clock() 返回的不是标准的“小时”或“秒”,而是一个表示硬件执行脉冲次数的大整数,类型为 clock_t。
clock_t start = clock();
// 4. 空循环等待:只要当前时间与起始时间的差值小于设定的延迟周期,就一直循环
// 注意:while 语句后面紧跟了一个孤零零的分号,代表这是一个空语句体
while (clock() - start < delay) {
// 什么都不做,纯粹让 CPU 等待
}
cout << "计时结束!"<<endl;
return 0;
}
//计时开始!
//计时结束!
类型别名
1、 using(现代 C++ 推荐),语法采用“赋值”的形式,可读性极高,完全可以替代 typedef。通用格式:
using aliasName = typeName;
示例:
#include <iostream>
#include <type_traits> // 用于检查类型是否相同
int main() {
// 1. 定义类型别名
using byte = char;
using byte_pointer = char *;
// 2. 使用别名声明变量
byte ch = 'A';
byte_pointer ptr = &ch;
// 3. 打印变量的值和通过指针修改后的值
std::cout << "--- 基本使用 ---" << std::endl;
std::cout << "ch 的初始值: " << ch << std::endl;
// 将字符指针(char*)强制转换为无类型指针(void*),以便 std::cout 能够打印出该指针所指向的内存地址。
// 如果不进行转换,std::cout 会把 char* 当作字符串打印,而不是打印地址。
std::cout << "ptr 指向的地址: " << static_cast<void *>(ptr) << std::endl;
*ptr = 'B'; // 通过别名指针修改值
std::cout << "修改后 ch 的值: " << ch << std::endl;
// 4. 证明: 连续声明多个指针时,using 别名不会像 #define 那样出错
std::cout << "\n--- 安全性测试 (连续声明) ---" << std::endl;
byte_pointer pa, pb; // pa 和 pb 都是 char* 类型
pa = &ch;
// pb = ch; // 如果取消注释这行会报错,因为 pb 是指针,不能直接赋值字符。
// 这证明了 pb 是 char* 别名,而不是像 #define 那样导致 pb 变成普通的 char。
std::cout << "连续声明测试成功!pa 和 pb 均为指针类型。" << std::endl;
// 5. 验证: 别名并没有创建新类型,它与原类型完全等价
std::cout << "\n--- 类型等价性验证 ---" << std::endl;
// std::is_same_v 是 C++17 提供的模板,用于在编译期判断两种类型是否完全相同
if constexpr (std::is_same_v<byte, char>) {
std::cout << "验证成功: byte 和 char 是完全相同的类型。" << std::endl;
}
if constexpr (std::is_same_v<byte_pointer, char *>) {
std::cout << "验证成功: byte_pointer 和 char* 是完全相同的类型。" << std::endl;
}
return 0;
}
/*
--- 基本使用 ---
ch 的初始值: A
ptr 指向的地址: 0x6a3e9ff967
修改后 ch 的值: B
--- 安全性测试 (连续声明) ---
连续声明测试成功!pa 和 pb 均为指针类型。
--- 类型等价性验证 ---
验证成功: byte 和 char 是完全相同的类型。
验证成功: byte_pointer 和 char* 是完全相同的类型。
*/
2、 typedef(传统方法),通用格式:
typedef typeName aliasName;
3、 #define(不推荐)纯粹的文本替换,由预处理器处理,没有类型检查,且容易引发针声明 Bug。通用格式:
#define aliasName typeName
基于范围的 for 循环
基于范围的 for 循环,大幅简化了对数组或模板容器类(如 std::vector, std::array)中每个元素执行相同操作的传统循环任务。它不仅消除了手动管理下标越界的风险,也让代码更加直观。
1、只读遍历(传值)
#include <iostream>
int main() {
double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49};
std::cout << "--- 1. 尝试在传值循环中修改 x ---" << std::endl;
for (double x : prices) {
std::cout << "原值: " << x;
x = x * 0.80; // :warning: 这里修改的是局部副本 x,而不是原数组
std::cout << " -> 循环内修改后的 x: " << x << std::endl;
}
std::cout << "\n--- 2. 检查原数组是否受到影响 ---" << std::endl;
std::cout << "原数组 prices 的实际内容为: " << std::endl;
// 再次遍历原数组,查看真实的数据
for (double x : prices) {
std::cout << x << " ";
}
std::cout << std::endl;
std::cout << "\n结论:原数组的数据没有任何改变!证明传值循环只修改了临时变量 x。" << std::endl;
return 0;
}
/*
--- 1. 尝试在传值循环中修改 x ---
原值: 4.99 -> 循环内修改后的 x: 3.992
原值: 10.99 -> 循环内修改后的 x: 8.792
原值: 6.87 -> 循环内修改后的 x: 5.496
原值: 7.99 -> 循环内修改后的 x: 6.392
原值: 8.49 -> 循环内修改后的 x: 6.792
--- 2. 检查原数组是否受到影响 ---
原数组 prices 的实际内容为:
4.99 10.99 6.87 7.99 8.49
结论:原数组的数据没有任何改变!证明传值循环只修改了临时变量 x。
*/
2、修改元素(传引用)
#include <iostream>
int main() {
// :hammer_and_wrench: 修正错误:加上方括号 [] 正确声明 double 数组
double prices[] = {4.99, 10.99, 6.87, 7.99, 8.49};
std::cout << "--- 1. 修改前:原数组的初始内容与地址 ---" << std::endl;
// 使用 std::cout 的默认表现形式打印原数组中每个元素的内存地址
for (int i = 0; i < 5; ++i) {
std::cout << "prices[" << i << "] 的地址: " << &prices[i] << " | 值: " << prices[i] << std::endl;
}
std::cout << "\n";
std::cout << "--- 2. 使用引用 & 进行打折修改并实时观察地址 ---" << std::endl;
int index = 0;
// 这里的 x 是原数组元素的别名
for (double &x : prices) {
// 打印循环变量 x 的地址,对比是否与上方原数组对应元素的地址完全相同
std::cout << "循环变量 x 的地址: " << &x << " (正指向 prices[" << index++ << "])" << std::endl;
x = x * 0.80; // 直接修改了原数组对应的内存空间
}
std::cout << "打折操作完成。\n" << std::endl;
std::cout << "--- 3. 修改后:再次检查原数组 ---" << std::endl;
for (double x : prices) {
std::cout << x << " ";
}
std::cout << std::endl;
std::cout << "\n结论:通过地址对比可以发现,&x 的地址与原数组元素的真实地址完全一致!" << std::endl;
std::cout << "这铁证了后续代码操作的直接就是原数组对应的内存空间。" << std::endl;
return 0;
}
/*
--- 1. 修改前:原数组的初始内容与地址 ---
prices[0] 的地址: 0x2bed1ffd00 | 值: 4.99
prices[1] 的地址: 0x2bed1ffd08 | 值: 10.99
prices[2] 的地址: 0x2bed1ffd10 | 值: 6.87
prices[3] 的地址: 0x2bed1ffd18 | 值: 7.99
prices[4] 的地址: 0x2bed1ffd20 | 值: 8.49
--- 2. 使用引用 & 进行打折修改并实时观察地址 ---
循环变量 x 的地址: 0x2bed1ffd00 (正指向 prices[0])
循环变量 x 的地址: 0x2bed1ffd08 (正指向 prices[1])
循环变量 x 的地址: 0x2bed1ffd10 (正指向 prices[2])
循环变量 x 的地址: 0x2bed1ffd18 (正指向 prices[3])
循环变量 x 的地址: 0x2bed1ffd20 (正指向 prices[4])
打折操作完成。
--- 3. 修改后:再次检查原数组 ---
3.992 8.792 5.496 6.392 6.792
结论:通过地址对比可以发现,&x 的地址与原数组元素的真实地址完全一致!
这铁证了后续代码操作的直接就是原数组对应的内存空间。
*/
第6章 分支语句和逻辑运算语句
逻辑运算符
| 逻辑操作 | 传统符号形式 | 现代文本关键字 (C++11+) | 作用描述 |
|---|---|---|---|
| 逻辑与 (AND) | && | and | 两边表达式都为 true 时,结果才为 true |
| 逻辑或 (OR) | || | or | 两边表达式只要有一个为 true,结果就为 true |
| 逻辑非 (NOT) | ! | not | 将表达式的布尔值取反(true 变 false,反之亦然) |
<cctype> 库
#include <iostream>
#include <cctype> // 必须引入该头文件以使用 isalpha, isdigit, isspace, ispunct
int main() {
char ch;
// 初始化各类字符的计数器
int alphaCount = 0;
int digitCount = 0;
int spaceCount = 0;
int punctCount = 0;
int otherCount = 0;
std::cout << "请输入一段文字(输入 @ 符号并回车结束输入):" << std::endl;
// 使用 cin.get(char) 循环读取每一个字符
// 它与 cin >> ch 的区别在于:cin.get() 不会跳过空格、制表符和换行符
while (std::cin.get(ch) && ch != '@') {
// 使用 if else if 结构对字符类型进行多重判断
if (std::isalpha(ch)) {
// 检查字符是否为字母(A-Z, a-z)
alphaCount++;
}
else if (std::isdigit(ch)) {
// :warning: 注意:函数名为 isdigit,用于测试字符是否为数字(0-9)
digitCount++;
}
else if (std::isspace(ch)) {
// 检查字符是否为空白(空格 ' '、换行 '\n'、水平制表符 '\t'、回车 '\r' 等)
spaceCount++;
}
else if (std::ispunct(ch)) {
// 检查字符是否为标点符号(如逗号、句号、感叹号、括号等)
punctCount++;
}
else {
// 处理不属于以上任何一类的其他特殊字符(例如不可见字符)
otherCount++;
}
}
// 输出统计结果
std::cout << "\n--- 字符统计结果 ---" << std::endl;
std::cout << "字母字符数量 (isalpha): " << alphaCount << std::endl;
std::cout << "数字字符数量 (isdigit): " << digitCount << std::endl;
std::cout << "空白字符数量 (isspace): " << spaceCount << std::endl;
std::cout << "标点符号数量 (ispunct): " << punctCount << std::endl;
std::cout << "其他字符数量: " << otherCount << std::endl;
return 0;
}
/*
请输入一段文字(输入 @ 符号并回车结束输入):
aAbB1122 ,."'
@
--- 字符统计结果 ---
字母字符数量 (isalpha): 4
数字字符数量 (isdigit): 4
空白字符数量 (isspace): 7
标点符号数量 (ispunct): 4
其他字符数量: 0
*/
- 必须用 std::cin.get(ch)?如果使用传统的 std::cin >> ch,C++ 会自动跳过所有的空格、制表符和换行符。这样的话,控制台永远无法读入任何空白,isspace(ch) 的判断分支就变成了摆设。而 cin.get(ch) 会如实读取你键盘输入的每一个字节。
库函数的工作原理:这些函数在底层接收的是字符的 ASCII 码值,并返回一个 int 值:如果条件成立(是对应类型的字符),返回非零值(在 C++ 条件判断中等价于 true)。如果条件不成立,返回 0(等价于 false)。
| 函数分类 | 函数名称 | 检查/转换目的 | 成功时的返回值 (满足条件) | 失败时的返回值 (不满足条件) |
|---|---|---|---|---|
| :light_bulb: 文本与数字 | isalpha(c) | 检查是否为 字母 (A-Z, a-z) | 非零值 (true) | 0 (false) |
| (基础数据验证) | ||||
| isdigit(c) | 检查是否为 十进制数字 (0-9) | 非零值 (true) | 0 (false) | |
| isalnum(c) | 检查是否为 字母或数字 | 非零值 (true) | 0 (false) | |
| isxdigit(c) | 检查是否为 十六进制数字 (0-9, a-f, A-F) | 非零值 (true) | 0 (false) | |
| :input_latin_uppercase: 大小写状态 | islower(c) | 检查是否为 小写字母 (a-z) | 非零值 (true) | 0 (false) |
| (文本状态检测) | ||||
| isupper(c) | 检查是否为 大写字母 (A-Z) | 非零值 (true) | 0 (false) | |
| :printer: 格式与排版 | isspace(c) | 检查是否为 标准空白字符 (空格、换行、回车、水平/垂直制表符、进纸符) | 非零值 (true) | 0 (false) |
| (符号语境划分) | ||||
| ispunct(c) | 检查是否为 标点符号 (除字母、数字、空白外的可打印字符) | 非零值 (true) | 0 (false) | |
| :magnifying_glass_tilted_left: 屏幕可视性 | isprint(c) | 检查是否为 所有可打印字符 (包含可见字符与空格) | 非零值 (true) | 0 (false) |
| (输出安全控制) | ||||
| isgraph(c) | 检查是否为 图形可见字符 (可打印,但不包含空格) | 非零值 (true) | 0 (false) | |
| iscntrl(c) | 检查是否为 控制字符 (不可见的机制指令字符,如退格、删除) | 非零值 (true) | 0 (false) | |
| :counterclockwise_arrows_button: 文本转换器 | tolower(c) | 将大写字母 转换为小写 | 对应的小写字符 | 原样返回原参数 c |
| (数据流重塑) | ||||
| toupper(c) | 将小写字母 转换为大写 | 对应的大写字符 | 原样返回原参数 c |
返回值陷阱:虽然逻辑上返回 true/false,但这些函数在标准 C++ 中的底层实际返回值是 int(通过非零代表真)。如果需要严格的布尔值类型,应将其赋值给 bool 变量,或在需要时显式转换。
符号范围风险:在处理中文字符、拓展 ASCII 码时,直接传入 char 可能会因为符号位拓展导致内存越界崩溃。现代 C++ 开发中,最安全的做法是将字符强转为 unsigned char 再传入
char myChar = 'A';
if (std::isalpha(static_cast<unsigned char>(myChar))) { ... }
条件运算符?!
- 条件运算符(Conditional Operator)是 C++ 中唯一的一个三目运算符(Ternary Operator),也就是说它需要三个操作数。它可以看作是 if else 结构的紧凑简写形式,常用于在单行代码中根据条件进行赋值。
- 条件运算符有一个非常严格的类型限制:: 两边的 表达式2 和 表达式3 的类型必须相同,或者能够隐式转换为同一种类型。
表达式1 ? 表达式2 : 表达式3
demo:
#include <iostream>
int main() {
// 初始化测试数据
int a = 35;
int b = 72;
std::cout << "当前变量值: a = " << a << ", b = " << b << "\n" << std::endl;
// --- 1. 使用传统的 if-else 结构 ---
int max1; // 必须先声明变量
if (a > b) {
max1 = a;
} else {
max1 = b;
}
std::cout << "【if-else 结构】计算出的最大值 max1 = " << max1 << std::endl;
// --- 2. 使用条件运算符(三目运算符) ---
// 可以在声明变量的同时直接初始化,代码极其简洁
int max2 = (a > b) ? a : b;
std::cout << "【条件运算符】 计算出的最大值 max2 = " << max2 << std::endl;
// --- 3. 验证两者结果是否完全一致 ---
if (max1 == max2) {
std::cout << "\n验证成功:两种方法计算出的最大值完全等价!" << std::endl;
} else {
std::cout << "\n验证失败:结果不一致。" << std::endl; // 理论上绝对不会执行到这里
}
return 0;
}
/*
当前变量值: a = 35, b = 72
【if-else 结构】计算出的最大值 max1 = 72
【条件运算符】 计算出的最大值 max2 = 72
验证成功:两种方法计算出的最大值完全等价!
*/
switch 语句
switch 语句是 C++ 中一种专门用来处理多分支选择的控制流语句。当一个变量的值有多种特定的离散可能(如菜单选择、状态机),且需要根据不同值执行不同的代码时,使用 switch 比编写一长串 if else if 更加整洁且高效。
语法:
switch (控制表达式) {
case 常量表达式1:
// 代码块 1
break;
case 常量表达式2:
// 代码块 2
break;
// 可以有任意多个 case 语句
default:
// 当所有 case 都不匹配时执行的代码
break; // 最后一个 break 可省略,但建议加上
}
:warning: 三大硬性限制:
- 控制表达式的类型限制:switch 后面的括号里,其计算结果必须是整数类型(如 int, char, bool, short, long)或枚举类型(enum)。绝对不能是浮点数(float, double)或字符串(std::string)。
- case 后面的值限制:case 后面必须是编译期常量表达式(如固定的数字 5、字符 ‘A’ 或经过 const 修饰的变量)。绝对不能写范围(如 case x > 10: 是非法的)。
- 穿透效应(Fall-through)与 break:每个 case 分支末尾如果没有写 break;,程序不会自动退出 switch,而是会顺次执行下一个 case 里的代码,直到遇到 break 或整个结构结束。
#include <iostream>
int main() {
int choice;
std::cout << "===== 智能饮料贩卖机 =====" << std::endl;
std::cout << "1. 可口可乐 (可乐)" << std::endl;
std::cout << "2. 百事可乐 (可乐)" << std::endl;
std::cout << "3. 雪碧" << std::endl;
std::cout << "4. 纯净水" << std::endl;
std::cout << "请选择商品编号 (1-4): ";
std::cin >> choice;
std::cout << "\n--- 购买结果 ---" << std::endl;
switch (choice) {
// :glowing_star: 巧妙利用“穿透效应”:case 1 和 case 2 执行相同的逻辑
case 1:
case 2:
std::cout << "成功购买:可乐一瓶!" << std::endl;
break; // 退出 switch
case 3:
std::cout << "成功购买:雪碧一瓶!" << std::endl;
break;
case 4:
std::cout << "成功购买:纯净水一瓶!" << std::endl;
break;
// :glowing_star:兜底保障:当用户输入 1,2,3,4 以外的内容时触发
default:
std::cout << "错误:输入的编号不存在,已自动退款。" << std::endl;
break;
}
std::cout << "感谢您的光临!" << std::endl;
return 0;
}
/*
===== 智能饮料贩卖机 =====
1. 可口可乐 (可乐)
2. 百事可乐 (可乐)
3. 雪碧
4. 纯净水
请选择商品编号 (1-4): 1
--- 购买结果 ---
成功购买:可乐一瓶!
感谢您的光临!
*/
枚举demo:
#include <iostream>
// 1. 正确定义枚举类型,赋予其类型名称 Color
enum Color {
red, orange, yellow, green, blue, violet, indigo
};
int main() {
using namespace std;
// :glowing_star: 现代 C++20 特性:using enum Color;
// 如果支持 C++20 编译器,可以使用该语句。为了保持对旧标准的兼容性,
// 这里采用标准的作用域规则,由于是全局匿名/标准枚举,直接使用 red、orange 是可以的。
cout << "Enter color code (0-6): ";
int code;
cin >> code;
while (code >= red && code <= indigo) {
// 2. 将 int 类型的 code 显式转换为 Color 枚举类型进行匹配
switch (static_cast<Color>(code)) {
case red:
cout << "Her lips were red.\n";
break;
case orange:
cout << "Her hair was orange.\n";
break;
case yellow:
cout << "Her shoes were yellow.\n";
break;
case green:
cout << "Her nails were green.\n";
break;
// :hammer_and_wrench: 修正原代码拼写错误:补充 case 关键字与冒号
case blue:
cout << "Her sweatsuit was blue.\n";
break;
case violet:
cout << "Her eyes were violet.\n";
break;
case indigo:
cout << "Her mood was indigo.\n";
break;
}
// 3. 在循环体末尾更新 code 的值,避免死循环
cout << "Enter color code (0-6): ";
cin >> code;
}
cout << "Bye\n";
return 0;
}
/*
Enter color code (0-6): 1
Her hair was orange.
Enter color code (0-6): 6
Her mood was indigo.
Enter color code (0-6): 0
Her lips were red.
Enter color code (0-6): 7
Bye
*/