当前位置: 首页 > news >正文

7c结构体

文章目录

    • 一、结构体的设计
    • 二、结构体变量的初始化
        • 2.1结构体在内存表示;
        • **2.2**结构体类型声明和 结构体变量的定义和初始化
          • 只声明结构体类型
          • 声明类型的同时定义变量p1
          • 用已有结构体类型定义结构体变量p2
          • *定义变量的同时赋初值。*
          • 匿名声明结构体类型
        • 2.3 结构体嵌套及其初始化
          • 嵌套结构体定义的变量
          • 嵌套匿名结构体类型的声明
          • 为内部嵌套的 匿名结构体 加上名字
        • 2.4使用typedef 简化 定义
            • c语言中
            • CPP中
    • 三.结构体的自引用
      • 只能使用指针类型
      • 使用 typedef 的注意事项
    • 四、结构体的互引用
    • 五 结构体成员访问
      • 普通变量
      • 结构体指针
      • 结构体数组
    • 六 结构体作为函数参数
    • 七 结构体内存对齐
      • 对齐规则
      • **为什么存在内存对齐?**
        • 设计结构体时的技巧
      • 计算结构体大小
        • 结构体大小计算 - 三步曲
      • 修改默认对齐数
    • 八 变长结构体(柔性数组)
    • 什么是柔性数组?
    • 柔性数组的特点
    • 柔性数组的使用
    • 模拟实现柔性数组的功能
    • 柔性数组的优势

一、结构体的设计

结构体的定义形式为

struct 结构体名
{成员列表(可以是基本的数据类型,指针,数组或其他结构类型)
};

举个例子来说吧;

客观事务(实体)是复杂的,要描述它必须从多方面进行;也就是用不同的数据类型来描述不同的方面;用学生实体来说:

学生拥有什么? 学号、姓名、性别、年龄;

struct Student
{char s_id[8];char s_name[8];char s_sex[4];int s_age;
};

注意以下几点;

(1)关键字struct是数据类型说明符,指出下面说的是结构体类型;

(2)标识符Student是结构体的类型名;

(3)最后的分号一定要写;

二、结构体变量的初始化

结构体是一种数据类型,也就是说可以用它来定义变量。

结构体就像一个“模板”,定义出来的变量都具有相同的性质。可以将结构体比作“图纸”,结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的;

结构体是一种数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据、需要存储空间;

2.1结构体在内存表示;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

C语言中 struct 不可少

2.2结构体类型声明和 结构体变量的定义和初始化
只声明结构体类型
struct Stu        //类型声明
{char name[15];  //名字int age;        //年龄
};
声明类型的同时定义变量p1
struct Point
{int x;int y; 
}p1; //声明类型的同时定义变量p1
用已有结构体类型定义结构体变量p2
struct Point p2; //定义结构体变量p2
struct Stu ss; //定义结构体变量ss
定义变量的同时赋初值。
struct Point p3 = {x, y};  //x, y应该有值
struct Stu s = {"zhangsan", 20};
匿名声明结构体类型
//匿名结构体类型
struct 
{int a;char b;float c;
}x; //缺点就是除了x,因为没有tag,不能再定义其他该结构体的变量struct
{int a;char b;float c;
}a[20], *p;

上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那么问题来了

//在上面代码的基础上,下面的代码合法吗?
p = &x;
12

ERROR:
编译器会把上面的两个声明当成完全不同的两个类型
所以是非法的。


2.3 结构体嵌套及其初始化
嵌套结构体定义的变量

结构体不仅可以单独使用,也可以在结构体中嵌套另一个结构体。如下面的例子:

struct Date{int year;int month;int day;};struct book{char title[30];char author[30];float value;struct Date date;};

首先声明一个book结构体,在这个结构体里面描述了书本的标题、作者、价格、出版日期。由于出版日期里面有包含了年月日信息,为了方便管理,就将出版日期也单独定义为一个结构体。这样就相当于在book结构体里面又嵌套了一个日期的结构体。

初始化方法如下:

struct book books[3]= {{"语文","张三",19.8,{2021,10,1}},{"数学","李四",21.3,{2021,10,2}},{"英语","王五",16.8,{2021,10,3}}};

在这里定义了一个结构体数组,每一个数组元素表示一本书的信息。在初始化的时候,书本的标题、作者、价格按照顺序依次写入,每一项之间用逗号隔开。接下来初始化日期,由于日期也是一个结构体,所以需要用大括号{ }将它括起来,然后在这个大括号里面依次填入日期信息,每一项之间用逗号隔开。初始化的时候也是将两个结构体嵌套起来。
  如果需要访问书本的日期时,就需要用两次点,来定位到具体位置上。比如要访问数学书年的信息,可以使用下面的方法。

books[1].date.year

books[1]首先定位到数学这本书,然后使用 .data 定位到数字书中的日期结构体,接着再使用 .year 定位到日期结构体中的年变量上。这样使用两次点就可定位到第二个结构体里面。如果结构体嵌套了三层,那么访问第三层结构体的时候,就需要用三个点号去定位。

下面使用printf()函数打印这三本书的信息。

 	printf("%s %s %f %d-%d-%d\r\n",books[0].title,books[0].author,books[0].value,books[0].date.year,books[0].date.month,books[0].date.day);printf("%s %s %f %d-%d-%d\r\n",books[1].title,books[1].author,books[1].value,books[1].date.year,books[1].date.month,books[1].date.day);printf("%s %s %f %d-%d-%d\r\n",books[2].title,books[2].author,books[2].value,books[2].date.year,books[2].date.month,books[2].date.day);

输出结果如下:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

嵌套匿名结构体类型的声明

由于结构体在声明的时候,也可以不指定结构名,相当于可以声明一个匿名的结构体,那么嵌套结构体的时候,也是可以声明一个嵌套的匿名结构体的。

struct book{char title[30];char author[30];float value;struct{int year;int month;int day;};};

在book结构体中嵌套的日期结构体没有具体的名字,是一个匿名的结构体,那么这个匿名结构体里面的对象要如何访问呢?C语言规定,对于匿名结构体里面的对象可以忽略它所在的结构体,直接通过名字访问。比如现在要访问语文书中日期月这个对象的话,可以直接使用下面的代码来访问。

books[1].month

值需要通过一个点加上具体对象名,就可以直接访问到嵌套的结构体里面。这样使用匿名结构体之后,可以使结构体中的对象访问更加的简单。对于嵌套的结构体,初始化方法是不变的。

struct book books[3]={{"语文","张三",19.8,{2021,10,1}},{"数学","李四",21.3,{2021,10,2}},{"英语","王五",16.8,{2021,10,3}}};

初始化方法和上面一样,但是访问具体对象的时候,就简单多了,下面打印这三本书的信息。

	printf("%s %s %f %d-%d-%d\r\n",books[0].title,books[0].author,books[0].value,books[0].year,books[0].month,books[0].day);printf("%s %s %f %d-%d-%d\r\n",books[1].title,books[1].author,books[1].value,books[1].year,books[1].month,books[1].day);printf("%s %s %f %d-%d-%d\r\n",books[2].title,books[2].author,books[2].value,books[2].year,books[2].month,books[2].day);

访问每本书里面的日期信息时,只需要一个点就可以直接访问,这样代码写起来也会简洁许多。输出结果如下:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  输出结果和为使用匿名结构体的时候也是一样的。

为内部嵌套的 匿名结构体 加上名字

当我们为内部的匿名结构体 加上名字时,如果要使程序正常运行,需要在嵌套内部创建结构体变量 struct date _date; 并且访问方式 变成 books[1]._date.month

#include<stdio.h>struct book
{char title[30];char author[30];float value;struct date			// 为嵌套结构体加上名字{int year;int month;int day;};struct date _date;  //需要加上这个
};int main()
{struct book books[3]= {{"语文","张三",19.8,{2021,10,1}},{"数学","李四",21.3,{2021,10,2}},{"英语","王五",16.8,{2021,10,3}}};
//	printf("%d",books[1].month);
printf("%d",books[1]._date.month);
}
2.4使用typedef 简化 定义

typedef是类型定义的意思。typedef struct 是为了使用这个结构体方便。
具体区别在于:
若struct node {}这样来定义结构体的话。在申请node 的变量时,需要这样写,struct node n;

若用typedef,可以这样写,typedef struct node{}NODE; 。在申请变量时就可以这样写,NODE n;
区别就在于使用时,是否可以省去struct这个关键字。

c语言中

1 首先:
在C中定义一个结构体类型要用typedef:

typedef struct Student
{
int a;
}Stu;

于是在声明变量的时候就可:Stu stu1;
如果没有typedef就必须用struct Student stu1;来声明
这里的Stu实际上就是struct Student的别名。
另外这里也可以不写Student(于是也不能struct Student stu1;了)

typedef struct
{
int a;
}Stu;

但在c++里很简单,直接

struct Student
{
int a;
};

于是就定义了结构体类型Student,声明变量时直接Student stu2;

CPP中

在c++中如果用typedef的话,又会造成区别:

struct Student
{
int a;
}stu1;//stu1是一个变量
typedef struct Student2
{
int a;
}stu2;//stu2是一个结构体类型的别名

使用时可以直接访问stu1.a
但是stu2则必须先 stu2 s2;
然后 s2.a=10;

三.结构体的自引用

只能使用指针类型

在结构中包含一个类型为该结构本身的成员是否可以呢?得写成 指针的形式 才可以

//代码1
struct Node
{int data;struct Node next;//这里是变量的格式!!不可以,因为不知道所占据的内存大小,无法分配内存!
}
//可行否?否

正确的自引用方式:

//代码2
struct Node
{
int data;
struct Node* next;//可以,写成指针的形式,指针的大小是确定的,32位=4字节,64位=8字节
};

使用 typedef 的注意事项

定义结构体中的结构体变量时,要注意该结构体已经先声明好,之后才可以定义。

注意:

//代码3
typedef struct
{int data;Node* next;
}Node;
//这样不可以,得让编译器先知道Node是什么!//解决方案
typedef struct _Node
{int data;struct _Node* next;
}Node;

使用typedef时,如果自引用的话,需要带上标签 "struct _student_t “,如果定义成员直接使用"student_t” 则会报错,因为在结构体内部定义结构体变量时,它会去找这个结构体,但该结构体还未声明完成,所以无法被引用和定义。

#include "stdio.h"typedef struct _student_t
{struct _student_t *a;short b;int value;
}student_t;int main(void)
{student_t c;printf("len = %d",sizeof(c));
}

四、结构体的互引用

这里其实我们和自引用一样,注意两点即可。

1:结构体中定义结构体成员一定要注意,不能有像自引用那样的“无限嵌套”情况发生。

2:定义结构体中的结构体变量时,要注意该结构体已经先声明好,之后才可以定义。

对于2的声明,我们可以使用“不完全声明”来实现。

这里因为在struct _student1_t结构体中,定义a结构体变量,而该结构体a在此前还未声明好,因此定义是非法的,我们加上struct _student2_t这个声明,即可定义。


#include "stdio.h"struct _student2_t;  //不完全声明
struct _student1_t
{struct _student2_t *a;short b;int value;
};
struct _student2_t
{struct _student1_t *a;short b;int value;
};int main(void)
{struct _student2_t c;struct _student1_t d;printf("len = %d,%d",sizeof(c),sizeof(d));
}

五 结构体成员访问

普通变量

结构体变量使用 . 访问;

结构体变量.对象

#include<stdio.h>
#include<string.h>
struct Date
{int year;int month;int day;
};
struct Student
{char s_name[20];struct Date birthday;float score;
};
int main()
{struct Student stu = { "liuwen",2000,10,1,99.9 };printf("name=%s\nbirtyday=%d.%d.%d\nscore=%f\n", stu.s_name, stu.birthday.year, stu.birthday.month, stu.birthday.day, stu.score);stu.score = 77;printf("name=%s\nbirtyday=%d.%d.%d\nscore=%f\n", stu.s_name, stu.birthday.year, stu.birthday.month, stu.birthday.day, stu.score);return 0;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:对结构体变量整体赋值有三种情况

(1)定义结构体变量(用{}初始化)

(2)用已定义的结构体变量初始化

(3)结构体类型相同的变量可以作为整体相互赋值;

在C语言中不存在结构体类型的强制转换。

结构体指针

内置类型可以定义指针变量,结构体类型也可以定义结构体类型指针;

结构体类型指针访问成员的获取和赋值形式:

(1)(*p). 成员名(.的优先级高于*,(*p)两边括号不能少)

(2) p->成员名(->指向符)

示例:

#include<stdio.h>
#include<string.h>
//#define _CRT_SECURE_NO_WARNINGS
struct Inventory//商品
{char description[20];//货物名int quantity;//库存数据
};
int main()
{struct Inventory sta = { "iphone",20 };struct Inventory* stp = &sta;char name[20] = { 0 };int num = 0;(*stp).quantity = 30;stp->quantity = 30;strcpy_s(name,sizeof(stp->description),stp->description);printf("%s %d\n", stp->description, stp->quantity);printf("%s %d\n", (*stp).description, (*stp).quantity);return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5bCP5ZyG6IS4,size_20,color_FFFFFF,t_70,g_se,x_16

结构体数组

**结构体数组,是指数组中的每一个元素都是一个结构体类型。**在实际应用中,C语言结构体数组常被用来表示有相同的数据结构的群体,比如一个班的学生,一个公司的员工等;

例如:

#include<stdio.h>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
struct Student
{char s_name[20];//姓名int age;//年龄float score;//成绩
};
int main()
{struct Student cla[] ={{"liuwen",18,149.9},{"qnge",18,145},{"anan",19,188},};return 0;
}

六 结构体作为函数参数

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;
}

上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

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

七 结构体内存对齐

//练习1
struct S1
{char c1;int i; char c2;
};
printf("%d\n",sizeof(struct S1));  //12//结构体S1和S2成员变量相同,次序不同,但是两个结构体占据的大小不同,为什么会这样呢?!!!//练习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)); //16//练习4-结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));  // 32

对齐规则

首先得掌握结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到该成员变量的对齐数的整数倍的地址处。
    对齐数 = 编译器默认的对齐数与该成员大小的较小值,即对齐数=min(8, sizeof(该成员))。VS中默认的值为8,以上例子的结果是在VS中得出的。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍,即sizeof(结构体) = n * max(对齐数1,对齐数2,…)。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。
注:VS中的默认对齐数为8,不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。

为什么存在内存对齐?

大部分的参考资料都是这样说的:
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

设计结构体时的技巧

其实在我们设计结构体的时候,如果结构体成员的顺序设计得合理的话,是可以避免不必要的内存消耗的。
两个结构体的成员变量相同,但是成员变量的顺序不同,可能就会出现结构体的大小不同的情况:

struct S1
{char a;char b;int c;
};//结构体1
struct S2
{char a;int c;char b;
};//结构体2
123456789101112

我们可以看到,结构体1和结构体2的成员变量一模一样,可是当我们按照内存对齐规则来计算两个结构体的大小的时候,会发现两个结构体的大小不一样,在VS编译器下第一个结构体大小为8,第二个结构体大小为12。

可以见得,结构体成员变量的顺序不同,可能会造成内存不必要的损失。将占用空间小的成员尽量集中在一起,可以有效地避免内存不必要的浪费。

计算结构体大小

//例如
struct S1
{char c1;//对齐到地址0处,占据1个字节int i;//i的对齐数=min(8, sizeof(int))=min(8, 4)=4,对齐到地址4处,占据4个字节char c2;//c2的对齐数=min(8, sizeof(char))=min(8,1),对齐到地址8(因为i占据前面的地址4到7】)处
};  // 12   sizeof(S1) = 整数倍的最大对齐数 = n * max(1(c1的对齐数),4(i的对齐数),1(c2的对齐数))= n*4 >= 9(c1(地址0起,占据1个字节,第1,2,3地址为空字节) + i(地址4起,占据4,5,6,7个字节,) + c2(地址8起占据1个字节,到地址9)),即S1占据字节的示意图为:[c1,空,空,空,i1,i2,i3,i4,c2]struct S2
{char c1;//同上,地址0处,占据1个字节char c2;//地址1处,占据1个字节int i;//地址4处,占据4个字节, 即S2占据字节示意图为: [c1,c2,空,空,i1,i2,i3,i4]
};  // 8  sizeof(S2)= n*max(1,1,4)=n*4>=8,n=2即可

S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。

结构体大小计算 - 三步曲

知道了结构体内存对齐规则,我们就可以计算结构体的大小了。计算结构体的大小可分为三个步骤。我们拿下面这个结构体举例:

struct S
{double d;char c;int i;
};
123456

第一步:找出每个成员变量的大小将其与编译器的默认对齐数相比较,取其较小值为该成员变量的对齐数。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
注:我使用的是VS编译器,故默认对齐数为8。

第二步:根据每个成员对应的对齐数画出它们在内存中的相对位置。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第三步:通过最大对齐数决定最终该结构体的大小。

通过图我们可以知道,绿色部分(double d成员占用)+红色部分(char c成员占用)+紫色部分(int i成员占用)+红色与紫色之间的白色部分(浪费掉了)总共占用了16个字节的内存空间。
我们需要将它们总共占用的内存空间(16)与结构体成员的最大对齐数(8)相比较,结构体的总大小为最大对齐数的整数倍,此时16正好是8的整数倍,所以该结构体在VS编译器下的大小就16个字节。即创建一个该类型的结构体变量,内存需为其开辟16个字节的内存空间。

注意:大多数情况下,成员变量已经占用的总字节个数并不一定正好为其成员变量中的最大对齐数的整数倍,这时我们需要将其扩大为最大对齐数的整数倍。

修改默认对齐数

要修改编译器的默认对齐数,我们需要借助于以下预处理命令:

#pragma pack()
1

如果在该预处理命令的括号内填上数字,那么默认对齐数将会被改为对应数字;如果只使用该预处理命令,不在括号内填写数字,那么会恢复为编译器默认的对齐数。

#include <stdio.h>#pragma pack(4)//设置默认对齐数为4
struct S1
{char a;//1/4->1int b;//4/4->4char c;//1/4->1
};//12
#pragma pack()//取消设置的默认对齐数,还原为默认#pragma pack(1)//设置默认对齐数为1
struct S2
{char a;//1/1->1int b;//4/1->1char c;//1/1->1
};//6
#pragma pack()//取消设置的默认对齐数,还原为默认int main()
{printf("%d\n", sizeof(struct S1));//打印结果为12printf("%d\n", sizeof(struct S2));//打印结果为6return 0;
}

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

之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8struct S1
{char c1;int i;char c2;
};//#pragma pack() // 取消设置的默认对齐数,还原为默认#pragma pack(1)  //设置默认对齐数为1struct S2
{char c1;int i;char c2;
}int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S2)); //6return 0;
}

结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。


八 变长结构体(柔性数组)

什么是柔性数组?

柔性数组这个概念相信大多数人博友都没有听说过,但是它确实存在。
在C99中,结构(结构体)的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
比如:

struct S
{int n;int arr[];//柔性数组成员
};
12345

或者是:

struct S
{int n;int arr[0];//柔性数组成员
};
12345

柔性数组的特点

一、结构中柔性数组成员前面必须至少有一个其他成员
比如,当你创建含有柔性数组成员的结构体时,结构体成员不能单单只有一个柔性数组成员:

struct Er
{int arr[];
};//error
1234

除了柔性数组成员之外,结构体成员中应该至少再包含一个其他非柔性数组成员。

二、sizeof返回的这种结构大小不包括柔性数组的内存
所以,当你用sizeof来计算一个含有柔性数组成员的结构体大小时,计算出的结果不包括柔性数组成员在内。
比如:

#include <stdio.h>
struct S
{int n;int arr[];//柔性数组成员
};
int main()
{printf("%d\n", sizeof(struct S));//结果为4return 0;
}
123456789101112

三、包含柔性数组成员的结构用malloc函数进行内存的的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
比如,对于该结构体:

struct S
{int n;int arr[];//柔性数组成员
};
12345

你想用他的柔性数组成员存放5个整型元素,那么你应该这样开辟空间:

#include <stdio.h>
#include <stdlib.h>
struct S
{int n;int arr[];//柔性数组成员
};
int main()
{//开辟struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));return 0;
}
12345678910111213

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

柔性数组的使用

我们可以利用柔性数组实现以下功能:

  1. 要求:用结构体将数字100和0~4五个数字进行封装。
  2. 要求改为:用结构体将数字100和0~9十个数字进行封装。
#include <stdio.h>
#include <stdlib.h>
struct S
{int n;int arr[];//柔性数组成员
};
int main()
{//开辟动态内存空间struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));ps->n = 100;//将结构体中第一个元素赋值为100int i = 0;for (i = 0; i < 5; i++){ps->arr[i] = i;//将柔性数组中下标为i的元素赋值为i}//调整所开辟的动态内存空间的大小struct S* ptr = realloc(ps, sizeof(struct S) + 10 * sizeof(int));if (ptr != NULL)//开辟成功{ps = ptr;}for (i = 5; i < 10; i++){ps->arr[i] = i;//将柔性数组中下标为i的元素赋值为i}//释放开辟的动态内存空间free(ps);ps = NULL;return 0;
}
12345678910111213141516171819202122232425262728293031323334

注意:柔性数组的使用与动态开辟内存的知识密不可分。

模拟实现柔性数组的功能

其实,我们若不借用柔性数组也能实现以上功能:

#include <stdio.h>
#include <stdlib.h>
struct S
{int n;int* arr;
};
int main()
{//开辟动态内存空间struct S* ps = (struct S*)malloc(sizeof(struct S));ps->arr = (int*)malloc(5 * sizeof(int));ps->n = 100;//将结构体中第一个元素赋值为100int i = 0;for (i = 0; i < 5; i++){ps->arr[i] = i;//将柔性数组中下标为i的元素赋值为i}//调整所开辟的动态内存空间的大小int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));if (ptr != NULL)//开辟成功{ps->arr = ptr;}for (i = 5; i < 10; i++){ps->arr[i] = i;//将柔性数组中下标为i的元素赋值为i}//释放开辟的动态内存空间free(ps->arr);ps->arr = NULL;free(ps);ps = NULL;return 0;
}
12345678910111213141516171819202122232425262728293031323334353637

柔性数组其实也就是结构体中的一个数组,准确来说,是一个空间大小可以自由变换的数组,那么我们在结构体中定义一个指针,使指针指向的空间可以自由变换(即指针指向的是动态开辟的内存),也就达到了这个效果。

注意: 这里释放动态内存空间时,需要先释放ps->arr指向的动态内存空间,再释放ps指向的动态内存空间。 如果我们先释放的是ps指向的动态内存空间,那么ps->arr所指向的空间就再也找不到了。

柔性数组的优势

一、方便内存释放
我们可以看到,用柔性数组解决这个问题的时候,我们只需要释放一次动态内存。而模拟实现柔性数组的时候,我们需要释放两次动态内存,最重要的是这两次释放内存的顺序还不能颠倒,如若颠倒了释放顺序就会导致有一块动态开辟的内存空间不能得到释放,最终导致内存泄漏。

二、有益于提高访问速度,也有益于减少内存碎片
其实第一种用柔性数组解决问题的时候,内存中开辟的空间是连续的:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
而第二种模拟实现柔性数组的方法,在开辟内存的时候差不多是这样的:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
看似在分配内存的时候连续还是不连续好像没什么影响,但是你是否知道有一个概念叫内存碎片。

越多的不连续内存分配会产生越多的内存碎片,内存碎片越多,我们对内存的利用率就越低下,所以我们应该尽量避免不连续的内存分配。

其次,CPU在向存储器中提取数据时,会遵循局部性原理。
局部性原理: CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
所以,将相关联的数据存储在一起(即连续存储),会提高CPU的访问速度。

将柔性数组中下标为i的元素赋值为i
}
//释放开辟的动态内存空间
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
12345678910111213141516171819202122232425262728293031323334353637


柔性数组其实也就是结构体中的一个数组,准确来说,是一个空间大小可以自由变换的数组,那么我们在结构体中定义一个指针,使指针指向的空间可以自由变换(即指针指向的是动态开辟的内存),也就达到了这个效果。注意: **这里释放动态内存空间时,需要先释放ps->arr指向的动态内存空间,再释放ps指向的动态内存空间。** 如果我们先释放的是ps指向的动态内存空间,那么ps->arr所指向的空间就再也找不到了。## 柔性数组的优势**一、方便内存释放**
我们可以看到,用柔性数组解决这个问题的时候,我们**只需要释放一次动态内存**。而模拟实现柔性数组的时候,我们需要释放两次动态内存,最重要的是这两次释放内存的顺序还不能颠倒,如若颠倒了释放顺序就会导致有一块动态开辟的内存空间不能得到释放,最终导致内存泄漏。**二、有益于提高访问速度,也有益于减少内存碎片**
其实第一种用柔性数组解决问题的时候,内存中开辟的空间是连续的:
[外链图片转存中...(img-ho7r675Z-1728046495445)]
而第二种模拟实现柔性数组的方法,在开辟内存的时候差不多是这样的:
[外链图片转存中...(img-DFxklRP4-1728046495445)]
看似在分配内存的时候连续还是不连续好像没什么影响,但是你是否知道有一个概念叫[内存碎片](https://www.baidu.com/link?url=9s9au9wO8nEET93wWMyr_bbymkaED2Ak1WBYReOY6ALNE9LN348ZS_GBY0lFcakrIGjruWoYpePf_HUAyJAVyHtTy5XC2mkaG0-MSCDAkkfgeOZW1FxuwqMTXtzmTe5K&wd=&eqid=e8df25e600007b7a0000000660473e00)。越多的不连续内存分配会产生越多的内存碎片,内存碎片越多,我们对内存的利用率就越低下,所以我们应该尽量避免不连续的内存分配。其次,CPU在向存储器中提取数据时,会遵循局部性原理。
**局部性原理**: CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
所以,将相关联的数据存储在一起(即连续存储),会提高CPU的访问速度。

相关文章:

7c结构体

文章目录 一、结构体的设计二、结构体变量的初始化2.1结构体在内存表示&#xff1b;**2.2**结构体类型声明和 结构体变量的定义和初始化只声明结构体类型声明类型的同时定义变量p1用已有结构体类型定义结构体变量p2*定义变量的同时赋初值。*匿名声明结构体类型 2.3 结构体嵌套及…...

浅聊前后端分离开发和前后端不分离开发模式

1.先聊聊Web开发的开发框架Spring MVC 首先要知道&#xff0c;Spring MVC是Web开发领域的一个知名框架&#xff0c;可以开发基于请求-响应模式的Web应用。而Web开发的本质是遵循HTTP&#xff08;Hyper Text Transfer Protocol: 超文本传输协议&#xff09;协议【发请求&#xf…...

RabbitMQ篇(死信交换机)

目录 一、简介 二、TTL过期时间 三、应用场景 一、简介 当一个队列中的消息满足下列情况之一时&#xff0c;可以成为死信&#xff08;dead letter&#xff09; 消费者使用basic.reject或者basic.nack声明消费失败&#xff0c;并且消息的requeue参数设置为false消息是一个过…...

HBase 的 MemStore 详解

一、MemStore 概述 MemStore 是 HBase 的内存存储区域&#xff0c;它是一个负责缓存数据写入操作的组件。每当有写操作&#xff08;如 Put 或 Delete&#xff09;发生时&#xff0c;数据会首先被写入到 MemStore 中&#xff0c;而不是直接写入磁盘。MemStore 类似于数据库中的缓…...

【嵌入式软件-数据结构与算法】01-数据结构

摘录于老师的教学课程~~(*๓╰╯๓)~~内含链表、队列、栈、循环队列等详细介绍~~ 基础知识系列 有空再继续更~~~ 目录 【链表】 一、单链表 1、存储结构&#xff1a;带头结点的单链表 2、单链表结点类型的定义 3、创建单链表 1&#xff09;头插法 2&#xff09;尾插法 …...

Windows应用开发-解析AVI视频文件

本Windows应用解析AVI视频文件&#xff0c;以表格的方式显示AVI文件结构。并可以将结果保存到bmp图片。下面是&#xff0c;使用该应用解析一部AVI电影获得的图片。 应用开发信息 定义一个INFO结构&#xff0c;包含两个字符串对象&#xff0c;一个ULONGLONG变量&#xff0c;和…...

探索TCP协议的奥秘:Python中的网络通信

引言 在网络通信的世界里&#xff0c;TCP协议&#xff08;传输控制协议&#xff09;就如同一座桥梁&#xff0c;连接着数据的发送方和接收方。作为一名拥有20年实战经验的编码专家&#xff0c;我深知TCP协议在构建稳定、可靠的网络应用中的重要性。今天&#xff0c;我将带领大…...

每日学习一个数据结构-树

文章目录 树的相关概念一、树的定义二、树的基本术语三、树的分类四、特殊类型的树五、树的遍历六、树的应用场景 树的遍历一、前序遍历二、中序遍历三、后序遍历使用java代码实现遍历总结 树的相关概念 树是一种重要的非线性数据结构&#xff0c;在计算机科学中有着广泛的应用…...

简单PCL库读文件(linux vscode编译)

#include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/common/common.h> #include <iostream>int main(int argc, char** argv) {if (argc ! 2) {std::cerr << "请指定 PCD 文件路径" << std::endl;return -…...

【自动驾驶】最近计划看的论文

将对应的论文链接贴出来&#xff0c;当作监督自己。 方向&#xff1a;端到端自动驾驶 方法论文代码UniADhttps://arxiv.org/pdf/2212.10156https://github.com/OpenDriveLab/UniADVADhttps://arxiv.org/pdf/2303.12077https://github.com/hustvl/VADUADhttps://arxiv.org/pdf…...

vue3学习:axios输入城市名称查询该城市天气

说来惭愧&#xff0c;接触前端也有很长一段时间了&#xff0c;最近才学习axios与后端的交互。今天学习了一个查询城市天气的案例&#xff0c;只需输入城市名称&#xff0c;点击“查询”按钮便可以进行查询。运行效果如下&#xff1a; 案例只实现了基本的查询功能&#xff0c;没…...

影刀RPA实战:Excel拆分与合并工作表

1.影刀操作excel的优势 Excel&#xff0c;大家都不陌生&#xff0c;它是微软公司推出的一款电子表格软件&#xff0c;它是 Microsoft Office 套件的一部分。Excel 以其强大的数据处理、分析和可视化功能而闻名&#xff0c;广泛应用于商业、教育、科研等领域。可以说&#xff0…...

STM32三种启动模式:【详细讲解】

STM32在上电后&#xff0c;从那里启动是由BOOT0和BOOT1引脚的电平决定的&#xff0c;如下表&#xff1a; BOOT模式选引脚启动模式BOOT0BOOT1X0主Flash启动01系统存储器启动11内置SRAM启动 BOOT 引脚的值在重置后 SYSCLK 的第四个上升沿时被锁定。在重置后,由用户决定是如何设…...

Ray_Tracing_The_Next_Week

1动态模糊 动态模糊在摄影中就是快门的速度慢&#xff0c;捕捉光的时间长&#xff0c;物体运动时进行捕捉成像&#xff0c;拍出来的结果是这个运动过程每一帧的平均值 我们的思路是&#xff1a; 每一条光线都拥有自己存在的一个时间点。随着时间变化随机生成光线,一般来说我…...

DBT hook 实战教程

本文将介绍dbt中在模型和seed级别使用post-hook的几个具体示例。dbt中的Post-hooks是一个强大而简单的特性&#xff0c;它在构建模型之后(如果是pre-hook&#xff0c;甚至在此之前)执行SQL语句。这些语句实际上(几乎)可以是任何东西&#xff0c;从将表复制到另一个数据库/模式&…...

SpringBoot整合JPA详解

SpringBoot版本是2.0以上(2.6.13) JDK是1.8 一、依赖 <dependencies><!-- jdbc --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency><!--…...

【微服务】springboot 实现动态修改接口返回值

目录 一、前言 二、动态修改接口返回结果实现方案总结 2.1 使用反射动态修改返回结果参数 2.1.1 认识反射 2.1.2 反射的作用 2.1.3 反射相关的类 2.1.4 反射实现接口参数动态修改实现思路 2.2 使用ControllerAdvice 注解动态修改返回结果参数​​​​​​​ 2.2.1 注解…...

【前端开发入门】html快速入门

目录 引言一、html基础模板内容二、html文档流三、html 标签1.块级元素2.行内元素3.功能性元素4.标签嵌套 四、html编码习惯五、总结 引言 本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容&#xff0c;并不代表这部分内容不…...

python配置环境变量

方法一&#xff1a;首先卸载重新安装&#xff0c;在安装时勾选增加环境变量 方法二&#xff1a;我的电脑-属性-高级系统配置 手动添加环境变量&#xff0c;路径为python的安装路径 检查&#xff1a;查看环境变量是否安装成功 安装第三方lib winr&#xff0c;输入cmd pip ins…...

从0到1:培训机构排课小程序开发笔记一

业务调研 随着人们生活水平的提高&#xff0c;健康意识和学习需求日益增强&#xff0c;私教、健身和培训机构的市场需求迅速增长。高效的排课系统不仅可以提升机构的管理效率&#xff0c;还能提高学员的满意度。解决传统的排课方式存在的时间冲突、信息不对称、人工操作繁琐等…...

方法重载(Overload)

前言 在前面的学习中&#xff0c;我们学到了重写(Override),这里我们主要进行重载(Overload)的介绍&#xff0c;同时对重写和重载的区别进行分析。 1. 重载(Overload) #方法重载 在同一个类中定义多个同名但参数不同的方法。我们称方法与方法之间构成方法重载 在Java中&…...

[论文笔记]SGPT: GPT Sentence Embeddings for Semantic Search

引言 解码器Transformer的规模不断壮大&#xff0c;轻松达到千亿级参数。同时由于该规模&#xff0c;基于提示或微调在各种NLP任务上达到SOTA结果。但目前为止解码器Transformer还无法应用在语义搜索或语句嵌入上。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比…...

基于微信小程序的旅游拼团系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

富格林:警悟可信经验安全投资

富格林指出&#xff0c;黄金具有不错的投资价值&#xff0c;一直以来备受投资者的喜爱&#xff0c;近年来大家也纷纷加入现货黄金市场为己增值财富。但是要为投资安全护航的前提&#xff0c;是需要投资者使用合适可信的方法以及掌握相对应的投资技巧。下面富格林将总结以下可信…...

【Linux】使Ubuntu自适应窗口大小并与主机共享文件

LInux虚拟机版本ubuntu-20.04.6&#xff0c;VM版本VMware Workstation 17 Pro VMware Tools™ 是一组服务和模块&#xff0c;是VMware公司在其虚拟化平台中提供的一套工具集&#xff0c;旨在提高虚拟机的性能和稳定性。它们支持 VMware 产品中的多种功能特性&#xff0c;有助于…...

C++ 语言特性18 - static_assert 介绍

一&#xff1a;概述 在 C 中&#xff0c;static_assert 是一种用于在编译时进行断言的机制&#xff0c;确保某些编译时条件成立。如果条件不成立&#xff0c;则编译器会生成错误&#xff0c;阻止代码的编译。static_assert 在 C11 中引入&#xff0c;目的是帮助程序员在编译时捕…...

centos 7.9系统redis6.2.6哨兵模式部署

由于系统需要处理大量的数据并发请求,所以借助于Redis的高性能,可以有效提升整个系统的处理效率。这里采用redis6.2版本源码编译部署哨兵模式,提高整个系统的可用性,避免单点故障。 1. Redis基本环境安装 centos7安装redis 6.2.6 采用源码编译方式安装。 服务器主机名:…...

编程基础:详解 C++ 中的 `std::sort` 函数

编程基础&#xff1a;详解 C 中的 std::sort 函数 在C编程中&#xff0c;排序是非常常见的操作&#xff0c;而std::sort是C标准库中用于排序的一个高效函数。它提供了灵活的排序功能&#xff0c;可以使用默认排序规则或自定义的比较函数。本文将深入探讨std::sort的用法、参数要…...

51单片机的宠物自动投喂系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温湿度传感器DS1302时钟模块蓝牙步进电机按键、蜂鸣器等模块构成。适用于猫猫/狗狗宠物自动喂食器等相似项目。 可实现基本功能: 1、LCD1602实时显示北京时间和温湿度 2、温湿度传感器DHT11采集环境温湿度 3、时…...

MongoDB快速实战与基本原理

目录 链接:https://note.youdao.com/ynoteshare/index.html?id=5e038498891617c552667b853742fdc1&type=note&_time=1727935558812 Mongo数据库的特点: mongo数据库和关系型数据库的区别: ​编辑 关系型数据库和文档型数据库的主要概念对比: 下载和启动(具体…...