typedef 与类型别名

在 C++ 中,using 是一个关键字,用来为已有数据类型创建别名。语法为:

using 别名 = 原类型;

例如:

using Distance = double;   // 将 Distance 定义为 double 的别名

定义完成后,即可在任何需要类型的地方使用该别名:

Distance milesToDestination{ 3.4 };   // 实际类型为 double

编译器遇到别名时,会用原类型替换。示例:

#include <iostream>

int main()
{
    using Distance = double;

    Distance milesToDestination{ 3.4 };
    std::cout << milesToDestination << '\n';   // 输出 3.4
    return 0;
}

供进阶读者

类型别名亦支持模板化,见 13.14 课 —— 类模板实参推导(CTAD)与推导指引。

类型别名的命名惯例

历史上命名方式并不统一,常见有三种:

  1. _t 结尾(如 size_tnullptr_t),继承自 C,但现代 C++ 已不推荐;POSIX 保留全局 _t 后缀,可能产生冲突。
  2. _type 结尾(如 std::string::size_type),但同样存在不一致。
  3. 无后缀,首字母大写。现代 C++ 推荐用 PascalCase 命名自定义类型,以区分变量/函数名(小写开头)并避免碰撞。

示例:

void printDistance(Distance distance);

Distance 是类型,distance 是形参名,C++ 区分大小写,合法且清晰。

最佳实践

自定义类型别名使用首字母大写且无后缀(除非有特定理由)。

作者注

本教程部分旧课仍用 _t/_type,欢迎指出以便统一。

类型别名并非新类型

别名只是为原类型引入新标识符,创建独立类型,二者完全可互换。因此可能出现语法正确但语义荒谬的代码:

int main()
{
    using Miles = long;
    using Speed = long;

    Miles distance{ 5 };
    Speed mhz{ 3200 };

    distance = mhz;   // 合法,但语义错误
}

编译器仅看到 longlong,不会阻止此类错误,故别名不具备类型安全;尽管如此,它们仍有益处。

警告

务必避免混用语义本应不同却共享同一别名的值。

附注

部分语言支持强 typedef(strong typedef),会生成真正的新类型,混用时报错。C++20 无原生支持,但可用第三方库实现;enum class 与之类似(见 13.6 课)。

类型别名的作用域

作用域规则与变量相同:块内定义的别名仅在该块可见;全局定义则文件内可见。若需在多文件使用,可放在头文件:

mytypes.h

#ifndef MYTYPES_H
#define MYTYPES_H

using Miles = long;
using Speed = long;

#endif

通过 #include 后,别名即拥有全局作用域。

typedef

typedef 是早期创建别名的方式,语法:

typedef long Miles;   // 与 using Miles = long; 等效

为兼容 C 仍保留,但现代 C++ 推荐 using

typedef 语法缺陷:

  1. 顺序易错:
    typedef Distance double;   // 错
    typedef double Distance;   // 对
    
  2. 复杂类型可读性差:
    typedef int (*FcnType)(double, char);          // 难读
    using FcnType = int(*)(double, char);          // 易读
    
  3. 名称“typedef”暗示“定义新类型”,实为别名。

最佳实践

优先使用类型别名(using)而非 typedef

术语

  • C++ 标准用 typedef names 统称 typedef 与类型别名。
  • 日常中“typedef”常泛指两者,因功能一致。

何时使用类型别名

  1. 平台无关编码
    intlong 等大小随平台变化。跨平台程序常用别名包含位宽信息,如 int8_tint16_tint32_t,配合预处理器:

    #ifdef INT_2_BYTES
        using int8_t  = char;
        using int16_t = int;
        using int32_t = long;
    #else
        using int8_t  = char;
        using int16_t = short;
        using int32_t = int;
    #endif
    

    标准库中的固定宽度整数(std::int16_tstd::uint32_t)和 size_t 亦为此类别名。

  2. 简化复杂类型
    例如:

    using VectPairSI = std::vector<std::pair<std::string, int>>;
    

    大幅减少打字与错误。

  3. 描述值语义
    函数返回裸 int 时语义不明:

    using TestScore = int;
    TestScore gradeTest();
    

    虽仅适用于大量相关函数,否则用注释更佳。

  4. 便于维护
    若需将 short 改为 long,只需修改别名:

    using StudentId = long;  // 原为 short
    

    但跨类型族修改需重测代码!

缺点与结论

别名引入额外标识符,若未提升可读性/可维护性则弊大于利。
糟糕别名会把熟悉类型(如 std::string)藏在新名字后,甚至隐藏智能指针语义,反而有害。

最佳实践

仅在显著提升可读性或可维护性时使用别名;多位置使用优于少量使用。

小测验

问题 #1

给定函数原型:

int printData();

将其返回类型改为类型别名 PrintError,请写出类型别名定义及更新后的函数原型。

(参考答案略)

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"