一.接口的抽象以及封装
比如说对电机驱动进行抽象。在多场景下,如果要驱动很多电机多种电机:大疆电机、小米电机、步进电机、伺服电机...但希望不管是什么种类的电机,都要驱动,那就要统一接口。
c++
class MotorDriver {
public:
// 工厂模式创建不同类型电机
static std::shared_ptr<MotorDriver> create_motor(...);
// 纯虚函数定义统一接口
virtual void init_motor() = 0;
virtual void motor_mit_cmd(float f_p, float f_v, float f_kp, float f_kd, float f_t) = 0;
virtual float get_motor_pos() = 0;
// ...
};
virtual:虚函数 → 允许子类重写
= 0 :纯虚函数 → 父类不实现,子类必须实现
拥有纯虚函数的类叫 抽象类 ,不能直接创建对象
arduino
#ifndef MOTOR_DRIVER_HPP
#define MOTOR_DRIVER_HPP
#include <memory>
// 抽象基类:统一所有电机接口
class MotorDriver {
public: // 电机类型枚举 enum class MotorType { STEPPER, SERVO };
// 工厂方法:创建对应电机
static std::shared_ptr<MotorDriver> create_motor(MotorType type); //
//纯虚函数:统一接口 ==========
virtual void init_motor() = 0;
virtual void motor_mit_cmd(float f_p, float f_v, float f_kp, float f_kd, float f_t) = 0; virtual float get_motor_pos() = 0;
// 多态必须虚析构
virtual ~MotorDriver() = default;
};
// 步进电机子类
class StepperMotor : public MotorDriver
{ public: void init_motor() override;
void motor_mit_cmd(float f_p, float f_v, float f_kp, float f_kd, float f_t) override; float get_motor_pos() override; };
// 伺服电机子类
class ServoMotor : public MotorDriver {//继承
public: void init_motor() override;//重写虚函数
void motor_mit_cmd(float f_p, float f_v, float f_kp, float f_kd, float f_t) override; float get_motor_pos() override; };
#endif
记住 子类重写override,重新实现这个函数功能。父类不实现功能,只定义接口。
二. this指针的使用
this 指针是隐含在每个非静态成员函数中的指针,指向调用该函数的对象。 this指针的作用
//c++
health = this->health_; // 访问成员变量
info = this->info_; // 访问成员变量
ans = this->createThread(); // 调用成员函数
为什么需要this指针? 当局部变量名与成员变量名冲突时,this指针优先要类的成员变量。
arduino
class ImgDecode {
int width; // 成员变量
void setWidth(int width) { // 参数width = 局部变量
this->width = width; // this->width = 成员变量
}
};
三. 工厂模式下的静态成员函数
js
class LidarFactory {
public:
// 静态成员函数:不需要创建对象就能调用
static std::unique_ptr<LidarInterface> createLidar(LidarType type);
static std::unique_ptr<LidarInterface> createLidar(const std::string& type_str);
static bool isSupported(LidarType type);
static std::vector<std::string> getSupportedTypes();
};
调用静态成员函数
js
// 不需要创建 LidarFactory 对象,直接类名::调用
auto lidar = LidarFactory::createLidar("ydlidar");
注意:在这里,使用 unique_ptr 是为了防止内存泄漏。原始指针需要手动 delete,如果中途函数返回或抛出异常,delete 不会执行,就会导致内存泄漏。而 unique_ptr 是智能指针,在析构时会自动释放内存,非常安全。另外, unique_ptr 是独占所有权,不需要引用计数,性能也很好。因为雷达对象只需要一个拥有者。 shared_ptr 的引用计数机制有额外的性能开销,而且在多线程环境下需要原子操作保证计数正确。使用 unique_ptr 更轻量,且语义更清晰------明确表示只有一个拥有者。
arduino
class YdLidarNodeV2 {
private:
std::unique_ptr<LidarInterface> lidar_; // 成员变量
public:
bool initialize() {
// 使用工厂创建雷达(自动管理内存)
lidar_ = LidarFactory::createLidar("ydlidar");
if (!lidar_) return false;
// 调用多态方法
lidar_->initialize(config);
return true;
}
~YdLidarNodeV2() {
// 不需要手动 delete!
// unique_ptr 析构时自动释放内存
}
};
四.指针解引用
- 看代码时注意 :遇到 -> 和 * 就想"这是解引用,在访问指针指向的内容"
- 记住口诀 :
- ptr = 指针(地址)
- *ptr = 指针指向的内容(解引用)
- ptr->member = 访问结构体成员(等价于 (*ptr).member
- 指针是存储地址的变量,解引用就是通过这个地址访问真正的数据。在项目中, get_data(FrameBuf *frame_buf) 函数接收一个指针,然后用 frame_buf->start 来修改传入的FrameBuf变量,这样才能把相机数据传递出来。
1函数需要修改传入的变量
javascript
// 如果不用指针
void get_data(FrameBuf frame_buf) {
frame_buf.start = ...; // 修改的是副本,外面的变量不会变!
}
// 用指针才能真正修改
void get_data(FrameBuf *frame_buf) {
frame_buf->start = ...; // 修改的是原变量
}
2传递大数据时省内存
arduino
// 直接传结构体(会拷贝,慢且占内存)
void process_image(cv::Mat img); // 拷贝整个图像
// 传指针(只传地址,快且省内存)
void process_image(cv::Mat *img); // 只传4/8字节的地址
3动态分配内存
arduino
int *arr = new int[100]; // 在堆上分配100个int的空间
delete[] arr; // 用完要释放
c++
// 1. 创建变量
FrameBuf frame_buf;
// 2. 调用函数,传递地址
v4l2.get_data(&frame_buf);
// 3. 函数内部解引用,填充数据
int V4l2::get_data(FrameBuf *frame_buf) {
// ... 获取数据 ...
frame_buf->start = mmap_buffer[buf.index].start; // 解引用写入
frame_buf->length = buf.bytesused;
}
// 4. 回到主函数,使用数据
msg.data.assign(frame_buf.start, frame_buf.start + frame_buf.length);
