从零到一学习c++(基础篇--筑基期九-语句)

  从零到一学习C++(基础篇) 作者:羡鱼肘子

温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。

 温馨提示2:本篇会尽量用更加通俗的语言介绍c++的基础,用通俗的语言去解释术语,但不会再大白话了哦。常见,常看,常想,渐渐的就会发现术语也是很简单滴。

 温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。

从零到一学习c++(基础篇--筑基期八-表达式)-优快云博客

温馨提示4:个人感觉对环境的适应还是挺重要的。

一、基础语句类型

1. 表达式语句:就是做一件事

简单来说:就像你每天做的小任务,比如“倒一杯水”“关灯”。
代码本质:任何表达式后加 ; 就是一个语句。

int x = 5;       // 赋值表达式语句
x++;             // 自增表达式语句
cout << "Hi";    // 输出表达式语句

2. 复合语句(块):打包多个任务

简单理解:把多个任务装进一个“盒子”({}),比如“早晨流程”:{刷牙 → 洗脸 → 吃早餐}。
代码本质:用 {} 包裹多个语句,形成独立作用域。

{ // 开始一个块
    int x = 10;        // 这个 x 只在块内有效
    cout << x << endl; // 输出 10
} // 块结束,x 被销毁
// cout << x;         // 这里会报错,x 不存在了

3. 选择语句:条件判断

(1) if-else:二选一 

一个栗子:如果温度大于30° → 开空调;否则 → 不开。

还可以是这样if-else if-else (理论上可以有很多的else if 但是不建议这样写哦) 

int temperature = 25;
if (temperature > 30) {
    cout << "开空调!";
} else if (temperature < 10) {
    cout << "穿羽绒服!";
} else {
    cout << "出门散步~";
}

(2) switch:多选一

我们来看看饮料机:选择饮料机按钮:1→可乐,2→雪碧,其他→水。

int choice = 2;
switch (choice) {
    case 1:
        cout << "可乐";
        break;  // 必须写 break,否则会继续执行下一个 case!
    case 2:
        cout << "雪碧";
        break;
    default:
        cout << "水";
}

4. 迭代语句:重复做某件事

(1) for 循环:明确次数

就像:做 10 个俯卧撑 → 从1数到 10 。

for (int i = 0; i < 10; i++) { // i从0到9
    cout << "第 " << i+1 << " 个俯卧撑!" << endl;
}

(2) while 循环:条件满足就一直做

就像:只要没吃饱 → 继续吃

int hunger = 80; // 饥饿值(0-100)
while (hunger > 30) {
    cout << "吃一口饭..." << endl;
    hunger -= 20;
}

do-while:条件循环

就像:先吃一口看看饱了没,只要没吃饱 → 继续吃

do { /*...*/ } while (cond);

温馨小贴士:while VS do-while

核心区别

特性while 循环do-while 循环
执行顺序先检查条件,条件满足才执行循环体先执行一次循环体,再检查条件
最少执行次数0 次(条件不满足时直接跳过)至少 1 次(无论如何都会先执行一次)
适用场景需要先检查条件再执行的情况必须至少执行一次的情况
语法while (条件) { ... }do { ... } while (条件);(注意分号)

对比

1. while 循环:先检查,后行动

  • 你准备去超市买苹果,但先检查钱包是否有钱

    • 有钱 → 进去买苹果。

    • 没钱 → 直接回家,不进去。

2. do-while 循环:先行动,后检查

  • 先冲进超市拿苹果,然后到收银台检查钱包:

    • 有钱 → 结账离开。

    • 没钱 → 放下苹果离开,但已经拿过苹果了。


场景1:用户输入验证

1. while 循环

要求用户必须输入正整数,否则重新输入:

int num;
cout << "请输入一个正整数:";
cin >> num;

// 先检查输入是否合法,不合法就循环要求重新输入
while (num <= 0) {
    cout << "输入错误!请重新输入:";
    cin >> num;
}

需求:需要先写一遍输入代码,导致重复!


2. do-while 循环

优化代码,避免重复:

int num;
do {
    cout << "请输入一个正整数:";
    cin >> num;
} while (num <= 0); // 输入后检查条件

优势:无论用户第一次输入是否正确,至少会执行一次输入操作,代码更简洁。


关键细节

  1. do-while 末尾必须有分号

    do { ... } while (条件); // 分号不可少!
  2. 避免无限循环
    确保循环体内有改变条件的代码,例如:

    int i = 0;
    do {
        cout << i << endl;
        i++; // 必须修改条件,否则无限循环!
    } while (i < 5);

小结:如何选择?

  • 用 while:当可能不需要执行循环体时(例如遍历链表前检查是否为空)。

  • 用 do-while:当必须至少执行一次循环体时(例如菜单交互、输入验证)。

一句话记住

  • while:“先看路,再开车”。

  • do-while:“先开车,再看路”。

(3) 范围 for(C++11 现代特性)

就像:自动遍历购物清单里的每个商品。

vector<string> list = {"苹果", "牛奶", "面包"};
for (const auto& item : list) { // 自动遍历容器
    cout << "买了:" << item << endl;
}

5. 跳转语句:改变执行顺序

(1) break:立刻停止

for (int i = 0; i < 100; i++) {
    if (i == 5) {
        break; // 当i=5时,直接跳出循环
    }
    cout << i << endl; // 只会输出0-4
}

(2) continue:跳过当前,继续下次

for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue; // 跳过i=2的情况
    }
    cout << i << endl; // 输出0,1,3,4
}

(3) return:从函数返回

int add(int a, int b) {
    return a + b; // 返回结果,函数结束
}

二、现代C++特性增强

1. 范围 for 循环(C++11)(底层是迭代器)

简化容器遍历,支持自定义类型(需实现 begin()/end()):

std::vector<int> vec{1, 2, 3};
for (const auto& elem : vec) {  // 只读用 const auto&
    std::cout << elem;
}

2. 带初始化的 if/switch(C++17)

限制变量作用域,增强代码安全性:

if (auto it = m.find(key); it != m.end()) {
    // it 仅在块内可见
}
switch (int x = compute(); x) { /*...*/ }

 温馨小贴士:

我们先来看看代码吧

1. 带初始化的 if 语句

if (auto it = m.find(key); it != m.end()) {
    // it 仅在块内可见
}
  • 功能:在 if 的条件部分声明变量并判断条件。

  • 关键点

    1. 变量初始化auto it = m.find(key) 在条件中初始化迭代器 it(假设 m 是 map 或类似容器)。

    2. 条件判断it != m.end() 检查是否找到键值对。

    3. 作用域限制it 仅在 if 块内可见,外部无法访问,避免命名污染。(这里是关键哦)

  • 对比传统写法

    auto it = m.find(key); // 变量暴露在外部作用域
    if (it != m.end()) {
        // ...
    }

2. 带初始化的 switch 语句

switch (int x = compute(); x) {
    case 1: /*...*/ break;
    case 2: /*...*/ break;
    default: /*...*/
}
  • 功能:在 switch 的条件部分初始化变量并作为判断依据。

  • 关键点

    1. 变量初始化int x = compute() 执行函数 compute() 并将返回值赋给 x

    2. 判断依据switch 根据 x 的值跳转到对应 case

    3. 作用域限制x 仅在 switch 块内可见。(这里很重要)

  • 对比传统写法

    int x = compute(); // 变量暴露在外部作用域
    switch (x) {
        // ...
    }

现代C++特性(C++17)(强化认知阶段)

1. 带初始化的条件语句

  • 允许在 if/switch 的条件中声明变量,语法为:
    if (初始化语句; 条件) { ... }
    switch (初始化语句; 变量) { ... }

  • 核心优势

    • 作用域限制:变量仅在条件语句块内有效,避免命名冲突。

    • 代码简洁性:将变量声明与条件判断合并,逻辑更紧凑。

    • 安全性:防止变量在外部被误用。

2. 应用场景

  • if 语句:适用于需要临时变量进行条件判断的场景(如查找容器元素)。

  • switch 语句:适用于需要根据计算结果进行多分支选择的场景。


示例对比(传统 vs 现代)

传统写法

// if 示例
auto it = m.find(key); // 变量在外部作用域
if (it != m.end()) {
    // ...
}
// 此处仍可访问 it,可能引发误操作!

// switch 示例
int x = compute(); // 变量在外部作用域
switch (x) {
    case 1: /*...*/ break;
}
// 此处仍可访问 x

现代写法(C++17)

// if 示例
if (auto it = m.find(key); it != m.end()) {
    // 仅在此块内可访问 it
}
// 此处无法访问 it,避免误用

// switch 示例
switch (int x = compute(); x) {
    case 1: /*...*/ break;
}
// 此处无法访问 x

小结一下:

  • 作用域控制:通过将变量声明限制在条件语句内,提升代码安全性。

  • 代码可读性:将变量初始化与条件判断合并,逻辑更清晰。

  • 适用性:适用于需要临时变量的条件判断场景。

3. constexpr if(C++17)(目前作为了解内容,后续会结合项目来演示学习)

编译期条件判断,简化模板代码:

template <typename T>
void process(T val) {
    if constexpr (std::is_integral_v<T>) {
        // 仅对整型编译此分支
    } else {
        // 其他类型
    }
}

4. 结构化绑定(C++17)(这个很重要哦)

解包元组或结构体,常用于范围 for

pair<string, int> student = {"Tom", 85};
auto& [name, score] = student; // 解包 pair
cout << name << "得了" << score << "分";

5. 异常处理改进(作为了解,就是提到的话自己知道有这个东西就行)

  • noexcept 说明符(C++11):声明函数不抛出异常。

  • try/catch:现代C++推荐用智能指针等资源管理技术替代裸 new/delete,减少异常风险。

温馨小贴士:(初步认知阶段,会有一点的困难,但是不要放弃哦,坚持下去会有结果的)

一、noexcept 说明符(C++11)

1. 核心作用

  • 声明函数不抛出异常:告知编译器该函数不会抛出异常,允许编译器进行优化。

  • 优化性能:编译器无需为noexcept函数生成异常处理代码,减少二进制体积。

  • 接口约束:调用方可以依赖此声明,无需处理该函数抛出的异常。

2. 语法与规则

void func() noexcept;      // 不抛出任何异常
void func() noexcept(true); // 等价于 noexcept
void func() noexcept(false); // 可能抛出异常

3. 示例分析

#include <stdexcept>

// 传统函数可能抛出异常
int unsafe_divide(int a, int b) {
    if (b == 0) throw std::invalid_argument("除数不能为0");
    return a / b;
}

// 使用 noexcept 声明不抛异常(但实际可能抛出,需谨慎!)
int safe_divide(int a, int b) noexcept {
    return a / b; // 若b=0,触发未定义行为(程序终止)
}

int main() {
    try {
        // unsafe_divide(10, 0); // 抛出异常,可以被捕获
        safe_divide(10, 0);     // noexcept函数抛出异常,程序终止
    } catch (const std::exception& e) {
        std::cerr << "捕获异常: " << e.what() << std::endl;
    }
    return 0;
}

4. 适用场景

  • 移动构造函数/赋值运算符:标准库容器在重新分配内存时优先使用noexcept移动操作。

  • 高性能关键路径:确保函数不会因异常影响性能。

  • 明确接口行为:例如析构函数默认noexcept,手动声明可增强可读性。


二、智能指针替代 new/delete(后期会单独学习智能指针,这个是内存管理的基础是非常非常重要的)

1. 核心改进

  • 资源自动释放:通过RAII(资源获取即初始化)管理内存,避免因异常导致内存泄漏。

  • 异常安全:即使异常发生,智能指针的析构函数仍会释放资源。

2. 对比示例

传统写法(裸指针 + try/catch

void process_file() {
    int* data = new int[100]; // 动态分配内存
    try {
        // 可能抛出异常的操作
        read_file(data);       // 假设 read_file 可能抛出异常
        process(data);
    } catch (const std::exception& e) {
        delete[] data;         // 必须手动释放!
        throw;                 // 重新抛出异常
    }
    delete[] data;             // 正常流程释放
}

问题:若read_fileprocess抛出异常且未在catch块中释放内存,会导致内存泄漏。


现代写法(智能指针)

#include <memory>

void process_file() {
    auto data = std::make_unique<int[]>(100); // 使用 unique_ptr
    try {
        read_file(data.get());
        process(data.get());
    } catch (const std::exception& e) {
        // 无需手动释放内存!unique_ptr 会在退出作用域时自动释放
        throw;
    }
}

优势:无论是否发生异常,unique_ptr都会在离开作用域时自动释放内存。


3. 智能指针类型

类型

用途

异常安全性

std::unique_ptr

独占所有权,不可复制

离开作用域时自动释放内存

std::shared_ptr

共享所有权,引用计数管理

引用计数归零时自动释放内存

std::weak_ptr

解决 shared_ptr 循环引用问题

不增加引用计数


三、综合应用示例

场景:读取文件并解析数据

#include <memory>
#include <fstream>
#include <vector>
#include <stdexcept>

// 使用 unique_ptr 管理文件资源
void read_and_process(const std::string& filename) {
    auto file = std::make_unique<std::ifstream>(filename);
    if (!file->is_open()) {
        throw std::runtime_error("无法打开文件");
    }

    std::vector<int> data;
    int value;
    while (*file >> value) {
        data.push_back(value);
    }

    if (data.empty()) {
        throw std::runtime_error("文件内容为空");
    }

    // 处理数据(假设 process_data 可能抛出异常)
    process_data(data); // noexcept(false)
}

int main() {
    try {
        read_and_process("data.txt");
    } catch (const std::exception& e) {
        std::cerr << "错误: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}

关键点分析

  1. 资源管理std::ifstreamunique_ptr管理,即使异常发生,文件句柄也会正确关闭。

  2. 异常传播:所有可能的错误(如文件打开失败、数据为空)通过异常向上传递,由main统一处理。

  3. 代码简洁性:无需在多个catch块中重复释放资源。


四、小结一下

1. noexcept 的核心价值

  • 性能优化:减少异常处理开销。

  • 接口清晰化:明确函数是否可能抛出异常。

2. 智能指针的核心价值

  • 资源安全:杜绝因异常导致的内存/资源泄漏。

  • 代码简洁:避免手动try/catch和资源释放逻辑。

3. 现代C++最佳实践

  • 优先使用智能指针:替代裸new/delete

  • 合理使用 noexcept:在明确不抛异常的函数上标记,但避免滥用。

  • 减少显式 try/catch:依赖RAII和异常传播机制,而非深层嵌套的异常捕获。

6. 协程支持(C++20)(了解就好)

  • co_await/co_return:异步编程模型(需编译器支持)。

    generator<int> seq() {
        for (int i=0; ; ++i) {
            co_yield i; // 生成值并暂停
        }
    }

三、一些建议

  1. 优先使用范围 for:简化遍历,避免迭代器错误。

  2. 限制变量作用域:如用带初始化的 if/switch

  3. 编译期分支优化:用 constexpr if 替代运行时条件。

  4. 避免 goto:用函数或结构化的控制流替代。

一句话理解语句

  • 表达式语句:做一件小事(x = 5;)。

  • 复合语句:打包一组任务({...})。

  • 选择语句:根据条件选择路线(if/switch)。

  • 循环语句:重复执行直到满足条件(for/while)。

  • 跳转语句:改变执行顺序(break/return)。

 喔吼吼,又学完了一部分,太棒了!!!一起去在下一篇学习函数吧😀

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚戏师

多谢道友

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值