39.1 从变量到对象
最初:
double angle = 30;
double speed = 10;
数据散着放。
后来:
struct Joint
{
double angle;
double speed;
};
把相关数据组合。
再后来:
class Joint
{
public:
void setAngle(double angle)
{
this->angle_ = angle;
}
private:
double angle_ = 0.0;
};
变成:
对象
=
数据
+
操作这些数据的函数
所以一个:
Joint j;
对象可以粗略画成:
j
┌────────────────────┐
│ angle_ │
│ │
│ setAngle() │
└────────────────────┘
39.2 class 和对象不是一回事
class Joint
{
...
};
是:
类型/设计规则。
而:
Joint j;
才是:
创建具体对象。
对应:
class
= 图纸
object
= 按图纸造出来的东西
39.3 构造函数不是"造对象的命令"
class Robot
{
public:
Robot()
{
}
};
这里只是定义:
Robot 对象出生的时候怎么初始化。
真正:
Robot robot;
才开始创建对象。
过程:
Robot robot;
↓
需要创建一个Robot对象
↓
准备对象存储空间
↓
调用Robot()
↓
对象初始化
↓
robot可以使用
39.4 初始化列表
例如:
class Robot
{
public:
Robot(int id)
: id_(id)
{
}
private:
int id_;
};
Robot robot(3);
过程:
参数 id = 3
↓
创建 robot
↓
直接初始化成员 id_ = 3
↓
执行 {}
所以:
: id_(id)
不是赋值语句,而是:
初始化成员。
39.5 继承
class Child : public Parent
表示:
Child 是 Parent 的派生类。
一个 Child 对象包含:
Child对象
┌───────────────────┐
│ Parent父类部分 │
├───────────────────┤
│ Child自己的部分 │
└───────────────────┘
于是:
class NumberPublisher : public rclcpp::Node
表示:
NumberPublisher对象
┌────────────────────────┐
│ rclcpp::Node部分 │
├────────────────────────┤
│ publisher_ │
│ timer_ │
│ count_ │
└────────────────────────┘
39.6 父类构造
NumberPublisher()
: Node("number_publisher"),
count_(0)
意味着:
创建NumberPublisher
↓
先调用Node构造函数
Node("number_publisher")
↓
初始化count_=0
↓
执行NumberPublisher构造函数体
注意:
Node("number_publisher")
不是另外造一个没关系的 Node。
它是在构造:
当前 NumberPublisher 对象内部的 Node 父类部分。
39.7 this 到底是什么
只要你正在:
某个对象.成员函数()
那么这个成员函数内部就隐含一个:
this
它指向:
当前对象。
例如:
Joint j1;
j1.setAngle(30);
进入:
void setAngle(double angle)
之后:
this
↓
j1
于是:
this->angle_
就是:
j1自己的angle_
39.8 this 不是对象里的普通成员变量
不要画成:
Joint
├── angle_
└── this ×
更合适理解:
调用成员函数时
C++偷偷额外告诉函数:
"当前对象地址在这里。"
↓
this
这是以后理解成员函数非常关键的模型。
39.9 this 和继承
this->create_publisher(...)
时:
this
↓
当前NumberPublisher对象
↓
寻找create_publisher
↓
NumberPublisher自己没有
↓
父类Node有
↓
调用Node提供的函数
所以继承的功能不需要把源码重新写进子类。
子类对象能够使用继承得到的成员接口。
39.10 public / private
例如:
class Joint
{
public:
void setAngle(double angle)
{
angle_ = angle;
}
private:
double angle_;
};
外部:
j.angle_ = 30; // 不允许
但是:
j.setAngle(30); // 允许
然后类内部:
angle_ = angle;
允许。
所以封装逻辑:
外部
↓
public接口
↓
类内部逻辑
↓
private数据
39.11 对象与指针
直接对象:
Robot robot;
访问:
robot.run();
因为:
robot
= 对象
指针:
Robot* p = &robot;
此时:
p
↓
robot对象
访问:
p->run();
所以锁死:
对象 .
指针 ->
39.12 指针最根本的作用
现阶段你只需要一句:
指针主要解决"我怎么找到另一个对象"。
它不是:
数据运输管道。
例如:
publisher_->publish(msg);
不是:
msg进入publisher_这个指针
而是:
publisher_
↓
找到Publisher对象
↓
调用Publisher对象的publish()
↓
把msg作为函数参数传进去
39.13 智能指针
裸指针:
Robot* p;
重点是:
保存地址。
智能指针:
std::shared_ptr<Robot>
除了可以找到对象,还:
参与管理对象生命周期。
创建:
auto p = std::make_shared<Robot>();
含义:
创建Robot对象
+
创建shared_ptr管理这个对象
+
把智能指针返回给p
所以不是"只有指针没有对象"。
对象真实存在。
39.14 make_shared
例如:
auto node =
std::make_shared<NumberPublisher>();
可以拆成三个概念:
NumberPublisher
→ 要创建的对象类型
make_shared
→ 创建对象并用shared_ptr管理
node
→ 得到的智能指针变量
关系:
node
│
▼
NumberPublisher对象
39.15 node、this、publisher_ 都是指针,但角色不同
这是目前最重要的表:
| 名字 | 指向谁 | 用来干什么 |
|---|---|---|
node |
NumberPublisher 对象 | main() 找到并管理整个节点对象 |
this |
当前 NumberPublisher 对象 | 成员函数知道"我正在操作哪个对象" |
publisher_ |
Publisher 对象 | NumberPublisher 找到自己的发布器 |
timer_ |
Timer 对象 | NumberPublisher 找到自己的定时器 |
所以:
都是"指向对象",但它们指向的对象完全不同。
39.16 Lambda
[this]()
{
publishNumber();
}
分成:
[this]
捕获列表:
让 lambda 记住当前对象的
this指针。
()
参数列表:
没有参数。
{
publishNumber();
}
函数体:
调用当前对象的成员函数。
所以 Timer 保存这个 lambda 后:
Timer到时间
↓
执行lambda
↓
lambda拿到this
↓
找到NumberPublisher
↓
调用publishNumber()
39.17 本章最终对象图
main()
node
│
│ shared_ptr
▼
┌───────────────────────────────┐
│ NumberPublisher对象 │
│ │
│ Node父类部分 │
│ │
│ count_ │
│ │
│ publisher_ ──────────────┐ │
│ │ │
│ timer_ ──────────────┐ │ │
└──────────────────────│───│────┘
│ │
▼ ▼
Timer Publisher
对象 对象
Timer
│
└─保存lambda
│
└─保存this
│
└────→ NumberPublisher对象
这一张图就是第 39 章核心。