c语言——结构体详解

什么是结构体

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体的声明

我举个例子:


struct Student//声明一个学生的结构体
{
  char id[20];//学号
  char name[20];//姓名
  char sex[10];//性别
  int age;//年龄
};

其实结构体的声明可以分为三种方式

1. 比较好的方式:


#include <stdio.h>
struct student //结构体类型的说明与定义分开声明
{
  int age;  //年龄
  float score; //分数
  char sex;   //性别
};
int main ()
{
  struct student a={ 20,79,'f'}; //定义
  printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );
  return 0;
}

2. 比较浪费的方式:


#include <stdio.h>
struct student //声明时直接定义
{
  int age;  //年龄
  float score;  //分数
  char sex;   //性别
 //这种方式比较浪费,只能用一次
} a={21,80,'n'};
int main ()
{
  printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );
}

3. 避免使用的方式:

#include <stdio.h>
struct   //直接定义结构体变量,没有结构体类型名。这种方式最差
{
  int age;
  float score;
  char sex;
} t={21,79,'f'};
 
 
int main ()
{
  printf("年龄:%d 分数:%f 性别:%c\n", t.age, t.score, t.sex);
  return 0;
}

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

我就以上面这个结构体声明为例:

```c

struct Student//声明一个学生的结构体
{
  char id[20];//学号
  char name[20];//姓名
  char sex[10];//性别
  int age;//年龄
};
int main()
{
	//按照结构体成员的顺序初始化
	struct Stu s = { "张三", 20, "男", "20230818001" };
	printf("name: %s\n", s.name);
	printf("age : %d\n", s.age);
	printf("sex : %s\n", s.sex);
	printf("id : %s\n", s.id);

	//按照指定的顺序初始化
	struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥"};
	printf("name: %s\n", s2.name);
	printf("age : %d\n", s2.age);
	printf("sex : %s\n", s2.sex);
	printf("id : %s\n", s2.id);
	return 0;
}

匿名结构体

我们在声明结构体的时候可以不完全的声明


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

struct
{
	int a;
	char b;
	float c;
}a[20], * p;

匿名结构体也称为未命名结构体,由于没有名称,因此不会创建它们的直接对象(或变量),通常我们在嵌套结构或联合中使用它们。

值得注意的是,匿名结构体类型可以说是“一次性用品”,用了一次以后再也用不了了。

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

struct
{
	int a;
	char b;
	float c;
}a[20], * p;

int main()
{
	p = &x;
	return 0;
 }

如上代码是错误的,虽然两个匿名结构体,看上去它们的成员都是一样的,表面上是同一类型,但实际上他们是两种不同的类型。因此,因为在定义的时候没有写结构体类型名称,编译器会把它们当做两种不同的类型,然后报错。

结构体自引用

结构体内部的成员不可以是该结构体本身。

struct Node
{
 int data;
 struct Node next;
};

这种类型的自引用是非法的,因为成员next是另一个结构体,类型是struct Node它的内部还要包含自己的成员next。
这个next还将包括自己的成员next,这样重复下去,永无止境,像是一个永远不会终止的递归程序。

那么应该如何正确的自引用呢?

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

如果在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易发生问题。

typedef struct
{
    int data;
    Node* next;
}Node

在使用typedef创建Node并且其结构成员可以自引用,但是上述示例在定义next时,Node并没有创建,所以在结构体内部定义next时,结构体类型并没有创建,所以不合法。

那么解决方法也很简单——定义结构体就不要使用匿名结构体了

typedef struct Node
{
 int data;
 struct Node* next;
}Node;

结构体内存对齐

我们首先要了解结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

举例:

这里我用vs进行演示,vs的默认对齐数是8.

1.

struct S1
{
 char c1;
 int i;
 char c2;
};
printf("%d\n", sizeof(struct S1));

在这里插入图片描述
S1实际上只需要6个字节,但是因为对齐规则,浪费了6个字节,所以用了12个字节。

2.

struct S2
{
	char c1;
	char c2;
	int i;
};
printf("%d\n", sizeof(struct S2));

在这里插入图片描述

8个字节符合第三条对齐规则。

3.

struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));

在这里插入图片描述

这里的最大对齐数是8,16个字节符合对齐规则

4.

struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
 char c1;
 struct S3 s3;
 double d;
};
printf("%d\n", sizeof(struct S4));

在这里插入图片描述
根据第四条对齐规则:
结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
S3的最大对齐数是8,S4的最大对齐数也是8,所以32个字节符合规则。

结构体传参

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* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s); //传结构体
	print2(&s); //传地址
	return 0;
}

上面两种方式相比,printf2会更好,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下
降。

第二种同样可以达到第一种的效果,而且效率更高。

结构体实现位段

什么是位段?位表示的是二进制位

注意点:
1.位段的成员必须是int、unsigned int 或者signed int、char。
2.位段的成员名后面有一个冒号和一个数字。

我举个例子:

struct S
{
 int _a : 2;
 int _b : 5;
 int _c : 10;
 int _d : 30;
};

如果结构体中的成员a中放了0,1,2,3这几个数字,其实用两个bit位就足够用了,所以位段可以很大程度上的节省空间。
2+5+10+30=47(bit),如果照这样来说,那么6(48bit)个字节就足够了,我们来验证一下。
在这里插入图片描述
虽然和我所想不一样,但是还是节省了一半的空间,达到了我想要的效果。

要想知道为什么,我们就得了解一下位段的内存分配:

  1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
    4.一个位段必须储存在同一个储存单元中,不能跨两个储存单元

一样还是举个例子:

struct S
{
 char a : 3;
 char b : 4;
 char c : 5;
 char d : 4;
};
int main()
{
	printf("%zd", sizeof(struct S));
	return 0;
}

在这里插入图片描述
那具体是怎么存放的呢?我再来演示一下:

#include <stdio.h>

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;

	printf("%zd", sizeof(s));
	return 0;
}

在这里插入图片描述

我们调试一下看对不对:
在这里插入图片描述

希望这篇博客对你有所帮助!!!!!
在这里插入图片描述

  • 36
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
引用\[1\]:C语言字节对齐问题详解中提到了C语言中的字节对齐问题。在结构体中,为了提高内存访问的效率,编译器会对结构体进行字节对齐。这意味着结构体的成员在内存中并不是紧凑排列的,而是按照一定的规则进行对齐。具体的对齐规则取决于编译器和编译选项。\[1\] 引用\[2\]:在C语言中,可以使用宏offsetof来获取结构体成员相对于结构体开头的字节偏移量。这个宏非常有用,可以帮助我们计算出每个结构体成员相对于结构体开头的偏移字节数。通过这个宏,我们可以更好地理解结构体的内存布局。\[2\] 引用\[3\]:在C语言中,指针和结构体的组合常常用于处理复杂的数据结构。指针可以指向结构体的成员,通过指针可以方便地对结构体进行操作。指针和结构体的组合可以实现更灵活的数据处理和内存管理。\[3\] 综上所述,C语言中的指针结构体组合可以用于处理复杂的数据结构,而字节对齐问题则是在结构体中为了提高内存访问效率而进行的优化。通过使用宏offsetof,我们可以更好地理解结构体的内存布局。 #### 引用[.reference_title] - *1* *3* [结构体指针,C语言结构体指针详解](https://blog.csdn.net/weixin_34069265/article/details/117110735)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C语言结构体详解](https://blog.csdn.net/m0_70749276/article/details/127061692)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值