[C语言]--自定义类型: 结构体

embedded/2024/9/25 17:07:33/

目录

前言

一、结构体类型的声明

1.结构的声明

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

3.结构的特殊声明 

 4.结构的自引用

二、结构体内存对齐

1.对齐规则

2.为什么存在内存对齐?

三、结构体传参 

四、结构体实现位段

1.什么是位段

2.位段的内存分配

3.位段的跨平台问题

 4.位段的应用

5.位段使用的注意事项 

总结


前言

我们已经将C语言中的内置类型学完了,下面我们来学习一下C语言的自定义类型,结构体。


一、结构体类型的声明

1.结构的声明

结构是一些值的集合,这些值称为成员变量。

结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚至是其他结构体。

struct tag
{member-list;
}variable-list;
  • tag : 称呼 ,表示这个结构体的名字
  • member-list : 成员列表 ,是写成员变量的地方
  • variable-list  : 变量列表 ,是写属于这个结构的全局变量的地方,可写可不写

 eg:

struct Stu{char name[20];int age;char sex;   }s1,s2,s3;

 这里的s1,s2,s3的作用s4相同

Struct Stu s4;

 这里创建了一个Stu类型的全局变量s4

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

#include <stdio.h>struct Stu
{char name[20];int age;char sex;
};int main() {//方式一:根据结构体成员顺序书写struct Stu s1 = { "张山",32,"男" };//方式二:根据指定顺序书写  struct Stu s2 = { .name = "李斯" ,.sex = "男" , .age = 23 };//输出方式printf("name: %d", s1.age);return 0;
}

3.结构的特殊声明 

匿名的结构体类型,即省略tag(称呼)

struct{char name[20];int age;char sex;   }s1;

 这种声明只能用一次

 4.结构的自引用

这里先介绍两种数据结构,顺序表链表

 顺序表可以用数组来实现,没啥好说的

在链表中,这五个方块都是一个节点,分别存储着数据和下个节点的位置,这样才能在纷乱的位置中依次找出数据

那我们可以这这样做吗?

struct INT
{int data;struct INT n;
};

 可是这样做是错的,因为sizeof(struct INT)是多少呢?

我们无法计算它的大小,它会无穷的大

正确的自引用方式:

struct INT
{int data;struct INT* n;
};

一个结构体指针变量,这个变量指向下个节点的地址,并且指针变量可以求出大小 ,没有上述的问题

还有,当结构体自引用时,夹杂了typedef对结构体类型重命名的问题

typedef struct INT
{int data;INT* n;
}INT;

 这里会出现错误,因为结构体内提前使用INT,无法分辨INT是啥类型

解决办法: 定义结构体不要使用匿名结构体

typedef struct INT
{int data;struct INT* n;
}INT;

二、结构体内存对齐

1.对齐规则

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

举个例子: 

int main() {struct S1{char c1;    //1  8  ->  1int i;      //4	 8  ->  4char c2;    //1	 8  ->  1};printf("%zd\n", sizeof(struct S1));
}

 

规则1, 结构体第一个变量c1在结构体变量起始位置偏移量为0处

规则2,根据对齐数,c2的对齐数是4,所以跳过前面,到4处; c3的对齐数是1,到8处

规则3,c1,c2,c3的对齐数分别是1,4,1  最大对齐数是4,所以该结构体总大小为12

2.为什么存在内存对齐?

大部分的参考资料都是这样说的:

1. 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

总体来说:结构体的内存对齐是拿空间来换取时间的做法。

当我们要满足对齐,又要节省空间,办法是:

让占用空间小的成员尽量集中在一起

当然,结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。

#pragma 这个预处理指令,可以改变编译器的默认对齐数。

#pragma pack(1)   //设置默认对⻬数为1 #pragma pack()    //取消设置的对⻬数,还原为默认

三、结构体传参 

传参分为两种

struct Stu
{char name[1000];int num;
};struct Stu s = { "大明", 1000};//结构体传参
void print1(struct Stu s)
{printf("%s\n", s.name);
}//结构体地址传参
void print2(struct Stu* ps)
{printf("%d\n", ps->num);
}int main()
{print1(s);  //传结构体print2(&s); //传地址return 0;
}

当然print2 比 print1 好 

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下 降。

结论:

结构体传参的时候,要传结构体的地址。 

四、结构体实现位段

1.什么是位段

位段和结构体类似,但有两个不同的地方

  1. 位段的成员类型有要求,只能是int、unsigned int 、signed int ,只是在C99中位段的成员类型有其他类型(如char....)
  2. 位段的成员名后面有一个冒号(:)和一个数字(这个数字表示比特位的数量)

例子:

struct A
{int a:2;int b:5;int c:10;
};

这个结构体的大小是4个字节

为什么呢?

2.位段的内存分配

位段的空间是按照成员类型来分配内存的,我们再举个例子

struct A
{char a:2;char b:4;char c:6;char d:7;
};

如: 第一个成员类型是char类型,那么会先给1个字节

 这里还要说明一点,位段的储存顺序是不确定的,可能是从左往右,也可能是从右往左,我们假设是从右往左

在这个字节中,我们将a存入,a占两个比特位,接着存入b,b占4个比特位

但当我们要存入c时,剩下的空间不够了(利用还是舍弃不确定,这里舍弃),于是再根据c的类型,生成一个字节

d同理

 

综上所示,这个结构体的大小就是3个字节 

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

3.位段的跨平台问题

  1. int位段被当成有符号还是无符号数是不确定的
  2. 位段中最大的数目不能确定。(16位机器最大16,32位机器最大32。当写成27时,在16位机器就会出现问题)
  3. 位段中成员在内存中从左向右分配,还是从右向左分配,标准尚未定义
  4. 当一个结构体包含两个位段,第二个位段成员比较大时,无法容纳于第一个位段剩余的位时,是舍弃剩余的位段还是利用,这是不确定的

总结:

跟结构相比,位段可以达到相同的效果,并且可以很好的节约空间,但有跨平台的问题存在 

 4.位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。

5.位段使用的注意事项 

因为同一个字节中可能会有多个位段成员,而这些成员的起始位置并不是某个字节的起始位置,所以这些位置处是没有地址的

内存中每个字节分配一个地址,但字节内的bit位是没有地址的

因此我们不能对位段的成员使用&操作符


总结

结构体在C语言中并不难,学完后找一些题做做就行啦


http://www.ppmy.cn/embedded/116724.html

相关文章

中国蚁剑(antSword)安装使用

antSword下载 antSword-Loader下载 作者&#xff1a;程序那点事儿 日期&#xff1a;2024/09/12 19:35 中国蚁剑&#xff08;AntSword&#xff09;是一款跨平台的开源网站管理工具&#xff0c;旨在满足渗透测试人员的需求。它是一个功能强大的工具&#xff0c;可以帮助用户管理…

Stable Diffusion 使用详解(11)--- 场景ICON制作

目录 背景 controlNet 整体描述 Canny Lineart Depth 实际使用 AI绘制需求 绘制过程 PS打底 场景模型选择 设置提示词及绘制参数 controlnet 设置 canny 边缘 depth 深度 lineart 线稿 效果 背景 这段时间不知道为啥小伙伴似乎喜欢制作很符合自己场景的ICON。…

开源网安受邀参加2024中国新能源汽车零部件交易会

近日&#xff0c;2024中国新能源汽车零部件交易会在十堰国际会展中心举行。开源网安车联网安全实验室携车联网安全相关产品及解决方案亮相本次交易会&#xff0c;保障智能网联汽车“车、路、云、网、图、边”安全&#xff0c;推动智能网联汽车技术突破与产业化发展。 中国新能源…

Apollo自动驾驶项目分析(一:整体框架)

1. Apollo 的代码结构和整体框架 Apollo 是百度开发的自动驾驶平台&#xff0c;支持从感知到控制的全栈自动驾驶技术。其代码结构清晰&#xff0c;模块划分明确&#xff0c;涵盖了自动驾驶的多个核心方面。 主要的代码模块和目录如下&#xff1a; modules/&#xff1a;核心功…

【华为】用策略路由解决双出口运营商问题

需求描述 不同网段访问互联网资源时&#xff0c;走不同的出口&#xff0c;即PC1走电信出口&#xff0c;PC2走移动出口。 客户在内网接口下应用策略路由后往往出现无法访问内网管理地址的现象&#xff0c;该举例给出解决办法。 拓扑图 基础配置 #sysname R1 # # interface G…

行人3d目标检测-车辆3d目标检测-3d目标检测(代码+教程)

在计算机视觉领域&#xff0c;准确地识别并定位物体对于多种应用来说至关重要&#xff0c;比如自动驾驶、机器人导航以及增强现实等。其中&#xff0c;三维边界框&#xff08;3D Bounding Box&#xff09;估计是一项关键技术&#xff0c;它允许系统不仅能够检测到图像中的物体位…

QT事件过滤器(1)

在 Qt 中&#xff0c;事件过滤是一种用于 拦截和处理对象事件 的机制。它允许一个对象监听和处理另一个对象的事件&#xff0c;比如键盘输入、鼠标点击等&#xff0c;而不必修改对象本身的代码。通过事件过滤&#xff0c;可以拦截并阻止事件的进一步传播。 事件机制概述 Qt 中…

为什么结构化 Prompt 如此有效?

你好&#xff0c;我是三桥君 在今年&#xff0c;我研究了结构化编写Prompt的方法&#xff0c;并观察到这种结构化、模板化的Prompt能够有效地突破ChatGPT 3.5的限制&#xff0c;实现所谓的“越狱”。然而&#xff0c;为什么ChatGPT会对这种结构化Prompt如此有效呢&#xff1f;…