怕什么真理无穷,进一寸有一寸的欢喜。——读《胡适谈读书》
当你想将类定义在某个命名空间中时,可以这样做:
namespace MyNamespace {
class MyClass {
public:
MyClass(); //构造函数的声明
//以下是类的其他组成部分,如成员函数和成员变量等
private:
//私有成员
};
}
在这个例子中,我们将 MyClass 定义在了 MyNamespace 命名空间中。
使用命名空间可以防止全局范围内的命名冲突。在大型项目中,这个功能尤其有用。你可以使用 :: 操作符访问命名空间内的类。
例如:
MyNamespace::MyClass mc;
这行代码创建了一个 MyNamespace 命名空间中 MyClass 类的对象 mc。
这是在C++中使用typedef给特定的数据类型重新定义一个新的名字,常用于简化一些复杂的类型声明。
此处 boost::shared_ptr< ::geometry_msgs::PoseStamped const> 是一个使用boost库实现的智能指针,它指向一个不可修改的(const)geometry_msgs::PoseStamped 类型的实例。boost::shared_ptr 是一个可以自动释放内存的智能指针,当没有引用它所指向的对象时,它会自动删除那个对象并释放其内存空间。
typedef boost::shared_ptr< ::geometry_msgs::PoseStamped const> PoseStampedConstPtr; 这行代码的意思就是,给这个类型起个别名叫做 PoseStampedConstPtr。
这么做的好处是,之后在代码中我们只需要使用 PoseStampedConstPtr 这个简洁的名字来声明该类型的变量,而不需要每次都写出完整的 boost::shared_ptr< ::geometry_msgs::PoseStamped const>。例如:
PoseStampedConstPtr ps_ptr;
这样就创建了一个名为 ps_ptr 的 PoseStampedConstPtr 类型的对象,极大的简化了代码的复杂度。
::geometry_msgs::PoseStamped const 描述了一个特定类型的ROS (Robot Operating System) 消息。
在这里:
geometry_msgs是一个命名空间,包含了用于地理信息处理的多种消息类型。PoseStamped是geometry_msgs命名空间中的一种消息类型,通常用于描述物体的位置和方向 (姿态)。它包括两部分:一个Pose对象和一个Header对象。Pose对象包含了物体的空间位置 (position) 和方向 (orientation)。Header对象包含了此消息的时间戳和坐标系 (坐标系通常是相对于某个参照物的位置和方向)。前面的
::表示geometry_msgs是全局命名空间中的一个命名空间,而不是当前命名空间或者某个类或者结构体内部的一个命名空间。最后的
const表示这种类型的对象是不可修改的,也就是说,一旦创建,你就不能改变它的状态。这常用于类的成员函数中,你想要防止该函数修改类的状态。或者作为函数参数,当你想防止函数修改传入的参数时。
将整句看在一起,< ::geometry_msgs::PoseStamped const> 描述了一个指向 geometry_msgs::PoseStamped 类型实例的常量指针。
::geometry_msgs::PoseStamped const 是一个常量的 geometry_msgs::PoseStamped 对象的指针。这个对象的类型是 geometry_msgs::PoseStamped, 设为 const 表示它是不可更改的。
有几个关于这个常量指针的关键点需要注意:
指向的对象不可修改:一旦这个
::geometry_msgs::PoseStamped const类型的对象被初始化,你就不能改变它的内容。也就是说,你是不能对该对象的任何属性进行修改的,例如,你可以读取其中的Pose或者Header,但是不能更改它们。安全性:它通常在某些函数的参数中使用,用以防止函数内部修改传递进来的参数的值,这在大型软件项目中很重要,因为它增加了代码的可靠性。
需要对 ROS 有一定了解:对
::geometry_msgs::PoseStamped的理解需要你对 ROS (机器人操作系统) 有一定了解。在 ROS中,消息是节点之间通信的主要方式,而geometry_msgs::PoseStamped是广泛用勾画机器人在三维空间中的位置和方向(姿态)。可读性:虽然这样的声明看起来有些复杂,但实际上它提供了很好的可读性。一看这样的变量,你就知道它是一个指向常量
::geometry_msgs::PoseStamped对象的指针,这有助于理解代码的用途。
总的来说,这是一个防止被修改的 ::geometry_msgs::PoseStamped 对象的指针,增加了代码的稳定性和可靠性,降低了在编写和阅读代码时出错的概率。
当我们在C++中使用双冒号(::)操作符时,其主要作用为:
- 指示一个全局命名空间的名字,如果单独使用双冒号(比如::name),表示的是全局的name,在任何地方都是唯一的。
- 访问一个类或结构体的静态成员。
- 在类的内部,访问类的成员。
在你所提到的::geometry_msgs::PoseStamped这个例子中,双冒号被用来指明指定的命名空间。首先,”geometry_msgs”是ROS消息类型的一个命名空间,”PoseStamped”是这个命名空间下一种类型的消息。当我们在程序中写geometry_msgs::PoseStamped,表示我们要使用的是geometry_msgs命名空间下的PoseStamped。
但是,如果在前面加了两个冒号,变成::geometry_msgs::PoseStamped,那意思就改变了。它表示的是全局命名空间下的geometry_msgs::PoseStamped。这样写的目的是为了避免命名的混淆和冲突。假如在当前的局部作用域内也有一个名字为”geometry_msgs”的命名空间,那么写geometry_msgs::PoseStamped可能就会产生冲突,编译器也许会混淆我们到底要使用哪一个命名空间。而如果我们写::geometry_msgs::PoseStamped,则明确告诉编译器我们要使用的是全局命名空间下的”geometry_msgs”,从而避免了命名冲突。
void EGOReplanFSM::waypointCallback(const geometry_msgs::PoseStampedPtr &msg)
{
if (msg->pose.position.z < -0.1)
return;//Z<-0.1即未起飞
cout << "Triggered!" << endl;
// trigger_ = true;
init_pt_ = odom_pos_;
// Eigen::Vector3d end_wp(msg->pose.position.x, msg->pose.position.y, msg->pose.position.z);
Eigen::Vector3d end_wp(msg->pose.position.x, msg->pose.position.y, 1.0);
planNextWaypoint(end_wp);// rviz指点规划
}
//是不是return,就不会执行下面的代码
是的,你的理解完全正确。在这段代码中,return 语句被用于立即终止当前的函数运行。如果条件(msg->pose.position.z < -0.1)满足,那么 return 会被执行,并且函数 EGOReplanFSM::waypointCallback 的余下部分(也就是位于 return 之后的代码)都不会被执行。
这种使用 return 来提前结束函数的编程技巧通常被称为 “提前返回” 或者 “提前退出”。这种方法可以使代码更简洁,易于理解,同时避免了不必要的代码执行,可能会提高程序的效率。
Quaterniond q(4, 1, 2, 3);//要注意Eigen中四元数赋值的顺序,实数w在首;
cout << "四元数:\n" << q << endl; //但是实际上它的内部存储顺序是[x y z w]
cout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << endl; //单个取值
q = vec4;
cout << "旋转矩阵:\n" << q.toRotationMatrix() << endl; //将四元数转换为旋转矩阵
请你解释一下q = vec4;这部分
在你的代码中,q = vec4; 这行代码的作用是将 vec4(向量)中的值赋给 q(四元数)。
假设 vec4 是一个四维向量或者有4个元素的数组,并且已经被初始化。在这种情况下,q = vec4; 的作用是将 vec4 中的值按顺序赋给四元数 q。具体来说,vec4中的第一个值赋给q的实部w,其余的值分别赋给q的虚部x, y, z。
注意,Eigen中的 Quaterniond 类使得四元数可以直接从一个四维向量(Eigen::Vector4d)进行初始化。因此,当你写 q = vec4; 时,vec4 中的值就会直接复制到四元数 q。
这段代码看起来应该是假设 vec4 已经被初始化并且包含了四元数所需要的4个值。如果这不是你代码的情境,需要调整来适应vec4的具体定义方式。
Vector3d v3d_a = { 1,2,3 };
cout << "Vector3d构造一:\n" << v3d_a << endl;
Vector3d v3d_b;
v3d_b << 4, 5, 6;
cout << "Vector3d构造二:\n" << v3d_b << endl;
这其中的v3d_b << 4, 5, 6;解释一下
在你的代码中,Vector3d v3d_b;声明了一个 Vector3d 类型的对象 v3d_b,并且v3d_b << 4, 5, 6;这一行代码中的 << 运算符被重载来实现特定的功能。
Vector3d 存在于很多数学库中,比如Eigen库,这是一个用于表示三维向量的类。在这个类中,可能定义了一个 << 运算符重载函数,可以用于对这个类的对象进行赋值。
那么v3d_b << 4, 5, 6;这行代码所做的就是将 4, 5, 6 这三个值通过 << 运算符赋给 v3d_b。因此 v3d_b 向量的三个分量值就被设置为了 4, 5, 6。
这个 << 运算符的使用方式,有点类似于数据流的插入操作,提供了一种简短的连续赋值方式,使得代码更加简洁明了。
然而,你需要确认一下这段代码的具体语境,因为 << 运算符的具体行为取决于它如何在 Vector3d 类中被重载。只有在确切知道这个类的实现情况,我们才能准确解析这里的 << 运算符到底做了什么。如果有可能,我建议你查看一下 Vector3d 类的文档或源码,来找出 << 运算符重载的具体行为。
int continously_called_times_{0};
这行代码定义了一个名为 continously_called_times_ 的 int 类型变量,并且将其初始化为 0。详细解释如下:
int 是一个关键字,代表这个变量的数据类型是整数。
continously_called_times_ 是这个变量的名称。在变量名后面加上下划线 _ 是一种命名习惯,常用于标识类或结构体的私有成员。这不是必需的,只是一种编程风格。
{0} 是使用 {} 初始化器来初始化这个整数变量。这和 =0 功能上是一样的,都表示将变量初始化为 0。
因此,这行代码的含义是“定义一个名为 continously_called_times_ 的整数变量,并将其初始化为 0”。
if ((target_type_ == TARGET_TYPE::PRESET_TARGET) &&
(wp_id_ < waypoint_num_ - 1) &&
(end_pt_ - pos).norm() < no_replan_thresh_)
解释一下<
在这段代码中, < 是一个比较运算符,它用于比较两个值的大小。
如果左边的值小于右边的值,那么表达式的结果就是 true,否则结果就是 false。
所以, (wp_id_ < waypoint_num_ - 1) 这个表达式的含义是判断 wp_id_ 是否小于 waypoint_num_ - 1。 如果 wp_id_ 的值小于 waypoint_num_ - 1,那么这个表达式的结果就是 true;否则,结果就是 false。
(end_pt_ - pos).norm() < no_replan_thresh_ 这个表达式则是判断两点之间的距离((end_pt_ - pos).norm())是否小于 no_replan_thresh_。 如果距离小于 no_replan_thresh_,表达式的结果就是 true,否则就是 false。
这个 if 表达式则是要求三个条件都满足(也就是都为 true): target_type_ 等于 TARGET_TYPE::PRESET_TARGET,wp_id_ 小于 waypoint_num_ - 1,以及两点之间的距离小于 no_replan_thresh_。
只有当这三个条件都满足时,if 语句中的代码块才会被执行。
在C++编程中,::符号是一个范围解析运算符,用来指定命名空间或类或者枚举类型内部的具体元素。
在你给出的这段代码中,TARGET_TYPE是一个枚举类型,它包括MANUAL_TARGET, PRESET_TARGET, REFENCE_PATH这三个枚举值。当我们想要引用枚举中的一个值时,通常需要在该值前面加上枚举类型的名字和::,以此来明确指出这个值是属于哪个枚举类型的。
所以,TARGET_TYPE::PRESET_TARGET就是用来引用TARGET_TYPE这个枚举类型中的PRESET_TARGET枚举值。相应地,如果我们想要引用TARGET_TYPE中的其他值,也需要使用同样的方式,例如TARGET_TYPE::MANUAL_TARGET或TARGET_TYPE::REFENCE_PATH。
<< 在 C++ 中主要有两个用途。
- 作为位运算符:
<<是位左移运算符。它的功能是将左操作数的所有位向左移动指定的位数(由右操作数指定)。例如,a << 2将a的所有位向左移动两位,原来a最右边的两位将被丢弃,左边空出来的两位填充为0。
int a = 5; // 二进制形式为 0000 0101
int b = a << 1; // 结果为 10,二进制形式为 0000 1010
- 作为流插入运算符:如果操作数是 iostream 对象(如 std::cout 或 std::cin 等),那么
<<就变成了流插入运算符。在这种情况下,它被用来插入数据到流中(例如,输出数据到控制台或写入数据到文件等)。
std::cout << "Hello, World!";
这行代码的意思是将 “Hello, World!” 这个字符串插入到 cout 流中,也就是将其输出到控制台。
switch (exec_state_)
{
case INIT:
{
if (!have_odom_)
{
goto force_return;
// return;
}
changeFSMExecState(WAIT_TARGET, "FSM");
break;
}
case WAIT_TARGET:
{
if (!have_target_ || !have_trigger_)
goto force_return;
// return;
else
{
// if ( planner_manager_->pp_.drone_id <= 0 )
// {
// changeFSMExecState(GEN_NEW_TRAJ, "FSM");
// }
// else
// {
changeFSMExecState(SEQUENTIAL_START, "FSM");
// }
}
break;
}
case SEQUENTIAL_START: // for swarm 单机用不到
{
// cout << "id=" << planner_manager_->pp_.drone_id << " have_recv_pre_agent_=" << have_recv_pre_agent_ << endl;
if (planner_manager_->pp_.drone_id <= 0 || (planner_manager_->pp_.drone_id >= 1 && have_recv_pre_agent_))//单机,drone_id=0
{
if (have_odom_ && have_target_ && have_trigger_)
{
bool success = planFromGlobalTraj(10); // zx-todo
if (success)
{
changeFSMExecState(EXEC_TRAJ, "FSM");
publishSwarmTrajs(true);
}
else
{
ROS_ERROR("Failed to generate the first trajectory!!!");
changeFSMExecState(SEQUENTIAL_START, "FSM");
}
}
else
{
ROS_ERROR("No odom or no target! have_odom_=%d, have_target_=%d", have_odom_, have_target_);
}
}
break;
}
/*Second:生成全局轨迹后,执行以下状态*/
case GEN_NEW_TRAJ:
{
// Eigen::Vector3d rot_x = odom_orient_.toRotationMatrix().block(0, 0, 3, 1);
// start_yaw_(0) = atan2(rot_x(1), rot_x(0));
// start_yaw_(1) = start_yaw_(2) = 0.0;
/*Third 进行重规划优化*/
bool success = planFromGlobalTraj(10); // zx-todo
if (success)
{
/*开始执行,此时如果检测到有碰撞会进行重规划*/
changeFSMExecState(EXEC_TRAJ, "FSM");
flag_escape_emergency_ = true;
publishSwarmTrajs(false);
}
else
{
changeFSMExecState(GEN_NEW_TRAJ, "FSM");
}
break;
}
case REPLAN_TRAJ:
{
if (planFromCurrentTraj(1))
{
changeFSMExecState(EXEC_TRAJ, "FSM");
publishSwarmTrajs(false);
}
else
{
changeFSMExecState(REPLAN_TRAJ, "FSM");
}
break;
}
/*Five 执行*/
case EXEC_TRAJ:
{
/* determine if need to replan */
LocalTrajData *info = &planner_manager_->local_data_;
ros::Time time_now = ros::Time::now();
double t_cur = (time_now - info->start_time_).toSec();
t_cur = min(info->duration_, t_cur);
Eigen::Vector3d pos = info->position_traj_.evaluateDeBoorT(t_cur);
/* && (end_pt_ - pos).norm() < 0.5 */
if ((target_type_ == TARGET_TYPE::PRESET_TARGET) &&
(wp_id_ < waypoint_num_ - 1) &&
(end_pt_ - pos).norm() < no_replan_thresh_)
{
wp_id_++;
planNextWaypoint(wps_[wp_id_]);
}
else if ((local_target_pt_ - end_pt_).norm() < 1e-3) // close to the global target
{
if (t_cur > info->duration_ - 1e-2)
{
have_target_ = false;
have_trigger_ = false;
if (target_type_ == TARGET_TYPE::PRESET_TARGET)
{
wp_id_ = 0;
planNextWaypoint(wps_[wp_id_]);
}
changeFSMExecState(WAIT_TARGET, "FSM");
goto force_return;
// return;
}
/* Six 继续重规划*/
else if ((end_pt_ - pos).norm() > no_replan_thresh_ && t_cur > replan_thresh_)
{
changeFSMExecState(REPLAN_TRAJ, "FSM");
}
}
else if (t_cur > replan_thresh_)
{
changeFSMExecState(REPLAN_TRAJ, "FSM");
}
break;
}
case EMERGENCY_STOP:
{
if (flag_escape_emergency_) // Avoiding repeated calls
{
callEmergencyStop(odom_pos_);
}
else
{
if (enable_fail_safe_ && odom_vel_.norm() < 0.1)
changeFSMExecState(GEN_NEW_TRAJ, "FSM");
}
flag_escape_emergency_ = false;
break;
}
}
//这就是有限状态机吗
是的,这个代码是一种称为有限状态机(Finite State Machine, FSM)的设计模式的示例。
有限状态机是一种用来模拟对象状态转移的模型。在这个模型中,系统可以存在于一组有限的状态中,并且在某个时间只能处于其中一个状态。系统的状态可以根据一些条件或触发事件从一个状态转移到另一个状态。
在你给出的代码中,EXEC_STATE 枚举定义了所有可能的状态(如 INIT, WAIT_TARGET, SEQUENTIAL_START, GEN_NEW_TRAJ, EXEC_TRAJ, REPLAN_TRAJ, EMERGENCY_STOP 等),然后在 switch (exec_state_) 语句中,系统根据当前的 exec_state_ 值来决定下一步要执行的操作。当满足某些条件时,状态会改变(如 changeFSMExecState(WAIT_TARGET, "FSM")),从而导致在下一次 switch 语句中执行不同的代码段。
有限状态机在处理具有多个条件和转变的系统(如游戏、工作流程、实物模拟等)时非常有用,尤其是当有些行为只在某些特定的状态下发生,或者当某些事件改变系统状态时。在这段代码中,有限状态机被用于控制和管理一台无人机的飞行阶段(例如:初始化、等待目标、开始导航、执行轨迹、重新规划轨迹,紧急停止等),以及相应的动作和转变条件。