C深剖关键字(5)

C深剖关键字(5),第1张

C深剖关键字(5)

目录

一:union关键字

二:enum关键字

三:typedef关键字


一:union关键字

联合关键字:联合体内部共享空间,整个union的大小由内部最大的元素决定。

#include
union un
{
	int a;
	char b;
	// a和b共用一个空间,不过空间总大小由a决定
};
int main()
{
	// a和b共用一个空间,不过空间总大小由a决定
	printf("%dn", 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
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
union un
{
	int a;
	char b[5];
};
int main()
{
	printf("%dn", sizeof(union un)); // 8
	return 0;
}

根据我们之前的认知,联合体空间由最大元素决定,b的大小是5,按理来讲联合体大小应该是5,但运行起来大小却是8,联合体的整大小必须能整除联合体内任何一个元素的大小

 再看一段代码:

体现联合体和大小端的对应关系:

#include
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
enum color
{
	RED,
	YELLOW,
	BLACK,
	GREEN,
	BLUE
};
int main()
{
	enum color c = RED;
	printf("%dn", RED); // 0
	printf("%dn", BLACK); // 2
	printf("%dn", BLUE); // 4
	return 0;
}

枚举出来的本质就是整数,对应的就是某种字面值,不可被修改的,所以RED,BLACK,BLUE就是真正意义上的常量

为什么要存在枚举?

1:现实世界中,有一大堆具有相关性的常量需要被在代码中体现出来。

2:一旦我们枚举常量之后,所有常量的常量名不是数字而是直接用英文单词去代表,这样写出来的代码具有自描述性。    

枚举常量的设定:

如果将第一个枚举常量的内容赋予一个特定的数字,那么后续的枚举常量会呈现加1式的递增,也可以分段式递增:

#include
enum color
{
	RED=10,
	YELLOW,
	BLACK=-9,
	GREEN,
	BLUE
};
int main()
{
	enum color c = RED;
	printf("%dn", RED);   // 10
	printf("%dn", YELLOW);  // 11
	printf("%dn", BLACK);  // -9
	printf("%dn", GREEN);  // -8
	printf("%dn", BLUE);   // -7
	return 0;
}
三:typedef关键字

本质:类型重命名。

#include
//(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
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
#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
#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这两个关键字不能同时出现,所以上述代码会报错。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5691056.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存