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

news2024/11/29 8:56:10

文章目录

      • 一、内置类型与自定义类型
        • 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.coloradmin.cn/o/1556222.html

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

相关文章

基于java+SpringBoot+Vue的校友社交系统设计与实现

基于javaSpringBootVue的校友社交系统设计与实现 开发语言: Java 数据库: MySQL技术: SpringBoot MyBatis工具: IDEA/Eclipse、Navicat、Maven 系统展示 前台展示 后台展示 系统简介 整体功能包含&#xff1a; 校友社交系统是一个为校友提供一个交流互动、信息共享的平台…

【高数】汤家凤高等数学辅导讲义+1800错题整理

第一章 极限与连续 1. 2. 3. 4. 5. 6. 7. 第二章 导数与微分 高等数学辅导讲义 1. 2. 3. 4. 5. 6. 7. 8. 1800 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 第三章 一元函数微分学的应用 高等数学辅导讲义 中值定理 题型一 题型二 题型三 题型四 题型五 单调性、极值与…

三菱GX WORKS3连接FX5U系列PLC时,弹出窗口提示:用户认证功能或安全性强化模式未启用

三菱GX WORKS3连接FX5U系列PLC时&#xff0c;弹出窗口提示&#xff1a;用户认证功能或安全性强化模式未启用 如下图所示&#xff0c;使用GX WORKS3编程软件连接FX5U系列PLC&#xff0c; 首先&#xff0c;在连接目标中选择自己当前使用的网卡适配器&#xff0c;并将IP地址设置在…

HuTool工具箱验证JWT生成Token失败

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于…

IC-随便记

1、移远通信---通信模组 物联网解决方案供应商&#xff0c;可提供完备的IoT产品和服务&#xff0c;涵盖蜂窝模组(5G/4G/3G/2G/LPWA)、车载前装模组、智能模组&#xff08;5G/4G/边缘计算&#xff09;、短距离通信模组(Wi-Fi&BT)、GNSS定位模组、卫星通信模组、天线等硬件产…

【全栈小5】我的创作纪念日

目录 前言机缘收获粉丝和原创个人成就六边形战士 回顾文章原代码代码优化 憧憬 前言 全栈小5 &#xff0c;有幸再次遇见你&#xff1a; 还记得 2019 年 03 月 29 日吗&#xff1f; 你撰写了第 1 篇技术博客&#xff1a; 《前端 - 仿动态效果 - 展开信息图标》 在这平凡的一天&…

直播上瘾?

目前我们正处在日新月异高速发展的时代&#xff0c;各行各业都在接入 AI&#xff0c;各行各业都在涌向直播的时代。当然&#xff0c;历史的车轮不会因为个人的喜好而改变&#xff0c;我们唯一能做的就是拥抱变化&#xff0c;这样才不会活的很别扭。 “ PS&#xff1a;这就是我为…

网络安全:绕过 MSF 的一次渗透测试

这次渗透的主站是 一个 Discuz!3.4 的搭建 违法招 piao 网站&#xff0c; 配置有宝塔 WAF 用 Discuz!ML 3.X 的漏洞进行攻击&#xff0c;但是没有成功 发现主站外链会有一个发卡网&#xff0c;引导人们来这充值&#xff0c;是 某某发卡网&#xff0c;而且域名指向也是主站的 ip…

一篇文章带你搞定企业级完整性能测试流程!

大部分公司在最初试的阶段只会关心项目的基本功能&#xff0c;能用就可以。但是随着项目的成熟&#xff0c;用户量逐步的增大&#xff0c;线上经常就会出现一些系统崩溃&#xff0c;用户反映系统太慢等性能问题的爆发。所以&#xff0c;性能测试的需求就逐步变得迫切了。所以&a…

照片分享,欢迎家庭新成员HPE ProLiant DL580 Gen9

正文共&#xff1a;1234 字 29 图&#xff0c;预估阅读时间&#xff1a;1 分钟 距离上一台服务器HPE ProLiant DL360 Gen9开箱已经过去4年了&#xff0c;回忆满满&#xff08;风雨同舟&#xff0c;感谢HP Proliant DL360 Gen9陪我走过的四年&#xff09;&#xff1b;就在上周&a…

Kubernetes篇(二)— 集群环境搭建

目录 前言一、 环境规划集群类型安装方式主机规划 二、环境搭建主机安装环境初始化安装docker安装kubernetes组件准备集群镜像集群初始化安装网络插件 三、 服务部署 前言 本章节主要介绍如何搭建kubernetes的集群环境 一、 环境规划 集群类型 kubernetes集群大体上分为两类…

Java基础之运算符(整合)

文章目录 一.运算符算数运算符练习: 二.算术运算符的高级用法""操作的三种情况数字相加字符串相加字符相加 三.自增自减运算符基本用法 四.赋值运算符&关系运算符赋值运算符关系运算符逻辑运算符 五.短路逻辑运算符六.三元运算符 一.运算符 运算符: 对字面量或…

canvas画图,画矩形,圆形,直线,曲线可拖拽移动

提示&#xff1a;canvas画图&#xff0c;画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动 文章目录 前言一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动总结 前言 一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖…

计算机基础系列 —— 虚拟机代码翻译器(1)

“Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program.” ―Linus Torvalds 文中提到的所有实现都可以参考&#xff1a;nand2tetris_sol&#xff0c;但是最好还是自己学习课程实现一…

Adaboost集成学习 | Matlab实现基于LSTM-Adaboost长短期记忆神经网络结合Adaboost集成学习时间序列预测(股票价格预测)

目录 效果一览基本介绍模型设计程序设计参考资料效果一览 基本介绍 Adaboost集成学习 | Matlab实现基于LSTM-Adaboost长短期记忆神经网络结合Adaboost集成学习时间序列预测(股票价格预测) 模型设计 股票价格预测是一个具有挑战性的时间序列预测问题,可以使用深度学习模型如…

【Vue】动态样式

内联样式的动态样式 body(){ boxASelect:false, } v-bind:style"{borderColor:boxASelect ? red : #ccc}" <body><header><h1>Vue Dynamic Styling</h1></header><section id"styling"><div class"demo&quo…

HarmonyOS像素转换-如何使用像素单位设置组件的尺寸。

1 卡片介绍 基于像素单位&#xff0c;展示了像素单位的基本知识与像素转换API的使用。 2 标题 像素转换&#xff08;ArkTS&#xff09; 3 介绍 本篇Codelab介绍像素单位的基本知识与像素单位转换API的使用。通过像素转换案例&#xff0c;向开发者讲解了如何使用像素单位设…

JUC/多线程 模式(四)

一、同步模式之保护性暂停 即 Guarded Suspension &#xff0c;用在一个线程等待另一个线程的执行结果 产生结果的线程和使用结果的线程是一一对应的&#xff0c;有多少个生产结果的线程就有多少个使用结果的线程。 要点 有一个结果需要从一个线程传递到另一个线程&#xff0…

使用 CSS 实现多立方体悬停颜色效果实现

使用 CSS 实现多立方体悬停效果实现 效果展示 CSS 知识点 filter 属性的 hue-rotate 值运用使用 CSS 实现立方体 场景布局分析 从效果图可以看出&#xff0c;要实现 3*3 的立方体集合&#xff0c;我们需要考虑一下怎么安排小立方体的布局。我这里的做法是使用span实现单个小…

docker使用教程

寒假用了docker 2个月没用 结果还重新安装docker 忘了怎么用 为了免得以后忘写下下面内容 # If you dont have a docker installed, youll need to install docker curl -s https://get.docker.com/ | sh # Use pip to install docker-compose pip install docker-compose…