c++复习自存--流、异常处理、多线程与C++工程规范

使用流进行输入和输出

一、流的概述

C++ IO流是一套统一的数据传输抽象模型,把数据的输入/输出看作字符流的流动:

  1. 流的本质 :屏蔽控制台、文件、内存字符串等底层设备差异,使用统一>>输入、<<输出运算符读写数据;
  2. 分层结构:底层缓冲区 + 流对象,数据先存入缓冲区,缓冲区满/刷新时才真正传输;
  3. 四大流分类:标准控制台流、文件流、内存字符串流、错误流;
  4. 核心头文件:<iostream>(控制台)、<fstream>(文件)、<sstream>(字符串流)。

二、重要的C++流类和流对象

1. 预定义全局控制台对象(<iostream>

对象 作用 方向
std::cin 标准输入(键盘) 输入流 istream
std::cout 标准输出(控制台) 输出流 ostream
std::cerr 标准错误输出,无缓冲 输出流 ostream
std::clog 标准日志错误,带缓冲 输出流 ostream

2. 核心流类

  1. istream :输入流基类,提供>>读取、get()getline()等读取接口;
  2. ostream :输出流基类,提供<<写入、格式化控制接口;
  3. ifstream:文件输入流,读取磁盘文件;
  4. ofstream:文件输出流,写入磁盘文件;
  5. fstream:文件读写双向流;
  6. istringstream / ostringstream / stringstream:内存字符串流,在string缓冲区完成转换。

三、使用std::cout格式化输出到控制台

3.1 修改数字显示格式

依赖<iomanip>格式化操纵符,控制进制、浮点数精度、正负号:

cpp 复制代码
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
    int num = 20;
    // 进制:dec十进制(默认)、hex十六进制、oct八进制
    cout << hex << num << endl;
    cout << dec << num << endl;

    double pi = 3.1415926;
    // fixed固定小数位、setprecision设置精度
    cout << fixed << setprecision(4) << pi; // 保留4位小数
    // scientific科学计数法
    cout << scientific << pi;
    return 0;
}

常用操纵符:showpos显示正数加号、uppercase十六进制大写、noshowpos取消符号。

3.2 对齐文本与设置字段宽度

setw(n)设置输出占位宽度,left左对齐、right右对齐(默认)、setfill(c)填充空白字符:

cpp 复制代码
cout << setw(8) << setfill('-') << left << 123;
// 输出:123-----  总宽度8,左对齐,空白用'-'填充

注意:setw仅对下一个输出元素生效,不会持久生效。

四、使用std::cin进行输入

4.1 读取到基础类型变量

>>运算符自动以空白(空格、回车、Tab)分隔数据,自动匹配int、double、string等基础类型:

cpp 复制代码
int a; double b; string s;
cin >> a >> b >> s;

缺陷:遇到空格会截断字符串,无法读取带空格的一整行。

4.2 cin::get读取char*字符缓冲区

  1. cin.get(char& ch):读取单个字符,包含空格、换行;
  2. cin.get(buf, size):读取最多size-1个字符存入char数组,遇换行停止,换行符留在输入缓冲区;
cpp 复制代码
char buf[128];
cin.get(buf, 128); // 读取一行到字符数组

4.3 读取整行到std::string

getline(cin, str)读取一整行,包含空格,自动丢弃末尾换行符:

cpp 复制代码
string line;
getline(cin, line);

坑点:混用cin >>getline时,>>残留换行符会让getline读到空行,需用cin.ignore()清空缓冲区换行。

五、使用std::fstream处理文件

5.1 open()与close()打开、关闭文件

cpp 复制代码
#include <fstream>
// 输出文件流
ofstream out;
out.open("test.txt"); // 打开,不存在则创建
out.close(); // 主动关闭,刷新缓冲区

// 输入文件流
ifstream in;
in.open("test.txt");
if (!in.is_open()) { /* 打开失败判断 */ }
in.close();

文件打开模式(可组合):

  • ios::out:写文件,覆盖原有内容;ios::app追加写入;
  • ios::in:读文件;
  • ios::binary:二进制读写;ios::trunc打开时清空文件。

5.2 创建文本文件,<<写入文本

cpp 复制代码
ofstream out("doc.txt");
out << "姓名:张三" << endl;
out << "分数:" << 95.5;
out.close();

5.3 >>读取文本文件

cpp 复制代码
ifstream in("doc.txt");
string name; double score;
in >> name >> score;

整行读取文件:getline(in, str)循环读取直到文件末尾eof()

5.4 读写二进制文件

二进制读写不用<</>>(会转义换行),使用read()/write()直接操作字节缓冲区:

cpp 复制代码
// 二进制写入
ofstream out("data.bin", ios::binary);
int val = 100;
out.write((char*)&val, sizeof(int));
// 二进制读取
ifstream in("data.bin", ios::binary);
int res;
in.read((char*)&res, sizeof(int));

六、使用std::stringstream完成字符串转换

<sstream>提供内存流,无需磁盘,在string缓冲区完成数值与字符串互转:

cpp 复制代码
#include <sstream>
int main()
{
    // 数字转字符串 ostringstream
    ostringstream oss;
    oss << 123 << "," << 3.14;
    string str = oss.str();

    // 字符串提取数字 istringstream
    string s = "10 20.5";
    istringstream iss(s);
    int a; double b;
    iss >> a >> b;
    return 0;
}

典型用途:拼接动态文本、字符串拆分、类型互相转换,替代C语言atoi/sprintf


流通用补充要点

  1. 缓冲区刷新endl换行+刷新缓冲区;'\n'仅换行不刷新;cout.flush()强制刷新;
  2. 流状态判断cin.fail()读取失败、cin.eof()到达文件末尾、cin.clear()重置错误标志;
  3. 流对象不可拷贝,仅能传递引用;文件/字符串流离开作用域会自动析构关闭资源。

异常处理

什么是异常

异常是程序运行时发生的不可预期错误 (内存访问越界、参数非法、文件打开失败、运算错误等),会打断正常执行流程。

传统C语言依靠返回值/全局错误码判断错误,容易被忽略;C++提供独立的try-throw-catch异常机制,分离业务逻辑与错误处理,异常发生时自动向上逐层匹配捕获代码。

导致异常的常见原因

  1. 资源错误:文件不存在、网络连接断开、堆内存分配失败new bad_alloc
  2. 参数非法:数组/vector下标越界、函数传入空指针、数值超出合法范围;
  3. 运算故障:除零、数值溢出、类型转换失败;
  4. 标准库内置异常:std::out_of_rangestd::logic_errorstd::runtime_error
  5. 自定义业务错误:校验不通过、权限不足、流程非法。

使用 try-catch 捕获异常

catch(...) 捕获所有异常

万能捕获块,可匹配任意类型抛出的异常,一般放在所有catch分支最后,作为兜底容错:

cpp 复制代码
try {
    // 可能报错的业务代码
} catch (int e) {
    // 捕获int类型异常
} catch (...) {
    // 捕获其余全部未知异常
    cout << "发生未知异常" << endl;
}

缺点:无法获取异常携带的错误信息,仅适合兜底保护。

捕获特定类型的异常

按异常类型精准匹配,多个catch按从上到下顺序匹配,先写子类异常、后写基类异常:

cpp 复制代码
try {
    vector<int> vec{1,2,3};
    vec.at(10); // 越界抛出 out_of_range
} catch (const std::out_of_range& err) {
    cout << "容器越界:" << err.what() << endl;
} catch (const std::exception& err) {
    cout << "标准异常:" << err.what() << endl;
}

使用 throw 抛出特定类型异常

throw主动抛出任意类型数据(基础类型、字符串、异常类对象),中断当前代码,向上寻找匹配catch:

cpp 复制代码
void CheckAge(int age) {
    if (age < 0 || age > 150) {
        // 抛出字符串异常
        throw "年龄数值非法";
    }
    if (age > 120) {
        // 抛出标准异常对象
        throw std::invalid_argument("年龄超出合理人类范围");
    }
}

异常处理的工作原理

核心机制:栈展开(Stack Unwind)

  1. throw触发异常,停止当前代码执行;
  2. 逐层销毁当前作用域局部对象(智能指针、类变量自动析构,RAII保证资源释放,无内存泄漏);
  3. 向上逐层函数栈查找匹配类型的catch捕获块;
  4. 找到匹配catch后,执行异常处理逻辑;
  5. 若整个调用栈无任何匹配捕获,程序直接调用std::terminate终止运行。

std::exception 标准异常基类

头文件<exception>,所有标准库异常都公有继承该类,核心虚函数:

cpp 复制代码
virtual const char* what() const noexcept;

返回描述错误的字符串,所有派生异常都会重写该函数。

标准派生异常分类:

  1. 逻辑异常logic_error:编译/代码逻辑问题,如invalid_argumentout_of_range
  2. 运行时异常runtime_error:运行时外部故障,如bad_alloc内存分配失败。

从 std::exception 派生自定义异常类

业务场景自定义专属异常,统一继承std::exception,重写what()

cpp 复制代码
#include <exception>
#include <string>
class UserErr : public std::exception {
private:
    std::string msg;
public:
    explicit UserErr(std::string m) : msg(m) {}
    const char* what() const noexcept override {
        return msg.c_str();
    }
};
// 抛出自定义异常
throw UserErr("用户ID不存在");

继续前行(多核、多线程、工程规范)

当今的处理器有何不同

现代CPU核心特性:

  1. 多核架构:单CPU封装多个独立计算核心,可并行执行多段代码;
  2. 超线程(SMT):单个核心虚拟出2个逻辑线程,提升CPU流水线利用率;
  3. 缓存分层:L1/L2/L3高速缓存,缓解内存读写瓶颈;
  4. 单核主频提升停滞,性能提升依靠多核并行计算,串行程序无法利用多核算力。

如何更好地利用多个内核:C++多线程

线程是什么

线程是进程内独立的执行流,共享进程堆、全局变量,私有栈、寄存器;

一个进程可创建多个线程,多核CPU可同时调度多个线程并行运行;

C++11标准引入原生线程库,头文件<thread>,跨平台统一接口。

为何编写多线程程序

  1. 多核性能压榨:并行拆分计算任务,缩短总运行时间;
  2. 阻塞任务解耦:IO等待(文件/网络)放入子线程,主线程保持响应;
  3. 并发服务:服务端同时处理多个客户端请求。

线程如何交换数据

  1. 共享内存:全局变量、堆对象,多线程同时读写会产生数据竞争
  2. 线程私有传递:创建线程时通过参数拷贝传递数据,无竞争;
  3. 同步容器/消息队列:线程间单向投递数据,规避裸共享变量。

使用互斥量、信号量同步线程

多线程共享资源必须同步,防止数据竞争:

  1. std::mutex 互斥锁 :同一时间仅一个线程持有锁,保护临界区读写;lock_guard/unique_lock自动解锁;
  2. std::condition_variable 条件变量:线程等待/唤醒,实现生产者-消费者模型;
  3. 信号量 semaphore(C++20):控制同时访问资源的最大线程数量。

多线程技术带来的问题

  1. 数据竞争:无同步读写共享变量,结果错乱、未定义行为;
  2. 死锁:多个线程互相持有对方需要的锁,全部永久阻塞;
  3. 伪共享:多线程修改同一缓存行不同变量,频繁缓存失效,性能暴跌;
  4. 线程创建销毁开销、线程调度切换损耗;
  5. 内存重排:CPU/编译器指令乱序,需std::atomic原子变量保障内存可见性。

编写杰出的C++代码 核心规范

  1. 内存安全:优先unique_ptr/shared_ptr智能指针,杜绝裸指针内存泄漏、野指针;
  2. 资源管理:RAII范式,文件、套接字、锁依靠对象生命周期自动释放;
  3. 类型安全:多用const、引用、enum类,减少强制类型转换;
  4. 容器优先:STL容器替代手动数组,算法替代手写循环;
  5. 异常规范:区分可恢复异常与致命错误,不吞底层异常;
  6. 并发规范:共享资源强制同步,原子变量处理简单计数,减少锁竞争;
  7. 现代C++风格:lambda、移动语义、auto、范围for,降低代码冗余;
  8. 可读性优先:清晰命名、拆分函数、避免超长复杂逻辑。
相关推荐
geovindu1 小时前
CSharp: Dijkstra Algorithms
开发语言·后端·算法·c#
XR1234567881 小时前
食品饮料与制药行业GMP合规网络建设:无尘车间的网络不能成为污染源
开发语言·网络·php
学逆向的2 小时前
汇编——位运算
开发语言·汇编·算法·网络安全
zhangjw342 小时前
第29篇:Java伪共享与对象分配:并发性能优化的关键
java·开发语言·性能优化
知无不研2 小时前
缺省参数与缺省函数的区别
c++·语法
Java面试题总结3 小时前
Python 开发技巧 · 高级装饰器 —— 从基础到工业级实战
开发语言·python
liulilittle4 小时前
Exhaustive drift-fix simulation V2 — KCC on shared-bottleneck wired path.
开发语言·网络·python·tcp/ip·计算机网络·信息与通信·通信
名字还没想好☜4 小时前
Go error 处理:errors.Is/As 与错误包装
开发语言·后端·golang·go·错误处理
红糖奶茶4 小时前
Python 中 while 循环计数异常的常见原因分析与正确搓搓
开发语言·python