C++ Primer Plus(第6版)读书笔记【2】

第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
*/

正文完
 0
评论(没有评论)