C 中的结构 - 存储、指针、函数和自引用结构

0. 结构体的内存分配

当声明某种类型的结构变量时,结构成员被分配连续(相邻)的内存位置。

struct student{char name[20];int roll;char gender;int marks[5];} stu1;

此处,内存将分配给name[20]rollgendermarks[5]st1这意味着or的大小struct student将是其成员大小的总和。不是吗?让我们检查。

void main()
{printf("Sum of the size of members = %I64d bytes\n", sizeof(stu1.name) + sizeof(stu1.roll) + sizeof(stu1.gender) + sizeof(stu1.marks));printf("Using sizeof() operator = %I64d bytes\n",sizeof(stu1));
}/* Output */
Sum of the size of members = 45 bytes
Using sizeof() operator = 48 bytes

使用sizeof()运算符给出的3字节数多于成员大小的总和。为什么?这3个字节在内存中在哪里?我们先回答第二个问题。我们可以打印成员的地址来查找这些3字节的地址。

void main()
{printf("Address of member name = %d\n", &stu1.name);printf("Address of member roll = %d\n", &stu1.roll);printf("Address of member gender = %d\n", &stu1.gender);printf("Address of member marks = %d\n", &stu1.marks);
}/* Output */
Address of member name = 4225408
Address of member roll = 4225428
Address of member gender = 4225432
Address of member marks = 4225436

图片1

我们观察到该数组marks[5]不是从 分配的,而是4225433从 分配的4224536。但为什么?

数据对齐

在研究数据对齐之前,了解处理器如何从内存读取数据非常重要。

处理器在一个周期内读取一个字。对于32 位处理器,该字为4 字节;对于64 位处理器,该字为 8 字节。周期数越少,CPU 的性能越好。

实现这一目标的一种方法是对齐数据。对齐意味着任何大小的基本数据类型的变量t将始终(默认情况下)具有 的倍数的地址t。这本质上是数据对齐。这种情况每次都会发生。

某些数据类型的对齐地址
数据类型大小(以字节为单位)地址
char11的倍数
short22的倍数
int,float44的倍数
doublelong*(指针)88的倍数
long double1616的倍数
结构填充

可能需要在结构的成员之间插入一些额外的字节以对齐数据。这些额外的字节称为填充

在上面的示例中,3字节充当填充。如果没有它们,marks[0] 类型int(地址为 4 的倍数)的基地址将为4225433(不是 4 的倍数)。

您现在大概可以明白为什么不能直接比较结构了。

图片2

结构成员对齐

为了解释这一点,我们将举另一个例子(你会明白为什么)。

struct example{int i1;double d1;char c1;} example1;void main()
{printf("size = %I64d bytes\n",sizeof(example1));
}

输出会是什么?让我们应用我们所知道的。

i1是4个字节。其后将填充 4 个字节,因为 的地址应能被 8 整除。对于和d1,其后将分别填充 8 和 1 个字节。因此,输出应为 4 + 4 + 8 + 1 = 17 字节。d1c1

图3

 /* Output */
size = 24 bytes

什么?又错了!如何?通过数组struct example,我们可以更好的理解。我们还将打印 的成员的地址example2[0]

void main()
{struct example example2[2];printf("Address of example2[0].i1 = %d\n", &example2[0].i1);printf("Address of example2[0].d1 = %d\n", &example2[0].d1);printf("Address of example2[0].c1 = %d\n", &example2[0].c1);}/* Output */
Address of example2[0].i1 = 4225408
Address of example2[0].d1 = 4225416
Address of example2[0].c1 = 4225424

假设 的大小example2[0]是 17 字节。这意味着 的地址example2[1].i1将为4225425。这不可能的,因为 的地址int应该是 4 的倍数。从逻辑上讲, 的可能地址example2[1].i1似乎是4225428,4 的倍数。

这也是错误的。你发现了吗?now的地址example2[1].d1将是 (28 + 4 ( i1) + 3 ( padding)),4225436它不是 8 的倍数。

为了避免这种不对齐,编译器为每个结构引入了对齐。这是通过在最后一个成员之后添加额外的字节来完成的,称为结构成员对齐

图4

在本节开头讨论的示例中,它不是必需的(因此是另一个示例)。

一个简单的记住方法是通过这个规则 -结构地址和结构长度必须是 的倍数t_max。这里,t_max是结构中成员所占用的最大大小

对于struct example,8 字节是 的最大大小d1。因此,结构末尾有 7 个字节的填充,使其大小为 24 个字节。

这两点将帮助您找到任何结构的大小 -
  1. 任何数据类型都将其值存储在其大小倍数的地址中。

  2. 任何结构的大小都是成员所占用的最大字节数的倍数。

尽管我们可以降低 CPU 周期,但仍会浪费大量内存。将填充量减少到可能的最小值的一种方法是按成员变量大小的递减顺序声明成员变量。

如果我们遵循这一点struct example,结构的大小就会减少到 16 个字节。填充从 7 个字节减少到 3 个字节。

图5

struct example{double d1; int i1;char c1;} example3;void main()
{printf("size = %I64d bytes\n",sizeof(example3));
}/* Output */
size = 16 bytes
结构填料

包装与填充相反。它防止编译器填充并删除未分配的内存。对于 Windows,我们使用该#pragma pack指令,它指定结构成员的打包对齐方式。

#pragma pack(1)struct example{double d1; int i1;char c1;} example4;void main()
{printf("size = %I64d bytes\n",sizeof(example4));
}/* Output */
size = 13 bytes

图6

这可确保成员在 1 字节边界上对齐。换句话说,任何数据类型的地址都必须是 1 字节或其大小(以较小者为准)的倍数。

1. 指针

指针作为成员

结构也可以将指针作为成员。

struct student{char *name;int *roll;char gender;int marks[5];};void main()
{   int alexRoll = 44;struct student stu1 = { "Alex", &alexRoll, 'M', { 76, 78, 56, 98, 92 }};
}

使用.(点运算符),我们可以再次访问成员。由于roll现在有 的地址alexRoll,我们将不得不取消引用stu1.roll来获取值(而不是stu1.(*roll))。

printf("Name: %s\n", stu1.name);printf("Roll: %d\n", *(stu1.roll));printf("Gender: %c\n", stu1.gender);for( int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n", i, stu1.marks[i]);/* Output */
Name: Alex
Roll: 43
Gender: M
Marks in 0th subject: 76
Marks in 1th subject: 78
Marks in 2th subject: 56
Marks in 3th subject: 98
Marks in 4th subject: 92
结构体指针

与整数指针、数组指针和函数指针一样,我们也有结构体指针或结构体指针。

struct student {char name[20];int roll;char gender;int marks[5];
};struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}};struct student *ptrStu1 = &stu1;

在这里,我们声明了一个ptrStu1类型为 的指针struct studentstu1我们已将的地址分配给ptrStu1

ptrStu1存储 的基地址stu1,它是结构体第一个成员的基地址。增加 1 将使地址增加sizeof(stu1)字节。

printf("Address of structure = %d\n", ptrStu1);
printf("Adress of member `name` = %d\n", &stu1.name);
printf("Increment by 1 results in %d\n", ptrStu1 + 1);/* Output */
Address of structure = 6421968
Adress of member 'name' = 6421968
Increment by 1 results in 6422016

我们可以通过两种方式访问stu1​​using的成员。ptrStu1使用 *(间接运算符)或使用->中缀或箭头运算符)。

对于*,我们将继续使用.(点运算符),而对于 ,->我们将不需要点运算符。

printf("Name w.o using ptrStu1 : %s\n", stu1.name);
printf("Name using ptrStu1 and * : %s\n", (*ptrStu1).name);
printf("Name using ptrStu1 and -> : %s\n", ptrStu1->name);/* Output */
Name without using ptrStu1: Alex
Name using ptrStu1 and *: Alex
Name using ptrStu1 and ->: Alex

同样,我们也可以访问和修改其他成员。请注意,使用时括号是必需的,*因为点运算符 ( .) 的优先级高于*

结构数组

我们可以创建一个类型数组struct student并使用指针来访问元素及其成员。

struct student stu[10];/* Pointer to the first element (structure) of the array */
struct student *ptrStu_type1 = stu;/* Pointer to an array of 10 struct student */
struct student (*ptrStu_type2)[10] = &stu;

请注意,ptrStu_type1是 一个指向stu[0]while的指针,而ptrStu_type2是一个指向整个数组 10 的指针struct student。加 1 将ptrStu_type1指向stu[1]

我们可以使用ptrStu_type1循环来遍历元素及其成员。

for( int i = 0; i <  10; i++)
printf("%s, %d\n", ( ptrStu_type1 + i)->name, ( ptrStu_type1 + i)->roll);

2. 功能

作为成员发挥作用

函数不能是结构的成员。但是,使用函数指针,我们可以使用(点运算符)调用函数.。但是,不建议这样做。

 struct example{int i;void (*ptrMessage)(int i);};void message(int);void message(int i)
{printf("Hello, I'm a member of a structure. This structure also has an integer with value %d", i);
}void main()
{struct example eg1 = {6, message};eg1.ptrMessage(eg1.i);
}

我们在内部声明了两个成员,一个integeri和一个函数指针。函数指针指向一个接受eger 并返回 的函数。ptrMessagestruct exampleintvoid

message就是这样一个函数。我们eg16和初始化message。然后我们使用和 pass.来调用该函数。ptrMessageeg1.i

结构作为函数参数

与变量一样,我们可以将单个结构成员作为参数传递。

#include <stdio.h>struct student {char name[20];int roll;char gender;int marks[5];
};void display(char a[], int b, char c, int marks[])
{printf("Name: %s\n", a);printf("Roll: %d\n", b);printf("Gender: %c\n", c);for(int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n",i,marks[i]);
}
void main()
{struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}};display(stu1.name, stu1.roll, stu1.gender, stu1.marks);
}/* Output */
Name: Alex
Roll: 43
Gender: M
Marks in 0th subject: 76
Marks in 1th subject: 98
Marks in 2th subject: 68
Marks in 3th subject: 87
Marks in 4th subject: 93

请注意,该结构是在最顶部的struct student外部声明的。main()这是为了确保它在全球范围内可用并且display()可以使用。

如果该结构体在内部定义main(),其范围将被限制为main().

当结构成员数量很大时,传递结构成员的效率不高。然后结构变量可以传递给函数。

void display(struct student a)
{printf("Name: %s\n", a.name);printf("Roll: %d\n", a.roll);printf("Gender: %c\n", a.gender);for(int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n",i,a.marks[i]);
}
void main()
{struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}};display(stu1);
}

如果结构的大小很大,那么传递它的副本不会非常有效。我们可以将结构指针传递给函数。在这种情况下,结构的地址作为实际参数传递。

void display(struct student *p)
{printf("Name: %s\n", p->name);printf("Roll: %d\n", p->roll);printf("Gender: %c\n", p->gender);for(int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n",i,p->marks[i]);
}
void main()
{struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}};struct student *ptrStu1 = &stu1;display(ptrStu1);
}

将结构数组传递给函数类似于将任何类型的数组传递给函数。数组的名称,即结构体数组的基地址,被传递给函数。

void display(struct student *p)
{   for( int j = 0; j < 10; j++){printf("Name: %s\n", (p+j)->name);printf("Roll: %d\n", (p+j)->roll);printf("Gender: %c\n", (p+j)->gender);for(int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n",i,(p+j)->marks[i]);}
}void main()
{struct student stu1[10];display(stu1);
}
结构作为函数返回

我们可以返回一个结构变量,就像任何其他变量一样。

#include <stdio.h>struct student {char name[20];int roll;char gender;int marks[5];
};struct student increaseBy5(struct student p)
{for( int i =0; i < 5; i++)if(p.marks[i] + 5 <= 100){p.marks[i]+=5;}return p;
}void main()
{struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}};stu1 = increaseBy5(stu1);printf("Name: %s\n", stu1.name);printf("Roll: %d\n", stu1.roll);printf("Gender: %c\n", stu1.gender);for(int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n",i,stu1.marks[i]);
}/* Output */
Name: Alex
Roll: 43
Gender: M
Marks in 0th subject: 81
Marks in 1th subject: 98
Marks in 2th subject: 73
Marks in 3th subject: 92
Marks in 4th subject: 98

该函数increaseBy5()对加分后分数小于或等于 100 的科目加 5 分。注意,返回类型是 类型的结构体变量struct student

返回结构成员时,返回类型必须是该成员的类型。

结构体指针也可以由函数返回。

#include <stdio.h>
#include <stdlib.h>struct rectangle {int length;int breadth;
};struct rectangle* function(int length, int breadth)
{struct rectangle *p  = (struct rectangle *)malloc(sizeof(struct rectangle));p->length = length;p->breadth = breadth;return p;
}void main()
{struct rectangle *rectangle1 = function(5,4);printf("Length of rectangle = %d units\n", rectangle1->length);printf("Breadth of rectangle = %d units\n", rectangle1->breadth);printf("Area of rectangle = %d square units\n", rectangle1->length * rectangle1->breadth);
}/* Output */
Length of rectangle = 5 units
Breadth of rectangle = 4 units
Area of rectangle = 20 square units

struct rectangle请注意,我们已经使用动态分配了 size 的内存malloc()。由于它返回一个void指针,我们必须将其类型转换struct rectangle指针。

3. 自指结构

我们讨论过指针也可以是结构的成员。如果指针是结构体指针怎么办?结构体指针可以与结构体类型相同,也可以不同

自引用结构是那些具有与其成员相同类型的结构指针的结构。

struct student {char name[20];int roll;char gender;int marks[5];struct student *next;
};

这是一个自引用结构,其中nextstruct student类型结构指针。我们现在将创建两个结构变量stu1stu2用值初始化它们。然后我们将把 的地址存储stu2在 的next成员中stu1

void main()
{struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}, NULL};struct student stu2 = { "Max", 33, 'M', {87, 84, 82, 96, 78}, NULL};stu1.next = &stu2;
}

图7

stu2我们现在可以访问使用stu1和的成员next

void main()
{printf("Name: %s\n", stu1.next->name);printf("Roll: %d\n", stu1.next->roll);printf("Gender: %c\n", stu1.next->gender);for(int i = 0; i < 5; i++)printf("Marks in %dth subject: %d\n",i,stu1.next->marks[i]);
}/* Output */
Name: Max
Roll: 33
Gender: M
Marks in 0th subject: 87
Marks in 1th subject: 84
Marks in 2th subject: 82
Marks in 3th subject: 96
Marks in 4th subject: 78

假设我们想要在 后添加一个不同的结构体变量stu1,即在之间插入另一个结构体变量stu1stu2。这很容易做到。

void main()
{struct student stu3 = { "Gasly", 23, 'M', {83, 64, 88, 79, 91}, NULL};st1.next = &stu3;stu3.next = &stu2;
}

图8

现在stu1.next存储 的地址stu3。并stu3.next有地址stu2。我们现在可以使用访问所有三个结构stu1

  printf("Roll Of %s: %d\n", stu1.next->name, stu1.next->roll);printf("Gender Of %s: %c\n", stu1.next->next->name, stu1.next->next->gender);/* Output */
Roll Of Gasly: 23
Gender Of Max: M

stu1请注意我们如何使用结构指针在,stu3和之间形成链接stu2我们在这里讨论的内容构成了链表的起点。

自引用结构在创建数据结构(例如链表堆栈队列等)时非常有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/188105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

zabbix 监控

zabbit 监控 非常成熟的监控软件。 运维人员&#xff0c;尽快系统服务器的状态&#xff0c;网站的流量&#xff0c;服务进程的运行状态。 保证整个集群的工作正常。7*24 zabbix是什么&#xff1a; web界面提供的一种可视化监控服务软件。 分布式的方式系统监控以及网络监控…

ts学习05-typeScript中的类

类的定义 class Person {name: string; //属性 前面省略了public关键词constructor(n: string) {//构造函数 实例化类的时候触发的方法this.name n;}run(): void {console.log(this.name);} } var p new Person("张三");p.run();class Person {name: string;//…

JOSEF约瑟 剩余电流保护器 CLJ3-100A+LH30 导轨安装

CLJ3系列剩余电流动作继电器 系列型号&#xff1a; CLJ3-100A剩余电流动作继电器 CLJ3-250A剩余电流动作继电器 CLJ3-400A剩余电流动作继电器 CLJ3-630A剩余电流动作继电器 LH30剩余电流互感器 LH80剩余电流互感器 LH100剩余电流互感器 LH140剩余电流互感器 一、产品概…

特征融合12种经典魔改方案汇总,附配套模型和代码

特征融合&#xff08;Feature Fusion&#xff09;是深度学习中的一种重要技术&#xff0c;它可以帮助模型更好地理解数据的内在结构和规律&#xff0c;提高模型的性能和泛化能力。另外&#xff0c;特征融合还可以提高模型的分类准确率&#xff0c;减少过拟合风险&#xff0c;帮…

Java核心知识点整理大全25-笔记

目录 25. Hadoop 25.1.1. 概念 25.1.2. HDFS 25.1.2.1. Client 25.1.2.2. NameNode 25.1.2.3. Secondary NameNode 25.1.2.4. DataNode 25.1.3. MapReduce 25.1.3.1. Client 25.1.3.2. JobTracker 25.1.3.3. TaskTracker 25.1.3.4. Task 25.1.3.5. Reduce Task 执行…

CSS特效024:旋转的通透立方体

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

15分钟了解国外Lead广告联盟,为什么做国外广告联盟要用到指纹浏览器?

说到国外Lead广告联盟&#xff0c;可能很多人会问这是啥&#xff1f;其实呢&#xff0c;作为搞外贸的&#xff0c;如果你想增加你的收入&#xff0c;做国外广告联盟也是不错的选择&#xff0c;只要你有正确的方法和策略&#xff0c;就能够成功赚取丰厚的佣金。今天龙哥我就给大…

101.套接字-Socket网络编程3

目录 1.字节序 主机字节序&#xff08;小端&#xff09; 网络字节序&#xff08;大端&#xff09; 字节序转换函数 2.IP地址转换函数 3.套接字地址结构 通用 socket 地址结构 专用 socket 地址结构 Socket套接字的目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计…

前后端联调

JavaWenb开发 pom.xml 文件 配置 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://ma…

ROS2 Gazebo:三维物理仿真平台

介绍 Gazebo是ROS系统中最为常用的三维物理仿真平台&#xff0c;支持动力学引擎&#xff0c;可以实现高质量的图形渲染&#xff0c;不仅可以模拟机器人及周边环境&#xff0c;还可以加入摩擦力、弹性系数等物理属性。 比如我们要开发一个火星车&#xff0c;那就可以在Gazebo中…

OpenHarmony 关闭息屏方式总结

前言 OpenHarmony源码版本&#xff1a;4.0release 开发板&#xff1a;DAYU / rk3568 一、通过修改系统源码实现不息屏 修改目录&#xff1a;base/powermgr/power_manager/services/native/profile/power_mode_config.xml 通过文件中的提示可以知道DisplayOffTime表示息屏的…

STM32_CAN通讯波特率和采样点计算与设置

STM32_CAN通讯波特率设置 CAN参数计算与设置CAN时钟 STM32的 CAN外设位时序中只包含 3 段&#xff0c;分别是同步段 SYNC_SEG、位段BS1 及位段 BS2,采样点位于 BS1及BS2 段的交界处。 其中,SYNC_SEG段固定长度为 1T,可以在重新同步期间增长或缩短,该长度 SJW 也可在位时序寄存…

什么是死锁?如何产生死锁?死锁的必要条件?怎么解决死锁?

🔒1、什么是死锁 死锁是一个非常让程序猿烦恼的问题,一旦所写的程序有了死锁,那么程序就无法执行下去,会出现严重的 bug,并且死锁非常隐蔽,我们不会轻易发现它,在开发阶段,不经意期间我们就会写出死锁,很难检测出来。 那么什么是死锁呢?竟然让我们如此烦恼。 “死…

Android textView 显示: STRING_TOO_LARGE

在Android中&#xff0c;字符串资源的长度限制是32KB&#xff0c;getString()方法返回的字符串资源的大小超过这个限制&#xff0c;就会抛出STRING_TOO_LARGE 这个错误。 我本地的临界值是&#xff1a;32.3 KB (33,090 字节) 小于等于33090时&#xff0c;能正常显示&#xff…

基于OpenSSL和nginx搭建本地https服务器(详细实操版)

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

腾讯云最新优惠券领取入口,总面值2000元代金券,新用户、老用户、企业用户均可领取!

腾讯云推出年末感恩回馈活动&#xff0c;新老用户可免费领取总面值2000元的代金券礼包&#xff0c;适用于多种预付费产品&#xff0c;最高可抵扣36个月订单&#xff0c;领取后30天内有效。 领取入口&#xff1a; https://curl.qcloud.com/UpmL4ho3 领取说明&#xff1a; 腾…

git push 报错 error: src refspec master does not match any 解决

git报错 ➜ *** git:(main) git push -u origin "master" error: src refspec master does not match any error: failed to push some refs to https://gitee.com/***/***.git最新版的仓库初始化后 git 主分支变成了 main 方法 1.把 git 默认分支名改回 master …

VSCode搭建STM32开发环境

1、下载安装文件 链接&#xff1a;https://pan.baidu.com/s/1WnpDTgYBobiZaXh80pn5FQ 2、安装VSCodeUserSetup-x64-1.78.2.exe软件 3、 在VSCode中安装必要的插件 3、配置Keil Assistant插件 4、在环境变量中部署mingw64编译环境

Git的介绍和下载安装

Git的介绍和下载安装 概述 Git是一个分布式版本控制工具, 通常用来管理项目中的源代码文件(Java类、xml文件、html页面等)进行管理,在软件开发过程中被广泛使用 Git可以记录文件修改的历史记录并形成备份从而实现代码回溯, 版本切换, 多人协作, 远程备份的功能Git具有廉价的…

USB Type-C的基本原理

1 USB Type-C的基本原理 1.1 基本特性 Figure 1-1 USB Type-C接头外形 USB Type-C&#xff08;简称USB-C&#xff09;的基本特性&#xff1a; 1. 接口插座的尺寸与原来的Micro-USB规格一样小&#xff0c;约为8.3mm X 2.5mm 2. 可承受1万次反复插拔 3. 支持正反均可插入的“正反…