【C语言】自定义类型:结构体

 

目录

一.结构体的声明

1.结构的声明

2.结构体变量的创建和初始化

3.结构的特殊声明

4.结构体的自引用

 二.结构体内存对齐(计算结构体大小)

1.对齐规则

2.修改默认对齐数

3.结构体传参

三.结构体实现位段

1.位段

2.位段的内存分配

3,位段的注意事项

 下面代码演示的是取地址操作的注意事项:

4.实际应用场景

 拓展:

网络协议中的典型应用


一.结构体的声明

1.结构的声明

struct stu {
	char name[20];
	int age;
	char sex[5];
	char id[20]
};

 

2.结构体变量的创建和初始化

#include 
struct Stu {
	char name[20];
	int age;
	char sex[5];
	char id[20]
};
int main() {
	struct Stu s1 = { "zhangsan",20,"nan","20211111" };
	struct Stu s2 = {.age = 20,.name = "lisi",.id = 20231111,.sex = "nan"};
	return 0;
}

 

3.结构的特殊声明

匿名结构体类型

struct {
	int a;
	char b;
	float c;
}x;

匿名的结构体类型结构,如果没有对结构体类型重命名,则在结构体后创建的x只能使用一次

 

4.结构体的自引用

struct node {
	int data;
	struct node* next;
};

 

 

 二.结构体内存对齐(计算结构体大小)

1.对齐规则

①结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处

②从第二个成员变量开始,都要对齐到对齐数的整数倍处

对齐数 = 编译器默认的一个对齐数该成员变量大小较小值

vs中默认对齐数为8,gcc中没有默认对齐数

③结构体的总大小为最大对齐数的整数倍

④如果嵌套了结构体,嵌套的成员对齐到自己成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数

 

2.修改默认对齐数

#pragma pake(1);   :设置默认对齐数为1

#pragma pake();   :取消设置的对齐数,还原为默认

 

3.结构体传参

#include
struct S {
	int data[100];
	int num;
};
struct S s = { {1,2,3,},100 };
void My_printf(struct S* ps){
	printf("%d\n", ps->num);
}
int main() {
	My_printf(&s);
	return 0;
}

结构体传参时,要传递结构体的地址


 

 

三.结构体实现位段

1.位段

位段(Bit Field)是结构体中一种特殊的成员,允许以位为单位指定成员所占的内存空间。这种机制常用于节省存储空间或直接访问硬件寄存器中的特定位。

struct 结构体名 {
    数据类型 成员名 : 位宽;
};

数据类型:通常为 intunsigned int 或 _Bool(C99 引入)。其他类型可能因编译器而异。

位宽:指定成员占用的位数,必须为非负整数且不超过数据类型本身的位数。

成员名后面加上冒号和数字。

例如:

struct A {
	int _a : 2;
	int _b : 5;
	int c : 10;
};

 

2.位段的内存分配

①位段的类型可以是int,unsigned int,signed int,char类型。

②位段的空间上是按照需要,以四个字节(int)或一个字节(char)的方式来开辟的。

③位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段,

例如:

#include
struct S {
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;

};
int main() {
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

开辟空间演示:

【C语言】自定义类型:结构体_第1张图片

在vs中,一个字节的内部,内存是从右向左使用的(避免剩余空间的浪费)

先在低地址处创建一个字节内存大小,在内存内部中的右侧开辟a的空间(三个比特位),由于在第一次开辟的一个字节内存中还有剩余空间并且大小能够被b(四个比特位)使用,因此在a 的右边存放b。c(五个比特位),第一次开辟的空间不够,因此在较高地址处新开辟一个字节的空间,用来存放c;以此类推。

 

3,位段的注意事项

  1. 内存对齐:位段成员的实际布局受编译器对齐规则影响,不同平台可能有差异。

  2. 跨平台兼容性:位段的存储顺序(高位优先或低位优先)由编译器决定,涉及跨平台时需谨慎。

  3. 取地址操作:无法对位段成员使用取地址运算符 &,因为其可能不按字节边界对齐

 下面代码演示的是取地址操作的注意事项:

由于位段成员可能共用一个字节,这样有些成员的起始位置不是该成员的起始位置 ,那么这些位置处是没有位置的。(内存中每个字节分配一个地址,一个字节的内部bit位是没有地址的

所有不能对位段成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能先输入放在一个变量中,然后赋值给位段的成员。

#include
struct S {
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 20;

};
int main() {

	struct S s = { 0 };
	//scanf("%d",&s.b);这是错误的写法
	//正确写法
	int b = 0;
	scanf("%d", &b);
	s._b = b;
	return 0;
}

 

4.实际应用场景

  • 硬件寄存器映射:直接操作硬件寄存器中的特定位(如状态标志)。
  • 协议解析:紧凑存储协议头部的标志位(如 TCP 头部中的控制位)。
  • 资源受限环境:嵌入式系统中节省内存空间。

 

 拓展:

网络协议中的典型应用

  1. IP协议头:IP头中的版本号(4位)、头部长度(4位)、服务类型(8位)等字段使用位段实现。
  2. TCP协议头:TCP头中的控制标志(如SYN、ACK、FIN等)通常占用1位,组合成6位的标志字段。
  3. 自定义协议:私有协议中常用位段压缩多个状态标志,减少传输开销。

 

你可能感兴趣的:(C,c语言,数据结构,开发语言)