C语言(七)自定义类型(结构体,枚举,联合)踩过的坑

1.结构体

struct
主要要考虑到结构体在内存中的布局

1.1 匿名结构体

C语言支持匿名结构体,但在实际中没啥用,匿名结构体很少用。匿名函数(lambda 入 表达式)(回调函数)在实际中特别有用,但C语言不支持。

// 匿名结构体类型
struct{
	int a;
	char b;
	float c;
}x;
struct{
	int a;
	char b;
	float c;
}a[20], *p;

上面的两个结构体在声明的时候省略掉了结构体标签,即是匿名结构体,在上面代码的基础上:

p = &x;

上面的赋值是不合法的,编译器会把上面的两个结构体声明当成完全不同的两个类型,也就是说:两个匿名结构体即使定义相同,编译器也不会认账的,仍然认为是不同的类型。

1.2 结构体自引用

struct Student{
	char name[1024];
	int score;
	struct Student s;		// 该处存在错误
};

以上结构体自引用存在问题,若如此引用将导致无限循环,编译不通过。

struct Student{
	char name[1024];
	int score;
	struct Student* s;		// 修改后,正确
};

以上自引用正确,变成结构体指针后,自引用成员只占8个字节(64位系统)。

1.3 结构体初始化和赋值的区别

struct Student s = {"zhangsan", 100, NULL};	// 初始化
s = {"lisi", 90, NULL};	// 赋值

注意:C语言不支持上面第二行的赋值操作,但C++支持(操作符重载实现的)

1.4 结构体内存对齐

结构体成员占用的内存空间的相对位置和代码中结构体变量的定义写法密切相关(即就是说:定义的结构体成员变量的顺序,就是每个成员在内存中存放的顺序,地址由低到高,依次存放)
计算一个结构体占的字节数的规则比较复杂,结构体内存对齐(空间换时间)
内存对齐规则:

  • 第一个成员的地址在与结构体变量偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
    (VS中默认的对齐数值为8,Linux中默认的对齐数值为4)
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

内存对齐的意义: 内存对齐是以空间换时间,方便调试,方便结构体跨平台移植。
借助预处理指令就可以修改对齐数
#pragma 的作用:①保证头文件只包含一次;②设置对齐数。

#pragma	pack(1)		// 设置默认对齐数为1
#pragma	pack()		// 还原默认的对齐数

1.5 结构体传参:

struct S{
	int data[1000];
	int num;
};
struct S s = {{1, 2, 3, 4}, 1000};
// 结构体传参
void print1(struct S s){
	printf("%d\n", s.num);
}
// 结构体地址传参
void print2(struct S* ptr_s){
	printf("%d\n", ptr_s->num);
}
int main(){
	print1(s);	// 传结构体
	print2(&s);	// 传结构体地址
	return 0;
}

结构体会导致内存开销大
结构体指针只需要传8个字节(64位系统)

  • 传指针虽有好处,但对结构体成员进行修改,会影响到原结构体,加const可避免修改外部结构体
  • 注:参数是指针,需进行合法性校验
  • C++的传引用既不需要校验,也能节省开销

1.6 位段(位域)

结构体中的某个成员占几位(网络协议中用的多)

struct A{
	int a_:2;
	int b_:5;
	int c_:10;
	int d_:30;
};

C语言标准规定:只有有限的几种数据类型可以用于位域。
在ANSI C中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);到了 C99,_Bool 也被支持了。

2.枚举

enum
(可正可负可为零的整型,小数不行)

  • 枚举同一时刻只能有一个值
  • 枚举默认从0开始

常量占内存,常量在计算机中与代码存储在同一专门的内存空间里(代码段)
C中没有严格区分int和枚举常量
C++中 enum 和 int 是不同的类型

enum Sex{
	MALE,
	FEMALE = -1,
	UNKNOWN
};

并且枚举常量可以相同,因为我们有时同样的数值要用不同的名称
此时 MALE 和 UNKNOWN 都为 0(向后按顺序排:0,-1,0,1,···)
枚举优点:

  • 增加代码可读性(和 #define 差不多)
  • 类型检查,严格校验(核心,比 #define 好)

3.联合(共用体)

union
联合体和结构体类似
同一块内存空间中的数据可以有不同的理解方式
如:0xff,理解成char是-1,理解成unsigned char是255
① 联合体的第一种应用场景(判断计算机大小端字节序):

int IsLittleEnd(){
	union Un{
		char a;
		int b;
	}u;
	u.b = 0x11223344;
	if(u.a == 0x11){
		return 0;
	}
	return 1;
}

② 联合体的第二种应用场景(IP地址的转换):
联合体结合结构体进行IP地址的转换
IP地址的两种表示方式:

  • 点分十进制 192.168.1.1 (0-255) (三个点分四个字节,分出每个字节并转成十进制)
  • 无符号整数 uint32_t 的方式表示
union IP{
	struct{
		char d1;
		char d2;
		char d3;
		char d4;
	}b;
	uint32_t a;
}ip;

int main(){
	ip.a = 0x1;
	printf("%d.%d.%d.%d\n", ip.b.d1, ip.b.d3, ip.b.d3, ip.b.d4);
	return 0;
}

输出:

1.0.0.0

联合体的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个成员)
联合体的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值