【C语言-自定义数据类型】4. typedef——给类型起别名,让代码更顺眼

开发技术 作者: 精灵星空 SKY315.COM 更新于: 浏览: 4
码字发文不易,转载请注明出处【精灵星空 SKY315.COM】

前面咱们把结构体、共用体、枚举都讲完了。今天说个“辅助型”的知识点——typedef。

这东西不复杂,说白了就一句话:给已有的类型起个新名字。 但你别小看它,实际开发里用好了,代码的可读性能上一个台阶。

咱们先从一个最简单的场景说起。

为啥要给类型起别名?

你写代码的时候,肯定遇到过这种类型名:

unsigned long long int

哇,这一长串,每次写都要敲半天,看着也累。而且你想想,如果这个类型代表的是“文件大小”,那写unsigned long long int fileSize远不如写FileSize fileSize来得清楚。

typedef就是让你给类型起个短名字,或者起个更有意义的名字。

语法特别简单:

typedef 已有类型 新名字;

就这么一行,分号结尾。

基本用法:给基本类型起别名

typedef int Age;          // Age就是int
typedef float Score;      // Score就是float
typedef unsigned long long int FileSize;   // 长名字变短

以后你就能用这些新名字来定义变量了:

Age myAge = 25;           // 等价于 int myAge = 25;
Score mathScore = 95.5;   // 等价于 float mathScore = 95.5;
FileSize size = 1024000;  // 等价于 unsigned long long int size = ...

你看,Age myAge是不是比int myAge更有意义?一看就知道这个变量存的是年龄。FileSize size一看就知道是文件大小,比unsigned long long int清楚多了。

给结构体起别名——这是最常用的

这个你其实已经见过了。前面讲结构体的时候,咱们用typedef给结构体起了别名:

typedef struct Student {
    int id;
    char name[20];
    float score;
} Student;

没有typedef的时候,你定义变量得写:

struct Student s1;   // 必须写struct

加了typedef之后:

Student s1;          // 不用写struct了

省了六个字母,代码也干净了不少。

你仔细观察一下typedef定义结构体的语法——它把Student放在了两个地方。第一个Student是结构体的“标签”(tag),第二个Student是typedef起的别名。你完全可以给别名起个不同的名字:

typedef struct Student {
    int id;
    char name[20];
    float score;
} Stu;    // 别名是Stu

那以后就用Stu s1;来定义变量。

但通常大家习惯让标签和别名保持一致,看起来更统一。

给指针起别名

指针的类型写起来也挺啰嗦的:

int *p1, *p2;   // 两个int指针

如果你要定义很多指向int的指针,可以用typedef简化:

typedef int* IntPtr;   // IntPtr就是int*

IntPtr p1, p2;         // p1和p2都是int指针

注意:IntPtr p1, p2;和int *p1, *p2;等价,但比int *p1, p2;安全得多。因为int *p1, p2;里p2是int,不是指针,容易看错。用IntPtr就清楚多了,两个都是指针。

给数组起别名

数组类型也能起别名:

typedef int IntArray[10];   // IntArray就是一个长度为10的int数组

IntArray a;   // 等价于 int a[10];

IntArray a定义了一个长度为10的int数组。注意这里IntArray是类型,不是变量。a才是变量。

这个用法相对少见,你知道有这回事就行。

给函数指针起别名——这个特别有用

函数指针的声明是出了名的难看:

int (*funcPtr)(int, int);

这玩意儿你每次写都得琢磨半天,一不小心就少写个括号,意思全变了。

用typedef可以给它穿件好看的衣服:

typedef int (*FuncPtr)(int, int);   // FuncPtr是类型名

现在你可以直接用它来定义函数指针变量了:

FuncPtr p;          // 定义了一个函数指针
p = add;            // 让它指向add函数
int result = p(3, 5);   // 调用

是不是清爽多了?你不再需要每次写那个绕来绕去的int (*p)(int, int)了。

看个完整的例子:

#include <stdio.h>

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

typedef int (*Operation)(int, int);   // 起个别名

int calculate(int x, int y, Operation op) {
    return op(x, y);
}

int main() {
    printf("%d\n", calculate(10, 5, add));   // 15
    printf("%d\n", calculate(10, 5, sub));   // 5
    return 0;
}

Operation这个别名一出来,代码可读性立马就不一样了。别人一看Operation op就知道这是个操作函数,不用去猜那个长长的函数指针声明是什么意思。

typedef和#define的区别——这是高频面试题

你可能会想:我用#define也能给类型起别名啊,为啥非得用typedef?

#define IntPtr int*
IntPtr p1, p2;

看起来IntPtr就是int*,效果应该一样吧?

不一样。 这里有个大坑。

#define是纯粹的文本替换。IntPtr p1, p2;在预处理之后变成了:

int* p1, p2;

这时候p1是int*,但p2是int!因为*只跟p1结合了,p2前面的int是分开的。

而用typedef就没这个问题:

typedef int* IntPtr;
IntPtr p1, p2;   // p1和p2都是int*

因为IntPtr是一个完整的类型名,编译器会把p1和p2都当成IntPtr类型。

总结区别:

 
  typedef #define
处理阶段 编译阶段 预处理阶段
本质 类型别名 文本替换
类型检查 有 无
作用域 遵循C语言作用域 从定义处到文件末尾
指针问题 安全 有坑

所以给类型起别名,优先用typedef,别用#define。

一个完整的例子:让链表代码更清爽

咱们用typedef把链表的代码梳理一下:

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

typedef Node* NodePtr;   // 给节点指针起个别名

NodePtr create_node(int value) {
    NodePtr newNode = (NodePtr)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        return NULL;
    }
    newNode->data = value;
    newNode->next = NULL;
    return newNode;
}

void print_list(NodePtr head) {
    NodePtr p = head;
    while (p != NULL) {
        printf("%d -> ", p->data);
        p = p->next;
    }
    printf("NULL\n");
}

int main() {
    NodePtr head = create_node(10);
    head->next = create_node(20);
    head->next->next = create_node(30);

    print_list(head);

    // 释放
    NodePtr p = head;
    while (p != NULL) {
        NodePtr temp = p;
        p = p->next;
        free(temp);
    }

    return 0;
}

你看,NodePtr这个别名让函数签名和变量声明都清楚了不少。NodePtr head比Node* head更明确地表达了“这是一个指向节点的指针”。

几个常见的坑

坑1:typedef后面忘了分号

typedef int Age    // 错误!少了分号
typedef int Age;   // 正确

坑2:typedef和#define混着用

typedef int* IntPtr;
#define IntPtr2 int*

这俩看起来像,但行为不一样,别混。

坑3:给指针起别名时把*写错位置

typedef int* IntPtr;    // 正确:IntPtr是int*
typedef int *IntPtr;    // 也对,空格无所谓
typedef int (*FuncPtr)(); // 函数指针,括号不能省

坑4:在typedef里定义变量

typedef int Age;   // Age是类型名,不是变量
Age = 10;          // 错误!Age不是变量,不能赋值
Age myAge = 10;    // 正确,用Age定义变量

坑5:以为typedef创建了新类型

typedef只是起了个别名,没有创建新类型。typedef int Age之后,Age和int是完全一样的类型,编译器不区分它们。你写Age a = 10;跟int a = 10;在编译器眼里是一回事。

实际开发中的常见用法

在实际项目里,typedef最常见的用法有这么几种:

1. 给结构体起别名,省掉struct关键字

typedef struct {
    int x;
    int y;
} Point;

2. 给函数指针起别名,简化回调声明

typedef void (*Callback)(int);
void register_callback(Callback cb);

3. 定义平台无关的类型

typedef unsigned int uint32;
typedef unsigned char uint8;

4. 简化复杂类型声明

typedef int (*Matrix)[10];   // 指向10个int的数组的指针

总结一下:

typedef就一句话:给类型起别名。

核心要点:

  • 语法:typedef 已有类型 新名字;

  • 给结构体起别名可以省掉struct关键字

  • 给函数指针起别名可以让代码可读性大幅提升

  • 跟#define不同,typedef有类型检查,更安全

  • 它不创建新类型,只是起个新名字

typedef在实际开发中特别常用,尤其是大型项目里。你几乎每打开一个C语言项目的头文件,都能看到一堆typedef。学会它,你就能看懂别人写的代码,也能让自己写的代码更清晰。

回去自己动手,把之前写的结构体、链表代码里的类型都用typedef简化一遍。再把那个函数指针的例子用typedef重写,对比一下可读性。练熟了,typedef就是你写代码时的好帮手。

咱们“用户自己建立数据类型”这一章到这儿就全部讲完了。结构体、共用体、枚举、typedef,这四个工具你都有了。以后你写复杂程序的时候,就能根据实际需要,自己“造”出合适的类型来用。