这是一个C语言系列文章,如果是初学者的话,建议先行阅读之前的文章。笔者也会按照章节顺序发布。
之前的文章中已经介绍过了基础数据类型、结构体、共同体以及枚举,本文介绍在C语言中如何自定义类型,或者也可以说是如何给现有类型定义别名。
自定义类型主要通过typedef关键字来定义,根据不同的写法我将自定义分为两类:定义变量类型和定义函数指针。
在定义变量类型是,typedef
的一般形式为:
typedef 原始类型名 自定义类型名;
可以看到,对于变量类型的自定义,更多的相当于是对变量类型的一个重命名。下面给出一些例子:
typedef int int32_t;
typedef unsigned int uint32_t;
typedef float r32_t;
typedef double r64_t;
这里将int
、unsigned int
、float
、double
四个类型分别又定义了一个类型别名。这并非是从此就不能用这四个类型关键字定义变量了,而是额外增加了4个类型等价于之前的4个类型。
假如,我们有如下代码:
#include <stdio.h>
int main(void)
{
int i = 10;
float f = 3.14;
printf("%d %f\n", i, f);
return 0;
}
那么利用新定义的4个类型,我们可以将其改为:
#include <stdio.h>
typedef int int32_t;
typedef unsigned int uint32_t;
typedef float r32_t;
typedef double r64_t;
int main(void)
{
int32_t i = 10;
r32_t f = 3.14;
printf("%d %f\n", i, f);
return 0;
}
可以看到main的返回值依旧是int。这段代码的含义与前一段代码的含义完全一致。
下面来看看结构体的类型自定义:
struct person {
char name[64];
unsigned long age:63;
unsigned long sex:1;
};
这里有几种写法给struct person定义类型别名:
//方法一,形式与变量类型自定义完全一致
typedef struct person person_t;
//方法二,在定义结构体的同时进行类型别名的定义,
//方法是在struct关键字前加typedef,并在}后跟类型别名,然后才是分号结束
typedef struct person {
char name[64];
unsigned long age:63;
unsigned long sex:1;
} person_t;
//方法三,有时候我们只需要自定义的类型别名,而不需要结构体名,所以结构体名可以省略不写
typedef struct {
char name[64];
unsigned long age:63;
unsigned long sex:1;
} person_t;
共同体与结构体类似:
union identity {
struct person employee;
struct person child;
struct person parent;
};
//方法一
typedef union identity identity_t;
//方法二
typedef union identity {
struct person employee;
struct person child;
struct person parent;
} identity_t;
//方法三
typedef union {
struct person employee;
struct person child;
struct person parent;
} identity_t;
枚举类型同理:
enum week {
Monday,
Tuesday = 0,
Wednesday,
Thursday,
Friday = Wednesday+99,
Saturday,
Sunday
};
//方法一
typedef enum week week_t;
//方法二
typedef enum week {
Monday,
Tuesday = 0,
Wednesday,
Thursday,
Friday = Wednesday+99,
Saturday,
Sunday
} week_t;
//方法三
typedef enum {
Monday,
Tuesday = 0,
Wednesday,
Thursday,
Friday = Wednesday+99,
Saturday,
Sunday
} week_t;
C语言中,函数除却函数名不同以外,函数的返回值类型、参数个数、参数类型都有可能不同。但是,也存在一类函数,它们除了名字不同外,其他全部相同。这样的函数,它们同属一个类型。换言之,函数是有类型的,它们的类型是由返回值类型、参数个数、参数类型共同定义的。
函数类型的一般形式如下:
typedef 返回值类型 (*函数类型名) (参数列表);
其中参数列表处仅给出参数类型即可。
这里定义的是一个函数指针,我们在指针一文中提到过,每一个函数都有其对应的内存地址,因为函数代码也是要放入内存中运行的。因此,一个能存储函数位置的变量,是一个指针变量,这种指针变量称作函数指针。
我们看几个例子,这里我们沿用上一小节中结构体定义和其类型别名。假设我们有如下函数定义:
int add(int num1, int num2)
{
return num1+num2;
}
person_t *modify(person_t *someone)
{
someone->age = 28;
return someone;
}
那么这些函数对应的函数指针定义就是:
typedef int (*ptr_add) (int, int);
typedef person_t *(*ptr_modify)(person_t *);
有了指针,我们来看下如何使用这些指针:
int main(void)
{
ptr_add my_add = add; //定义了一个ptr_add类型的函数指针变量my_add
ptr_modify my_modify = modify; //定义了一个ptr_modify类型的函数指针变量my_modify
printf("add result: %d\n", my_add(1, 2));//函数指针的使用与正常函数调用是一样的,就相当于给函数起了一个别名
person_t John = {"John", 18, 0};
person_t *ret = my_modify(&John);
printf("age: %lu\n", ret->age);
return 0;
}
#include <stdio.h>
typedef struct person person_t;
typedef void (*eat_t)(person_t *);
struct person {
unsigned long id:54;
unsigned long age:8;
unsigned long sex:1;
unsigned long hungry:1;
eat_t eat;
};
void reset_hungry(person_t *someone)
{
someone->hungry = 0;
}
int main(void)
{
int i;
person_t group[3];
for (i = 0; i < sizeof(group)/sizeof(person_t); ++i) {
group[i].id = i;
group[i].age = 18;
group[i].sex = 0;
group[i].hungry = 1;
group[i].eat = reset_hungry;
}
for (i = 0; i < sizeof(group)/sizeof(person_t); ++i) {
group[i].eat(&group[i]);
}
for (i = 0; i < sizeof(group)/sizeof(person_t); ++i) {
printf("id:%lu hungry:%lu\n", group[i].id, group[i].hungry);
}
return 0;
}
例子很简单,定义了一个person_t类型,这是个结构体,模拟了人的id、年龄、性别、饥饿与否。
由于每个人的进食都是一样的,因此定义了一个进食类型的函数指针放在person_t中,以便在需要进食时调用指针指向的函数。
main函数中第一轮for循环用来初始化person_t数组,所有person都是饥饿的。第二轮for循环用来调用eat函数指针变量指向进食函数来进食。第三轮for循环输出每个person当前的饥饿状态。
输出结果:
id:0 hungry:0
id:1 hungry:0
id:2 hungry:0