C++ 类型别名(typedef 与 using)

在编写 C++ 程序时,像 std::vector<std::string> 这样的数据类型写起来比较长。还有一些基本类型如 int、double,单看类型名很难一眼看出它在业务里代表什么含义。

遇到这种情况,我们可以给已经存在的类型起一个简短好懂的新名字,这个新名字就叫做 “类型别名”。在 C++ 中,定义类型别名主要有 2 种方式:一种是 C++11 引入的 using 关键字,另一种是从 C 语言沿用下来的 typedef 关键字。

使用 using 定义类型别名(推荐)

在 C++ 中,使用 using 定义类型别名的写法非常直观,格式跟平时给变量赋值很像:等号左边写新起的别名,等号右边写原来的类型。

语法:

using 别名 = 原类型;

说明:

其中,“别名” 是我们自己起的新名称,习惯上首字母大写(如 UserId、Score),方便一眼看出这是一个类型名。“原类型” 既可以是 int、double 等基本类型,也可以是结构体、容器或指针等复合类型,注意语句末尾的英文分号(;)不能漏掉。

比如在记录学生信息时,学号和年龄都可以使用 int 保存。如果给它们起上带有业务含义的类型别名,不仅代码更容易读懂,后期维护起来也省事得多。

示例 1:使用别名表达业务含义

#include <iostream>
#include <string>

// 给基本类型定义别名
using StudentId = int;
using Score = double;

struct Student {
    StudentId id;        // 学号
    std::string name;    // 姓名
    Score score;         // 成绩
};

int main() {
    Student stu = {1001, "Jack", 95.5};

    std::cout << "学号:" << stu.id << '\n';
    std::cout << "姓名:" << stu.name << '\n';
    std::cout << "成绩:" << stu.score << '\n';
    return 0;
}

运行结果如下。

学号:1001
姓名:Jack
成绩:95.5

分析:

在这个例子中,using StudentId = int; 和 using Score = double; 分别为 int 和 double 起了一个新名字。在结构体 Student 里看到 StudentId 和 Score,我们一眼就能看清每个成员代表的业务含义。

这样做还有一个很实在的好处:假如以后学号超出了 int 的范围,需要升级成 long long。这时我们只需要把头部的 using StudentId = int; 改成 using StudentId = long long;,所有用到 StudentId 的地方就会同步生效,不用到处翻找修改。

除了表达业务含义之外,类型别名还经常用来缩短冗长的复杂类型。比如前面学过的 vector 容器,当元素是字符串时完整类型名要写成 std::vector<std::string>,使用 using 简化后代码会清爽很多。

示例 2:简化较长的容器类型

#include <iostream>
#include <string>
#include <vector>

// 给较长的容器类型起简短别名
using CourseList = std::vector<std::string>;

int main() {
    CourseList courses = {"C++", "Python", "SQL"};

    for (const std::string& course : courses) {
        std::cout << course << '\n';
    }
    return 0;
}

运行结果如下。

C++
Python
SQL

分析:

using CourseList = std::vector<std::string>; 表示把较长的容器类型简化成了 CourseList。之后无论是定义容器变量还是声明函数形参,我们直接写 CourseList 就可以了。

此外,给指针起别名还能避开连续声明的陷阱。前面学习指针时我们知道,直接写 int* p1, p2; 的话,只有 p1 是指针,p2 其实是普通 int 整数。如果我们先定义指针别名再连续声明变量,情况就不一样了。

using IntPtr = int*;
IntPtr p1 = nullptr, p2 = nullptr;    // p1 和 p2 都是 int* 指针

编译器会把 IntPtr 当作一个完整的指针类型,因此 p1 和 p2 都会成为真正的 int* 指针,不会出现第 2 个变量退化成普通整数的问题。

注意 类型别名只是给原有类型起了一个新名字,并不会创建出全新的独立类型。比如同时定义了 using StudentId = int; 和 using ProductId = int;,编译器底层依然把它们都当成 int,把 StudentId 变量赋给 ProductId 变量并不会报错。

使用 typedef 定义类型别名

在阅读早期的 C++ 项目或 C 语言兼容代码时,我们还会经常遇到 typedef 关键字。其中,typedef 是英文 “type definition(类型定义)” 的缩写,它的作用与 using 完全相同,也是给已有类型起别名。

语法:

typedef 原类型 别名;

说明:

需要特别注意的是,typedef 的书写顺序与 using 恰好相反。typedef 是先写 “原类型”,最后才写 “别名”,两者中间用空格隔开且没有等号。

示例 3:使用 typedef 定义别名

#include <iostream>
#include <vector>

typedef unsigned int UserId;
typedef std::vector<double> PriceList;

int main() {
    UserId uid = 2001;
    PriceList prices = {19.9, 35.0, 58.5};

    std::cout << "用户编号:" << uid << '\n';
    std::cout << "第 1 件商品价格:" << prices[0] << '\n';
    return 0;
}

运行结果如下。

用户编号:2001
第 1 件商品价格:19.9

分析:

在上面代码中,UserId 是 unsigned int 的别名,PriceList 是 std::vector<double> 的别名。无论底层通过 typedef 还是 using 定义,后续定义变量的方式完全一样,下面 2 种方式是等价的。

// 方式 1(推荐)
using UserId = unsigned int;
using PriceList = std::vector<double>;

// 方式 2
typedef unsigned int UserId;
typedef std::vector<double> PriceList;

其中,方式 1 把新名字放在等号左边,而方式 2 把新名字写在后面。面对基本类型或普通容器时,大家从右往左找到分号前的最后一个词,就能认出哪个是别名。

using 与 typedef 的区别

当原类型像 int 或普通容器这样比较简单时,using 和 typedef 看起来差别不大。可一旦遇到上一章学过的函数指针这类复杂类型,typedef 的别名就会嵌到符号中间,两者的可读性差距立刻显现出来。

// 方式 1(推荐)
using Operation = int (*)(int, int);

// 方式 2
typedef int (*Operation)(int, int);

从上面的对比可以看出,采用方式 2(typedef)给函数指针起别名时,新名字 Operation 会夹在星号、括号与参数列表中间,阅读时得仔细分辨哪个词才是别名。

而采用方式 1(using)时,不管等号右边的原类型有多复杂,新起的别名永远固定在等号左边,小伙伴们一眼就能认出来。定义好 Operation 别名后,我们在编写接收回调函数的接口时,就能直接使用它简化形参声明了。

示例 4:简化函数指针形参

#include <iostream>

// 给函数指针类型定义别名
using Operation = int (*)(int, int);

int add(int a, int b) {
    return a + b;
}

// 使用别名 Operation 声明形参
void printResult(int a, int b, Operation op) {
    std::cout << "计算结果:" << op(a, b) << '\n';
}

int main() {
    printResult(6, 4, add);
    return 0;
}

运行结果如下。

计算结果:10

分析:

using Operation = int (*)(int, int); 表示把接收两个 int 并返回 int 的函数指针类型命名为 Operation。随后我们定义 printResult() 函数时,第 3 个形参直接写成 Operation op,比写 int (*op)(int, int) 直观好懂得多。

此外,在后面学习模板编程时我们还会看到,using 可以直接给模板起别名,而 typedef 做不到这一点。因此在实际开发中,小伙伴们编写现代 C++ 新代码时统一使用 using 即可,了解 typedef 主要是为了看懂旧项目和第三方库代码。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号