Skip to content

using 别名

using 关键字用于给类型定义别名。语法为:

using 新名字 = 原类型;

1. typedef 基础写法

using 出现之前,C 语言用 typedef 定义类型别名。先回顾 typedef 的写法,才能理解 using 好在哪。

1.1 普通类型

// typedef 的语法:typedef 原类型 新名字;
typedef float fp32;         // fp32 等价于 float
typedef unsigned char u8;   // u8   等价于 unsigned char

fp32 angle = 3.14f;         // 等价于 float angle = 3.14f;
// using 写同样的事:新名字在 = 左边,原类型在右边
using fp32 = float;
using u8   = unsigned char;

1.2 函数指针类型

typedef 定义函数指针的语法较难读,因为新名字被夹在返回类型和参数列表之间:

// typedef 函数指针语法拆解:
//
//   typedef  返回类型  (*新名字)(参数列表);
//            ↑        ↑       ↑
//            返回int  是指针  参数(float,float,int)
//
typedef int (*ComputeFunc)(float a, float b, int mode);
//          ↑  * 表示这是一个指针类型,指向函数
// 解读:ComputeFunc 是一个指针类型,
//       指向 "参数为(float,float,int)、返回int" 的函数

typedef 的问题在于:阅读顺序不是从左到右的,需要先找到被夹在中间的"新名字",再反向解读两边的类型信息。函数签名越复杂,这种"夹心"写法就越难读。

// using 写同一件事:新名字在最左边,然后 =,然后类型,从左到右自然阅读
using ComputeFunc = int (*)(float a, float b, int mode);
//                  ↑返回  ↑指针  ↑参数

1.3 模板别名(复习 template)

template 是 C++ 的泛型机制。在模板定义中使用占位符(如 T),编译器在调用时根据实际类型生成具体代码:

// 模板函数:T 是占位符
template <typename T>
T myMax(T a, T b) { return a > b ? a : b; }

myMax(3, 5);      // 编译器生成 int    myMax(int, int)
myMax(3.0, 5.0);  // 编译器生成 double myMax(double, double)

using 支持模板别名——给模板类绑定部分参数后起名。typedef 做不到这一点:

// ✅ using:模板参数 T 可以保留
template <typename T>
using Vec3 = Eigen::Matrix<T, 3, 1>;   // 三维列向量

Vec3<float>  vf;  // Eigen::Matrix<float,  3, 1>
Vec3<double> vd;  // Eigen::Matrix<double, 3, 1>

// ❌ typedef:不支持模板参数,无法表达 "T 待定" 的别名

2. 工程代码(GSRL 源码)

2.1 行为树:函数指针别名

源码位置: GSRL/Algorithm/inc/alg_behavior_tree.hpp

行为树中的 BTActionBTCondition 继承自 BTNode。它们不自己实现业务逻辑,而是通过函数指针让用户注册回调。

结构关系(伪代码):

BTNode(基类,定义虚函数 tick())
  ├── BTAction : public BTNode
  │     定义  using ActionFunc = BTStatus (*)(void *context)
  │     存储  ActionFunc m_action     # 用户注册的函数指针
  │     重写  tick() → 调用 m_action(m_context)
  │
  └── BTCondition : public BTNode
        定义  using ConditionFunc = bool (*)(void *context)
        存储  ConditionFunc m_condition
        重写  tick() → 调用 m_condition(m_context) → 返回 成功/失败

源码片段(alg_behavior_tree.hpp):

class BTAction : public BTNode
{
public:
    using ActionFunc = BTStatus (*)(void *context);  // 函数指针别名

private:
    ActionFunc m_action;  // 指向用户注册的回调函数
    void *m_context;      // 传给回调的上下文(通常是类实例指针)

public:
    BTAction(ActionFunc action, void *context = nullptr);
    BTStatus tick() override;  // 内部调用 m_action(m_context)
};

class BTCondition : public BTNode
{
public:
    using ConditionFunc = bool (*)(void *context);  // 函数指针别名

private:
    ConditionFunc m_condition;
    void *m_context;

public:
    BTCondition(ConditionFunc condition, void *context = nullptr);
    BTStatus tick() override;  // 内部调用 m_condition(m_context)
};

实际调用位置: Chariot/inc/crt_chassis_behavior.hppChariot/src/crt_chassis_behavior.cpp

ChassisBehaviorTree 类用 BTActionBTCondition 作为成员,注册 3 个条件节点 + 10 个动作节点:

// 声明(crt_chassis_behavior.hpp)
class ChassisBehaviorTree
{
    // 条件节点(使用 BTCondition)
    BTCondition m_fallDetection;          // 倒地检测
    BTCondition m_stairClimbingCondition; // 上台阶信号检测
    BTCondition m_jumpCondition;          // 跳跃信号检测

    // 动作节点(使用 BTAction)
    BTAction m_recoveryStateMachine; // 自起状态机
    BTAction m_stairClimbingAction;  // 上台阶执行
    BTAction m_jumpStateMachine;     // 跳跃状态机
    BTAction m_remoteTargetSetting;  // 遥控目标设定
    BTAction m_balanceLQR;           // LQR 平衡控制
    BTAction m_turnControl;          // 转向控制
    BTAction m_powerControl;         // 功率控制
    BTAction m_antiSplitCompensation;// 防劈叉补偿
    BTAction m_legLengthControl;     // 腿长控制
    BTAction m_rollControl;          // Roll 控制

    // 组合节点 ...
};

// 构造时传入回调函数(crt_chassis_behavior.cpp)
ChassisBehaviorTree::ChassisBehaviorTree()
    : m_fallDetection(fallDetectionCallback, this)       // 注册回调
    , m_stairClimbingCondition(stairClimbingConditionCallback, this)
    , m_recoveryStateMachine(recoveryStateMachineCallback, this)
    , m_balanceLQR(balanceLQRCallback, this)
    // ... 其余节点同理
{}
流程:
  ChassisBehaviorTree 构造 → 给每个 BTAction/BTCondition 传入回调函数指针
  → 行为树 tick() 遍历节点 → 节点调用存储的函数指针 → 执行实际业务逻辑

2.2 模板类型别名:KalmanFilter 和 RLSFilter

源码位置: GSRL/Algorithm/inc/alg_filter.hppGSRL/Algorithm/src/alg_filter.cpp

KalmanFilter 是模板类(继承自 Filter<T>),内部使用 Eigen 矩阵库。矩阵类型由模板参数决定,原始名称很长(如 Eigen::Matrix<T, StateSize, StateSize>)。在类内用 using 起短名,之后整个类内部使用短名即可:

template <typename T, int StateSize, int MeasSize, int ControlSize>
class KalmanFilter : public Filter<T>
{
public:
    // 用 using 将带模板参数的 Eigen 类型简化
    using StateVector   = Eigen::Vector<T, StateSize>;              // 状态向量
    using MeasVector    = Eigen::Vector<T, MeasSize>;               // 测量向量
    using StateMatrix   = Eigen::Matrix<T, StateSize, StateSize>;   // 状态矩阵
    using ObsMatrix     = Eigen::Matrix<T, MeasSize, StateSize>;    // 观测矩阵
    using GainMatrix    = Eigen::Matrix<T, StateSize, MeasSize>;    // 增益矩阵
    using ControlMatrix = Eigen::Matrix<T, StateSize, ControlSize>; // 控制矩阵

private:
    StateVector   m_state;        // 直接用短名
    StateMatrix   m_covariance;
    StateMatrix   m_transition;
    // ...
};

类外使用时,通过 类名::别名 拿到正确的类型:

KalmanFilter<fp32, 3, 1, 0> kf;                              // 三维状态、一维观测
KalmanFilter<fp32, 3, 1, 0>::StateVector state;              // 等价于 Eigen::Vector<fp32, 3>

有时不仅类内定义别名,类外还会把常用的模板实例化组合固化:

// alg_filter.cpp — 固定模板参数,命名常用维度的滤波器
using KalmanFilter1D = KalmanFilter<fp32, 1, 1, 0>;  // 一维
using KalmanFilter2D = KalmanFilter<fp32, 2, 1, 0>;  // 二维位置-速度
using KalmanFilter3D = KalmanFilter<fp32, 3, 1, 0>;  // 三维位置-速度-加速度

// alg_filter.hpp — RLS 滤波器同理
using RLSFilter2D = RLSFilter<fp32, 2>;
using RLSFilter3D = RLSFilter<fp32, 3>;
using RLSFilter4D = RLSFilter<fp32, 4>;
两层 using 的关系:

alg_filter.hpp 类内:
  KalmanFilter 模板类
    ├── using StateVector = Eigen::Vector<T, StateSize>   ← 简化类内使用的类型名
    └── ...

alg_filter.cpp 类外:
  using KalmanFilter2D = KalmanFilter<fp32, 2, 1, 0>     ← 固化模板参数
  → 用户只需写 KalmanFilter2D,不用每次写完整的模板参数

2.3 引入其他类的类型

源码位置: GSRL/Algorithm/inc/alg_pid.hppChariot/inc/crt_chassis.hpp

当类 A 需要使用类 B 中定义的类型时,用 using 引入,可以缩短访问路径,也让外部用户通过类 A 的接口直接找到类型定义。

复习:子类与父类(参考 培训/面向对象编程.md

class Parent {
public:
    int value;
    void foo();
};

class Child : public Parent {
    // Child 继承了 Parent 的 value 和 foo()
    // 外部可以通过 Child 实例访问 Parent 的 public 成员
};

例1 — CascadePID: 继承自 Controller,内部使用 SimplePID。通过 usingSimplePID 的类型暴露到自己的接口中:

class SimplePID {
public:
    struct PIDParam { fp32 Kp, Ki, Kd, outputLimit, integralLimit; };
    struct PIDData  { fp32 integral, lastError; };
};

class CascadePID : public Controller   // CascadePID 是 Controller 的子类
{
public:
    using PIDParam = SimplePID::PIDParam;  // 用户写 CascadePID::PIDParam 即可
    using PIDData  = SimplePID::PIDData;

private:
    SimplePID m_outerLoop;  // 外环(成员变量,不是继承来的)
    SimplePID m_innerLoop;  // 内环

public:
    CascadePID(PIDParam &outerParam, PIDParam &innerParam);
};

例2 — Chassis: 引入数学库的类型,在类内起短名:

class Chassis
{
public:
    using Vector3f  = GSRLMath::Vector3f;   // 不必每次写 GSRLMath::Vector3f
    using Matrix33f = GSRLMath::Matrix33f;
};

2.4 等价替换:避免代码重复

源码位置: GSRL/Device/inc/dvc_motor.hpp

当两个电机的通信协议和控制逻辑完全相同时,不需要把 A 的代码复制一份给 B。直接用 using 让 B 成为 A 的别名即可:

// M2006 电机与 M3508 电机协议相同,直接复用
using MotorM2006 = MotorM3508;

// DM2325 电机与 DM4310 电机协议相同,直接复用
using MotorDM2325 = MotorDM4310;
对比:没有 using 的情况

做法A(重复代码):
  class MotorM3508 { ... 全部实现 ... };
  class MotorM2006 { ... 复制粘贴一遍完全相同的代码 ... };
  → 占用双倍 ROM 空间,两处需要同步维护

做法B(using 别名):
  class MotorM3508 { ... 全部实现 ... };
  using MotorM2006 = MotorM3508;  // M2006 就是 M3508,不产生新代码
  → 零额外 ROM 占用,一处维护

3. 动手写

3.1 普通类型别名

#include <cstdint>

// 简化常用类型,提高可读性
using fp32 = float;
using fp64 = double;
using u8   = uint8_t;
using u16  = uint16_t;
using u32  = uint32_t;

// 使用
fp32 voltage   = 24.0f;    // 等价于 float voltage
u8   motorId   = 1;        // 等价于 uint8_t motorId
u32  timestamp = 1000000;  // 等价于 uint32_t timestamp

3.2 函数指针:状态机跳转表

#include <cstdint>

// 状态枚举
enum class RobotState : uint8_t {
    IDLE,
    MOVING,
    JUMPING,
    STATE_COUNT   // 状态总数
};

class Robot;

// ✅ 用 using 定义状态处理函数的指针类型
//    解读:StateHandler 是 "指向 void 函数(Robot*) 的指针" 的别名
using StateHandler = void (*)(Robot *robot);

class Robot
{
public:
    // 跳转表:每种状态对应一个处理函数
    StateHandler stateTable[static_cast<uint8_t>(RobotState::STATE_COUNT)];
    RobotState currentState;

    void init()
    {
        // 注册各状态的处理函数
        stateTable[static_cast<uint8_t>(RobotState::IDLE)]    = handleIdle;
        stateTable[static_cast<uint8_t>(RobotState::MOVING)]  = handleMoving;
        stateTable[static_cast<uint8_t>(RobotState::JUMPING)] = handleJumping;
        currentState = RobotState::IDLE;
    }

    void update()
    {
        uint8_t idx = static_cast<uint8_t>(currentState);
        if (stateTable[idx] != nullptr) {
            stateTable[idx](this);  // 查表调用对应函数
        }
    }

private:
    static void handleIdle(Robot *robot)    { /* 空闲 */ }
    static void handleMoving(Robot *robot)  { /* 移动 */ }
    static void handleJumping(Robot *robot) { /* 跳跃 */ }
};

同样功能用 typedef 对比:

// ❌ typedef:新名字夹在中间
typedef void (*StateHandler)(Robot *robot);
//      返回 ↑指针  ↑名字      ↑参数

// ✅ using:新名字在左边,= 后面是类型
using StateHandler = void (*)(Robot *robot);

3.3 模板别名

#include <vector>

// ✅ using:一行定义模板别名
template <typename T>
using Vec = std::vector<T>;

Vec<int>    vi;               // 等价于 std::vector<int>
Vec<float>  vf;               // 等价于 std::vector<float>

// ❌ typedef:不支持模板别名。
//    变通方法是用 struct 包一层(每次使用都要写 ::type):
template <typename T>
struct VecWrapper {
    typedef std::vector<T> type;   // 类型藏在 struct 里
};
VecWrapper<int>::type vi;          // 使用时要写 ::type,不直观

4. 总结

场景 typedef using
基础类型 typedef float fp32; using fp32 = float;
函数指针 typedef void (*Func)(int); using Func = void (*)(int);
模板别名 ❌ 不支持 template<typename T> using Vec = std::vector<T>;
引入类成员类型 能写但不推荐 using PIDParam = SimplePID::PIDParam;
等价替换(复用类) 不支持(只能复制代码) using MotorM2006 = MotorM3508;

作者: Qing | 修改日期: 2026-07-18