目录
一:union关键字
联合关键字:联合体内部共享空间,整个union的大小由内部最大的元素决定。
#include<stdio.h>
union un
{
int a;
char b;
// a和b共用一个空间,不过空间总大小由a决定
};
int main()
{
// a和b共用一个空间,不过空间总大小由a决定
printf("%d\n", sizeof(union un)); // 4
return 0;
}
联合体的访问和结构体类似都可以采用 . 操作符或 -> 指向操作符。
union un x;
x.a = 10;
union un* p = &x;
p->a;
union 的空间布局问题:
联合体的空间开辟由元素最大的为准,在这里由a决定。四字节,那b占用的是a的低地址处还是高地址处呢?
任何变量在开辟空间的时候,这个变量开辟后都有地址,这个地址一定是众多字节中最小的
我们将联合体变量的地址和联合体内最大元素的地址打印出来看看:
不难看出,在数值大小上是一样的
对于b来说,在申请空间的时候,所有的空间申请都是由较低地址处向上开始分配的,所以b是在最低地址开始的,换言之,内部成员b开辟的空间和联合体本身和a变量的地址值是一样的。
结论:
联合体内所有成员的起始地址都是一样的,每一个都是第一个元素。
b永远在a的低地址处!!!
利用联合体的空间分布可以巧妙判断出大小端:
这里要把一组二进制序列 0x 00 00 00 01保存在a对应的空间里,此时四个字节每个都有地址,而地址具有高低之分,而我们的数据按字节对应的1bit位进行划分的时候,数据就有高低权值位之别,所以存储方案有两种,一种高权值位放在高地址处,低权值低地址处,如我们上图的左边存法,01放在低地址处,第二种方案相反,如上图右边将01放在高地址处,因为b永远在a的低地址处,b占一个字节,如上图x.b=1红色记号笔划分出,如果存储方案是第一种,那么b=1,如果是第二种,那么b=0,而第一种存储方案正式小端的存储法则,第二种正是大端的存储法则。
如代码展示:
#include<stdio.h>
union un
{
int a;
char b;
};
int main()
{
union un x;
x.a = 1;
if (x.b == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
注意:联合体的整大小必须能整除联合体内任何一个元素的大小
#include<stdio.h>
union un
{
int a;
char b[5];
};
int main()
{
printf("%d\n", sizeof(union un)); // 8
return 0;
}
根据我们之前的认知,联合体空间由最大元素决定,b的大小是5,按理来讲联合体大小应该是5,但运行起来大小却是8,联合体的整大小必须能整除联合体内任何一个元素的大小
再看一段代码:
体现联合体和大小端的对应关系:
#include<stdio.h>
union un
{
int i;
char a[4];
}*p, u;
int main()
{
p = &u;
p->a[0] = 0x39;
p->a[1] = 0x38;
p->a[2] = 0x37;
p->a[3] = 0x36;
printf("0x%x", p->i); //0x36373839
return 0;
}
二:enum关键字
enum枚举关键字作用:枚举一堆的常量,内部的常量直接可以被当作数据使用,枚举本身也是新增或设计了一种类型,换言之,我们可以使用枚举类型直接定义变量。如下:
#include<stdio.h>
enum color
{
RED,
YELLOW,
BLACK,
GREEN,
BLUE
};
int main()
{
enum color c = RED;
printf("%d\n", RED); // 0
printf("%d\n", BLACK); // 2
printf("%d\n", BLUE); // 4
return 0;
}
枚举出来的本质就是整数,对应的就是某种字面值,不可被修改的,所以RED,BLACK,BLUE就是真正意义上的常量
为什么要存在枚举?
1:现实世界中,有一大堆具有相关性的常量需要被在代码中体现出来。
2:一旦我们枚举常量之后,所有常量的常量名不是数字而是直接用英文单词去代表,这样写出来的代码具有自描述性。
枚举常量的设定:
如果将第一个枚举常量的内容赋予一个特定的数字,那么后续的枚举常量会呈现加1式的递增,也可以分段式递增:
#include<stdio.h>
enum color
{
RED=10,
YELLOW,
BLACK=-9,
GREEN,
BLUE
};
int main()
{
enum color c = RED;
printf("%d\n", RED); // 10
printf("%d\n", YELLOW); // 11
printf("%d\n", BLACK); // -9
printf("%d\n", GREEN); // -8
printf("%d\n", BLUE); // -7
return 0;
}
三:typedef关键字
本质:类型重命名。
#include<stdio.h>
//(1)
// 对结构体类型进行重命名
typedef struct stu {
char name[16];
int age;
char sex;
}stu_t;
//(2)
//对unsigned int 重命名 u_int 简化
typedef unsigned int u_int;
//(3)
//对指针int*重命名
typedef int* int_p;
//(4)
typedef int a[10]; // 此刻a相当于一种数组类型
int main()
{
u_int x = 0;
int_p p = NULL;
stu_t s;
a b;
return 0;
}
类型重命名,可以对一些不太好理解的数据类型进行简化。
但是也不是说typedef可以随便的重命名,如果对指针或者数组进行重命名的时候,那么使用的时候,就会忽略一些细节。比如说数组的元素有几个,类型是什么,指针是几维指针,什么类型的指针。过度的typedef本质其实变相就是一种让人困扰的东西,但是比较推荐大家在结构体的时候运用typedef关键字
typedef和#define的区别
先看代码:
此段代码用的是typedef
#include<stdio.h>
typedef int* int_p;
int main()
{
//int* a, b;
// 此时a为指针,b为int 整型类型
//int* a = NULL, b = 0; //可读性太差
int_p a, b;
// 此时a 和 b均为指针,等价于int* a, * b;
return 0;
}
千万不要把typedef类型重命名看作某种替换,不能直接把int_t理解为int*,二者不能直接替换。而应将int_t理解成一种全新的类型,所以就不存在*会和a先结合还是和b先结合的问题。这个*的类型会对其后的所有定义的变量全部起效,int_p就作为一种独立类型去使用。
再来用#define试试
#include<stdio.h>
#define ptr_t int*
int main()
{
ptr_t a, b, c;
//等价于
int* a, b, c;
//a为int*, b和c均为int;
return 0;
}
结论:
typedef类型重命名,并不是本质的文本替换,形成了一个新的独立的类型
宏define做的是纯纯文本替换
案例:
#include<stdio.h>
#define INT32 int
typedef int int32;
int main()
{
unsigned INT32 a;
//因为宏define是文本替换,所以INT32就相当于int,所以就是ungsigned int a;
//unsigned int32 b; 代码报错
return 0;
}
问题:typedef static int int32_t 行不行?
从图中很容易看出是不行的,此时编译器出错。
在32个关键字中,有五个存储类型关键字:
而存储类型关键字有个特点:
存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。
所以typedef和static这两个关键字不能同时出现,所以上述代码会报错。