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

文章详细解释了结构体内存分配中的关键概念,如成员连续分配、数据对齐、填充、成员对齐以及自引用结构在链表中的应用。通过实例展示了如何计算结构体大小、处理填充和成员地址,并介绍了结构体指针和结构数组的使用。
摘要由CSDN通过智能技术生成

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我们在这里讨论的内容构成了链表的起点。

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

  • 11
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K_n_i_g_h_t_1990

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值