当前位置: 首页 > news >正文

嵌入式开发核心知识点详解教程

嵌入式开发核心知识点详解教程

目录

  • IO多路复用详解
  • C++高级特性
  • 进程管理
  • 数据结构实现
  • GPIO与串口通信
  • 网络协议详解

IO多路复用详解

1. select、poll、epoll 深入对比

API层面对比

select

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);// 使用示例
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;int ret = select(sockfd + 1, &readfds, NULL, NULL, &tv);

特点

  • 使用固定大小的 fd_set(通常1024个文件描述符)
  • 有文件描述符数量上限(FD_SETSIZE)
  • 每次调用都需要重新初始化 fd_set
  • 跨平台性好(POSIX标准)

poll

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);struct pollfd {int fd;         // 文件描述符short events;   // 请求的事件(POLLIN、POLLOUT等)short revents;  // 返回的事件
};// 使用示例
struct pollfd fds[2];
fds[0].fd = sockfd1;
fds[0].events = POLLIN;
fds[1].fd = sockfd2;
fds[1].events = POLLIN;int ret = poll(fds, 2, 5000);  // 5秒超时

特点

  • 没有文件描述符数量上限(仅受系统资源限制)
  • 仍需要遍历整个数组检查就绪事件
  • 使用 pollfd 结构体更直观

epoll(Linux专属):

#include <sys/epoll.h>int epoll_create(int size);  // 创建epoll实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);// 使用示例
int epfd = epoll_create(1);struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for(int i = 0; i < nfds; i++) {if(events[i].events & EPOLLIN) {// 处理可读事件}
}

特点

  • 通过内核维护事件表
  • 不需要每次传递完整的文件描述符列表
  • 只返回就绪的文件描述符
  • 高效的事件通知机制

性能对比
特性selectpollepoll
文件描述符数量有上限(1024)无上限无上限
性能O(n)O(n)O(1)
每次调用开销需要重建fd_set需要传递整个数组只返回就绪fd
内核支持跨平台跨平台Linux专属
适用场景少量连接中等连接大量连接

性能分析

select/poll的问题

  • 每次调用都需要将文件描述符集合从用户空间复制到内核空间
  • 需要遍历整个集合查找就绪的文件描述符
  • 当连接数增加时,性能线性下降

epoll的优势

  • 使用事件驱动模型,只有就绪的文件描述符才会被返回
  • 通过内核维护的红黑树管理文件描述符
  • 通过回调机制避免遍历

触发方式对比

水平触发(Level Triggered, LT)

// select和poll都是水平触发
// epoll默认也是水平触发// 特点:
// - 只要文件描述符处于就绪状态,就会一直通知
// - 如果没有完全处理完数据,下次调用仍会通知
// - 不会丢失事件,但可能会有重复通知

边缘触发(Edge Triggered, ET)

// 仅epoll支持边缘触发
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;  // 设置边缘触发
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);// 特点:
// - 只在状态变化时通知一次
// - 必须一次性读取所有数据
// - 效率高,但编程复杂
// - 需要配合非阻塞IO使用

示例对比

// 水平触发(LT)
char buf[1024];
int n = read(fd, buf, 100);  // 只读100字节
// 下次epoll_wait仍会通知,因为还有数据未读// 边缘触发(ET)
char buf[1024];
while((n = read(fd, buf, sizeof(buf))) > 0) {// 必须循环读取直到返回EAGAIN
}
// 下次只有新数据到达才会通知

适用场景总结

select

  • ✅ 少量文件描述符(<100)
  • ✅ 跨平台需求
  • ✅ 超时精度要求高(微秒级)
  • ❌ 不适合高并发服务器

poll

  • ✅ 中等数量文件描述符(100-1000)
  • ✅ 需要跨平台
  • ✅ 没有文件描述符数量限制
  • ❌ 性能仍受连接数影响

epoll

  • ✅ 大量并发连接(>10000)
  • ✅ 高性能服务器(Nginx、Redis)
  • ✅ 长连接场景
  • ❌ 仅限Linux平台

实际应用选择

连接数 < 1000      → select/poll
连接数 > 1000      → epoll
跨平台需求         → select/poll
追求极致性能       → epoll + ET模式

C++高级特性

2. 面向对象三大特性

封装(Encapsulation)

概念
将数据和操作数据的方法封装在一起,形成独立的对象。对象内部数据私有化,只能通过公共方法访问。

目的

  • 隐藏实现细节
  • 保护数据不被随意修改
  • 提高代码安全性

示例

class BankAccount {
private:double balance;      // 私有数据string accountNo;public:// 公共接口BankAccount(string no) : accountNo(no), balance(0) {}void deposit(double amount) {if(amount > 0) {balance += amount;}}bool withdraw(double amount) {if(amount > 0 && amount <= balance) {balance -= amount;return true;}return false;}double getBalance() const {return balance;}
};// 使用
BankAccount account("123456");
account.deposit(1000);
// account.balance = -100;  // 错误!不能直接访问私有成员
account.withdraw(500);
cout << account.getBalance();  // 正确的访问方式

继承(Inheritance)

概念
允许一个类(子类/派生类)从另一个类(父类/基类)继承属性和方法,实现代码复用。

继承方式

class Animal {
protected:string name;int age;public:Animal(string n, int a) : name(n), age(a) {}void eat() {cout << name << " is eating." << endl;}virtual void speak() {  // 虚函数,支持多态cout << "Animal sound" << endl;}
};// public继承:父类的public和protected成员在子类中保持原访问权限
class Dog : public Animal {
private:string breed;public:Dog(string n, int a, string b) : Animal(n, a), breed(b) {}void speak() override {  // 重写父类虚函数cout << name << " barks: Woof!" << endl;}void fetch() {cout << name << " is fetching." << endl;}
};class Cat : public Animal {
public:Cat(string n, int a) : Animal(n, a) {}void speak() override {cout << name << " meows: Meow!" << endl;}
};// 使用
Dog dog("Buddy", 3, "Golden Retriever");
dog.eat();      // 继承自Animal
dog.speak();    // Dog的实现
dog.fetch();    // Dog特有的方法Cat cat("Whiskers", 2);
cat.eat();      // 继承自Animal
cat.speak();    // Cat的实现

多态(Polymorphism)

概念
同一操作作用于不同对象时,可以有不同的行为表现。主要通过虚函数和函数重载实现。

运行时多态(虚函数)

class Shape {
public:virtual double area() const = 0;  // 纯虚函数virtual void draw() const = 0;virtual ~Shape() {}  // 虚析构函数
};class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}double area() const override {return 3.14159 * radius * radius;}void draw() const override {cout << "Drawing Circle" << endl;}
};class Rectangle : public Shape {
private:double width, height;public:Rectangle(double w, double h) : width(w), height(h) {}double area() const override {return width * height;}void draw() const override {cout << "Drawing Rectangle" << endl;}
};// 多态的应用
void printShapeInfo(const Shape& shape) {shape.draw();cout << "Area: " << shape.area() << endl;
}int main() {Circle circle(5.0);Rectangle rect(4.0, 6.0);// 同一个函数,不同的行为printShapeInfo(circle);     // 调用Circle的实现printShapeInfo(rect);       // 调用Rectangle的实现// 指针的多态Shape* shapes[2];shapes[0] = new Circle(3.0);shapes[1] = new Rectangle(2.0, 4.0);for(int i = 0; i < 2; i++) {shapes[i]->draw();cout << "Area: " << shapes[i]->area() << endl;delete shapes[i];}return 0;
}

编译时多态(函数重载)

class Calculator {
public:// 函数重载int add(int a, int b) {return a + b;}double add(double a, double b) {return a + b;}int add(int a, int b, int c) {return a + b + c;}
};Calculator calc;
cout << calc.add(1, 2);           // 调用int版本
cout << calc.add(1.5, 2.3);       // 调用double版本
cout << calc.add(1, 2, 3);        // 调用三参数版本

三大特性总结

特性关键字作用实现方式
封装private/protected隐藏细节,保护数据访问控制
继承: public/protected/private代码复用,建立关系派生类
多态virtual接口统一,行为多样虚函数/重载

3. fork vs vfork

基本区别

fork

  • 子进程完全拷贝父进程的数据段和堆栈
  • 使用写时复制(Copy-On-Write, COW)优化
  • 子进程和父进程执行顺序不确定

vfork

  • 子进程和父进程共享数据段
  • 保证子进程先执行
  • 子进程必须立即调用 exec()_exit()

详细对比

区别1:内存管理

#include <unistd.h>
#include <stdio.h>int global_var = 10;// fork示例
void test_fork() {int local_var = 20;pid_t pid = fork();if(pid == 0) {// 子进程global_var = 100;local_var = 200;printf("Child: global=%d, local=%d\n", global_var, local_var);} else {// 父进程sleep(1);  // 等待子进程执行printf("Parent: global=%d, local=%d\n", global_var, local_var);// 输出:global=10, local=20(父子进程数据独立)}
}// vfork示例
void test_vfork() {int local_var = 20;pid_t pid = vfork();if(pid == 0) {// 子进程global_var = 100;local_var = 200;printf("Child: global=%d, local=%d\n", global_var, local_var);_exit(0);  // 必须使用_exit()} else {// 父进程printf("Parent: global=%d, local=%d\n", global_var, local_var);// 输出:global=100, local=200(共享数据)}
}

区别2:执行顺序

// fork - 执行顺序不确定
pid_t pid = fork();
if(pid == 0) {printf("Child\n");
} else {printf("Parent\n");
}
// 可能输出:Child Parent 或 Parent Child// vfork - 保证子进程先执行
pid_t pid = vfork();
if(pid == 0) {printf("Child\n");_exit(0);
} else {printf("Parent\n");
}
// 一定输出:Child Parent

区别3:写时复制(COW)

// fork使用COW技术
pid_t pid = fork();
if(pid == 0) {// 子进程首次写入时才真正复制数据int x = 100;  // 不触发复制(局部变量)global_var = 50;  // 触发复制,复制全局数据段
} else {// 父进程数据不受影响
}

区别4:危险的vfork使用

// 错误示例1:vfork后使用return
pid_t pid = vfork();
if(pid == 0) {printf("Child\n");return 0;  // 错误!会破坏父进程的栈
}// 错误示例2:子进程依赖父进程的后续操作
pid_t pid = vfork();
if(pid == 0) {// 子进程先执行,但需要父进程的数据// 此时父进程还未运行,可能导致死锁
}
// 父进程的操作// 正确示例:立即exec
pid_t pid = vfork();
if(pid == 0) {execl("/bin/ls", "ls", "-l", NULL);_exit(1);  // exec失败时退出
}

使用建议

使用fork的场景

// 1. 需要独立地址空间
pid_t pid = fork();
if(pid == 0) {// 子进程可以安全地修改数据modify_data();continue_execution();
}// 2. 父子进程都需要继续执行
pid_t pid = fork();
if(pid == 0) {handle_request();  // 子进程处理请求
} else {accept_connection();  // 父进程接受新连接
}

使用vfork的场景

// 立即exec,不需要复制内存
pid_t pid = vfork();
if(pid == 0) {execl("/usr/bin/program", "program", arg1, arg2, NULL);_exit(1);
}

对比表

特性forkvfork
内存复制使用COW延迟复制不复制,共享内存
执行顺序不确定子进程先执行
性能较慢(需要复制)较快
安全性高(数据独立)低(共享数据)
使用限制必须立即exec或_exit
推荐使用✅ 一般场景⚠️ 仅用于立即exec

现代建议

  • vfork已过时,大多数情况应使用fork
  • fork配合COW技术性能已经很好
  • 如需创建新进程执行程序,使用 posix_spawn() 更安全

4. 深拷贝 vs 浅拷贝

概念对比

浅拷贝(Shallow Copy)

  • 只复制指针的值
  • 多个对象共享同一块动态分配的内存
  • 可能导致内存泄漏或双重释放

深拷贝(Deep Copy)

  • 复制指针指向的内存内容
  • 为每个对象分配独立的内存空间
  • 对象间互不影响

代码示例
#include <iostream>
#include <cstring>class String {
private:char* data;public:// 构造函数String(const char* str = "") {if(str) {data = new char[strlen(str) + 1];strcpy(data, str);} else {data = new char[1];data[0] = '\0';}std::cout << "Constructor: " << data << std::endl;}// 浅拷贝构造函数(默认)String(const String& other) {data = other.data;  // 只复制指针std::cout << "Shallow Copy: " << data << std::endl;}// 深拷贝构造函数(正确)String(const String& other) {data = new char[strlen(other.data) + 1];  // 分配新内存strcpy(data, other.data);  // 复制内容std::cout << "Deep Copy: " << data << std::endl;}// 析构函数~String() {std::cout << "Destructor: " << data << std::endl;delete[] data;}void print() const {std::cout << "Data: " << data << std::endl;}void modify(const char* newStr) {delete[] data;data = new char[strlen(newStr) + 1];strcpy(data, newStr);}
};

浅拷贝的问题
void test_shallow_copy() {String s1("Hello");String s2 = s1;  // 浅拷贝// 问题1:共享同一块内存// s1.data和s2.data指向同一地址s1.modify("World");s2.print();  // 输出:World(s2也被修改了!)// 问题2:双重释放// 当s1和s2析构时,都会delete同一块内存// 导致程序崩溃!
}// 内存示意图
// 浅拷贝:
// s1.data ----→ ["Hello\0"]
// s2.data ----↗// 深拷贝:
// s1.data → ["Hello\0"]
// s2.data → ["Hello\0"]  (独立的内存)

深拷贝的实现
class String {
private:char* data;public:// 深拷贝构造函数String(const String& other) {if(other.data) {// 1. 分配新内存data = new char[strlen(other.data) + 1];// 2. 复制内容strcpy(data, other.data);} else {data = nullptr;}}// 深拷贝赋值运算符String& operator=(const String& other) {if(this != &other) {  // 防止自赋值// 1. 释放原有内存delete[] data;// 2. 分配新内存并复制if(other.data) {data = new char[strlen(other.data) + 1];strcpy(data, other.data);} else {data = nullptr;}}return *this;}// 移动构造函数(C++11)String(String&& other) noexcept {data = other.data;other.data = nullptr;  // 转移所有权}// 移动赋值运算符String& operator=(String&& other) noexcept {if(this != &other) {delete[] data;data = other.data;other.data = nullptr;}return *this;}~String() {delete[] data;}
};// 使用示例
void test_deep_copy() {String s1("Hello");String s2 = s1;  // 深拷贝s1.modify("World");s1.print();  // 输出:Worlds2.print();  // 输出:Hello(不受影响)// 析构时各自释放自己的内存,安全!
}

三/五/零法则

三法则(Rule of Three)
如果类需要自定义以下之一,通常需要自定义全部三个:

  1. 析构函数
  2. 拷贝构造函数
  3. 拷贝赋值运算符

五法则(Rule of Five, C++11)
在三法则基础上增加:
4. 移动构造函数
5. 移动赋值运算符

零法则(Rule of Zero)
尽量使用智能指针等RAII类,避免手动管理内存

// 零法则示例
class String {
private:std::unique_ptr<char[]> data;  // 使用智能指针size_t length;public:String(const char* str) {length = strlen(str);data = std::make_unique<char[]>(length + 1);strcpy(data.get(), str);}// 不需要自定义析构函数、拷贝/移动函数// 编译器生成的默认版本已经足够
};

实际应用场景

何时使用浅拷贝

// 简单类型,无动态内存
class Point {int x, y;// 默认浅拷贝足够
};// 共享资源(配合引用计数)
class SharedData {std::shared_ptr<int> data;// 使用智能指针自动管理
};

何时必须深拷贝

// 管理动态资源
class Image {unsigned char* pixels;int width, height;// 必须深拷贝pixels
};// 包含文件句柄等系统资源
class File {int fd;// 需要深拷贝或禁止拷贝
};

5. 函数重载(Function Overloading)

基本概念

定义
在同一作用域内,可以定义多个名称相同但参数列表不同的函数。

要求

  • 函数名必须相同
  • 参数列表必须不同(类型、个数或顺序)
  • 返回值可以相同也可以不同
  • 不能仅通过返回值区分重载

函数重载示例
#include <iostream>
using namespace std;class Math {
public:// 1. 参数个数不同int add(int a, int b) {cout << "add(int, int)" << endl;return a + b;}int add(int a, int b, int c) {cout << "add(int, int, int)" << endl;return a + b + c;}// 2. 参数类型不同double add(double a, double b) {cout << "add(double, double)" << endl;return a + b;}// 3. 参数顺序不同void print(int x, double y) {cout << "print(int, double): " << x << ", " << y << endl;}void print(double x, int y) {cout << "print(double, int): " << x << ", " << y << endl;}// 4. const修饰符不同(成员函数)void display() {cout << "display() - non-const" << endl;}void display() const {cout << "display() - const" << endl;}
};// 使用示例
int main() {Math math;cout << math.add(1, 2) << endl;           // 调用add(int, int)cout << math.add(1, 2, 3) << endl;        // 调用add(int, int, int)cout << math.add(1.5, 2.5) << endl;       // 调用add(double, double)math.print(1, 2.5);    // 调用print(int, double)math.print(1.5, 2);    // 调用print(double, int)math.display();        // 调用非const版本const Math cmath;cmath.display();       // 调用const版本return 0;
}

函数重载的本质

名称修饰(Name Mangling)
编译器在编译时根据参数类型修饰函数名,使其在底层拥有不同的名称。

// C++源代码
void func(int x);
void func(double x);
void func(int x, int y);// 编译后的符号(示例)
_Z4funci     // func(int)
_Z4funcd     // func(double)
_Z4funcii    // func(int, int)// 查看符号
// Linux: nm program | c++filt
// Windows: dumpbin /SYMBOLS program.obj

重载解析规则

编译器按以下顺序匹配函数:

1.#### 重载解析规则(续)

编译器按以下顺序匹配函数:

1. 精确匹配

void func(int x);
void func(double x);func(10);      // 精确匹配 func(int)
func(10.5);    // 精确匹配 func(double)

2. 类型提升

void func(int x);
void func(double x);func('a');     // char提升为int,匹配func(int)
func(true);    // bool提升为int,匹配func(int)

3. 标准类型转换

void func(int x);
void func(double x);func(10L);     // long转换为int,匹配func(int)
func(10.5f);   // float转换为double,匹配func(double)

4. 用户定义的转换

class MyInt {
public:operator int() const { return value; }
private:int value;
};void func(int x);
MyInt mi;
func(mi);  // 通过operator int()转换

5. 可变参数匹配

void func(int x);
void func(...);  // 可变参数func(10);        // 匹配func(int)
func("hello");   // 匹配func(...)

常见错误与陷阱

错误1:仅返回值不同

int func(int x);
double func(int x);  // 错误!不能仅通过返回值区分// 编译器无法确定调用哪个
func(10);  // 编译错误

错误2:默认参数引起歧义

void func(int x);
void func(int x, int y = 0);func(10);  // 错误!两个函数都匹配,产生歧义

错误3:类型转换引起歧义

void func(int x);
void func(double x);func(10L);  // 可能有歧义:long可以转换为int或double

正确示例:避免歧义

void func(int x);
void func(long x);     // 明确区分
void func(double x);func(10);    // 匹配func(int)
func(10L);   // 匹配func(long)
func(10.5);  // 匹配func(double)

模板与重载
// 1. 函数模板
template<typename T>
T max(T a, T b) {cout << "Template version" << endl;return (a > b) ? a : b;
}// 2. 普通重载函数(更特化)
int max(int a, int b) {cout << "Non-template version" << endl;return (a > b) ? a : b;
}int main() {max(1, 2);        // 调用非模板版本(更匹配)max(1.5, 2.5);    // 调用模板版本max<int>(1, 2);   // 显式指定模板,调用模板版本return 0;
}

运算符重载

运算符重载也是函数重载的特殊形式:

class Complex {
private:double real, imag;public:Complex(double r = 0, double i = 0) : real(r), imag(i) {}// 重载 + 运算符Complex operator+(const Complex& other) const {return Complex(real + other.real, imag + other.imag);}// 重载 << 运算符(友元函数)friend ostream& operator<<(ostream& os, const Complex& c) {os << c.real << " + " << c.imag << "i";return os;}// 重载 == 运算符bool operator==(const Complex& other) const {return real == other.real && imag == other.imag;}
};// 使用
Complex c1(1, 2);
Complex c2(3, 4);
Complex c3 = c1 + c2;  // 调用operator+
cout << c3 << endl;    // 调用operator
if(c1 == c2) { }       // 调用operator==

6. 智能指针(Smart Pointers)

概念

定义
智能指针是C++中用于自动管理动态分配内存的类模板,封装了原始指针,通过RAII机制自动释放内存。

作用

  • 自动管理内存,防止内存泄漏
  • 避免悬空指针
  • 异常安全

三种智能指针

1. unique_ptr(独占所有权)

#include <memory>
#include <iostream>class Resource {
public:Resource() { std::cout << "Resource acquired" << std::endl; }~Resource() { std::cout << "Resource released" << std::endl; }void use() { std::cout << "Using resource" << std::endl; }
};void test_unique_ptr() {// 创建unique_ptrstd::unique_ptr<Resource> ptr1(new Resource());// 或使用make_unique(C++14,推荐)auto ptr2 = std::make_unique<Resource>();ptr1->use();// 不能拷贝,只能移动// std::unique_ptr<Resource> ptr3 = ptr1;  // 错误!std::unique_ptr<Resource> ptr3 = std::move(ptr1);  // 正确// ptr1现在为空if(!ptr1) {std::cout << "ptr1 is null" << std::endl;}// ptr3拥有资源ptr3->use();// 离开作用域,自动释放资源
}// 使用场景:函数返回动态对象
std::unique_ptr<Resource> createResource() {return std::make_unique<Resource>();
}// 数组版本
void test_unique_ptr_array() {std::unique_ptr<int[]> arr(new int[10]);arr[0] = 42;// 自动释放数组
}

2. shared_ptr(共享所有权)

#include <memory>void test_shared_ptr() {// 创建shared_ptrstd::shared_ptr<Resource> ptr1 = std::make_shared<Resource>();std::cout << "Reference count: " << ptr1.use_count() << std::endl;  // 1{// 共享所有权std::shared_ptr<Resource> ptr2 = ptr1;std::cout << "Reference count: " << ptr1.use_count() << std::endl;  // 2ptr1->use();ptr2->use();}// ptr2离开作用域,引用计数减1std::cout << "Reference count: " << ptr1.use_count() << std::endl;  // 1// ptr1离开作用域,引用计数归零,资源被释放
}// 使用场景:多个对象共享同一资源
class Node {
public:int data;std::shared_ptr<Node> next;Node(int d) : data(d) {}~Node() { std::cout << "Node " << data << " destroyed" << std::endl; }
};void test_linked_list() {auto head = std::make_shared<Node>(1);head->next = std::make_shared<Node>(2);head->next->next = std::make_shared<Node>(3);// 自动管理整个链表的内存
}

3. weak_ptr(弱引用)

#include <memory>// 解决shared_ptr的循环引用问题
class Parent;
class Child;class Parent {
public:std::shared_ptr<Child> child;~Parent() { std::cout << "Parent destroyed" << std::endl; }
};class Child {
public:std::weak_ptr<Parent> parent;  // 使用weak_ptr打破循环~Child() { std::cout << "Child destroyed" << std::endl; }
};void test_weak_ptr() {{auto parent = std::make_shared<Parent>();auto child = std::make_shared<Child>();parent->child = child;child->parent = parent;  // weak_ptr不增加引用计数// 检查weak_ptr是否有效if(auto p = child->parent.lock()) {std::cout << "Parent is alive" << std::endl;}}// 正确释放:Parent和Child都被销毁
}// weak_ptr的典型用法
void observer_pattern() {std::shared_ptr<Resource> resource = std::make_shared<Resource>();std::weak_ptr<Resource> observer = resource;// 临时访问资源if(auto res = observer.lock()) {res->use();} else {std::cout << "Resource no longer exists" << std::endl;}resource.reset();  // 释放资源if(auto res = observer.lock()) {res->use();} else {std::cout << "Resource no longer exists" << std::endl;  // 输出这个}
}

智能指针对比
类型所有权拷贝引用计数典型用途
unique_ptr独占❌ 只能移动独占资源、工厂函数
shared_ptr共享✅ 可拷贝共享资源、容器中存储
weak_ptr不拥有✅ 可拷贝不计数观察者、打破循环引用

实际应用示例

示例1:工厂模式

class Widget {
public:virtual void draw() = 0;virtual ~Widget() {}
};class Button : public Widget {
public:void draw() override {std::cout << "Drawing button" << std::endl;}
};// 工厂函数返回unique_ptr
std::unique_ptr<Widget> createWidget(const std::string& type) {if(type == "button") {return std::make_unique<Button>();}return nullptr;
}void use_factory() {auto widget = createWidget("button");if(widget) {widget->draw();}// 自动释放
}

示例2:缓存系统

#include <unordered_map>class Cache {
private:std::unordered_map<int, std::weak_ptr<Resource>> cache;public:std::shared_ptr<Resource> get(int id) {auto it = cache.find(id);if(it != cache.end()) {// 尝试提升weak_ptrif(auto res = it->second.lock()) {std::cout << "Cache hit" << std::endl;return res;}}// 缓存未命中,创建新资源std::cout << "Cache miss" << std::endl;auto res = std::make_shared<Resource>();cache[id] = res;return res;}
};

示例3:自定义删除器

// 管理C风格资源
void test_custom_deleter() {// 文件指针auto file = std::unique_ptr<FILE, decltype(&fclose)>(fopen("test.txt", "w"),fclose);if(file) {fprintf(file.get(), "Hello, World!");}// 自动调用fclose// Lambda删除器auto ptr = std::unique_ptr<int, std::function<void(int*)>>(new int(42),[](int* p) {std::cout << "Deleting " << *p << std::endl;delete p;});
}

进程管理

7. for循环中的fork()

问题分析

示例代码

#include <unistd.h>
#include <stdio.h>int main() {for(int i = 0; i < 3; i++) {fork();printf("i=%d, pid=%d\n", i, getpid());}return 0;
}

问题:循环3次后,一共创建多少个进程?

答案:8个进程(包括父进程)


详细分析

第1次循环(i=0)

开始:1个进程↓ fork()
结束:2个进程(P0, P1)

第2次循环(i=1)

开始:2个进程(P0, P1)
P0 → fork() → P2
P1 → fork() → P3
结束:4个进程(P0, P1, P2, P3)

第3次循环(i=2)

开始:4个进程
P0 → fork() → P4
P1 → fork() → P5
P2 → fork() → P6
P3 → fork() → P7
结束:8个进程

计算公式
进程数 = 2^n
其中 n 为循环次数

推导过程

  • 第0次循环前:1个进程
  • 第1次循环后:1 × 2 = 2个进程
  • 第2次循环后:2 × 2 = 4个进程
  • 第3次循环后:4 × 2 = 8个进程
  • 第n次循环后:2^n个进程

进程树可视化
循环0次:
P0循环1次:P0/P1循环2次:P0/ \P1  P2/P3循环3次:P0/ \P1  P2/ \ / \P3 P4 P5 P6/P7

实战示例

示例1:控制进程数量

int main() {int n = 3;for(int i = 0; i < n; i++) {pid_t pid = fork();if(pid == 0) {// 子进程:执行任务后退出printf("Child %d doing work\n", i);sleep(1);exit(0);  // 子进程退出,不继续循环}}// 只有父进程到达这里// 等待所有子进程for(int i = 0; i < n; i++) {wait(NULL);}printf("All children done\n");return 0;
}
// 结果:创建3个子进程(而不是2^3=8个)

示例2:进程编号分析

void print_process_info() {for(int i = 0; i < 2; i++) {pid_t pid = fork();printf("Loop %d: PID=%d, Parent=%d, fork returned=%d\n",i, getpid(), getppid(), pid);}
}// 输出分析:
// Loop 0: PID=1000, Parent=999, fork returned=1001  (父进程)
// Loop 0: PID=1001, Parent=1000, fork returned=0    (子进程P1)
// Loop 1: PID=1000, Parent=999, fork returned=1002  (父进程)
// Loop 1: PID=1001, Parent=1000, fork returned=1003 (子进程P1)
// Loop 1: PID=1002, Parent=1000, fork returned=0    (子进程P2)
// Loop 1: PID=1003, Parent=1001, fork returned=0    (子进程P3)

常见面试题

题目1

int main() {fork();fork();printf("Hello\n");return 0;
}

:输出几次"Hello"?
:4次(2^2 = 4个进程)


题目2

int main() {for(int i = 0; i < 2; i++) {fork();}printf("X");return 0;
}

:输出几个"X"?
:4个(2^2 = 4个进程)


题目3

int main() {fork() && fork();printf("Y");return 0;
}

:输出几个"Y"?
:3个
分析

  • 第一个fork():父进程(返回>0,继续)→ 子进程(返回0,短路,不执行第二个fork)
  • 第二个fork():父进程执行 → 创建1个子进程
  • 总共3个进程

数据结构实现

8. 用C语言实现栈

数组实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_SIZE 100typedef struct {int data[MAX_SIZE];int top;  // 栈顶指针
} Stack;// 初始化栈
void initStack(Stack* s) {s->top = -1;
}// 判断栈是否为空
bool isEmpty(Stack* s) {return s->top == -1;
}// 判断栈是否已满
bool isFull(Stack* s) {return s->top == MAX_SIZE - 1;
}// 入栈
bool push(Stack* s, int value) {if(isFull(s)) {printf("Stack overflow!\n");return false;}s->data[++(s->top)] = value;return true;
}// 出栈
bool pop(Stack* s, int* value) {if(isEmpty(s)) {printf("Stack underflow!\n");return false;}*value = s->data[(s->top)--];return true;
}// 获取栈顶元素(不删除)
bool peek(Stack* s, int* value) {if(isEmpty(s)) {printf("Stack is empty!\n");return false;}*value = s->data[s->top];return true;
}// 获取栈的大小
int size(Stack* s) {return s->top + 1;
}// 测试
int main() {Stack s;initStack(&s);push(&s, 10);push(&s, 20);push(&s, 30);int value;peek(&s, &value);printf("Top: %d\n", value);  // 30while(!isEmpty(&s)) {pop(&s, &value);printf("Popped: %d\n", value);}return 0;
}

链表实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data;struct Node* next;
} Node;typedef struct {Node* top;int size;
} Stack;// 初始化栈
void initStack(Stack* s) {s->top = NULL;s->size = 0;
}// 判断栈是否为空
bool isEmpty(Stack* s) {return s->top == NULL;
}// 入栈
bool push(Stack* s, int value) {Node* newNode = (Node*)malloc(sizeof(Node));if(!newNode) {printf("Memory allocation failed!\n");return false;}newNode->data = value;newNode->next = s->top;s->top = newNode;s->size++;return true;
}// 出栈
bool pop(Stack* s, int* value) {if(isEmpty(s)) {printf("Stack underflow!\n");return false;}Node* temp = s->top;*value = temp->data;s->top = temp->next;free(temp);s->size--;return true;
}// 获取栈顶元素
bool peek(Stack* s, int* value) {if(isEmpty(s)) {return false;}*value = s->top->data;return true;
}// 销毁栈
void destroyStack(Stack* s) {int value;while(!isEmpty(s)) {pop(s, &value);}
}// 获取栈的大小
int getSize(Stack* s) {return s->size;
}int main() {Stack s;initStack(&s);push(&s, 10);push(&s, 20);push(&s, 30);printf("Stack size: %d\n", getSize(&s));int value;while(!isEmpty(&s)) {pop(&s, &value);printf("Popped: %d\n", value);}destroyStack(&s);return 0;
}

栈的应用

应用1:括号匹配

bool isValid(const char* s) {Stack stack;initStack(&stack);for(int i = 0; s[i] != '\0'; i++) {char ch = s[i];if(ch == '(' || ch == '[' || ch == '{') {push(&stack, ch);} else {if(isEmpty(&stack)) return false;int top;pop(&stack, &top);if((ch == ')' && top != '(') ||(ch == ']' && top != '[') ||(ch == '}' && top != '{')) {return false;}}}return isEmpty(&stack);
}// 测试
printf("%d\n", isValid("()[]{}"));      // 1 (true)
printf("%d\n", isValid("([)]"));        // 0 (false)
printf("%d\n", isValid("{[()]}"));      // 1 (true)

应用2:表达式求值(后缀表达式)

int evaluatePostfix(const char* expr) {Stack stack;initStack(&stack);for(int i = 0; expr[i] != '\0'; i++) {char ch = expr[i];if(ch >= '0' && ch <= '9') {push(&stack, ch - '0');} else if(ch == ' ') {continue;} else {int b, a;pop(&stack, &b);pop(&stack, &a);switch(ch) {case '+': push(&stack, a + b); break;case '-': push(&stack, a - b); break;case '*': push(&stack, a * b); break;case '/': push(&stack, a / b); break;}}}int result;pop(&stack, &result);return result;
}// 测试
printf("%d\n", evaluatePostfix("23+5*"));  // (2+3)*5 = 25

GPIO与串口通信

9. GPIO工作模式

GPIO(General Purpose Input/Output):通用输入输出端口

8种工作模式

4种输入模式

模式说明应用场景
浮空输入输入端悬空,电平不确定外部有明确驱动的信号
上拉输入内部上拉电阻,默认高电平按键、开关(按下接地)
下拉输入内部下拉电阻,默认低电平某些传感器
模拟输入ADC功能,读取模拟电压温度传感器、电位器

4种输出模式

模式说明应用场景
开漏输出只能输出低电平,需外部上拉I²C、单总线
开漏复用开漏输出 + 外设控制I²C外设
推挽输出可输出高/低电平LED、继电器
推挽复用推挽输出 + 外设控制SPI、UART TX

模式详解

开漏输出(Open-Drain)

VCC|
[R] 上拉电阻|
引脚 ---[NMOS]--- GNDNMOS导通:输出低电平
NMOS关断:引脚被上拉电阻拉高

应用

  • I²C总线(SCL、SDA)
  • 实现线与功能
  • 电平转换

推挽输出(Push-Pull)

VCC ---[PMOS]--- 引脚 ---[NMOS]--- GND输出高电平:PMOS导通,NMOS关断
输出低电平:PMOS关断,NMOS导通

应用

  • 驱动LED
  • 普通数字输出
  • SPI、UART等

STM32配置示例
#include "stm32f10x.h"// 配置GPIO为推挽输出(LED)
void GPIO_Config_LED(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);
}// 配置GPIO为上拉输入(按键)
void GPIO_Config_Button(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);
}// 配置GPIO为开漏输出(I²C)
void GPIO_Config_I2C(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;  // SCL, SDAGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  // 开漏复用GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
}

10. UART vs USART

基本概念

UART(Universal Asynchronous Receiver/Transmitter)

  • Universal:通用
  • Asynchronous:异步
  • 仅支持异步通信

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)

  • 在UART基础上增加了 Synchronous(同步)
  • 既支持异步,也支持同步通信

详细对比
特性UARTUSART
通信方式仅异步异步+同步
时钟信号同步模式有
数据速率较低同步模式更高
连接方式点对点点对点/多点
应用场景串口调试、GPS、蓝牙同步外设、高速通信

异步通信(UART/USART)

特点

  • 无时钟信号
  • 通过起始位、停止位同步
  • 双方约定波特率
http://www.dtcms.com/a/449594.html

相关文章:

  • 操作系统应用开发(二十六)RustDesk tls证书不匹配错误—东方仙盟筑基期
  • 如何制作个人网站住建局查询房产信息
  • 西安网站建设hyk123wordpress帖子添加代码
  • 乐理知识学习内容
  • 新手SEO教程:高效提升网站访问量的实用技巧与策略
  • 代码文件内容
  • 一款基于ESP32的导航小车
  • 自己建设网站赚钱湘潭网站建设 要选磐石网络
  • Python图形界面——TKinter
  • 深圳策划公司网站建设大型网站制作品牌
  • Django 配置与安装完整指南
  • seo网站优化方法网站建设技术指标
  • Javaweb(BeanUtils)
  • Oracle数据库imp/exp
  • 自己做的网站怎么上传手机百度网页版主页
  • 昆明网站定制建设项目验收在哪个网站公示
  • 大模型开发 - 01 Spring AI 核心特性一览
  • 手赚网 类似网站怎么建设在局域网内访问本机的asp网站
  • AS5600 驱动(HAL库400K硬件IIC+DMA、1MHZ软件IIC)
  • Oracle OCP认证考试题目详解082系列第1题
  • 做网站公司是干什么的安徽工程建设信息网实名制查询
  • Java EE初阶启程记12---synchronized 原理
  • 设计模式简要介绍
  • Python 数据结构综合速查:列表 / 字典 / 集合 / 元组对比
  • 宁波建设工程报名网站搭建一个网站的具体步骤
  • 第十七章:遍历万象,步步为营——Iterator的迭代艺术
  • 记一次vcenter server 无法同步主机的故障处理过程
  • 手搓20颗芯片|专栏开篇:从0到1搭建芯片设计与UVM验证体系
  • 《 Linux 点滴漫谈: 三 》Linux 的骨架:文件系统与目录结构的完整图谱
  • 跨境自建站模板库存网站建设公司