【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.mzph.cn/news/789342.shtml

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

相关文章

C++心决之内联函数+auto关键字+指针空值

目录 7.内联函数 7.1 概念 7.2 特性 8. auto关键字(C11) 8.1 类型别名思考 8.2 auto简介 8.3 auto的使用细则 8.4 auto不能推导的场景 9. 基于范围的for循环(C11) 9.1 范围for的语法 9.2 范围for的使用条件 10. 指针空值nullptr(C11) 10.1 C98中的指针空值 7.内联…

如何优化TCP?TCP的可靠传输机制是什么?

在网络世界中&#xff0c;传输层协议扮演着至关重要的角色&#xff0c;特别是TCP协议&#xff0c;以其可靠的数据传输特性而广受青睐。然而&#xff0c;随着网络的发展和数据量的激增&#xff0c;传统的TCP协议在效率方面遭遇了挑战。小编将深入分析TCP的可靠性传输机制&#x…

基于springboot+vue+Mysql的实习管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

VScode-配置文件

导入配置文件 ShiftCtrlp 输入&#xff1a; import 选择文件 点击确认 导出配置文件 设置选择导出 确认导出 保存为本地文件 保存文件

黄金票据攻击

黄金票据攻击——域内横向移动技术 一、黄金票据攻击介绍&#xff1a; 黄金票据攻击是一种滥用Kerberos身份认证协议的攻击方式&#xff0c;它允许攻击者伪造域控krbtgt用户的TGT&#xff08;Ticket-Granting Ticket&#xff09;。通过这种方法&#xff0c;攻击者可以生成有效…

【原创】基于springboot+vue疫苗预约管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

element-ui tableData导出为xlsx文件

下载 npm i / yarn add file-saver、xlsx库 引入 import FileSaver from “file-saver”; import XLSX from “xlsx”; const simexport (data) > {// if (data.create_time && data.create_time.length > 0) {// data.start_time parseTime(data.create_tim…

Rust vs C++:2024,谁更懂错误处理?

讲动人的故事,写懂人的代码 「席双嘉,听说你的C++项目又因为忘了检查返回值导致内存泄漏,又加班了?」 周五中午,在国内某科技巨头熙熙攘攘的员工餐厅,贾克强半开玩笑地戳了戳坐在隔壁的席双嘉,眼神中满是戏谑。 贾克强,一个热衷于Rust的程序员,总是乐于挑战和探索新…

mfw-攻防世界

题目 点击about发现Git感觉是Git泄露 直接访问.git 本来用githack去扒源码但是成功了没文件一脸懵&#xff0c; 后面换一个工具githacker注意二个之间有区别 githack和githacker 然后去结果里查看文件 发现flag文件但是 没什么用 <?php // TODO // $FLAG ; ?> 然后…

QCC发射(TX SOURCE)USB发射A2DP音乐,实现MIC声音到主机

之前写过 CSR8670/8675 发射&#xff08;TX SOURCE&#xff09;USB发射A2DP音乐&#xff0c;实现MIC声音到主机的文章&#xff0c;目前把该方案移植到QCC方案&#xff08;QCC3040 QCC3056&#xff09;。 因 CSR8670/8675成本比较贵&#xff0c;现在移植到QCC平台。 由于众多游…

Pytorch for training1——read data/image

blog torch.utils.data.Dataset create dataset with class torch.utils.data.Dataset automaticly import torch from torch.utils.data import Datasetclass MyDataset(Dataset):def __init__(self, data):self.data datadef __getitem__(self, index):# 根据索引获取样本…

华为OD面试手撕算法-合并排序数组

题目描述 本题是leetcode一道简单题&#xff1a;合并两个有序数组&#xff0c;但是对于时间和空间复杂度面试官明确给出了限制。 // 给定两个排序后的数组 A 和 B&#xff0c;其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法&#xff0c;将 B 合并入 A 并排序。 // 初始化…

对 NGINX、Kong 和 Amazon 的 API 管理解决方案进行基准测试:它们能否交付实时 API?

原文作者&#xff1a;Alessandro Fael Garcia of F5 原文链接&#xff1a;对 NGINX、Kong 和 Amazon 的 API 管理解决方案进行基准测试&#xff1a;它们能否交付实时 API&#xff1f; 转载来源&#xff1a;NGINX 开源社区 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.c…

HAL STM32 硬件I2C方式读取AS5600磁编码器获取角度例程

HAL STM32 硬件I2C方式读取AS5600磁编码器获取角度例程 &#x1f4cd;相关篇《STM32 软件I2C方式读取AS5600磁编码器获取角度例程》 ✨stm32使用硬件I2C去读取角度数据&#xff0c;通过STM32CubeMX工具配置工程&#xff0c;读取角度数据&#xff0c;只需要调用一个函数&#xf…

使用 RisingWave、NATS JetStream 和 Superset 进行实时物联网监控

在物联网&#xff08;IoT&#xff09;背景下&#xff0c;处理实时数据会遇到一些特定的障碍&#xff0c;如边缘计算资源不足、网络条件限制、扩展性存在问题、设备间有多样性差异。要克服这些挑战&#xff0c;需要高效的边缘计算技术、强大的安全措施、标准化协议、可扩展的管理…

接口自动化框架搭建(六):多进程执行

1&#xff0c;背景目的 当测试用例太多之后&#xff0c;想缩短执行时间&#xff0c;就需要多线程或者多进程执行。 多线程执行&#xff1a; 每条测试用例是独立的&#xff0c;测试用例之间的参数不能共同使用 采坑举例&#xff1a;接口自动化中请求头是公共参数&#xff0c;…

WebGIS 地铁交通线网 | 图扑数字孪生

数字孪生技术在地铁线网的管理和运维中的应用是一个前沿且迅速发展的领域。随着物联网、大数据、云计算以及人工智能技术的发展&#xff0c;地铁线网数字孪生在智能交通和智慧城市建设中的作用日益凸显。 图扑软件基于 HTML5 的 2D、3D 图形渲染引擎&#xff0c;结合 GIS 地图…

人人都离不开的算法:AI 时代的生存指南

文章目录 一、算法在生活中的“无处不在”二、算法在工作学习中的“智慧助力”三、算法在社会发展中的“驱动力量”四、算法带来的“双刃剑”效应五、应对算法挑战的策略《人人都离不开的算法——图解算法应用》编辑推荐1、通俗易懂2、技术科普3、贴近时代、贴近生活4、启发思考…

基于DWT(离散小波变换)的图像加密水印算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

华尔街基金经理为什么开始押注MVP币了?

目前&#xff0c;市场非常流行一种新兴的上市策略。 依靠正在被市场认可并有明显增长动力的 Meme 币&#xff0c;并围绕它构建一个社区&#xff0c;继而完成整个生态&#xff0c;最终&#xff0c;将由一系列产品完成生态的繁荣。 通过启动一个与热门 Meme 币原生集成的项目&a…