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.rhkb.cn/news/207291.html

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

相关文章

Python--使用布林线设计均值回归策略

在本教程中,我们将探讨均值回归的概念以及如何使用 Python 中的布林线设计交易策略。均值回归是一种流行的交易策略,它基于这样的假设:随着时间的推移,资产价格往往会恢复到历史平均水平。布林线 (Bollinger Bands) 由约翰布林格 (John Bollinger) 开发,是一种技术分析工具…

分类预测 | Matlab实现NGO-KELM北方苍鹰算法优化核极限学习机分类预测

分类预测 | Matlab实现NGO-KELM北方苍鹰算法优化核极限学习机分类预测 目录 分类预测 | Matlab实现NGO-KELM北方苍鹰算法优化核极限学习机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现NGO-KELM北方苍鹰算法优化核极限学习机分类预测&#xff08;完…

深度学习(一):Pytorch之YOLOv8目标检测

1.YOLOv8 2.模型详解 2.1模型结构设计 和YOLOv5对比&#xff1a; 主要的模块&#xff1a; ConvSPPFBottleneckConcatUpsampleC2f Backbone ----->Neck------>head Backdone 1.第一个卷积层的 kernel 从 6x6 变成了 3x3 2. 所有的 C3 模块换成 C2f&#xff0c;可以发现…

大模型下交互式数据挖掘的探索与发现

在这个数据驱动的时代&#xff0c;数据挖掘已成为解锁信息宝库的关键。过去&#xff0c;我们依赖传统的拖拉拽方式来建模&#xff0c;这种方式在早期的数据探索中起到了作用&#xff0c;但随着数据量的激增和需求的多样化&#xff0c;它的局限性逐渐显露。 >>>> 首…

IPtables防火墙详解

一、IPtables介绍 iptables是unix/linux自带的一款开放源代码的完全自由的基于包过滤(对OSI模型的四层或者是四层以下进行过滤)的防火墙工具&#xff0c;它的功能十分强大&#xff0c;使用非常灵活&#xff0c;可以对流入和流出服务器的数据包进行很精细的控制。主要针对网络访…

微软发布了Orca 2,一对小型语言模型,它们的性能超越了体积更大的同类产品

尽管全球目睹了OpenAI的权力斗争和大规模辞职&#xff0c;但作为AI领域的长期支持者&#xff0c;微软并没有放慢自己的人工智能努力。今天&#xff0c;由萨提亚纳德拉领导的公司研究部门发布了Orca 2&#xff0c;这是一对小型语言模型&#xff0c;它们在零样本设置下对复杂推理…

[Docker]十一.Docker Swarm集群raft算法,Docker Swarm Web管理工具

一.Docker Swarm集群raft算法讲解 Raft &#xff1a;一致性算法&#xff0c;在保证大多数管理节点存活的情况下&#xff0c;集群才能使用&#xff0c; 所以就要求如果集群的话&#xff0c; manager 节点必须 >3 台 &#xff0c;如果是两个台&#xff0c;其中一台宕机&#…

分享几种 Java8 中通过 Stream 对列表进行去重的方法

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 1. Stream 的 distinct…

pgz easyexcel如何给excel文件添加自定义属性

免费API方式 直接上传URL,自定义修改Excel 视频演示【内含接口地址】 https://www.ixigua.com/7304510132812153385 前情提示 | 功能说明 多选仅支持微软office、office365系列Excel。因为WPS宏功能需要企业版且付费生成xlsx、xlsm等文件,office和WPS均可以打开,均可以单…

数据结构---堆

1.堆的概念及结构 堆的性质&#xff1a; 堆中某个节点的值总是不大于或不小于其父节点的值堆总是一棵完全二叉树 2.举例说明 堆一般是把数组数据看做是一棵完全二叉树 小堆要求&#xff1a;任意一个父亲<孩子大堆要求&#xff1a;任意一个父亲>孩子 比如&#xff1…

【超详细】vue项目:Tinymce富文本使用教程以及踩坑总结+功能扩展

【【超详细】vue项目&#xff1a;Tinymce富文本使用教程以及踩坑总结功能扩展 引言&#xff1a;一、 开始二、快速开始1、安装Tinymce 三、封装成Vue组件1、文件结构2、index.vue3、dynamicLoadScript.js4、plugin.js5、toolbar.js 四、使用Tinymce组件五、业务逻辑实现1、添加…

【UE5】五大基类及其使用

UObject UObject表示对象&#xff0c;准确来说&#xff0c;虚幻引擎中的对象基础类为UObject UObject提供了以下功能&#xff1a; 垃圾收集&#xff08;Garbage collection&#xff09;引用自动更新&#xff08;Reference updating&#xff09;反射&#xff08;Reflection&am…

亚马逊云与生成式 AI 的融合——生成式AI的应用领域

文章目录 前言亚马逊云科技增强客户体验聊天机器人和虚拟助手亚马逊云科技 鸿翼&#xff1a;提供精准检索和问答&#xff0c;显著提升全球化售后服务体验AI 赋能的联络中心智能导购&个性化推荐智慧数字人 提升员工生成力和创造力对话式搜索亚马逊云科技 西门子&#xff1…

Vue3 Router跳转传参

最近遇到这个问题router跳转传参&#xff0c;真是要了老命了。 根据网上各位大神给出的方法&#xff0c;试了 import { useRouter } from vue-routerconst router useRouter()//1. 无法跳转 router.push(name:,params:{})//2. 可以跳转, 但需要在定义router同时定义占位符&a…

Redis7--基础篇4(Redis事务)

Redis事务是什么 可以一次执行多个命令&#xff0c;本质是一组命令的集合&#xff0c;一个事务中的所有命令都会序列化&#xff0c;按顺序串行&#xff0c;而不会被其他命令插入。 其作用就是在一个队列中&#xff0c;一次性、顺序、排他的执行一系列命令。 Redis事务 VS 数据…

使用gparted进行ubuntu虚拟机的磁盘扩容(解决gparted无法拖动分区的问题)

在学习内核编译下载linux内核源码的时候&#xff0c;由于源码非常大&#xff0c;下载的时候提示磁盘空间不足&#xff0c;我才意识到刚开始创建虚拟机的时候分配了20GB的空间现在已经快用光了。在VM的设置里可以进行扩容&#xff0c;我扩展到了30GB重启却发现空间并没有加到我使…

SQLite 和 SQLiteDatabase 的使用

实验七&#xff1a;SQLite 和 SQLiteDatabase 的使用 7.1 实验目的 本次实验的目的是让大家熟悉 Android 中对数据库进行操作的相关的接口、类等。SQLiteDatabase 这个是在 android 中数据库操作使用最频繁的一个类。通过它可以实现数据库的创建或打开、创建表、插入数据、删…

Python自动化测试——元素定位

1.selenium简介 Selenium是一个用于Web应用程序测试的工具。Selenium是直接运行在浏览器中&#xff0c;模拟用户操作web界面。支持多平台&#xff1a;windows、linux、MAC &#xff0c;支持多浏览器&#xff1a;ie、firefox、chrome等浏览器。 2. 启动浏览器 # 导入webdrive…

Apache Airflow (十四) :Airflow分布式集群搭建及测试

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

OBS Studio 30.0 正式发布:支持 WebRTC

导读OBS Studio 30.0 已正式发布。此版本移除了对 Ubuntu 20.04、Qt 5 和 FFmpeg 4.4 之前版本的支持。 OBS Studio 30.0 已正式发布。此版本移除了对 Ubuntu 20.04、Qt 5 和 FFmpeg 4.4 之前版本的支持。 主要变化包括&#xff1a; 支持 WebRTC&#xff08;详情查看 OBS Stu…