在之前的文章中,我们学到了结构体类型,而结构体其实归属于一个大类——自定义类型。那么今天我们就继续讲解关于自定义类型的知识~
一、类型命名关键字-typedef
typedef的作用其实就是标题的意思——为一种类型赋予新的名字。
① typedef在变量中的应用
光靠说的或许并体现不出它的作用,我们举一个例子:比如我们平常在编程中,想使用较大的数字,于是此时我们想要使用long long型的变量,但我们在定义变量时嫌弃long long类型名的名字太长,所以我们可以为long long取一个新的名字:
typedef long long LL;
int main()
{LL a = 0;a = 99999999999999999;printf("a = %lld\n", a);return 0;
}
如此便能够使用"LL"两个简短的字母来代替long long啦~
② typedef在结构体中的应用
在结构体创建后,正常来说想要初始化一个结构体,我们需要使用结构体类型名去初始化,而当我们觉得此类型名过于长,或者需要定义的结构体过多,麻烦时就可以使用typedef来为结构体类型名起一个别名,用于使初始化变得便捷。
struct Student {char name[20];char s;int num;
};
typedef struct Student s;
int main()
{s arr = { "abcdef",'A',10 };printf("%s %c %d", arr.name, arr.s, arr.num);return 0;
}
有结构体类型名:
typedef struct Student{char name[20];
}s;//结构体类型变量为:s
无结构体类型名:
typedef struct {char name[20];
}s;//结构体类型变量为:s
二、联合体
① 联合体类型声明与内存
联合体与结构体相似,联合体也是一个由多个成员变量所构成的,并且这些成员变量都可以是不同的类型。
union 联合体名
{数据类型 成员1;数据类型 成员2;...
};
联合体与结构体的区别是:结构体的内存是通过对齐数计算,并且最终内存必须是对齐数的整数倍。而对于联合体,编译器只为其最大成员分配足够内存空间(但联合体大小内存必须为所有成员变量内存的整数倍),所以联合体的特殊之处也就是"所有成员共用一块内存空间",所以联合体也叫做"共用体"。
union s
{char name[12];int age;
};
int main()
{union s stu = { 0 };printf("%d", sizeof(stu));return 0;
}
我们可以看到,当我们计算联合体的大小时,大小为12的name和大小为4的age,但最终得到的大小仅仅为12,这就是因为两者共用一块空间,并且如果当我们将char name[12]改成char name[10]时,会发现大小仍为12。这是因为联合体大小必须为其成员变量大小的整数倍。
char name[12]和int age在内存中的存储方式为:
② 联合体的初始化
看到这里大家或许觉得,联合体能存储这么多变量,并且占的内存还不多~处处都比结构体强,简直是百利而无一害呀~但其实不是这样的 T A T,让我们继续往下看⬇
当我们创建完联合体,想要为联合体的成员变量进行初始化时,如果只初始化一个成员变量:
union s
{char name[12];int age;
};
int main()
{union s stu = {"hello world"};printf("name = %s\n", stu.name);return 0;
}
此时这种初始化是能够成功进行的,但如果我们想同时初始化两个成员变量:就会出现这种报错的情况。交换一下位置也是一样的情况,这是因为成员变量们所占用的空间是共同的,当对一个变量进行初始化后,想要再对另一个变量初始化,所改变的内存就会影响第一个变量的值。
这就是联合体的坏处,它虽然能够一定程度上节省内存,但是并无法同时使用多个变量。
并且,需要注意的是:每次只能对一个成员变量进行初始化,所以想要改变不是第一个的成员变量,就不能直接输入值,而是必须指定相应的变量进行赋值:
union s
{char name[12];int age;int time;
};
int main()
{union s stu = {.age = 18};printf("age = %d\n", stu.age);return 0;
}
当我们在定义过char name[12]后,再定义了其他的变量,就不能够再去使用name:
union s
{char name[12];int age;int time;
};
int main()
{union s stu = {"zhang san"};//未初始化其他变量时输出name:printf("name = %s\n", stu.name);stu.age = 18;printf("age = %d\n", stu.age);//初始化age后再输出name:printf("name = %s\n", stu.name);return 0;
}
因为当我们第一次初始化name时,此时联合体中的内存是这样的:但当我们又初始化int后,联合体中的内存就是这样了:
(在对联合体进行初始化时,是从低字节还是高字节开始改变取决于当前编译器是大端环境还是小端环境)
③ 联合体的实际应用
当知道联合体的使用条件如此苛刻,各位是不是又觉得,联合体就没有使用的必要了呢?其实也不尽然,毕竟联合体与结构体相比,确确实实的能够节省相当多的空间了,仅仅几个变量或许看不出来,但变量非常多,非常复杂,并且有重复时,就能够体现出联合体的作用了:
比如此时我们要举办一个活动,上线三个新的产品:图书,衬衫,杯子。
而这三种商品除了共有的信息:"库存量","价格","商品标签"等,还有很多不共有的其他信息:
图书:书名,作者,页数
衬衫:设计,颜色,尺寸
被子:设计,材质
如果我们并不加以思考,直接使用结构体去进行创建变量:
struct commodity
{//共有信息:int number;//库存量double price;//价格int type;//商品标签//非共有信息:char name[20];//书名char writer[20];//作者int page;//页数char devise[30];//设计int color;//颜色int size;//尺寸int material;//材质
};
虽然是可以使用的,并且创建起来也简单,但是其中的缺点就是有很多不必要浪费的空间:比如我们想使用图书时,就不需要:"设计","颜色","尺寸","材质"这四个因素。而想单独使用其他两种商品时也是同理。
此时我们就可以使用联合体来代替结构体,将公共的部分作为成员变量放进结构体,而三种商品分别独自拥有的属性就使用联合体分别分装,在使用某种商品时,就用到某种商品的元素,而不会导致出现过多的内存浪费:
struct Commodity
{//共有信息:int number;//库存量double price;//价格int type;//商品标签union {struct{char name[20];//书名char writer[20];//作者int page;//页数}Book;struct{char devise[30];//设计int color;//颜色int size;//尺寸}Shirt;struct{char devise[30];//设计int material;//材质}Cup;}choose;
};
再让我们看一下两种不同的方法,所占内存大小的差别:
三、枚举类型
① 什么是枚举类型?
枚举类型能让你自定义随意的命名,创建有各种成员组成,它允许你为一组相关的常量赋予有意义的名字,以提高代码的可读性和可维护性。
那么枚举类型应该如何使用呢?其实就是将自己想要使用的常量进行列举~
比如我们上高中,理科生学习的科目就分别是:语文,数学,英语,物理,化学,生物,可以列举。
或者一个星期有七天:星期一,星期二,星期三,星期四,星期五,星期六,星期日,可以列举。
又或者一年有十二个月份,也可以一一列举。
② 枚举类型的定义
enum 枚举类型名
{常量1,常量2,...
};
(需要注意的是,常量后接的是" , ",而并不是" ; ")
了解了枚举类型的定义形式,让我们使用上面的例子,试着定义几个枚举吧:
高中科目:
enum subjects
{Chinese,mathematics,English,physics,chemistry,creature
};
星期:
enum Day
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
上面定义的subjects与Day都是枚举类型,并且其中所定义的成员也都是有值的:如果并未进行初始化,那么从第一个成员开始,默认第一个为0,第二个为1,第三个为2...直到结束。并且枚举类型也可以将其中的成员进行赋值,其后的成员按照该成员的值依次加1。
③ 枚举类型的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和 #define 定义的标识符比较枚举有类型检查,更加严谨
3. 便于调试,预处理阶段会删除 #define 定义的符号
4. 使用方便,一次可以定义多个常量
5. 枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用
④ 枚举类型的使用
我们可以使用枚举类型创建很多成员,并且为其赋值,然后使用枚举变量作为函数的参数。比如我们可以创建一个星期表,创建一个枚举类型存储星期一到星期七,通过我们输入的值,进入函数中判断枚举类型中的哪个变量,再进行打印。
enum Day
{Mon = 1, Tues, Wed, Thur, Fri, Sat, Sun
};
void printDay(enum Day day)
{switch (day){case Mon:printf("星期一\n");break;case Tues:printf("星期二\n");break;case Wed:printf("星期三\n");break;case Thur:printf("星期四\n");break;case Fri:printf("星期五\n");break;case Sat:printf("星期六\n");break;case Sun:printf("星期日\n");break;}
}
int main()
{enum Day num;printf("请输入1-7\n");scanf("%d", &num);printDay(num);return 0;
}
那么关于C语言的自定义类型相关知识就为大家讲解到这里啦,如果有哪里讲的不够清楚,或者有出错的地方,还请大家多多在评论区指出,我也会吸取教训,多多改正的!那么我们下期再见啦~