目录
1、枚举
枚举顾名思义就是一 一列举,把可能的取值列举出来。
比如我们现实生活中:一周的星期一到星期日是有限的 7 天,可以一 一列举。
凡是生活中可以一 一列举出来的,就可把这些值它们对应的类型就可以定义成枚举类型。 枚举类型就是一种类型,这是我们要注意的,就像结构体类型一样都是属于自定义类型的。
1.1 枚举类型的定义
枚举类型定义的关键字是:enum 。定义枚举类型的语句格式为:
enum 枚举类型名
{
//枚举的可能取值--枚举常量
};
如:
enum Day//星期 { Mon,//枚举的可能取值 Tues, Wed, Thur, Fri, Sat, Sun };
以上定义的 enum Day 就是枚举类型了。{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。 这些可能取值都是有值的,默认从 0 开始,一次递增 1。 如:#include<stdio.h> enum Day//星期 { Mon,//枚举的可能取值 Tues, Wed, Thur, Fri, Sat, Sun }; int main() { printf("枚举常量值如下:\n"); printf("Mon = %d\n", Mon); printf("Tues= %d\n", Tues); printf("Wed = %d\n", Wed); printf("Thur= %d\n", Thur); printf("Fri = %d\n", Fri); printf("Sat = %d\n", Sat); printf("Sun = %d\n", Sun); return 0; }
运行结果:
当然在定义的时候也可以赋初值,给一个枚举常量赋初值时,就会接连的改变后面的枚举常量的默认值。 如:#include<stdio.h> enum Day//星期 { Mon,//枚举的可能取值 Tues, Wed = 66, Thur, Fri, Sat = 88, Sun }; int main() { printf("枚举常量值如下:\n"); printf("Mon = %d\n", Mon); printf("Tues= %d\n", Tues); printf("Wed = %d\n", Wed); printf("Thur= %d\n", Thur); printf("Fri = %d\n", Fri); printf("Sat = %d\n", Sat); printf("Sun = %d\n", Sun); return 0; }
Mon 的默认值为 0、Tues 的默认值为 1,给枚举常量赋初值 Wed = 66,那么在 Wed 后的枚举常量 Thur 的默认初值变为 67,Fri 的默认初值变为 68。以此类推,给枚举常量赋初值 Sat = 88,那么在 Sat 后的枚举常量 Sun 的默认初值变为 89;运行结果如下:
总结:当给一个枚举常量赋初值时,就会相应的改变后面的枚举常量的默认初值。
注意:给枚举常量赋初值, 不是改变枚举常量(常量是不能改变的),而是给这个常量赋一个初始值,即使他是常量,他也应该有一个值,既然我们不想让它有其他的默认的初始值,就可以自定义的给它一个初始值。
1.2 枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?枚举的优点如下:(1)增加代码的可读性和可维护性。(2)和#define定义的标识符比较枚举有类型检查,更加严谨。(3)防止了命名污染(封装) 。(4)便于调试 。(5)使用方便,一次可以定义多个常量。
1.3 枚举的使用
(1)
#include<stdio.h> enum Day//星期 { Mon,//枚举的可能取值--枚举常量 Tues, Wed, Thur, Fri, Sat = 66,//给常量赋初始值,就相当于与常量 const int num = 88;这是一个常量,给它赋初始值,而不是改变它 Sun }; int main() { enum Day day = Mon;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。 enum Day temp = 66;//此种写法是不推荐的,因为存在类型差异。原因:66 是int类型,temp是枚举类型。 //Sat = 88;//这是错误的,Sat 是一个常量,不能改变的 printf("%d", sizeof(day));//enum Day枚举类型的大小为一个整型的大小,也就是:4 个字节。原因:只能拿一个枚举常量给枚举变量赋值,枚举常量是一个整型。 return 0; }
(2)
#include<stdio.h> //定义枚举类型 enum Day//星期 { exit,//枚举的可能取值--枚举常量 Mon, Tues, Wed, Thur, Fri, Sat, Sun }; void menu() { printf("-----------------------------\n"); printf("-----1.Mon 2.Tues---------\n"); printf("-----3.Wed 4.Thur---------\n"); printf("-----5.Fri 6.Sat----------\n"); printf("-----7.Sun 0.exit---------\n"); printf("-----------------------------\n"); printf("请输入你的选择:> "); } int main() { int input = 0; do { menu(); scanf("%d", &input); switch (input) { 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; case exit: printf("退出\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
总结:只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
2、联合体
定义不同数据类型的数据共占同一段内存空间,以满足某些特殊的数据处理要求,这种数据构造类型就是联合体。
2.1 联合体类型
联合体也是一种构造数据类型,和结构体类型一样,它也是由各种不同类型的数据组成,这些数据叫做联合体的成员。和结构体不同的是,在联合体中,C 语言编译系统使用了覆盖技术,使联合体的所有成员在内存中具有相同的首地址,共占同一段内存空间,这些数据可以相互覆盖,因此联合体也常常被称做是共用体,在不同的时间保存不同的数据类型和不同长度的成员的值。也就是说,在某一时刻,只有最新存储的数据是有效的。运用此种类型数据的优点是节省存储空间。
联合体类型定义的一般形式为:
union 联合体名
{
数据类型 1 成员名 1;
数据类型 2 成员名 2;
......
数据类型 n 成员名 n;
};
union 是 C 语言中的关键字,表明是在进行一个联合体类型的定义。联合体类型名是一个合法的 C 语言标识符,联合体类型成员的数据类型可以是 C 语言中的任何一个数据类型,最后的分号表示联合体定义的结束。如:
union ucode
{
char u1;
int u2;
long u3;
};
这里定义了一个名为 union ucode 的联合体类型,它包括 3 个成员,分别是字符型、整型和长整型。
说明:联合体类型的定义只是由用户构造了一个联合体,定义好后可以像 C 中提供的基本数据类型一样使用,即可以用它来定义变量、数组等。但定义联合体类型时,系统并不为其分配存储空间,而是为由该联合体类型定义的变量、数组等分配存储空间。
联合体变量的定义和结构体变量的定义一样,也是三种方法,这里就不多说了。
2.2 联合体变量的初始化
定义联合体变量的同时对其成员赋初值,就是对联合体变量的初始化。那么,对联合体变量初始化可以和结构体变量一样,在定义时直接对其各个成员赋初值吗? 如:
union ucode
{
char u1;
int u2;
};//定义联合体类型
union ucode a = {'a',45};//定义联合体类型的变量 a 并初始化(错误的)
union ucode a = {'a',45} 定义联合体类型的变量 a 并初始化,这是错误的形式。原因:这是因为联合体和结构体变量的存储结构不同,联合体变量中的成员是共用一个首地址,共占同一段内存空间,所以在任意时刻只能存放其中一个成员的值。也就是说,每一瞬时只能有一个成员起作用,所以,在对联合体类型的变量定义并初始化时,只能是对第 1个成员赋初值,初值需要用 “ { } ” 括起来。
联合体变量的引用 :
联合体变量不能整体引用,对联合体变量的赋值、使用都只能对变量的成员进行。联合体变量引用其成员的方法与访问结构体变量成员的方法相同。
2.3 联合体大小的计算
(1)联合的大小至少是最大成员的大小。 (联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员))
(2)当最大成员大小 不是 最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
如:
#include<stdio.h> union Un1 { char c[5];//char 1个字节-->默认对齐数为 8 --> 1 比 8 小--> 对齐数为 1 int i;//int 4 个字节 --> 默认对齐数为 8 --> 4 比 8 小--> 对齐数为 4-->为该联合体的最大对齐数 //最大是5字节,5不是最大对齐数的整数倍,就要对齐到最大对齐数的整数倍,也就是 8 }; union Un2 { short c[7];//14个字节 short 2个字节 --> 默认对齐数为 8 --> 2 比 8 小--> 对齐数为 2 int i;//4个字节 int 4 个字节 --> 默认对齐数为 8 --> 4 比 8 小--> 对齐数为 4 --> 为该联合体的最大对齐数 //最大的是14个字节,14不是最大对齐数的整数倍,就要对齐到最大对齐数的整数倍,也就是 16 }; int main() { //下面输出的结果是什么? printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2)); return 0; }
运行结果:
最后来看一道面试题: 判断当前计算机的大小端存储
什么是大小端字节序呢?也就是一个数据,存放在内中的存放的字节顺序。
把一个数存储到内存中,高字节的内容存放到低地址的,低字节的内容放在高地址的,就叫是大端字节序存储模式;低字节的内容存放到低地址的,高字节的内容放在高地址的,就叫是小端字节序存储模式。如下图:
(1)利用指针
//int a = 0x11223344;//int a=1;//0x00 00 00 01 // 低地址------------>高地址 //----[][][][][11][22][33][44][][][][][][][]----把一个数的高字节的内容存放到低地址的,低字节的内容放在高地址的,就叫是大端字节序存储模式 //----[][][][][44][33][22][11][][][][][][][]----把一个数的低字节的内容存放到低地址的,高字节的内容放在高地址的,就叫是小端字节序存储模式 //讨论一个数据,存放在内中的存放的字节顺序 //大小端字节序问题 #include<stdio.h> int main() { int a = 1; char* pchar = (char*)&a; if (*pchar == 1) { printf("字节序存储模式是小端\n"); } else { printf("字节序存储模式是大端\n"); } return 0; }
(2)利用联合体
#include<stdio.h> int check_sys() { union Un { char c; int i; }u; u.i = 1; //返回1,表示小端 //返回0,表示大端 return u.c; } int main() { int ret = check_sys(); if (ret == 1)printf("小端\n"); else printf("大端\n"); return 0; }