首页 > 其他分享 >自定义数据类型:结构体(C语言进阶)

自定义数据类型:结构体(C语言进阶)

时间:2023-01-07 17:31:43浏览次数:50  
标签:struct 自定义 int 数据类型 C语言 char 对齐 结构 字节

  • 结构体类型的声明
  • 结构体的自引用
  • 结构体内存对齐
  • 结构体传参

自学b站“鹏哥C语言”笔记。

一、结构体类型的声明

详见文章【初识结构体】第一部分。补充说明:

匿名结构体类型:省略结构体标签(tag)

struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}* p;
int main()
{
p = &x;
return 0;
}

注意:上述代码会报错。因为编译器认为这两个匿名结构体是不同的,则​​p = &x;​​会出错。

二、结构体的自引用

详见文章【初识结构体】第二部分。补充说明:

错误示例:

struct Node
{
int data;
struct Node 0;
};

注意:上述结构体Node的存储空间不可知

正确示例:运用指针

struct Node
{
int data;
struct Node* next;
}

注意:自引用不能使用匿名结构体类型,否则会出错。

三、结构体内存对齐

1.结构体内存对齐的规则

(1)结构体第一个成员放在与结构体变量偏移量为0的地址处。

(2)结构体其他成员要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = min { 编译器默认的一个对齐数,该成员大小 }

VS中默认的对齐数是8

(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

(4)特例:嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体总大小是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

例1:一般情况

#include <stdio.h>
struct S1
{
char c1;
int a;
char c2;
}

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

int main()
{
struct S1 s1 = {0};
struct S2 s2 = {0};
printf("%d\n", sizeof(s1));
printf("%d\n", sizeof(s2));
}

输出结果:12 8

解析:

char是1字节,int是4字节,VS中默认的对齐数是8。

那么,c1对齐数=min{1,8}=1

a对齐数=min{4,8}=4

c2对齐数=min{1,8}=1

S1的内存:第一行的数字指的是靠左这条线上的指针

0

1

2

3

4

5

6

7

8

9











c1

a

c2


最大对齐数=max{1,4,1}=4

目前,所有成员所占大小为9字节,不是最大对齐数的倍数。

因此,应补齐到12字节。

0

1

2

3

4

5

6

7

8

9

10

11

12














c1

a

c2


同理,S2的内存:

0

1

2

3

4

5

6

7

8










c1

c2

a


最大对齐数=max{1,4,1}=4

目前,所有成员所占大小为8字节,是最大对齐数的倍数。

因此,S2所占内存就是8字节。

例2:结构体嵌套情况

#include <stdio.h>
struct S3
{
double d;
char c;
int i;
};

struct S4
{
char c1;
struct S3 s3;
double d;
};

int main()
{
printf("%d\n", sizeof(struct S4));
}

输出结果:32

解析:

先计算S3:

double是8字节,char是1字节,int是4字节,VS中默认对齐数是8。

那么,double对齐数=min{8,8}=8

char对齐数=min{1,8}=1

int对齐数=min{4,8}=4

最大对齐数=max{8,1,4}=8

S3内存:和例1同理,得16字节

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16


















d

c

i


再计算S4:

char是1字节,S3是16字节,double是8字节,VS中默认对齐数是8。

那么,char对齐数=min{1,8}=1

S3对齐数=S3自己的最大对齐数=8

double对齐数=min{8,8}=8

最大对齐数=max{1,8,8}=8

S4内存:32字节

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32


































c1

s3

d


2.内存对齐的意义

为什么存在内存对齐?

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,即某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。比如,可能存在平台只能在地址为4的倍数处读取int类型的值。
  2. 性能原因:数据结构,尤其是栈,应该尽可能在自然边界上对齐。因为访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存只需要一次访问。

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

3.设计结构体

一个好的结构体设计,要能做到既节省空间又节省时间

节省时间已经通过内存对齐实现了。

我们需要在满足内存对齐的前提下,尽量节省空间:让占用空间小的成员尽量集中在一起

4.修改默认对齐数

​#pragma​​可以更改默认对齐数。

#pragma pack(4)//设置默认对齐数为4
struct S
{
char c1;
double d;
}
#取消设置的默认对齐数

5.宏

​offsetof(struct tag, 成员变量名)​​​能够输出成员相对首地址的偏移量,引用前要写​​#include <stddef.h>​​。

例1:

#include <stdio.h>
#include <stddef.h>
struct S
{
char c;
int i;
double d;
}

int main()
{
printf"%d\n", offsetof(struct S, c);
printf"%d\n", offsetof(struct S, i);
printf"%d\n", offsetof(struct S, d);

return 0;
}

输出结果:0 4 8

四、结构体传参

结论:结构体传参的时候,优先选择传地址

详见文章【初识结构体】。补充例题:

例1:

struct S
{
int a;
char c;
double d;
}

void Init(struct S tmp)
{
tmp.a = 100;
tmp.c = 'w';
tmp.d = 3.14;
}

int main()
{
struct S s = {0};
Init(s);
return 0;
}

运行后s仍然都是0。原因是传参传的是s值,改的是tmp,并没有改变s。

改进:

struct S
{
int a;
char c;
double d;
}

void Init(struct S* ps)
{
ps->a = 100;
ps->c = 'w';
ps->d = 3.14;
}

int main()
{
struct S s = {0};
Init(&s);
return 0;
}

运行后s改变了。原因是传参传的是s的地址,改变tmp的同时,也改变了s。

改进:打印出改变后的s值

#include <stdio.h>
struct S
{
int a;
char c;
double d;
}

void Init(struct S* ps)
{
ps->a = 100;
ps->c = 'w';
ps->d = 3.14;
}

void Print1(struct S tmp)
{
printf("%d %c %lf\n", tmp.a, tmp.c, tmp.d);
}

void Print2(struct S* ps)
{
printf("%d %c %lf\n", ps->a, ps->c, ps->d);
}

int main()
{
struct S s = {0};
Init(&s);
Print1(s);
Print2(&s);
return 0;
}

注意:

Print1函数的传参传的是s的值,可以实现。原因是该函数的目标不需要改变s的值。

当然,用传递s的值来实现也是可以的,如Print2函数。

标签:struct,自定义,int,数据类型,C语言,char,对齐,结构,字节
From: https://blog.51cto.com/u_15883132/5995699

相关文章

  • 数据的存储(C语言进阶)
    数据类型介绍内置数据类型的归类整型在内存中的存储:①原码、反码、补码②大小端字节序③char的存储内容浮点型在内存中的存储自学b站“鹏哥C语言”笔记。一、数据类型介绍......
  • 指针详解(C语言进阶)
    字符指针指针数组自学b站“鹏哥C语言”笔记。本章笔记不全。回顾:在文章【初识指针】中,我们已经了解到的指针概念有指针是一种变量,用来存放地址,地址唯一标识一块内存空间。指......
  • 【C语言 数据结构】二叉树
    文章目录​​二叉树​​​​一、二叉树的概念​​​​二、二叉树的基本形态​​​​三、二叉树的性质​​​​四、特殊的二叉树​​​​五、二叉树的存储结构​​​​5.1......
  • C语言校园跳蚤市场信息交流平台
    C语言校园跳蚤市场信息交流平台[任务描述]设计一个校园跳蚤市场信息交流平台,为同学们交换二手物品提供便利。[功能要求](1)管理员功能:管理员对待销或求购的二手物品......
  • 父子组件的v-modle双向数据绑定,ref和$refs,$nextTick,动态组件(component组件),自定义指令,
    父子组件的双向数据绑定我们先完成双向数据绑定,然后完成v-model的双向数据绑定父组件引入子组件,然后对子组件进行传值,动态显示出来名称<model:value=name></model>......
  • 自定义prometheus exporter
    packagemainimport( "flag" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/......
  • C语言程序设计课程设计[2023-01-07]
    C语言程序设计课程设计[2023-01-07]C语言程序设计课程设计要求一、课程设计目的1.进一步掌握和利用C语言进行程设计的能力;2.进一步理解和运用结构化程设计的思想和......
  • QT信号与槽使用自定义数据类型的参数引发的问题
    1.发现问题今天使用信号与槽遇到一个这样的问题,我自定义了一个信号类型:signals:voidupdate_product_info(int,ProductInfoSign);ProductInfoSign的定......
  • c语言的主要用途是什么?
    C语言的用途可以概括如下: 1)系统编程C语言可移植性好,性能高,能够直接访问硬件地址,而且到达某个地址的时间非常短,这使得C语言天生适合开发操作系统或者嵌入式应用程序。在......
  • 【数据结构】C语言实现的AVL树操作集
    看到网上完整的AVL树操作集较少,索性自己写了一个,望大佬指教!不多废话,上代码:AVLTREE.h头文件1#pragmaonce2#include<stdio.h>3#include<stdlib.h>4#inclu......