7c结构体

news2024/10/4 21:28:43

文章目录

    • 一、结构体的设计
    • 二、结构体变量的初始化
        • 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->1
	int b;//4/4->4
	char c;//1/4->1
};//12
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char a;//1/1->1
	int b;//4/1->1
	char c;//1/1->1
};//6
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
	printf("%d\n", sizeof(struct S1));//打印结果为12
	printf("%d\n", sizeof(struct S2));//打印结果为6
	return 0;
}
 

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

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

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8

struct S1
{
	char c1;
	int i;
	char c2;
};

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

#pragma pack(1)  //设置默认对齐数为1

struct S2
{
	char c1;
	int i;
	char c2;
}

int main()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S2)); //6
	
	return 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));
	//结果为4
	return 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;//将结构体中第一个元素赋值为100
	int 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;//将结构体中第一个元素赋值为100
	int 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的访问速度。





























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

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

相关文章

【笔记】选择题笔记408

无向图有16条边&#xff0c;其中度为4的顶点个数为3&#xff0c;度为3的顶点个数为4&#xff0c;其他顶点的度均小于3。图G所含的顶点个数至少是&#xff1a;11 总度数16232 度为2的顶点个数为x&#xff0c;度为1的顶点个数为y&#xff0c;度为0的顶点个数为z 由此可列出三元一…

工程机械车辆挖掘机自卸卡车轮式装载机检测数据集VOC+YOLO格式2644张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2644 标注数量(xml文件个数)&#xff1a;2644 标注数量(txt文件个数)&#xff1a;2644 标注…

从零开始Hadoop集群环境搭建

目录 1. Centos7.5硬件配置1.1 创建虚拟机1.2 虚拟机系统设置 2. IP地址和主机名称配置3. 软件配置3.1 安装 epel-release3.2 卸载虚拟机自带的JDK3.3 克隆虚拟机3.4 修改克隆虚拟机的IP3.5 JDK安装3.6 Hadoop安装 4. Hadoop目录结构 1. Centos7.5硬件配置 1.1 创建虚拟机 1.2…

CMU 10423 Generative AI:lec18(大模型的分布式训练)

这个文档主要讲解了分布式训练&#xff08;Distributed Training&#xff09;&#xff0c;特别是如何在多GPU上训练大规模的语言模型。以下是主要内容的概述&#xff1a; 1. 问题背景 训练大规模语言模型的主要挑战是内存消耗。 训练过程中&#xff0c;内存消耗主要来源于两个…

关于Mac管理员root权限的一些问题总结

&#x1f389; 前言 最近在学习Vue CLI的时候&#xff0c;发现在Vscode里面想要修改文件或者保存文件都会显示“permission denied”&#xff0c;即权限不足。于是想了一些解决方法&#xff0c;记录在此。 &#x1f389; 检查当前用户权限 打开终端&#xff0c;输入以下指令…

yolov8/9/10/11模型在工地安全帽检测中的应用【代码+数据集+python环境+GUI系统】

yolov8/9/10/11模型在工地安全帽检测中的应用【代码数据集python环境GUI系统】 yolov8/9/10/11模型在工地安全帽检测中的应用【代码数据集python环境GUI系统】 背景意义 在建筑工地、矿山、工厂等工业生产环境中&#xff0c;安全帽是保护工人头部免受伤害的重要劳保工具。然而…

销售业绩飞跃,通过CRM系统激发销售团队潜力

要让销售人员使用CRM系统&#xff0c;首先需要理解他们抗拒的原因。常见的抗拒理由包括时间不足、系统复杂、缺乏培训以及对成效的怀疑。为了克服这些障碍&#xff0c;企业可以采取一系列措施&#xff0c;如提供全面培训、通过案例展示价值、强调实际收益、逐步引入系统、领导层…

C语言第15课—数据在内存中的存储

文章目录 1. 整数在内存中的存储2. 大小端字节序和字节序判断3. 整数存储练习3.1 练习13.2 练习23.3 练习33.4 练习43.5 练习5 4. 浮点数在内存中的存储4.1 浮点数存的过程4.2 浮点数取的过程 1. 整数在内存中的存储 整数的2进制表示有三种方法&#xff1a;原码、反码、补码有符…

CSS3--美若天仙!?

免责声明&#xff1a;本文仅做分享~ 目录 CSS引入方式 选择器 盒子尺寸和背景色 文字控制属性 单行文字 垂直居中 字体族 font复合属性 文本对齐方式 文本修饰线 color 文字颜色 ----- 复合选择器 伪类选择器 超链接伪类 CSS特性 继承性 层叠性 优先级 Emmet …

37 预处理器与预处理指令、宏定义(定义常量、数据类型、替换文本、嵌套与取消)、带参宏(细节处理、与函数的区别)

目录 1 预处理器 2 预处理指令 2.1 位置 2.2 格式 2.3 换行 2.4 结束符 2.5 位置限制 3 宏定义 3.1 语法格式 3.2 使用宏定义常量 3.3 使用宏定义数据类型 3.4 宏定义的替换文本 3.5 宏定义嵌套 3.6 取消宏定义 4 带参数的宏定义 4.1 语法格式 4.2 案例演示 …

ElasticSearch学习笔记(三)Ubuntu 2204 server elasticsearch集群配置

如果你只是学习elasticsearch的增、删、改、查等相关操作&#xff0c;那么在windows上安装一个ES就可以了。但是你如果想在你的生产环境中使用Elasticsearch提供的强大的功能&#xff0c;那么还是建议你使用Linux操作系统。 本文以在Ubuntu 2204 server中安装elasticsearch 8.…

go的一些知识点

一.package 1.新建项目 新建一个itying文件夹&#xff0c;在里面使用命令 就能生成一个go项目。生成一个go.mod 2.调用别的包的代码 按照下面的目录层级生成代码 //clac.go package calcfunc Add(x, y int) int {return x y } func Sub(x, y int) int {return x - y }…

【Web】复现n00bzCTF2024 web题解(全)

目录 File Sharing Portal 方法一&#xff1a; 方法二&#xff1a; Focus-on-yourSELF Passwordless File Sharing Portal 附件的Dockerfile给了这么一段 # Add the cron job to the crontab RUN mkdir /etc/cron.custom RUN echo "*/5 * * * * root rm -rf /app…

<<迷雾>> 第6章 加法机的诞生(1)--全加器 示例电路

全加器的符号 info::操作说明 鼠标单击开关切换开合状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/cyjsjdmw-examples/assets/circuit/cyjsjdmw-ch03-01-full-adder.txt 原图 全加器的逻辑电路实现 info::操作说明 鼠标单击…

ACT调试pycharm报错

在运行ACT 代码时&#xff0c;根据官方readme使用命令行需要在wandb选择的时候输入3 但是&#xff0c;使用pycharm运行的时候会报错 wandb.errors.UsageError: api_key not configured (no-tty). call wandb.login(key[your_api_key]) 网上搜索都是说要注册什么key&#xf…

平衡BST:AVL树的实现与机制

目录 AVL树的简介 AVL节点的构建 AVL树体的构建 具体片段解析 旋转算法 AVL树的验证 AVL树的简介 AVL树是一种自平衡的二叉搜索树&#xff0c;它在19世纪60年代由Adelson-Velsky和Landis首次提出。在AVL树中&#xff0c;任何节点的两个子树的高度最大差别为1&#xff0c;这…

python-FILIP/字符串p形编码/数字三角形

一&#xff1a;FILIP 题目描述 给你两个十进制正整数 a,b​&#xff0c;输出将这两个数翻转后的较大数。 「翻转」在本题中的定义详见「说明 / 提示」部分。输入 第一行&#xff0c;两个十进制正整数 a,b。输出 第一行&#xff0c;a 和 b 翻转后的较大数。样例输入1 734 893 样…

《凡人修仙传》TXT精校全本|知轩藏书校对版!

看了动漫版&#xff0c;准备重温下原著&#xff0c;有好几年没看了。 最近找到了知轩藏书的校对版&#xff0c;堪称精校&#xff0c;nice&#xff01; TXT&#xff0c;14.5MB&#xff1a; https://pan.quark.cn/s/c6446be393fa

二叉树进阶学习——从中序和后续遍历序列构建二叉树

1.题目解析 题目来源&#xff1a;106.从中序和后序遍历序列构造二叉树 测试用例 2.算法原理 后序遍历&#xff1a;按照左子树->右子树->根节点的顺序遍历二叉树&#xff0c;也就是说最末尾的节点是最上面的根节点 中序遍历&#xff1a;按照左子树->根节点->右子树…

gm/ID设计方法学习笔记(一)

前言&#xff1a;为什么需要gm/id &#xff08;一&#xff09;主流设计方法往往侧重于强反型区&#xff08;过驱>0.2V&#xff09;&#xff0c;低功耗设计则侧重于弱反型区&#xff08;<0&#xff09;&#xff0c;但现在缺乏对中反型区的简单和准确的手算模型。 1.对于…