C语言允许使用关键字typedef
来为各种类型设置别名,但问题在于其语法相当混乱,用法大约是这么几种:
-
为原生类型赋予别名
typedef int count;
int类型就有了别名count
-
为函数取别名
typedef int (*intfunc)(int);
原型为
int (*)(int)
的函数指针就有了别名intfunc
,这通常用于指定函数的回调函数参数的类型.用的时候就是
intfunc intmap(x);
-
为数组取别名
typedef int intarr[5];
表示将int类型的长度为10的数组取别名为
intarr
用的时候intarr a = {1,2,3,4,5};
-
为函数数组取别名
typedef int (*intfunc_arr[10])(int);
表示将存放
int (*)(int)
类型函数指针的数组取别名为intfunc_arr
-
为结构体取别名
typedef struct { int x; } x_struct;
表示这个结构体别名叫
x_struct
.
C中提供了3种特殊的结构,用于描述复杂的结构化数据,分别是
- 结构体
- 联合体
- 枚举
结构体可以理解为一组有意义的特殊成员变量的组合,比如对于小朋友,有性别,姓名,年龄身高,体重这几个维度,每个小朋友都会有这些属性.我们就可以将小朋友定义为一个结构体
结构体的定义方式为:
struct 结构标签{
类型 成员变量名;
类型 成员变量名;
类型 成员变量名;
};
注意不要落了封号
我们来声明一个Child结构体:
struct Child{
const char *name;
const char *sex;
int age;
float weight;
float height;
};
ps:在结构体中字符串使用const char *
来保存你不想修改的字符串,也就是字符串字面值,这种方式只保存了字符串的指针,只有把字符串定义成字符数组如char name[20]
这样才能将字符串保存在结构体中,但一般我们不这么用,原因在于C语言中字符串常量本来就存放在代码段,没有必要到处保存
结构体是一种自定义类型,它和一般的类型如int呀char呀有相同的性质,不同之处是他是值是复合的,要非初始化上面的结构体结构体,只要这样:
struct Child Tom = {"Tom","m",8,48.5,1.5};
也可以声明变量后再赋值
struct Child Sam;
Sam.name = "Sam";
Sam.sex = "m";
Sam.age = 7;
Sam.weight = 45.7;
Sam.height = 1.57;
结构体变量作为函数参数传递的时候需要注意是传值的,这和一般的类型是一样的. 如果我们的函数参数是结构体的指针变量(传引用),那么和一般的指针变量一样,需要注意.
对于一个结构体变量,比如上文的Sam,访问方式就和第二种初始化方式一样,使用.
符号即可,特殊的地方在于如果是如下这种情况:
struct Child *Tom = {"Tom","m",8,48.5,1.5};
那么就比较尴尬了,需要记住*Tom.name
访问的其实是*(Tom.name)
,正确的写法应该是(*Tom).name
.为了避免用错括号的情况,c语言中在结构体变量的指针要访问其内部字段时使用->
代替.
Tom->name
我们可以使用typedef
关键字将结构体或者枚举,或者其他任何的类型取个别名,这样可以更加方便使用
typedef 原类型 类型别名;
基本上这也是结构体的正常使用方式,毕竟每次申明都要带上struct
关键字实在是太罗嗦了
例子:
#include <stdio.h>
#include <string.h>
typedef struct{
char name[10];
char sex[2];
int age;
float weight;
float height;
} Child;
int main(void) {
Child Tom = {"Tom","m",8,48.5,1.5};
Child Sam;
strcpy(Sam.name,"Sam");
strcpy(Sam.sex,"m");
Sam.age = 7;
Sam.weight = 45.7;
Sam.height = 1.57;
printf("%s 身高%fm\n",Tom.name,Tom.height);
printf("%s 体重%fkg\n",Sam.name,Sam.weight);
return 0;
}
Tom 身高1.500000m
Sam 体重45.700001kg
设想下有这样一个场景:
我们有若干个函数用于处理水族馆中的各种水产
/* 打印目录项 */
void catalog(const char *name, const char *species, int age){
printf("%s 是一种的 %s.这只%s已经%i岁了\n",
name,species,name,age);
}
/* 打印贴在水缸上的标签 */
void label(const char *name,const char *species, int age){
printf("名字: %s\n种类: %s\n%i 岁.\n",
name,species,age);
}
int main(){
catalog("皮皮虾","节肢动物",2);
label("皮皮虾","节肢动物",2);
return 0;
}
我们用来描述水产的参数现在有3个,这个数量还在可控范围内,但如果再多几个,那这个函数将变得非常不可读,同时,如果这些描述一旦有变化,修改这些函数将成为一件非常棘手的事儿,
结构体最大的作用就在这里,使用结构体将这些参数归类到一起,这样就不需要一次传入大量的参数了
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
/* 定义 */
typedef struct {
const wchar_t *name;
const wchar_t *species;
int age;
} Aquatic;
/* 打印目录项 */
void catalog(Aquatic *aquatic){
wprintf(L"%ls 是一种的 %ls.这只%ls已经%i岁了\n",
aquatic->name,aquatic->species,aquatic->name,aquatic->age);
}
/* 打印贴在水缸上的标签 */
void label(Aquatic *aquatic){
wprintf(L"名字: %ls\n种类: %ls\n%i 岁.\n",
aquatic->name,aquatic->species,aquatic->age);
}
int main(){
setlocale(LC_ALL, "zh_CN.UTF-8");
Aquatic mantis_shrimp = {L"皮皮虾",L"节肢动物",2};
catalog(&mantis_shrimp);
label(&mantis_shrimp);
return 0;
}
皮皮虾 是一种的 节肢动物.这只皮皮虾已经2岁了
名字: 皮皮虾
种类: 节肢动物
2 岁.
上面介绍了如何用typedef为结构体取别名,但我们看别人代码一般都不这么用,下面介绍几种常见的用法和其利弊:
-
结构体标签与别名一致
typedef struct T{ int x; } *T;
或者
struct T{ int x; }; typedef struct T *T;
这种方式如果要使用结构体的指针就直接使用
T
,如果使用结构体本身可以使用struct T
,这样的好处是相当简洁,但不好的地方在T
和struct T
很容易混淆. -
为结构体和其指针分别取不同的别名
typedef struct{ int x; } T,*T_p;
这种方式用结构体指针就使用
T_p
,使用结构体本身则可以使用T
这种方式的好处是结构体本身和指针都分开用不同的名字,不好的地方在结构体本身因为没有struct
关键字所以当代码很长的时候很容易语义上混淆. -
更加推荐的做法,
struct T{ int x; }; typedef struct T *T_p;
这种方式最老实,但也最清晰的,结构体指针就使用
T_p
,使用结构体本身则可以使用struct T
.
在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union),它的定义格式为:
union 共用体名{
成员列表
};
结构体和共用体的区别在于:
- 结构体的各个成员会占用不同的内存,互相之间没有影响;
- 共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员.
Union的作用就在于当成员间
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存.共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉.
#include <stdio.h>
union data{
int n;
char ch;
short m;
};
int main(){
union data a;
printf("%ld, %ld\n", sizeof(a), sizeof(union data) );
a.n = 0x40;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.ch = '9';
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.m = 0x2059;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.n = 0x3E25AD54;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
return 0;
}
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
枚举在python中并没有直接的实现,但C++中它是默认的一种自定义类型,它的声明关键字是enum
声明枚举类型:
enum 枚举类型名 {枚举值1,枚举值2...}
定义枚举变量:
枚举类型名 变量名 = 枚举值;
需要注意的是枚举类型变量只能赋值为枚举类型声明时的枚举值,其他都会报错,而枚举值实际上是对应声明时位置对应的整形数.
看个例子:
#include <stdio.h>
enum Weekday {MON,TUE,WED,THU,FRI,SAT,SUN};
int main(void) {
enum Weekday firstday = TUE;
printf("first day is %d\n",firstday);
return 0;
}
first day is 1
一个常见的用处是通过枚举值或者int型的数转成对应的字面量字符串.这个实现需要通过宏中#
操作.#
表示字符串化操作符,它把其后的串变成用双引号包围的串
可以参考下面的代码.
#include <stdio.h>
#define ENUM_CHIP_TYPE_CASE(x) case x: return(#x);
enum cvmx_chip_types_enum {
CVMX_CHIP_TYPE_NULL,
CVMX_CHIP_TYPE_DEPRECATED,
CVMX_CHIP_TYPE_OCTEON_SAMPLE,
CVMX_CHIP_TYPE_MAX
};
static inline const char *cvmx_chip_type_to_string(enum cvmx_chip_types_enum type)
{
switch (type)
{
ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_NULL)
ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_DEPRECATED)
ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_OCTEON_SAMPLE)
ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_MAX)
}
return "Unsupported Chip";
}
int main()
{
enum cvmx_chip_types_enum a;
printf("the result:%s \n",cvmx_chip_type_to_string(CVMX_CHIP_TYPE_OCTEON_SAMPLE));
printf("the result:%s \n",cvmx_chip_type_to_string(2));
return 0;
}
the result:CVMX_CHIP_TYPE_OCTEON_SAMPLE
the result:CVMX_CHIP_TYPE_OCTEON_SAMPLE