C++ 学习笔记(八):移动语义与右值引用
拷贝是把资源复制一份,移动是把资源的所有权直接转交出去。对于管理堆内存、文件句柄这类资源的对象,移动比拷贝快得多——拷贝要 malloc + memcpy,移动只是改几个指针。 一、左值和右值理解移动语义之前先搞清楚左值和右值。 左值(lvalue):有名字、有持久地址,可以出现在赋值号左边: 123int x = 10; // x 是左值int arr[4]; // arr[0] 是左值SensorData data; // data 是左值 右值(rvalue):临时的、没有名字,不能取地址,赋值后就消失: 1234int y = x + 1; // x + 1 是右值,计算完就没了func(); // 函数返回的临时对象是右值42 // 字面量是右值SensorData() // 临时构造的对象是右值 区分的简单方法:能不能对它取地址。&x 合法,&42 不合法。 二、右值引用C++11 新增了右值引用,用 && 表示,专门用来绑定右值: 12...
C++ 学习笔记(七):智能指针——unique_ptr 与 shared_ptr
裸指针本身不表达所有权——看到一个 int *p,你不知道它是谁分配的、谁负责释放、释放了没有。智能指针把所有权语义编进类型里,让编译器帮你管理内存。 一、unique_ptr:独占所有权unique_ptr 表示”这块内存只有我持有,我析构时自动释放”。 12345678#include <memory>std::unique_ptr<uint8_t[]> buf(new uint8_t[256]);// 或者用 make_unique(C++14)auto buf = std::make_unique<uint8_t[]>(256);buf[0] = 0xAB; // 正常使用,像普通指针一样// 离开作用域,buf 析构,内存自动释放 独占的意思是不能拷贝,只能移动: 123auto a = std::make_unique<int>(42);auto b = a; // ❌ 编译错误,不能拷贝auto b = std::move(a); // ✅ 转移所有权,a 变为 nullptr,b 持有资...
C++ 学习笔记(六):RAII——资源管理的核心思想
RAII 全称 Resource Acquisition Is Initialization,直译是”资源获取即初始化”,但这个名字没有把最重要的部分说清楚——真正的核心是资源释放和对象生命周期绑定。对象构造时获取资源,析构时释放资源,析构由编译器保证调用,资源就不会泄漏。 一、没有 RAII 的世界C 里管理资源的典型写法: 1234567891011121314151617181920void process(void) { uint8_t *buf = malloc(256); if (!buf) return; FILE *f = fopen("log.txt", "w"); if (!f) { free(buf); // 别忘了释放 buf return; } if (some_error()) { fclose(f); // 别忘了关文件 free(buf); // 别忘了释放 buf...
C++ 学习笔记(五):模板基础——函数模板与类模板
模板是 C++ 的编译期多态机制。写一份代码,编译器根据实际使用的类型自动生成对应版本,运行时没有额外开销。 一、函数模板写一个交换两个变量的函数,C 里要为每种类型单独写一个: 12void swap_int(int *a, int *b) { int t = *a; *a = *b; *b = t; }void swap_float(float *a, float *b) { float t = *a; *a = *b; *b = t; } C++ 里用函数模板: 123456789101112template<typename T>void swap(T &a, T &b) { T tmp = a; a = b; b = tmp;}int x = 1, y = 2;float p = 1.0f, q = 2.0f;swap(x, y); // 编译器生成 swap<int>swap(p, q); // 编译器生成 swap<...
C++ 学习笔记(四):继承与多态——虚函数和 vtable 的代价
项目里加了第二种传感器,轮询函数就要改一次;加了第三种,再改一次。继承和多态解决的就是这个问题——不同类型的对象,对外暴露同一套接口,上层代码不需要知道底下是什么。 一、继承:复用已有的类假设你有一个传感器基类: 12345678910class Sensor {public: Sensor(uint8_t addr) : addr_(addr) {} void set_address(uint8_t addr) { addr_ = addr; } uint8_t address() const { return addr_; }protected: uint8_t addr_;}; 温度传感器和湿度传感器都有地址,都需要 set_address,没必要重复写。用继承: 1234567891011121314151617181920class TempSensor : public Sensor {public: TempSensor(uint8_t add...
C++ 学习笔记(三):拷贝、赋值与 Rule of Three
对象被复制的时候发生了什么,是 C++ 里最容易踩坑的问题之一。很多 bug 不是逻辑错误,而是编译器默默帮你生成了一个”看起来能用,实则会崩”的拷贝函数。 一、默认拷贝:逐字节复制你没写任何拷贝相关的代码,编译器会生成一个默认拷贝构造函数,做的事是逐成员复制: 12345678910class GpioPin {public: GpioPin(uint32_t pin) : pin_(pin), state_(false) {}private: uint32_t pin_; bool state_;};GpioPin a(13);GpioPin b = a; // 调用拷贝构造函数,b.pin_ = 13,b.state_ = false 对于只含基本类型的类,默认拷贝没问题。 问题出在含有指针或资源的类: 123456789101112131415class UartDriver {public: UartDriver(uint32_t baud) : baud_(baud) {...
C++ 学习笔记(二):类与对象——构造、析构、访问控制
C 里你大概写过这种代码: 123456789typedef struct { uint8_t *buf; uint32_t baud; uint8_t initialized;} UART_Handle;void UART_Init(UART_Handle *h, uint32_t baud);void UART_Send(UART_Handle *h, const uint8_t *data, uint16_t len);void UART_Deinit(UART_Handle *h); 数据和操作是分开的——UART_Handle 只是一堆字段,函数靠第一个参数 *h 关联到数据。这没有任何问题,C 大型项目都是这么做的。 C++ 的类做的事情完全一样,只是把数据和函数打包在一起,编译器帮你传那个隐式的 *h: 12345678910class UartDriver {public: void init(uint32_t baud); void send(const uint8_t *data, uint...
C++ 学习笔记(一):从 C 到 C++,你已经会的和需要转变的
如果你已经用 C 写过嵌入式项目,上手 C++ 的成本比你想象的低——大部分 C 代码可以直接放进 .cpp 文件编译通过。真正需要转变的不是语法,而是思维方式:C 是”告诉机器怎么做”,C++ 还多了一层”把相关的数据和操作组织在一起”。 这篇文章不从头教 C++,而是专门讲 C 开发者切换到 C++ 时会碰到的差异点。 一、引用:比指针少一个 *,但不止于此C 里传指针是家常便饭: 1234567void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp;}swap(&x, &y); // 调用时要取地址 C++ 里用引用: 1234567void swap(int &a, int &b) { int tmp = a; a = b; b = tmp;}swap(x, y); // 调用时不需要 &,看起来像传值 语义上:引用是变量的别名,不是新变量。声明时必须初始化,之后不能改变绑定的对象。 ...




