【C语言基础】:自定义类型(一)--> 结构体

文章目录

      • 一、内置类型与自定义类型
        • 1.1 内置类型(基本数据类型)
        • 1.2 自定义类型
      • 二、结构体
        • 2.1 结构体的声明
        • 2.2 结构体变量的创建和初始化
        • 2.3 结构体的特殊声明
        • 2.4 结构体的自引用
      • 三、结构体内存对齐
        • 3.1 对齐规则
        • 3.2 为什么存在内存对齐
        • 3.3 修改默认对齐数
      • 四、结构体传参
      • 五、结构体实现位段
        • 5.1 什么是位段
        • 5.2 位段的内存分配
        • 5.4 位段的跨平台问题
        • 5.5 位段使用的注意事项

在这里插入图片描述
         书山有路勤为径,学海无涯苦作舟。
创作不易,宝子们!如果这篇文章对你们有帮助的话,别忘了给个免费的赞哟~

                  在这里插入图片描述

一、内置类型与自定义类型

在C语言中,有内置类型(也称为基本数据类型)和自定义类型(结构体)两种类型。

1.1 内置类型(基本数据类型)
  1. 整型(Integer types):用于表示整数值,包括:
  • int:通常表示整数,取决于编译器和系统架构,一般为4字节。
  • short int:短整数,通常为2字节。
  • long int:长整数,通常为4字节或8字节。
  • long long int:长长整数,通常为8字节。
  1. 字符型(Character type)
  • char:用于表示单个字符或小整数值,通常为1字节。
  1. 浮点型(Floating-point types):用于表示实数,包括:
  • float:单精度浮点数,通常为4字节。
  • double:双精度浮点数,通常为8字节。
  • long double:扩展精度浮点数,大小不定,通常大于8字节。
  1. 空类型(Void type)
  • void:表示无类型,常用于函数返回类型或指针类型。

这些内置类型是C语言提供的基本数据类型,用于表示基本数据,如整数、浮点数、字符等。

1.2 自定义类型

在C语言中,除了内置的基本数据类型外,还可以通过结构体(Structures)和枚举类型(Enums)来定义自定义类型。

  1. 结构体(Structures)

结构体是一种用户自定义的数据类型,用于组合不同类型的数据成员。它允许将多个不同类型的变量组合在一起,形成一个新的数据类型,以便更方便地操作相关数据。

  1. 枚举类型(Enums)

枚举类型是一种用户自定义的数据类型,用于定义一组相关的命名常量。它允许将一组有限的取值集合在一起,形成一个新的数据类型,以便更清晰地表示程序中的意图。

二、结构体

2.1 结构体的声明

在C语言中,定义结构体使用 struct 关键字,结构体的形式如下:

struct 结构体名 {数据类型 成员名1;数据类型 成员名2;// 更多成员...
};

【示例】:描述⼀个学⽣

struct Stu
{char name[20]; // 姓名int age;  // 年龄char set[5];  // 性别int id;  // 学号
};  // 分号不能丢
2.2 结构体变量的创建和初始化

初始化结构体变量:有几种方法可以初始化结构体变量:

  1. 按照结构体成员的顺序初始化:
#include<stdio.h>int main()
{struct Stu s = { "张三", 19, "男", "202201170248" };printf("%s\n", s.name);printf("%d\n", s.age);printf("%s\n", s.set);printf("%s\n", s.id);return 0;
}

在这里插入图片描述
2. 按照指定的顺序初始化
前面在说操作符时我们讲过,可以通过点操作符来访问结构体成员,这里同样可以通过点操作符来给结构体成员进行初始化。

int main()
{struct Stu s = { .age = 19, .id = "202201170248", .name = "张三", .set = "男" };printf("%s\n", s.name);printf("%d\n", s.age);printf("%s\n", s.set);printf("%s\n", s.id);return 0;
}

在这里插入图片描述

2.3 结构体的特殊声明

在声明结构的时候,可以不完全的声明。

匿名结构体类型

struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], *p;

注意

  1. 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。
  2. p = &x; 这种写法是不合法的,编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
2.4 结构体的自引用

结构体中的成员不仅可以是内置的数据类型,还可以是这个结构体本身,也就是结构体中包含指向相同类型结构体的指针或引用的情况。这种自引用的数据结构通常称为递归数据结构。

比如说定义一个链表的结点:

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

注意:这种自引用是错误的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。即无法确定 sizeof(struct Node) 的大小。

正确的自引用方式:

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

在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易出现引入问题。

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

这种也是错误的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。

解决方案如下:定义结构体不要使用匿名结构体了

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

三、结构体内存对齐

【示例】:计算结构体的大小。

struct S
{char c1;  // 占1个字节int i;  // 占4个字节char c2;  // 占1个字节};int main()
{struct S s = { 0 };printf("%zd\n", sizeof(s));return 0;
}

在代码中我们看到结构体中有两个char和一个int,那他的大小就是6个字节,但结果真的是这样吗?
在这里插入图片描述
运行之后发现是12个字节,这是为什么呢?
这说明结构体中的成员不是随便放的,这里面是有一定规则的,这就是结构体的内存对齐。

3.1 对齐规则

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

  1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值
  • VS 中默认的值为 8
  • Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
  1. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
    在这里插入图片描述

解析:对照规则1,第一个成员对齐到和结构体变量起始位置偏移量为0,也就是图中为0的位置(char占1个字节),其余的成员变量对齐到对齐数整数倍的位置(int占4个字节,VS的默认值为8,4小于8,即这里的对齐数为4),也就是4的整数倍(图中序号4)开始存,第三个成员变量也一样(char占1个字节小于8,即对齐数是1)。最后结构体总大小是最大对期数(第一个和第三个对齐数都是1,第二个对齐数是4)的整数倍,也就是4的倍数,由于已经占了9个字节,所以下一个4的倍数就是12,这里总共浪费了6个字节的空间大小。

【练习1】

struct S1
{char c1;  // 占1个字节char c2;  // 占1个字节int i;  // 占4个字节
};

在这里插入图片描述
在这里插入图片描述
【练习2】

struct S2
{double d;char c;int i;
};

在这里插入图片描述
在这里插入图片描述
【练习3】
结构体中嵌套结构体

struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};

在这里插入图片描述
解析:这里就要对应规则4,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,S3中最大的对齐数是8,即要对齐到8的整数倍处。结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍,也是8的倍数。
在这里插入图片描述

3.2 为什么存在内存对齐
  1. 平台原因 (移植原因)
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  2. 性能原因
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

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

设计结构体的时候,我们既要满足对齐,又要节省空间

  • 让占用空间小的成员尽量集中在一起
struct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};

S1 和 S2 类型的成员一模一样,但是 S1 和 S2 所占空间的大小有了一些区别(S1占12个字节,S2占8个字节)。

3.3 修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数。
【示例】

#include<stdio.h>
#pragma pack(1)  //设置默认对⻬数为1
struct S1
{char c1;int i;char c2;
};
#pragma pack()  //取消设置的对⻬数,还原为默认int main()
{struct S1 s1;printf("%zd\n", sizeof(s1));return 0;
}

在这里插入图片描述
结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。

四、结构体传参

【示例1】

#include<stdio.h>
struct S
{int arr[1000];int num;double d;
};
void print1(struct S s)
{for (int i = 0; i < 5; i++){printf("%d ", s.arr[i]);}printf("\n");printf("%d\n", s.num);printf("%lf\n", s.d);
}
int main()
{struct S s = { {1,2,3,4,5}, 100,3.14 };print1(s);return 0;
}

在这里插入图片描述
【示例2】

struct S
{int arr[1000];int num;double d;
};
void print2(const struct S* ps)
{for (int i = 0; i < 5; i++){printf("%d ", ps->arr[i]);}printf("\n");printf("%d\n", ps->num);printf("%lf\n", ps->d);
}
int main()
{struct S s = { {1,2,3,4,5}, 100,3.14 };print2(&s);return 0;
}

在这里插入图片描述
示例1示例2中首选示例2,因为示例传参时是将结构体在拷贝一份给形参,本身这个结构体所占的空间就比较大,在拷贝一份太占用空间,不太合适,而示例2传的是一个指针,可以通过这个指针直接访问这个结构体,不需要额外创建多余空间,当然,为了结构体内容不被修改,可以加一个const进行修饰。
原因

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降

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

五、结构体实现位段

5.1 什么是位段

位段的声明和结构是类似的,有两个不同:

  1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。
  2. 位段的成员名后边有一个冒号和一个数字。

【示例】

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};

A就是一个位段类型。

5.2 位段的内存分配
  1. 位段的成员可以是 int 、unsigned int 、signed int 或者是 char 等类型。
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

【示例】

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}

在这里插入图片描述

5.4 位段的跨平台问题
  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
  4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

5.5 位段使用的注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。

【示例】

struct S
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};int main()
{struct S s = { 0 };// scanf("%d", &s._b);  // 这是错误的// 正确的示范int b = 0;scanf("%d", &b);s._b = b;return 0;
}

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

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

相关文章

突破数据障碍—如何使用IP代理服务获取量子科学研究领域最新数据

写在前面 在这个数字化的时代&#xff0c;人们越来越关注隐私保护和网络访问自由。我最近也深入研究了一下IP代理服务&#xff0c;在规避地理限制、绕过封锁以及保护个人隐私方面&#xff0c;它确实发挥了关键作用。 一、基础介绍 起因是有个项目需要对量子领域进行深入的研究之…

SpringBoot修复http慢速攻击

web漏洞扫描工具发现 http慢速攻击&#xff0c;如下图&#xff1a; 修改springboot框架文件配置修复如下&#xff1a; server:port: 8080tomcat:max-threads: 200 # tomcat最大线程数connection-timeout: 3000 # 连接超时时间max-connections: 1000 # 最大连接数max-http-head…

ngAlain下使用nz-select与文件上传框出现灵异bug

bug描述 初始化页面&#xff0c;文件上传框无法出现&#xff1a; 但点击一次选择框以后&#xff0c;就会出现&#xff1a; 真的很神奇。。。 下面逐步排查看看是什么原因。 设想一&#xff1a; 选择框与文件框不可同时存在&#xff0c;删掉选择框看看&#xff1a; 还…

中望CAD专业版 2025:国产CAD制图设计新标杆,引领行业新风尚

随着科技的不断进步和行业的快速发展&#xff0c;计算机辅助设计&#xff08;CAD&#xff09;作为现代工程设计的重要工具&#xff0c;正日益受到广大设计师和工程师的青睐。在这个领域&#xff0c;中望CAD专业版 2025以其卓越的性能和人性化的设计&#xff0c;成为了国产CAD制…

Linux——线程控制

目录 前言 一、线程创建 1.创建线程 2.线程传递结构体 3.创建多线程 4.收到信号的线程 二、线程终止 三、线程等待 四、线程分离 五、取消线程 六、线程库管理的原理 七、站在语言角度理解pthread库 八、线程的局部存储 前言 前面我们学习了线程概念和线程创建&…

【数据分析面试】8.计算标准差(python)

题目&#xff1a; 编写一个名为 compute_deviation 的函数&#xff0c;该函数接受一个包含键和整数列表的字典列表&#xff0c;并返回一个字典&#xff0c;其中包含每个列表的标准差。 注意&#xff1a;请勿使用 NumPy 内置函数。 示例&#xff1a; 输入: input [{key: l…

目标检测、识别和语义分割的标注工具安装

计算机视觉 图像分类&#xff08;目标检测&#xff09;&#xff1a;一张图像中是否含某种物体物体定位&#xff08;目标检测与目标识别&#xff09;&#xff1a;确定目标位置和所属类别。语义分割&#xff08;目标分割和目标分类&#xff09;&#xff1a;对图像进行像素级分类…

【HTML】标签学习(下.2)

&#xff08;大家好哇&#xff0c;今天我们将继续来学习HTML&#xff08;下.2&#xff09;的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 二.列表标签 2.1 无序列表(重点) 2.2有序列表(理解) 2.3 自定义列表(重点…

Penpad Season 2 质押突破350ETH,还有望获Scroll生态空投

Scroll 是被 Vitalik Buterin 给予高度评价的 zk Rollup Layer2 项目&#xff0c;也一直是市场长期关注的重点。自 10 月 17 日宣布主网上线后&#xff0c;Scroll 的热度节节攀升&#xff0c;目前其 TVL 达到 1.5 亿美元&#xff0c;是众多 Layer2 生态中 TVL 数据增长最快的生…

30岁转行网络安全来得及吗?有发展空间吗?

30岁转行网络安全来得及吗&#xff1f;有发展空间吗&#xff1f; 30岁转行网络安全来得及吗?有发展空间吗? 现阶段&#xff0c;很多30岁左右的人群都面临就业难的问题&#xff0c;尤其是对于年龄已过30.没有一技之长的人。现阶段&#xff0c;网络安全行业已成了风口行业&am…

supersqli-攻防世界

题目 加个报错 1 and 11 #没报错判断为单引号字符注入 爆显位 1 order by 2#回显正常 1 order by 3#报错 说明列数是2 尝试联合查询 -1 union select 1,2# 被过滤了 return preg_match("/select|update|delete|drop|insert|where|\./i",$inject); select|update|d…

【HTML】制作一个简单的动态SVG图形

目录 前言 开始 HTML部分 CSS部分 效果图 总结 前言 无需多言&#xff0c;本文将详细介绍一段HTML和CSS代码&#xff0c;该代码用于创建一个动态的SVG图形&#xff0c;具体内容如下&#xff1a; 开始 首先新建文件夹&#xff0c;创建两个文本文档&#xff0c;其中HTML的文…