【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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

十七、InnoDB 一次更新事务的执行过程

一、InnoDB的一次更新事务是怎么实现的&#xff1f; InnoDB的一次更新事务涉及到多个组件和步骤&#xff0c;包括Buffer Pool、BinLog、UndoLog、RedoLog以及物理磁盘。 下面是一次完整的事务更新操作过程&#xff1a; 1. 加载数据到缓存中&#xff08;Buffer Pool&#xff0…

Huggingface模型下载

1. 基础信息 huggingface的模型排行榜&#xff08;需要翻墙&#xff09;&#xff1a;https://huggingface.co/spaces/mteb/leaderboard 2. 下载模型 2.1 手动一个个下载&#xff08;方式1&#xff09; 2. 使用huggingface-cli下载(方式2) pip install -U huggingface_hub h…

英文网站怎么推广,英文网站推广排名方案

英文网站的推广对于吸引国际用户、提升品牌知名度和增加业务收入至关重要。而在全球范围内&#xff0c;谷歌是最主要的搜索引擎之一&#xff0c;因此谷歌SEO排名优化是英文网站推广的重要手段之一。本文将介绍英文网站推广的方法&#xff0c;并重点探讨谷歌SEO排名优化推广策略…

STM32嵌套中断向量控制器NVIC

一、嵌套终端向量控制器NVIC 1.1NVIC介绍 NVIC&#xff08;Nest Vector Interrupt Controller&#xff09;&#xff0c;嵌套中断向量控制器&#xff0c;作用是管理中断嵌套 先级。 核心任务是管理中断优 管理中断嵌套&#xff1a;我们在处理某个中断的过程中还没处理完这个中…

Python环境下基于慢特征分析SFA的过程监控(TE数据)

近几年来&#xff0c;慢特征分析&#xff0c;作为一种新兴的非监督型特征提取算法&#xff0c;正在逐渐兴起。它以变量随时间的一阶导数的大小来衡量变量变化的快慢&#xff0c;并从建模数据中提取出变化最慢的潜在特征变量&#xff0c;称为“不变量”或“慢特征”。 因为工业…

在新能源充电桩、智能充电枪、储能等产品领域得到广泛应用的两款微功耗轨至轨运算放大器芯片——D8541和D8542

D8541和D8542是我们推荐的两款微功耗轨至轨运算放大器芯片&#xff0c;其中D8541为单运放&#xff0c; D8542为双运放&#xff0c;它特别适用于NTC温度采集电路、ADC基准电压电路、有源滤波器、电压跟随器、信号放大器等电路应用&#xff0c;在新能源充电桩、智能充电枪、…

显示器亮度调节,如何调屏幕亮度

我们常说的显示器亮度&#xff0c;其实就是屏幕亮度。在使用电脑的时候呢&#xff0c;屏幕亮度直接可以影响我们的视觉感官&#xff0c;太亮度或者过暗都会伤害视力&#xff0c;而且眼睛看着也不舒服。那么电脑如何调屏幕亮度呢?操作方法很简单。接下来小编为大家介绍&#xf…

2024测试员最佳跳槽频率是多少?进来看看你是不是符合!

最近笔者刷到一则消息&#xff0c;一位测试员在某乎上分享&#xff0c;从月薪5K到如今的20K&#xff0c;他总共跳了10次槽&#xff0c;其中还经历过两次劳动申诉&#xff0c;拿到了大几万的赔偿&#xff0c;被同事们称为“职场碰瓷人”。 虽说这种依靠跳槽式的挣钱法相当奇葩&…

预训练大模型最佳Llama开源社区中文版Llama2

Llama中文社区率先完成了国内首个真正意义上的中文版Llama2-13B大模型&#xff0c;从模型底层实现了Llama2中文能力的大幅优化和提升。毋庸置疑&#xff0c;中文版Llama2一经发布将开启国内大模型新时代。 作为AI领域最强大的开源大模型&#xff0c;Llama2基于2万亿token数据预…

ObjectiveC-04-类的创建以属性、方法定义详细

在本小节中&#xff0c;笔者会详细讲解下ObjC的类的相关内容&#xff0c;包括创建、构造、方法、属性以及属性读取等相关知识&#xff0c;先来看下类的组成&#xff1a; 类的创建 ObjC是在C语言基础上扩展的&#xff0c;在编写OS软件时可以混用两种语言。但它们之间是有区别的…

全球X射线源市场持续增长 我国高端产品研制能力较弱

全球X射线源市场持续增长 我国高端产品研制能力较弱 X射线源&#xff0c;即X射线发生器&#xff0c;是产生和发射X射线的装置&#xff0c;从阴极发射电子&#xff0c;经阴极与阳极之间的电场加速后&#xff0c;高速轰击阳极靶面&#xff0c;产生X射线射出&#xff0c;是X射线检…

LeetCode.2908. 元素和最小的山形三元组 I

题目 2908. 元素和最小的山形三元组 I 分析 首先&#xff0c;看到这道题&#xff0c;第一反应就是暴力方法&#xff0c;三层for循环&#xff0c;枚举每一种情况&#xff0c;代码如下 class Solution {public int minimumSum(int[] nums) {int min Integer.MAX_VALUE;for(i…

Linux 进程信号:内核中信号结构、阻塞信号、捕捉信号

目录 一、阻塞信号 1、信号的状态 2、内核中的信号 信号集&#xff08;Signal Set&#xff09; task_struct 结构体 信号处理函数&#xff08;Handler&#xff09; 信号传递与调度 3、“signal_struct结构体”与“信号集sigset_t” 4、信号集操作函数 5、信号屏蔽字si…

【Hadoop大数据技术】——Hive数据仓库(学习笔记)

&#x1f4d6; 前言&#xff1a; Hive起源于Facebook&#xff0c;Facebook公司有着大量的日志数据&#xff0c;而Hadoop是实现了MapReduce模式开源的分布式并行计算的框架&#xff0c;可轻松处理大规模数据。然而MapReduce程序对熟悉Java语言的工程师来说容易开发&#xff0c;但…

还在问要不要学Python?看完这篇你就知道了

前不久教育界的一个消息&#xff0c;引发了广泛的关注。 今年9月开学后&#xff0c;浙江三到九年级信息技术课将替换新教材&#xff0c;八年级将新增Python课程内容。新高一信息技术编程语言由VB替换为Python&#xff0c;大数据、人工智能、程序设计与算法按照教材规划五六年级…

抽象类和接口(java初识)

1.抽象类 1.1抽象类的概念 在面向对象中&#xff0c;所有对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息描绘一个具体的对象&#xff0c;这样的类就是抽象类。 例子&#xff1a; 说明…

【C语言终章】预处理详解(上)

【C语言终章】预处理详解&#xff08;上&#xff09; 当你看到了这里时&#xff0c;首先要恭喜你&#xff01;因为这里就是C语言的最后一站了&#xff0c;你的编程大能旅途也将从此站开始&#xff0c;为坚持不懈的你鼓个掌吧&#xff01; &#x1f955;个人主页&#xff1a;开敲…

代码随想录算法训练营三刷 day38 | 动态规划之 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯

三刷day38 509. 斐波那契数1 确定dp数组以及下标的含义2 确定递推公式3 dp数组如何初始化4 确定遍历顺序5 举例推导dp数组 70. 爬楼梯1 确定dp数组以及下标的含义2 确定递推公式3 dp数组如何初始化4 确定遍历顺序5 举例推导dp数组 746. 使用最小花费爬楼梯1 确定dp数组以及下标…

经验分享:开源知识库才是企业低成本搭建的最佳选择!

身为企业所有者的你&#xff0c;是否为建设企业的知识库而头疼&#xff1f;想要一个功能全面而又简单易用的知识库&#xff0c;但又担心成本过高&#xff1f;那我今天要分享的内容&#xff0c;可能会给你带来一些启示。那便是&#xff1a;开源知识库便是你企业低成本搭建的最佳…

Stable Diffusion 模型下载:epiCPhotoGasm(真实、照片)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 该模型对照片是什么有很高的了解&#xff0c;所以…