详细介绍:ESP32 (基于 ESP-IDF) 应用开发相关 C++ 知识体系
文章目录
想要高效地开发 ESP32 (基于 ESP-IDF) 的应用,掌握一定的 C++ 知识体系 能让你的代码更高效、可维护性更强。
C++ 基础语法
基础类型与语法
- 变量与常量 (int, float, bool, const)
| 类型 | 说明 | 示例 |
|---|---|---|
int | 整型 | int a = 10; |
float | 单精度浮点型 | float pi = 3.14f; |
double | 双精度浮点型 | double e = 2.71828; |
bool | 布尔型 | bool flag = true; |
char | 字符型 | char c = 'A'; |
const | 常量修饰符 | const int MAX = 100; |
- 作用域与生命周期(局部变量 / 全局变量 / static)
| 类型 | 作用域 | 生命周期 | 示例 |
|---|---|---|---|
| 局部变量 | 函数内有效 | 函数结束即销毁 | int x = 10; |
| 全局变量 | 整个文件可见 | 程序运行期间 | int g = 5; |
| static 变量 | 仅函数内可见,但值持续存在 | 整个程序运行期间 | static int counter = 0; |
- 控制语句(if, switch, for, while)
if / else
int score = 85;
if (score >= 90) {
cout << "Excellent" << endl;
} else if (score >= 60) {
cout << "Pass" << endl;
} else {
cout << "Fail" << endl;
}
switch
int day = 3;
switch (day) {
case 1: cout << "Monday"; break;
case 2: cout << "Tuesday"; break;
case 3: cout << "Wednesday"; break;
default: cout << "Invalid day"; break;
}
循环语句
// for 循环
for (int i = 0; i < 5; i++) {
cout << i << " ";
}
// while 循环
int n = 0;
while (n < 3) {
cout << n << " ";
n++;
}
// do-while 循环
int m = 0;
do {
cout << m << " ";
m++;
} while (m < 2);
- 函数定义与声明(参数传递、返回值、函数重载)
参数传递方式
| 方式 | 示例 | 特点 |
|---|---|---|
| 值传递 | int add(int x) | 传入副本,不影响原变量 |
| 引用传递 | int add(int &x) | 操作原变量(高效) |
| 指针传递 | int add(int *x) | 通过地址访问(常用于数组) |
// 函数声明(声明告诉编译器函数存在)
int add(int a, int b);
// 函数定义(实际实现)
int add(int a, int b) {
return a + b;
}
函数重载:C++ 允许多个同名函数,只要参数不同。
int sum(int a, int b) { return a + b; }
double sum(double a, double b) { return a + b; } // 重载
完整示例
#include <iostream>
using namespace std;
// 全局变量
int globalCount = 0;
// 函数声明
int add(int a, int b);
void showStaticCounter();
// 主函数入口
int main() {
// 局部变量
int x = 10;
float y = 3.14;
const int MAX = 100; // 常量
cout << "变量示例: x=" << x << ", y=" << y << ", MAX=" << MAX << endl;
// 控制语句
if (x > 5) {
cout << "x 大于 5" << endl;
}
// switch 示例
int option = 2;
switch (option) {
case 1: cout << "选项1" << endl; break;
case 2: cout << "选项2" << endl; break;
default: cout << "未知选项" << endl; break;
}
// 循环语句
cout << "for 循环输出: ";
for (int i = 0; i < 3; i++) {
cout << i << " ";
}
cout << endl;
// 调用函数
int result = add(5, 7);
cout << "add(5,7) = " << result << endl;
// static 示例
showStaticCounter();
showStaticCounter();
showStaticCounter();
return 0;
}
// 函数定义:两个数相加
int add(int a, int b) {
globalCount++; // 修改全局变量
return a + b;
}
// static 变量演示:在函数内部保持状态
void showStaticCounter() {
static int counter = 0; // 静态局部变量
counter++;
cout << "static counter 调用次数: " << counter << endl;
}
命名空间与文件组织
| 概念 | 作用 | 示例 |
|---|---|---|
namespace | 避免命名冲突 | Sensor::init() |
using namespace std; | 简化代码(不推荐在库中使用) | cout << "Hello"; |
.h / .cpp 分离 | 声明与实现隔离 | led_controller.h / led_controller.cpp |
- namespace 使用(避免命名冲突)
- namespace 名称 { … } 定义命名空间;
- :: 表示“作用域解析运算符”,用于访问命名空间内的内容。
命名空间用于避免命名冲突,将变量、函数、类等分组管理。
例如:两个模块中都有名为 init() 的函数,可通过命名空间区分。
#include <iostream>
namespace Sensor {
void init() {
std::cout << "Sensor initialized." << std::endl;
}
}
namespace Network {
void init() {
std::cout << "Network initialized." << std::endl;
}
}
int main() {
Sensor::init(); // 调用传感器初始化
Network::init(); // 调用网络初始化
return 0;
}
- using namespace std; 的作用与风险
using namespace std; 允许直接使用 std 中的类和函数,无需写前缀
#include <iostream>
using namespace std;
int main() {
cout << "Hello ESP-IDF C++!" << endl; // 无需写 std::
}
注:会引发命名冲突,特别是在大型项目或使用第三方库(如 ESP-IDF)时。
using std::cout;
using std::endl;
始终使用完整前缀
std::cout << "Hello" << std::endl;
- 头文件与实现文件分离(.h / .cpp)
在大型 ESP-IDF 项目中,建议将声明与实现分离,便于模块化与编译管理。
led_controller.h
#ifndef LED_CONTROLLER_H
#define LED_CONTROLLER_H
#include "driver/gpio.h"
namespace Device {
class LedController {
public:
explicit LedController(gpio_num_t pin);
void on();
void off();
void toggle();
private:
gpio_num_t _pin;
bool _state;
};
} // namespace Device
#endif // LED_CONTROLLER_H
- led_controller.cpp
#include "led_controller.h"
namespace Device {
LedController::LedController(gpio_num_t pin)
: _pin(pin), _state(false) {
gpio_set_direction(_pin, GPIO_MODE_OUTPUT);
}
void LedController::on() {
_state = true;
gpio_set_level(_pin, 1);
}
void LedController::off() {
_state = false;
gpio_set_level(_pin, 0);
}
void LedController::toggle() {
_state = !_state;
gpio_set_level(_pin, _state);
}
} // namespace Device
- main.cpp
#include <iostream>
#include "led_controller.h"
extern "C" void app_main() {
Device::LedController led(GPIO_NUM_2);
led.on();
vTaskDelay(pdMS_TO_TICKS(1000));
led.toggle();
vTaskDelay(pdMS_TO_TICKS(1000));
led.off();
std::cout << "LED toggled using C++ class in ESP-IDF" << std::endl;
}
指针与引用
- 指针的定义与解引用
- 指针(Pointer) 是保存 变量内存地址 的变量。
- 使用 * 定义指针类型。
- 使用 & 取变量的地址。
- 使用 * 解引用(访问地址中的值)。
int a = 10;
int* p = &a; // p 保存 a 的地址
cout << "a 的地址: " << &a << endl;
cout << "p 保存的地址: " << p << endl;
cout << "p 指向的值: " << *p << endl; // 解引用指针
- nullptr 与空指针判断
- 空指针 是不指向任何有效对象的指针。
- C++11 起推荐使用 nullptr(而不是旧的 NULL 或 0)。
int* p = nullptr; // 定义空指针
if (p == nullptr) {
cout << "p 是空指针" << endl;
}
int x = 42;
p = &x; // 让 p 指向有效地址
if (p != nullptr) {
cout << "p 指向的值为: " << *p << endl;
}
- 引用 (&) 与引用传参
- 引用(Reference) 是变量的别名。
- 使用 & 定义引用,必须在定义时初始化。
- 引用本身不是对象,没有独立地址。
- 常用于函数参数,避免复制开销。
int a = 5;
int& ref = a; // ref 是 a 的别名
ref = 10; // 改变 ref 也会改变 a
cout << "a = " << a << endl; // 输出 10
void addOne(int& num) {
num += 1; // 修改原变量
}
int x = 10;
addOne(x);
cout << "x = " << x << endl; // 输出 11
- 动态内存管理 (new / delete)
- 在堆(heap)上动态分配内存,用完后需手动释放。
- new:分配内存
- delete:释放内存
- new[] / delete[]:用于数组
int* p = new int(5); // 动态分配单个整数
cout << "值为: " << *p << endl;
delete p; // 释放内存
int* arr = new int[3]{1, 2, 3}; // 动态数组
for (int i = 0; i < 3; i++) {
cout << arr[i] << " ";
}
delete[] arr; // 释放数组内存
注:不释放内存会导致「内存泄漏」。
- 智能指针(std::unique_ptr / std::shared_ptr)
C++11 引入智能指针,用于自动管理内存,防止泄漏。
| 类型 | 特点 | 使用场景 |
|---|---|---|
std::unique_ptr | 独占所有权,不可拷贝 | 独占资源场景 |
std::shared_ptr | 共享所有权,引用计数自动释放 | 多个对象共享资源 |
#include <memory>
#include <iostream>
using namespace std;
struct Dog {
string name;
Dog(string n) : name(n) { cout << name << " 出生 " << endl; }
~Dog() { cout << name << " 被销毁 " << endl; }
};
int main() {
// unique_ptr 独占所有权
unique_ptr<Dog> d1 = make_unique<Dog>("Lucky");
// unique_ptr 不能拷贝,只能转移所有权
unique_ptr<Dog> d2 = move(d1);
if (!d1) cout << "d1 已转移所有权" << endl;
// shared_ptr 共享所有权
shared_ptr<Dog> s1 = make_shared<Dog>("Bobby");
{
shared_ptr<Dog> s2 = s1; // 引用计数 +1
cout << "引用计数: " << s1.use_count() << endl;
} // s2 离开作用域,计数 -1
cout << "引用计数: " << s1.use_count() << endl;
return 0;
}
完整示例
#include <iostream>
#include <memory>
using namespace std;
void addOneByRef(int& num) {
num += 1; // 引用传参
}
void addOneByPtr(int* num) {
if (num != nullptr)
*num += 1; // 通过指针解引用修改
}
int main() {
int a = 10;
int* p = &a;
cout << "指针解引用: *p = " << *p << endl;
// 空指针判断
int* np = nullptr;
if (np == nullptr) cout << "np 是空指针" << endl;
// 引用示例
int& ref = a;
ref = 20;
cout << "引用修改后 a = " << a << endl;
// 动态内存
int* heapVar = new int(50);
cout << "动态内存值: " << *heapVar << endl;
delete heapVar; // 手动释放
// 函数传参
addOneByRef(a);
cout << "引用传参后 a = " << a << endl;
addOneByPtr(p);
cout << "指针传参后 a = " << a << endl;
// 智能指针
unique_ptr<int> up = make_unique<int>(100);
cout << "unique_ptr 值: " << *up << endl;
shared_ptr<int> sp1 = make_shared<int>(200);
shared_ptr<int> sp2 = sp1;
cout << "shared_ptr 引用计数: " << sp1.use_count() << endl;
return 0;
}
栈(Stack)与堆(Heap)
| 特性 | 栈 (Stack) | 堆 (Heap) |
|---|---|---|
| 分配方式 | 编译器自动分配 / 释放 | 程序员手动分配 / 释放 (new / delete) |
| 生命周期 | 离开作用域自动销毁 | 手动释放,否则内存泄漏 |
| 访问速度 | 快(连续内存) | 慢(动态分配) |
| 使用场景 | 临时变量、函数参数 | 动态数组、对象、跨函数传递数据 |
| 容量大小 | 一般较小(几 KB ~ 几 MB) | 较大(系统可管理的全部 RAM) |
- 栈内存示例:栈上的变量由系统自动管理,不需要手动释放。
#include <iostream>
using namespace std;
void stackExample() {
int a = 10; // 栈上分配
int b = 20;
int sum = a + b;
cout << "Stack Example: sum = " << sum << endl;
} // 函数结束,a、b、sum 自动释放
int main() {
stackExample();
cout << "Stack memory automatically released." << endl;
return 0;
}
- 所有局部变量 (a, b, sum) 都存放在 栈上。
- 当函数 stackExample() 结束,变量自动销毁,无需 delete。
- 堆内存示例:堆内存由程序员动态分配,必须手动释放。
#include <iostream>
using namespace std;
void heapExample() {
int* ptr = new int(42); // 在堆上分配一个整数
cout << "Heap value = " << *ptr << endl;
delete ptr; // 必须释放,否则内存泄漏
ptr = nullptr; // 防止悬空指针
}
int main() {
heapExample();
cout << "Heap memory manually released." << endl;
return 0;
}
- new int(42) 在堆上分配空间;
- delete ptr; 释放堆内存;
- 设置 ptr = nullptr 是良好习惯。
- 数组对比示例(栈 vs 堆)
#include <iostream>
using namespace std;
int main() {
// 栈上数组
int stackArr[3] = {1, 2, 3};
cout << "Stack array first element: " << stackArr[0] << endl;
// 堆上数组
int* heapArr = new int[3]{4, 5, 6};
cout << "Heap array first element: " << heapArr[0] << endl;
delete[] heapArr; // 必须释放堆数组
return 0;
}
- 栈数组自动释放;
- 堆数组必须用 delete[] 释放。
- 在 ESP-IDF 开发中的应用差异
| 场景 | 推荐存储方式 | 理由 |
|---|---|---|
| 临时计算变量 | 栈 | 简单、安全、速度快 |
| FreeRTOS 任务栈空间 | 栈 | 每个任务有独立栈 |
| 长期存在的数据(如缓存) | 堆 | 可动态分配较大内存 |
与 C 接口交互(如 malloc) | 堆 | 与 C 函数兼容 |
- ESP-IDF 中堆内存函数
void* ptr = malloc(1024); // 与 new 类似
free(ptr);
- 智能指针(堆的安全封装):避免忘记释放 delete 导致的内存泄漏。
#include <iostream>
#include <memory>
using namespace std;
void smartPtrExample() {
unique_ptr<int> p = make_unique<int>(100); // 自动释放堆内存
cout << "Smart pointer value = " << *p << endl;
} // 作用域结束,自动调用 delete
| 项目 | 栈 (Stack) | 堆 (Heap) |
|---|---|---|
| 分配方式 | 编译器自动 | 程序员手动(new / delete) |
| 生命周期 | 离开作用域自动销毁 | 直到手动释放 |
| 性能 | 快 | 相对慢 |
| 内存大小 | 较小 | 较大 |
| 常见问题 | 栈溢出 | 内存泄漏 |
| 管理方式 | 自动 | 手动或智能指针管理 |
面向对象编程 (OOP)
类与对象的定义
- 类(Class) 是对象的蓝图,定义对象的属性(成员变量)和行为(成员函数)。
- 对象(Object) 是类的实例(实际存在的实体)。
成员变量与成员函数
class Person {
public:
string name;
int age;
void sayHello() {
cout << "Hello, I'm " << name << ", age " << age << endl;
}
};
int main() {
Person p; // 创建对象
p.name = "Tom";
p.age = 20;
p.sayHello();
return 0;
}
- 访问修饰符:public / protected / private
| 修饰符 | 访问范围 | 使用场景 |
|---|---|---|
public | 类外可访问 | 对外接口 |
protected | 派生类可访问,外部不可访问 | 继承使用 |
private | 仅类内可访问 | 内部实现 |
class Student {
private:
int id; // 私有成员,外部不能直接访问
public:
string name;
void setId(int newId) { id = newId; }
int getId() { return id; }
};
构造函数、析构函数(包括默认构造、拷贝构造、移动构造)
- 构造函数(Constructor)
- 在对象创建时自动调用。
- 用于初始化成员变量。
- 名称与类名相同,无返回值。
- 析构函数(Destructor)
- 在对象销毁时自动调用。
- 用于释放资源(如动态内存)。
- 构造函数(Constructor)
默认构造函数
class Car {
public:
string brand;
int year;
// 默认构造函数
Car() {
brand = "Unknown";
year = 2000;
cout << "默认构造函数调用 " << endl;
}
void info() {
cout << brand << " - " << year << endl;
}
};
带参数构造函数
Car(string b, int y) {
brand = b;
year = y;
cout << "带参数构造函数调用 " << endl;
}
拷贝构造函数
Car(const Car& other) {
brand = other.brand;
year = other.year;
cout << "拷贝构造函数调用 " << endl;
}
移动构造函数(C++11)
Car(Car&& other) noexcept {
brand = move(other.brand);
year = other.year;
cout << "移动构造函数调用 ⚡️" << endl;
}
拷贝构造函数: 拷贝构造函数用于创建一个新对象,作为现有对象的副本。它通常接受一个同类型对象的常量引用。
- 它创建一个新对象,该对象是原对象的完整副本。
- 原对象和新对象拥有独立的资源(深拷贝)。
- 如果类管理着动态内存或其他资源,拷贝构造函数需要分配新内存并复制内容。
- 拷贝构造函数可能会带来性能开销,特别是当对象很大或资源复制成本高时。
- 拷贝构造: 创建独立副本,安全但可能有性能开销
移动构造函数: 移动构造函数是C++11引入的新特性,它用于将资源从一个对象(通常是临时对象)转移到另一个对象,而无需复制。
- 它接受一个同类型对象的右值引用(使用&&)。
- 它“窃取”原对象的资源,而不是复制它们。
- 移动后,原对象处于有效但未定义的状态(通常为空状态),因此不能再使用其资源。
- 移动操作通常更高效,因为它避免了不必要的复制。
- 移动构造: 转移资源所有权,高效但原对象会失效
析构函数
~Car() {
cout << "析构函数调用 " << endl;
}
- this 指针
- 每个成员函数内部都有一个隐藏的指针 this。
- this 指向当前对象本身。
- 常用于区分成员变量与参数同名的情况。
class Box {
private:
int width;
public:
void setWidth(int width) {
this->width = width; // 区分成员变量与参数
}
int getWidth() { return this->width; }
};
- 类的初始化列表
初始化列表在构造函数执行前初始化成员变量,比在函数体内赋值更高效。
class Point {
private:
int x;
int y;
public:
Point(int a, int b) : x(a), y(b) { // 初始化列表
cout << "Point 初始化完成 (" << x << "," << y << ")" << endl;
}
};
完整示例
#include <iostream>
#include <string>
using namespace std;
class Car {
private:
string brand;
int year;
public:
// 默认构造函数
Car() : brand("Unknown"), year(2000) {
cout << "默认构造函数调用 " << endl;
}
// 带参数构造函数
Car(string b, int y) : brand(b), year(y) {
cout << "带参数构造函数调用 " << endl;
}
// 拷贝构造函数
Car(const Car& other) {
brand = other.brand;
year = other.year;
cout << "拷贝构造函数调用 " << endl;
}
// 移动构造函数
Car(Car&& other) noexcept {
brand = move(other.brand);
year = other.year;
cout << "移动构造函数调用 ⚡️" << endl;
}
// 析构函数
~Car() {
cout << "析构函数调用 (" << brand << ")" << endl;
}
// 成员函数
void showInfo() const {
cout << "品牌: " << brand << ", 年份: " << year << endl;
}
// 使用 this 指针
void setYear(int year) {
this->year = year; // this 指向当前对象
}
};
int main() {
cout << "=== 创建对象 ===" << endl;
Car c1; // 默认构造
Car c2("Tesla", 2024); // 带参数构造
c2.showInfo();
cout << "=== 拷贝构造 ===" << endl;
Car c3 = c2; // 拷贝构造
c3.showInfo();
cout << "=== 移动构造 ===" << endl;
Car c4 = move(c2); // 移动构造
c4.showInfo();
cout << "=== 修改属性 ===" << endl;
c1.setYear(2025);
c1.showInfo();
cout << "=== 程序结束,析构函数自动调用 ===" << endl;
return 0;
}
C++ 进阶特性
面向对象高级特性
- 继承 (class A : public B)
继承允许一个类获取另一个类的属性和方法。
class Derived : public Base { ... };
访问修饰符 public/protected/private 决定基类成员在派生类中的访问权限。
class Animal {
public:
void eat() { cout << "Animal eats" << endl; }
};
class Dog : public Animal { // 公有继承
public:
void bark() { cout << "Dog barks" << endl; }
};
int main() {
Dog d;
d.eat(); // 继承自 Animal
d.bark();
return 0;
}
- 多态 (virtual 函数、纯虚函数)
- 抽象类与接口类
虚函数 (Virtual Function):允许通过 基类指针/引用 调用派生类方法,实现运行时多态。
class Animal {
public:
virtual void speak() { cout << "Animal speaks" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Dog barks" << endl; }
};
int main() {
Animal* a = new Dog();
a->speak(); // 输出 Dog barks
delete a;
}
纯虚函数与抽象类:
- 纯虚函数:virtual void func() = 0;
- 包含纯虚函数的类称为 抽象类,不能实例化。
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() override { cout << "Draw Circle" << endl; }
};
int main() {
Shape* s = new Circle();
s->draw();
delete s;
}
- 函数重载、运算符重载
函数重载:同名函数,根据参数类型/个数不同自动区分。
class Math {
public:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
};
运算符重载:可以为自定义类型定义运算符行为。
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {}
Point operator+(const Point& p) {
return Point(x + p.x, y + p.y);
}
};
int main() {
Point p1(1,2), p2(3,4);
Point p3 = p1 + p2;
cout << p3.x << "," << p3.y << endl; // 输出 4,6
}
- 静态成员与单例模式
静态成员:static 修饰的成员属于类本身,而不是对象。
class Counter {
public:
static int count;
Counter() { count++; }
};
int Counter::count = 0;
int main() {
Counter c1, c2;
cout << "Count = " << Counter::count << endl; // 输出 2
}
单例模式(保证全局唯一实例)
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 构造私有化
public:
static Singleton* getInstance() {
if (!instance) instance = new Singleton();
return instance;
}
void show() { cout << "Singleton instance" << endl; }
};
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->show();
cout << "s1 == s2? " << (s1 == s2) << endl; // 输出 1 (true)
}
完整示例
#include <iostream>
using namespace std;
// 抽象基类
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
};
// 派生类
class Dog : public Animal {
public:
void speak() override { cout << "Dog barks" << endl; }
};
// 运算符重载示例
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {}
Point operator+(const Point& p) { return Point(x + p.x, y + p.y); }
};
// 静态成员 & 单例模式
class Counter {
public:
static int count;
Counter() { count++; }
};
int Counter::count = 0;
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (!instance) instance = new Singleton();
return instance;
}
void show() { cout << "Singleton instance" << endl; }
};
Singleton* Singleton::instance = nullptr;
int main() {
// 多态示例
Animal* a = new Dog();
a->speak();
delete a;
// 运算符重载
Point p1(1,2), p2(3,4);
Point p3 = p1 + p2;
cout << "Point p3: " << p3.x << "," << p3.y << endl;
// 静态成员
Counter c1, c2;
cout << "Counter count: " << Counter::count << endl;
// 单例模式
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->show();
cout << "s1 == s2? " << (s1 == s2) << endl;
return 0;
}
模板编程
- 函数模板 / 类模板
函数模板(Function Template):函数模板允许函数根据 传入类型自动生成 对应版本。
template<typename T>
T add(T a, T b) {
return a + b;
}
#include <iostream>
using namespace std;
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
cout << add(1, 2) << endl; // int 类型
cout << add(3.5, 2.5) << endl; // double 类型
}
类模板(Class Template):类模板允许类在实例化时指定类型。
template<typename T>
class Box {
private:
T value;
public:
Box(T v) : value(v) {}
void show() { cout << "Value: " << value << endl; }
};
- 模板参数与类型推导
模板类型可以通过 调用时自动推导。
template<typename T>
void print(T value) {
cout << value << endl;
}
int main() {
print(100); // 自动推导 T=int
print(3.14); // 自动推导 T=double
print("Hello"); // 自动推导 T=const char*
}
可以指定多个模板参数
template<typename T1, typename T2>
void show(T1 a, T2 b) {
cout << a << ", " << b << endl;
}
- typename / template 关键字
typename:用于声明模板类型参数
template<typename T> // 或 template<class T>
class Example { ... };
typename 还可用于 依赖类型的嵌套定义
template<typename T>
void func(typename T::value_type val) { ... }
template:在类模板内使用嵌套模板时,需要 template 关键字
template<class T>
struct A {
template<class U>
void func(U u) { ... }
};
- 模板特化与偏特化(了解即可)
完全特化(Full Specialization):针对特定类型重新实现模板
template<>
class Box<bool> { // bool 类型的特化
public:
void show() { cout << "This is a bool Box" << endl; }
};
偏特化(Partial Specialization):对一类类型进行定制
template<typename T>
class PtrBox<T*> { // 指针类型偏特化
public:
void show() { cout << "Pointer Box" << endl; }
};
完整示例
#include <iostream>
using namespace std;
// 函数模板
template<typename T>
T add(T a, T b) {
return a + b;
}
// 类模板
template<typename T>
class Box {
private:
T value;
public:
Box(T v) : value(v) {}
void show() { cout << "Value: " << value << endl; }
};
// 类模板特化(bool)
template<>
class Box<bool> {
public:
void show() { cout << "This is a bool Box" << endl; }
};
int main() {
// 函数模板示例
cout << add(10, 20) << endl;
cout << add(3.5, 2.5) << endl;
// 类模板示例
Box<int> b1(100);
Box<double> b2(3.14);
Box<bool> b3;
b1.show();
b2.show();
b3.show();
// 多模板参数
template<typename T1, typename T2>
auto multiply(T1 a, T2 b) { return a * b; }
cout << multiply(2, 5.5) << endl; // 11.0
return 0;
}
STL(标准模板库)
- 容器类:std::vector, std::map, std::queue, std::string
std::vector:动态数组,大小可变。
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
v.push_back(4); // 添加元素
v.pop_back(); // 删除末尾元素
for (int x : v) cout << x << " ";
cout << endl;
}
std::map:键值对容器(有序)。
#include <map>
int main() {
map<string, int> m;
m["Alice"] = 20;
m["Bob"] = 25;
for (auto& [key, value] : m)
cout << key << ": " << value << endl;
}
std::queue:队列,FIFO 结构。
#include <queue>
int main() {
queue<int> q;
q.push(1);
q.push(2);
cout << q.front() << endl; // 1
q.pop();
cout << q.front() << endl; // 2
}
std::string:字符串类,支持动态操作。
#include <string>
int main() {
string s = "Hello";
s += " World";
cout << s << endl;
}
- 算法:std::sort, std::find, std::for_each
std::sort
#include <algorithm>
#include <vector>
int main() {
vector<int> v = {3,1,4,2};
sort(v.begin(), v.end()); // 升序排序
for(int x : v) cout << x << " ";
}
std::find
#include <algorithm>
int main() {
vector<int> v = {1,2,3,4};
auto it = find(v.begin(), v.end(), 3);
if(it != v.end()) cout << "Found: " << *it << endl;
}
std::for_each
#include <algorithm>
#include <vector>
int main() {
vector<int> v = {1,2,3};
for_each(v.begin(), v.end(), [](int x){ cout << x*2 << " "; });
}
- 迭代器
STL 容器支持迭代器访问。
#include <vector>
int main() {
vector<int> v = {1,2,3};
for(auto it = v.begin(); it != v.end(); ++it)
cout << *it << " ";
}
- std::pair, std::tuple
std::pair
#include <utility>
int main() {
pair<int,string> p = {1,"Alice"};
cout << p.first << ", " << p.second << endl;
}
std::tuple
#include <tuple>
int main() {
tuple<int,string,double> t = {1,"Bob",3.14};
cout << get<0>(t) << ", " << get<1>(t) << ", " << get<2>(t) << endl;
}
- std::optional, std::variant(C++17)
std::optional:表示可能存在或不存在的值。
#include <optional>
int main() {
optional<int> opt; // 空
opt = 100; // 赋值
if(opt) cout << *opt << endl;
}
std::variant:类型安全的联合类型(类似 union)。
#include <variant>
int main() {
variant<int,string> v;
v = 10;
cout << get<int>(v) << endl;
v = "Hello";
cout << get<string>(v) << endl;
}
完整示例
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <string>
#include <algorithm>
#include <tuple>
#include <optional>
#include <variant>
using namespace std;
int main() {
// vector
vector<int> v = {3,1,4,2};
sort(v.begin(), v.end());
cout << "vector: ";
for(int x : v) cout << x << " ";
cout << endl;
// map
map<string,int> m = {{"Alice",20},{"Bob",25}};
for(auto& [k,v] : m) cout << k << ":" << v << " ";
cout << endl;
// queue
queue<int> q;
q.push(10); q.push(20);
cout << "queue front: " << q.front() << endl;
q.pop();
// pair & tuple
pair<int,string> p = {1,"Tom"};
tuple<int,string,double> t = {2,"Jerry",3.14};
cout << p.first << "," << p.second << endl;
cout << get<0>(t) << "," << get<1>(t) << "," << get<2>(t) << endl;
// optional & variant
optional<int> opt = 42;
if(opt) cout << "optional: " << *opt << endl;
variant<int,string> var = "Hello";
cout << "variant: " << get<string>(var) << endl;
return 0;
}
异常与错误处理
| 方法 | 特点 | 使用场景 |
|---|---|---|
| try/catch/throw | C++ 原生异常,自动栈展开 | 通用 C++ 应用 |
| esp_err_t + 返回值 | 明确错误码,不依赖异常 | ESP-IDF / 嵌入式项目 |
| ESP_ERROR_CHECK | 调试便利,失败自动 abort | ESP-IDF 常用调试宏 |
- ry / catch / throw
- throw:抛出异常
- try:包裹可能抛出异常的代码
- catch:捕获异常并处理
#include <iostream>
#include <stdexcept>
using namespace std;
int divide(int a, int b) {
if (b == 0) throw runtime_error("除数不能为零!");
return a / b;
}
int main() {
try {
int result = divide(10, 0); // 会抛出异常
cout << result << endl;
} catch (const runtime_error& e) {
cout << "捕获异常: " << e.what() << endl;
}
return 0;
}
- SP-IDF 通常禁用异常(-fno-exceptions),需掌握 错误码返回法 (esp_err_t)
ESP32 / ESP-IDF 默认禁用 C++ 异常(使用 -fno-exceptions 编译),
所以 通常不使用 try/catch/throw,而采用 错误码返回法。
esp_err_t 类型:ESP-IDF API 常用 esp_err_t 返回错误码
#include "esp_err.h"
#include <iostream>
using namespace std;
esp_err_t doSomething(int value) {
if (value < 0) return ESP_ERR_INVALID_ARG;
return ESP_OK;
}
int main() {
esp_err_t err = doSomething(-1);
if (err != ESP_OK) {
cout << "错误码: " << err << endl;
}
return 0;
}
- 误检查宏:ESP_ERROR_CHECK(x)
宏定义
#define ESP_ERROR_CHECK(x) do { \
esp_err_t rc = (x); \
if (rc != ESP_OK) { \
printf("ESP_ERROR_CHECK failed: %d\n", rc); \
abort(); \
} \
} while(0)
用法示例
#include "esp_err.h"
#include <iostream>
using namespace std;
esp_err_t initHardware(bool fail) {
if (fail) return ESP_ERR_INVALID_STATE;
return ESP_OK;
}
int main() {
cout << "初始化硬件..." << endl;
ESP_ERROR_CHECK(initHardware(false)); // 成功
cout << "初始化成功" << endl;
// ESP_ERROR_CHECK(initHardware(true)); // 若失败,程序会 abort
return 0;
}
注:ESP_ERROR_CHECK 在失败时会 打印错误并终止程序,常用于调试阶段。
完整示例
#include "esp_err.h"
#include <iostream>
using namespace std;
// 模拟函数:可能出错
esp_err_t divideEsp(int a, int b, int* result) {
if (b == 0) return ESP_ERR_INVALID_ARG; // 错误码返回
*result = a / b;
return ESP_OK;
}
int main() {
int a = 10, b = 0;
int result = 0;
#ifdef USE_CPP_EXCEPTION
// C++ 异常处理示例
try {
if (b == 0) throw runtime_error("除数不能为零!");
result = a / b;
cout << "结果: " << result << endl;
} catch (const runtime_error& e) {
cout << "捕获异常: " << e.what() << endl;
}
#else
// ESP-IDF 错误码处理示例
esp_err_t err = divideEsp(a, b, &result);
if (err != ESP_OK) {
cout << "错误码: " << err << endl;
} else {
cout << "结果: " << result << endl;
}
// 使用 ESP_ERROR_CHECK 宏
// ESP_ERROR_CHECK(divideEsp(a, b, &result)); // 若 b=0,会 abort
#endif
return 0;
}
C++11/14/17 特性(ESP-IDF 支持)
| 特性 | 描述 | ESP-IDF 使用建议 |
|---|---|---|
auto | 自动类型推导 | 方便迭代器、回调 |
constexpr | 编译期常量 | 用于数组大小、配置常量 |
| Lambda | 匿名函数 | 任务/回调、for_each |
| enum class | 类型安全枚举 | 推荐替代传统 enum |
| std::thread/std::mutex | C++ 原生线程 | ESP32 推荐 FreeRTOS 任务/信号量 |
| 移动语义 (T&&) | 高效资源转移 | 大对象/数组避免复制浪费 RAM |
- auto 类型推导
编译器根据初始化值自动推导变量类型。
#include <iostream>
#include <vector>
using namespace std;
int main() {
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"; // const char*
vector<int> v = {1,2,3};
for(auto it = v.begin(); it != v.end(); ++it) { // 自动推导迭代器类型
cout << *it << " ";
}
cout << endl;
}
- constexpr 常量表达式
在编译期计算的常量。
constexpr int square(int x) { return x*x; }
int main() {
constexpr int a = square(5); // 编译期计算
int arr[a]; // 可作为数组长度
cout << "a = " << a << endl;
}
- Lambda 表达式(在任务/回调中非常有用)
匿名函数,可用于回调、任务。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1,2,3,4};
// 简单 lambda
for_each(v.begin(), v.end(), [](int x){ cout << x*2 << " "; });
cout << endl;
// 捕获外部变量
int factor = 3;
for_each(v.begin(), v.end(), [factor](int x){ cout << x*factor << " "; });
cout << endl;
}
ESP-IDF 使用场景:可作为任务函数或回调
auto task = []() {
printf("FreeRTOS task lambda\n");
};
- enum class
enum class(强类型枚举):避免与其他枚举冲突,类型安全。
enum class Color { Red, Green, Blue };
Color c = Color::Red;
if(c == Color::Red) cout << "Red selected" << endl;
- std::thread / std::mutex(或使用 freertos/FreeRTOS.h 任务替代)
C++11 原生多线程(在 ESP32 上可以使用,FreeRTOS 更常用)
#include <thread>
#include <mutex>
#include <iostream>
using namespace std;
mutex mtx;
void threadFunc(int id) {
mtx.lock();
cout << "Thread " << id << " running" << endl;
mtx.unlock();
}
int main() {
thread t1(threadFunc, 1);
thread t2(threadFunc, 2);
t1.join();
t2.join();
}
ESP-IDF 通常使用 FreeRTOS
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void myTask(void* param) {
printf("FreeRTOS task running\n");
vTaskDelete(NULL);
}
void app_main() {
xTaskCreate(myTask, "task1", 2048, NULL, 5, NULL);
}
- 移动语义与右值引用 (T&&)
用于高效资源转移,减少拷贝。
#include <iostream>
#include <vector>
using namespace std;
class Buffer {
public:
vector<int> data;
Buffer(size_t n) : data(n, 0) {}
// 移动构造
Buffer(Buffer&& other) noexcept : data(move(other.data)) {
cout << "Move constructor called\n";
}
// 拷贝构造
Buffer(const Buffer& other) : data(other.data) {
cout << "Copy constructor called\n";
}
};
int main() {
Buffer b1(5);
Buffer b2 = move(b1); // 调用移动构造
}
ESP-IDF 使用场景:移动大数组、对象,减少 RAM 消耗。
综合示例(ESP-IDF + C++11/14/17)
#include <iostream>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <utility> // move
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
using namespace std;
mutex mtx;
// Buffer 类演示移动语义
class Buffer {
public:
vector<int> data;
Buffer(size_t n) : data(n, 0) {}
Buffer(Buffer&& other) noexcept : data(move(other.data)) { cout << "Move constructor\n"; }
};
// Lambda 任务示例
auto taskLambda = []() {
printf("FreeRTOS task running via lambda\n");
vTaskDelete(NULL);
};
int main() {
// auto 类型
auto x = 100;
cout << "x = " << x << endl;
// constexpr
constexpr int y = 5;
int arr[y] = {0};
cout << "constexpr array size = " << sizeof(arr)/sizeof(arr[0]) << endl;
// Lambda
vector<int> v = {1,2,3};
for_each(v.begin(), v.end(), [](int n){ cout << n*n << " "; });
cout << endl;
// enum class
enum class Color { Red, Green };
Color c = Color::Red;
if(c == Color::Red) cout << "Red selected" << endl;
// 移动语义
Buffer b1(10);
Buffer b2 = move(b1);
// C++ 线程 (ESP32 上建议用 FreeRTOS)
thread t1([](){ mtx.lock(); cout << "Thread running" << endl; mtx.unlock(); });
t1.join();
// ESP-IDF FreeRTOS 任务
xTaskCreate((TaskFunction_t)taskLambda, "task1", 2048, NULL, 5, NULL);
return 0;
}
ESP-IDF 开发相关 C++ 知识应用
FreeRTOS 与 C++ 结合
| 功能 | C++ 封装方式 | 说明 |
|---|---|---|
| 任务 | 类 + static 成员函数 | 通过 arg 访问对象成员 |
| Queue | 模板类 | send / receive 封装队列操作 |
| Semaphore / Mutex | 类封装 | lock / unlock |
| EventGroup | 类封装 | setBits / waitBits |
类中创建 FreeRTOS 任务(xTaskCreatePinnedToCore)
FreeRTOS 任务函数必须是 static 或普通函数,因为 C 风格函数指针不支持非静态成员函数。
在类中使用 静态成员函数作为任务入口,并通过 void* arg 传递对象指针访问成员变量。
使用类静态方法作为任务入口(static void task(void* arg))
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <iostream>
using namespace std;
class MyTask {
private:
int counter;
// 静态任务入口
static void taskFunc(void* arg) {
MyTask* self = static_cast<MyTask*>(arg); // 转回对象指针
for(int i=0; i<5; ++i) {
self->counter++;
printf("Counter: %d\n", self->counter);
vTaskDelay(pdMS_TO_TICKS(500));
}
vTaskDelete(NULL); // 任务结束
}
public:
MyTask() : counter(0) {}
// 创建任务
void start() {
xTaskCreatePinnedToCore(taskFunc, "MyTask", 2048, this, 5, NULL, 0);
}
};
extern "C" void app_main() {
MyTask t;
t.start();
}
- 在任务中访问对象成员变量
static void taskFunc(void* arg) {
MyTask* self = static_cast<MyTask*>(arg);
self->counter++; // 访问成员变量
}
- 使用 Queue, Semaphore, EventGroup 封装类管理资源
Queue 封装
#include "freertos/queue.h"
template<typename T>
class QueueWrapper {
private:
QueueHandle_t q;
public:
QueueWrapper(int size) {
q = xQueueCreate(size, sizeof(T));
}
bool send(const T& item, TickType_t timeout=portMAX_DELAY) {
return xQueueSend(q, &item, timeout) == pdTRUE;
}
bool receive(T& item, TickType_t timeout=portMAX_DELAY) {
return xQueueReceive(q, &item, timeout) == pdTRUE;
}
};
Semaphore 封装
#include "freertos/semphr.h"
class MutexWrapper {
private:
SemaphoreHandle_t mtx;
public:
MutexWrapper() { mtx = xSemaphoreCreateMutex(); }
void lock() { xSemaphoreTake(mtx, portMAX_DELAY); }
void unlock() { xSemaphoreGive(mtx); }
};
EventGroup 封装
#include "freertos/event_groups.h"
class EventGroupWrapper {
private:
EventGroupHandle_t eg;
public:
EventGroupWrapper() { eg = xEventGroupCreate(); }
void setBits(EventBits_t bits) { xEventGroupSetBits(eg, bits); }
EventBits_t waitBits(EventBits_t bits, bool clearOnExit=true) {
return xEventGroupWaitBits(eg, bits, clearOnExit, true, portMAX_DELAY);
}
};
综合示例
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include <iostream>
using namespace std;
class MyTask {
private:
int counter;
// 静态任务入口
static void taskFunc(void* arg) {
MyTask* self = static_cast<MyTask*>(arg);
for(int i=0; i<5; ++i) {
self->counter++;
printf("Counter: %d\n", self->counter);
vTaskDelay(pdMS_TO_TICKS(500));
}
vTaskDelete(NULL);
}
public:
MyTask() : counter(0) {}
void start() {
xTaskCreatePinnedToCore(taskFunc, "MyTask", 2048, this, 5, NULL, 0);
}
};
// Queue 封装
template<typename T>
class QueueWrapper {
private:
QueueHandle_t q;
public:
QueueWrapper(int size) { q = xQueueCreate(size, sizeof(T)); }
bool send(const T& item) { return xQueueSend(q, &item, portMAX_DELAY) == pdTRUE; }
bool receive(T& item) { return xQueueReceive(q, &item, portMAX_DELAY) == pdTRUE; }
};
// Mutex 封装
class MutexWrapper {
private:
SemaphoreHandle_t mtx;
public:
MutexWrapper() { mtx = xSemaphoreCreateMutex(); }
void lock() { xSemaphoreTake(mtx, portMAX_DELAY); }
void unlock() { xSemaphoreGive(mtx); }
};
// EventGroup 封装
class EventGroupWrapper {
private:
EventGroupHandle_t eg;
public:
EventGroupWrapper() { eg = xEventGroupCreate(); }
void setBits(EventBits_t bits) { xEventGroupSetBits(eg, bits); }
EventBits_t waitBits(EventBits_t bits) { return xEventGroupWaitBits(eg, bits, true, true, portMAX_DELAY); }
};
extern "C" void app_main() {
MyTask t;
t.start();
QueueWrapper<int> q(10);
q.send(42);
int value;
q.receive(value);
printf("Queue received: %d\n", value);
MutexWrapper mtx;
mtx.lock();
printf("Mutex locked\n");
mtx.unlock();
EventGroupWrapper eg;
eg.setBits(0x01);
EventBits_t bits = eg.waitBits(0x01);
printf("Event bits: 0x%x\n", bits);
}
驱动与外设抽象封装
| 外设 | 封装方式 | RAII | 单例模式 | 优点 |
|---|---|---|---|---|
| GPIO | 类封装 | ✔ 构造初始化,析构释放 | ✖ | 简化操作、自动管理 |
| I2C | 类封装 | ✔ 驱动安装/删除 | ✖ | 安全、复用 |
| UART | 类封装 | ✔ | ✔ 单例 | 系统控制台唯一实例 |
| SPI | 类封装 | ✔ 初始化/释放 | ✖ | 管理总线资源 |
- 把外设驱动(如 GPIO、I2C、UART、SPI)封装为 C++ 类
GPIO 驱动封装:使用 C++ 类封装 GPIO,RAII 管理资源(构造函数初始化,析构函数释放)。
#include "driver/gpio.h"
#include <iostream>
using namespace std;
class GPIO {
private:
gpio_num_t pin;
public:
GPIO(gpio_num_t p, gpio_mode_t mode) : pin(p) {
gpio_reset_pin(pin);
gpio_set_direction(pin, mode);
cout << "GPIO " << pin << " initialized\n";
}
void setHigh() { gpio_set_level(pin, 1); }
void setLow() { gpio_set_level(pin, 0); }
int read() { return gpio_get_level(pin); }
~GPIO() {
gpio_reset_pin(pin);
cout << "GPIO " << pin << " deinitialized\n";
}
};
extern "C" void app_main() {
GPIO led(GPIO_NUM_2, GPIO_MODE_OUTPUT);
led.setHigh();
vTaskDelay(1000 / portTICK_PERIOD_MS);
led.setLow();
}
I2C 驱动封装:封装初始化、读写,RAII 管理。
#include "driver/i2c.h"
#include <iostream>
using namespace std;
class I2CDevice {
private:
i2c_port_t port;
uint8_t addr;
public:
I2CDevice(i2c_port_t p, uint8_t a) : port(p), addr(a) {
i2c_config_t conf = {};
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = GPIO_NUM_21;
conf.scl_io_num = GPIO_NUM_22;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = 100000;
i2c_param_config(port, &conf);
i2c_driver_install(port, conf.mode, 0, 0, 0);
cout << "I2C device 0x" << hex << int(addr) << " initialized\n";
}
~I2CDevice() { i2c_driver_delete(port); }
void writeByte(uint8_t reg, uint8_t data) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr<<1)|I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_write_byte(cmd, data, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(port, cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(cmd);
}
};
- RAII 管理资源(构造打开 / 析构关闭)
- Singleton 模式用于驱动单例(例如系统 UART 控制台)
UART 封装 + 单例模式
#include "driver/uart.h"
#include <iostream>
using namespace std;
class UARTConsole {
private:
uart_port_t port;
UARTConsole() : port(UART_NUM_0) {
uart_config_t config = {115200, 8, UART_STOP_BITS_1, UART_PARITY_DISABLE, UART_HW_FLOWCTRL_DISABLE};
uart_param_config(port, &config);
uart_driver_install(port, 1024, 0, 0, NULL, 0);
cout << "UART console initialized\n";
}
public:
static UARTConsole& getInstance() {
static UARTConsole instance;
return instance;
}
void write(const char* msg) {
uart_write_bytes(port, msg, strlen(msg));
}
// 禁止拷贝
UARTConsole(const UARTConsole&) = delete;
void operator=(const UARTConsole&) = delete;
};
extern "C" void app_main() {
UARTConsole::getInstance().write("Hello UART\n");
}
SPI 驱动封装(示例)
#include "driver/spi_master.h"
#include <iostream>
using namespace std;
class SPIBus {
private:
spi_device_handle_t handle;
public:
SPIBus(spi_host_device_t host, int cs_pin) {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = GPIO_NUM_23;
buscfg.miso_io_num = GPIO_NUM_19;
buscfg.sclk_io_num = GPIO_NUM_18;
spi_bus_initialize(host, &buscfg, 1);
spi_device_interface_config_t devcfg = {};
devcfg.clock_speed_hz = 1*1000*1000;
devcfg.spics_io_num = cs_pin;
devcfg.mode = 0;
spi_bus_add_device(host, &devcfg, &handle);
cout << "SPI device initialized\n";
}
~SPIBus() {
spi_bus_remove_device(handle);
spi_bus_free(HSPI_HOST);
}
// TODO: read/write SPI data
};
综合示例(GPIO + UART 单例 + I2C)
extern "C" void app_main() {
// GPIO 输出
GPIO led(GPIO_NUM_2, GPIO_MODE_OUTPUT);
led.setHigh();
vTaskDelay(pdMS_TO_TICKS(500));
led.setLow();
// UART 输出(单例)
UARTConsole::getInstance().write("Hello UART Console\n");
// I2C 设备写入
I2CDevice dev(I2C_NUM_0, 0x3C);
dev.writeByte(0x00, 0xFF);
}
组件化与模块化设计
| 特性 | 说明 | 优点 |
|---|---|---|
| 独立模块类 | 每个功能单独封装 | 高内聚,易维护 |
| 接口类(纯虚类) | 定义模块交互 | 低耦合,便于替换或测试 |
| 静态库 / 组件导出 | 编译成组件,应用只依赖头文件 | 模块化,复用性强 |
| 应用层调用 | 通过接口操作 | 隐藏实现,便于扩展 |
- 每个功能模块(如传感器、网络、存储)单独成类或组件
- 高内聚:模块内部实现细节封装。
- 低耦合:模块间通过接口通信。
- 易测试与替换。
// Sensor.h
#pragma once
class Sensor {
public:
virtual void init() = 0; // 初始化传感器
virtual float readValue() = 0; // 获取数据
virtual ~Sensor() {}
};
// TemperatureSensor.h
#include "Sensor.h"
#include <iostream>
class TemperatureSensor : public Sensor {
public:
void init() override {
std::cout << "Temp sensor init" << std::endl;
}
float readValue() override {
return 25.0f; // 模拟温度
}
};
- 使用接口类定义模块交互(便于替换和测试)
- 通过 纯虚类 定义接口,模块内部实现隐藏。
- 方便替换实现或做 Mock 测试。
class Storage {
public:
virtual void writeData(const char* data) = 0;
virtual std::string readData() = 0;
virtual ~Storage() {}
};
// SPIFlashStorage.cpp
#include "Storage.h"
#include <iostream>
class SPIFlashStorage : public Storage {
public:
void writeData(const char* data) override {
std::cout << "Write to SPI flash: " << data << std::endl;
}
std::string readData() override {
return "SPI flash data";
}
};
void logSensorData(Sensor* sensor, Storage* storage) {
float value = sensor->readValue();
char buf[32];
sprintf(buf, "Value=%.2f", value);
storage->writeData(buf);
}
- 静态库或组件中导出 C++ 类接口
- 在 组件或静态库中导出接口,应用层只依赖接口头文件。
ESP-IDF 组件化设计示例
components/
my_sensor/
include/
Sensor.h
TemperatureSensor.h
src/
TemperatureSensor.cpp
CMakeLists.txt
idf_component_register(SRCS "TemperatureSensor.cpp"
INCLUDE_DIRS "include")
应用层使用
#include "TemperatureSensor.h"
#include "Storage.h"
extern "C" void app_main() {
TemperatureSensor tempSensor;
SPIFlashStorage storage;
tempSensor.init();
logSensorData(&tempSensor, &storage);
}
综合示例
#include <iostream>
using namespace std;
// 接口类
class Sensor {
public:
virtual void init() = 0;
virtual float readValue() = 0;
virtual ~Sensor() {}
};
class Storage {
public:
virtual void writeData(const char* data) = 0;
virtual string readData() = 0;
virtual ~Storage() {}
};
// 模块实现
class TemperatureSensor : public Sensor {
public:
void init() override { cout << "Temperature sensor init" << endl; }
float readValue() override { return 26.5f; }
};
class SPIFlashStorage : public Storage {
public:
void writeData(const char* data) override { cout << "Write: " << data << endl; }
string readData() override { return "SPI data"; }
};
// 模块交互
void logSensorData(Sensor* sensor, Storage* storage) {
float val = sensor->readValue();
char buf[32];
sprintf(buf, "Value=%.2f", val);
storage->writeData(buf);
}
// 应用层
extern "C" void app_main() {
TemperatureSensor tempSensor;
SPIFlashStorage storage;
tempSensor.init();
logSensorData(&tempSensor, &storage);
}
与 C 代码混用
| 功能 | C++ 封装方式 | 说明 |
|---|---|---|
| 调用 C 函数 | extern "C" | 防止名字改编,C++ 可调用 |
| 封装 IDF API | C++ 类 + 成员函数 | 对 esp_timer、esp_wifi 等 API 封装对象化接口 |
| 错误处理 | esp_err_t + 检查 / 宏 | 判断返回值是否 ESP_OK,调试方便 |
- extern “C”:在 C++ 文件中调用 C 函数
- C++ 编译器默认会进行名称改编(name mangling),导致 C 函数链接失败。
- 使用 extern “C” 告诉编译器按 C 规则编译,方便 C++ 调用 C 函数。
// my_c_code.h (C 文件头)
#ifndef MY_C_CODE_H
#define MY_C_CODE_H
#ifdef __cplusplus
extern "C" {
#endif
void my_c_function(int val);
#ifdef __cplusplus
}
#endif
#endif
// my_c_code.c
#include "my_c_code.h"
#include <stdio.h>
void my_c_function(int val) {
printf("C function called with %d\n", val);
}
// main.cpp
#include "my_c_code.h"
int main() {
my_c_function(42); // 调用 C 函数
return 0;
}
C++ 封装 IDF API(例如 esp_wifi_init()、esp_timer_start_once())
- ESP-IDF 大部分 API 是 C 风格,例如 esp_wifi_init()、esp_timer_start_once()。
- 可以在 C++ 类中封装,保持 对象化调用,同时处理 esp_err_t 返回值。
#include "esp_err.h"
#include "esp_timer.h"
#include <iostream>
using namespace std;
class TimerWrapper {
private:
esp_timer_handle_t timer;
static void timerCallback(void* arg) {
TimerWrapper* self = static_cast<TimerWrapper*>(arg);
cout << "Timer triggered!" << endl;
}
public:
TimerWrapper() {
esp_timer_create_args_t args = {};
args.callback = &TimerWrapper::timerCallback;
args.arg = this;
args.name = "my_timer";
esp_err_t err = esp_timer_create(&args, &timer);
if(err != ESP_OK) {
cout << "Timer create failed, err=" << err << endl;
}
}
void startOnce(uint64_t delay_us) {
esp_err_t err = esp_timer_start_once(timer, delay_us);
if(err != ESP_OK) {
cout << "Timer start failed, err=" << err << endl;
}
}
~TimerWrapper() {
esp_timer_delete(timer);
}
};
- 处理 esp_err_t 返回值
ESP-IDF API 都返回 esp_err_t,值为 ESP_OK 表示成功。
常用方式
esp_err_t err = esp_wifi_init(&cfg);
if(err != ESP_OK) {
cout << "WiFi init failed, err=" << err << endl;
}
使用宏封装
#define CHECK_ERR(x) do { \
esp_err_t __err = (x); \
if(__err != ESP_OK) { \
printf("Error %s:%d code=%d\n", __FILE__, __LINE__, __err); \
} \
} while(0)
CHECK_ERR(esp_wifi_init(&cfg));
综合示例
#include <iostream>
#include "esp_err.h"
#include "esp_timer.h"
using namespace std;
// C 函数示例
extern "C" void c_hello(int val) {
printf("C function called with %d\n", val);
}
// C++ 封装类
class TimerWrapper {
private:
esp_timer_handle_t timer;
static void timerCallback(void* arg) {
TimerWrapper* self = static_cast<TimerWrapper*>(arg);
cout << "Timer triggered!" << endl;
}
public:
TimerWrapper() {
esp_timer_create_args_t args = {};
args.callback = &TimerWrapper::timerCallback;
args.arg = this;
args.name = "cpp_timer";
esp_err_t err = esp_timer_create(&args, &timer);
if(err != ESP_OK) {
cout << "Timer create failed, err=" << err << endl;
}
}
void startOnce(uint64_t delay_us) {
esp_err_t err = esp_timer_start_once(timer, delay_us);
if(err != ESP_OK) {
cout << "Timer start failed, err=" << err << endl;
}
}
~TimerWrapper() {
esp_timer_delete(timer);
}
};
// 错误检查宏
#define CHECK_ERR(x) do { \
esp_err_t __err = (x); \
if(__err != ESP_OK) { \
printf("Error %s:%d code=%d\n", __FILE__, __LINE__, __err); \
} \
} while(0)
extern "C" void app_main() {
c_hello(100); // 调用 C 函数
TimerWrapper t;
t.startOnce(1000000); // 1秒后触发
// 示例 esp_err_t API 调用
esp_err_t err = esp_timer_start_once(timer, 500000); // 假设 timer 已定义
if(err != ESP_OK) cout << "Start failed: " << err << endl;
// 或使用宏
// CHECK_ERR(esp_timer_start_once(t.timer, 500000));
}
异步与回调机制
| 功能 | 实现方式 | 说明 |
|---|---|---|
| 回调机制 | std::function / Lambda | 灵活注册事件回调 |
| 任务间通信 | Queue / EventGroup | 异步消息传递、事件通知 |
| 异步执行 | std::future / FreeRTOS 事件组 | C++ 或 FreeRTOS 异步任务执行 |
- 注册事件回调(用 std::function / Lambda)
使用 std::function 存储回调函数,可接收 Lambda 或普通函数。
适用于异步事件通知(如传感器数据就绪、WiFi 事件)。
#include <iostream>
#include <functional>
using namespace std;
class EventSource {
private:
function<void(int)> callback; // 回调函数
public:
void registerCallback(function<void(int)> cb) {
callback = cb;
}
void triggerEvent(int value) {
if(callback) callback(value); // 调用回调
}
};
int main() {
EventSource evt;
// 注册 lambda 回调
evt.registerCallback([](int v){ cout << "Event received: " << v << endl; });
evt.triggerEvent(42); // 异步模拟触发事件
}
- 任务间通信(Queue、EventGroup)
FreeRTOS 提供队列和事件组用于任务间异步通信。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include <iostream>
using namespace std;
QueueHandle_t q;
EventGroupHandle_t eg;
void producer(void* arg) {
int data = 123;
xQueueSend(q, &data, portMAX_DELAY);
xEventGroupSetBits(eg, 0x01);
vTaskDelete(NULL);
}
void consumer(void* arg) {
int recvData;
xQueueReceive(q, &recvData, portMAX_DELAY);
EventBits_t bits = xEventGroupWaitBits(eg, 0x01, true, true, portMAX_DELAY);
printf("Consumer received: %d, event bits=0x%x\n", recvData, bits);
vTaskDelete(NULL);
}
extern "C" void app_main() {
q = xQueueCreate(1, sizeof(int));
eg = xEventGroupCreate();
xTaskCreate(producer, "producer", 2048, NULL, 5, NULL);
xTaskCreate(consumer, "consumer", 2048, NULL, 5, NULL);
}
- 使用 std::future 或 FreeRTOS 事件模拟异步执行
std::future + std::async:标准 C++ 异步任务(ESP32 编译需支持)。
FreeRTOS 可用事件组 +任务模拟异步执行。
#include <iostream>
#include <future>
using namespace std;
// 异步计算函数
int asyncAdd(int a, int b) {
return a + b;
}
int main() {
// std::future + std::async
future<int> result = async(asyncAdd, 10, 20);
cout << "Async result: " << result.get() << endl;
}
ESP-IDF / FreeRTOS 异步示例(事件 +任务)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include <stdio.h>
EventGroupHandle_t eg;
void asyncTask(void* arg) {
printf("Async task running...\n");
vTaskDelay(pdMS_TO_TICKS(500));
xEventGroupSetBits(eg, 0x01);
vTaskDelete(NULL);
}
extern "C" void app_main() {
eg = xEventGroupCreate();
xTaskCreate(asyncTask, "asyncTask", 2048, NULL, 5, NULL);
// 等待异步任务完成
EventBits_t bits = xEventGroupWaitBits(eg, 0x01, true, true, portMAX_DELAY);
printf("Async task finished, event bits=0x%x\n", bits);
}
综合示例(回调 + 异步 + 任务间通信)
#include <iostream>
#include <functional>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
using namespace std;
QueueHandle_t q;
class Sensor {
private:
function<void(float)> callback;
public:
void registerCallback(function<void(float)> cb) { callback = cb; }
void readValue() {
float value = 25.0f; // 模拟传感器
if(callback) callback(value); // 触发回调
xQueueSend(q, &value, portMAX_DELAY); // 异步任务发送
}
};
void consumerTask(void* arg) {
float val;
xQueueReceive(q, &val, portMAX_DELAY);
printf("Consumer received: %.2f\n", val);
vTaskDelete(NULL);
}
extern "C" void app_main() {
q = xQueueCreate(1, sizeof(float));
Sensor sensor;
// 注册回调
sensor.registerCallback([](float v){ printf("Callback: %.2f\n", v); });
// 启动消费者任务
xTaskCreate(consumerTask, "consumer", 2048, NULL, 5, NULL);
// 触发传感器读取
sensor.readValue();
}
ESP-IDF 实践常见 C++ 场景
| 应用场景 | 涉及知识点 |
|---|---|
| 创建一个传感器类(如 DHT11) | 类封装、构造函数、成员函数 |
| 异步任务执行 | FreeRTOS 任务、Lambda、静态任务函数 |
| Wi-Fi 连接管理 | 状态机设计、回调、单例模式 |
| 日志与调试输出 | esp_log.h + std::string 格式化 |
| 封装 SPI 驱动 | 析构清理、异常/错误码处理 |
| OTA 更新类 | 模板、RAII、错误处理 |
| NVS 存储封装类 | 封装 IDF C API 为 C++ 类接口 |
推荐学习顺序(针对 ESP32 开发者)
- 基础语法与OOP(类、构造、析构、继承)
- FreeRTOS 与任务通信(在类中用)
- C++11 特性(auto, lambda, smart pointer)
- 封装 ESP-IDF C API 为 C++ 类
- 模板与 STL 容器
- 异步回调与任务调度(std::function / FreeRTOS)

浙公网安备 33010602011771号