C语言学习系列—>一篇带你了解结构体

news2024/10/6 22:26:34

在这里插入图片描述

文章目录

  • 前言
  • 结构体类型
    • 概述
    • 声明
    • 特殊声明
    • 结构体的自引用
    • 结构体变量的创建和初始化
    • 结构成员访问操作符
    • 结构体内存对齐
    • 内存对齐的原因
    • 修改默认对齐方式
    • 结构体传参

前言

结构体是C语言中自定义类型之一,当内置类型不能满足的时候,我们就可以使用自定义类型,在后续数据结构的学习过程中会遇到很多关于结构体的内容,所以,小编将在学习结构体时的笔记分享一番。

结构体类型

概述

结构体是一个集合,里面的成员变量可以是不同类型的。

声明

struct tag  //tag是标签
{
member-list;   //成员列表
}variable-list;  //变量名称

code

struct Stu
{
	char name[20];   //名字
	int age;         //年龄
	float scr;       //分数
};

特殊声明

声明结构体的时候,可以不完全声明:

//匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], 

结构体的自引用

结构体的自引用:在结构体里面包含一个为该结构体本身的成员。

比如,定义一个链表结点:

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


//错误做法:
struct Node
{
	int data;
	struct Node next;
};

为什仫错误做法是错误做法?
这里的next是同一结构体类型中的next,next中又有一个next,无限套娃,是不行的。
正确的自引用是,在结构体声明里面包含一个结构体类型的指针。

注意!!

//错误:
typedef struct Node
{
	int data;
	Node* next;
}Node;

//正确:
typedef struct Node
{
	int data;
	struct Node* next;

}Node;

Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使⽤Node类型来创建成员变量,这是不⾏的。

结构体变量的创建和初始化

声明的同时定义变量为S1

struct S
{
	int x;
	int y;
}S1;

单独利用类型定义变量

struct S
{
	int x;
	int y;
};         //声明结构体

struct S S2;  //定义全局结构体变量

int main()
{
	struct S S3;     //定义一个局部结构体变量
	return 0;
}

结构体初始化

struct S
{
	int x;
	int y;
}s1={0,0};

struct S s2 = {1,2}; //初始化

int main()
{
	struct S s3 = {3,4};//初始化
	return 0;
}

结构成员访问操作符

结构成员访问操作符有两个⼀个是 . ,⼀个是 -> .

形式:

结构体变量.成员变量名
结构体指针—>成员变量名

code

#include <stdio.h>
#include <string.h>
struct Stu
{
	char name[15];//名字
	int age;
	//年龄
};
void print_stu(struct Stu s)
{
	printf("%s %d\n", s.name, s.age);
}
void set_stu(struct Stu* ps)
{
	strcpy(ps->name, "李四");
	ps->age = 28;
}
int main()
{
	struct Stu s = { "张三", 20 };
	print_stu(s);
	set_stu(&s);
	print_stu(s);
	return 0;
}

输出结果

张三 20
李四 28

结构体内存对齐

code

#include<stdio.h>

struct S1
{
	char a;
	int c;
	char b;
};

struct S2
{
	char a;
	char b;
	int c;
};

int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));

	return 0;
}

输出结果

12
8

为什么呢??

⾸先得掌握结构体的对⻬规则:

  1. 结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处
  2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
    对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
  • VS中默认的值为8
  • Linux中没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
  1. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

对上述代码内存进行解析:

struct S1:

在这里插入图片描述
这里为struct S1开辟一块空间

首先给char a开辟空间,char a是结构体第一个成员,根据规则:结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处,即,图中绿色位置

接下来,为第二个成员,int c开辟空间,根据规则: 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 这里的c是一个整型变量,自身大小为4,小编编译器是VS2019,默认对齐数为8,根据规则:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。 所以,c的对齐数为4。偏移量1,2,3都不是4的倍数,因此从4开始,开辟4个字节,即图中深红色的位置。

最后为 char b开辟空间,b是一个字符类型变量,自身大小为1,编译器的默认对齐数是8,和开辟 int c 一样,因此b的对齐数是1,偏移量8就是1的倍数,因此从8开始,开辟1个字节,即图中蓝色位置。

从0~8一共,此时结构体9个字节,根据规则:结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。所以需要再浪费几个空间,浪费到偏移量为11时,此时刚好开辟了12(12是4的倍数)个字节。

struct S2:
在这里插入图片描述
这里为struct S1开辟一块空间

首先给char a开辟空间,char a是结构体第一个成员,根据规则:结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处,即,图中绿色位置

接下来,为第二个成员,char b 开辟空间,根据规则: 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。,这里的b是一个字符类型变量,自身大小为1,VS默认对齐数为8,因此对齐数是1,偏移量1是1的倍数,从1开始开辟1个字节,即图中蓝色位置。

最后为 int c开辟空间,c是整型变量,自身大小为4,VS默认对齐数为8,因此对齐数为4,根据规则: 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。 偏移量2,3都不是4的倍数,从偏移量4开始,开辟4个字节。

此时,struct S2 开辟了8个字节,8是4的倍数,因此不需要再浪费空间了。

内存对齐的原因

参考资料:

  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 所占空间的⼤⼩有了⼀些区别

修改默认对齐方式

#pragma 这个预处理指令,可以改变编译器的默认对⻬数。

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对⻬数,还原为默认
int main()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S));
	return 0;
}

输出结果

6

在这里插入图片描述
此时VS默认对齐数为1,int i 的自身大小为4,根据规则:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。 因此,对齐数为1,偏移量1是1的倍数,和上面的一个代码就不一样了。

结构体传参

struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
		printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s); //传结构体
	print2(&s); //传地址
	return 0;
}

⾸选print2函数。

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

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

在这里插入图片描述

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

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

相关文章

怎样快速提取视频中的背景音乐和人声?

人声分离的需求在现在还是比较多的&#xff0c;例如做影视混剪、做配音等&#xff0c;都需要将视频或音频中的人声分离开&#xff0c;今天就来教大家如何快速提取视频中的背景音乐和人声&#xff01; 第一步&#xff1a;打开“音分轨”人声分离APP&#xff0c;点击主页“短视频…

自己写过比较蠢的代码:从失败中学习的经验

文章目录 引言1. 代码没有注释2. 长函数和复杂逻辑3. 不恰当的变量名4. 重复的代码5. 不适当的异常处理6. 硬编码的敏感信息7. 没有单元测试结论 &#x1f389; 自己写过比较蠢的代码&#xff1a;从失败中学习的经验 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&a…

以太坊智能合约的历史里程碑: 从DAO到数据隐私的技术演进

文章目录 系列文章目录前言一、时间线 项目介绍总结 前言 在短短的几年内&#xff0c;以太坊不仅成为了去中心化应用和智能合约的主导平台&#xff0c;而且也见证了区块链技术和应用的多次重大革命。本文详细回顾了自2016年至今&#xff0c;以太坊生态所经历的几个关键时刻与技…

天津乾瑞晟达积极加大研发投入 满足行业发展需求

天津乾瑞晟达新能源科技有限公司多年以来,坚持新能源汽车零部件的研发、生产以及销售等综合业务,成为了一家有实力的综合制造企业。为了满足行业发展需求,该公司积极加大研发投入,持续推动科技创新。 根据最新的财务报告显示,天津乾瑞晟达公司为了研发新的项目,确保可以使项目…

Spring面试题15:Spring支持几种bean的作用域?singleton、prototype、request的区别是什么?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring支持几种bean的作用域? Spring支持以下几种Bean的作用域: Singleton(单例):这是Spring默认的作用域。使用@Scope(“singleton”)注解或…

精品Python宠物领养网站系统失物招领

《[含文档PPT源码等]精品基于Python实现的宠物网系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;python 使用框架&#xff1a;Django 前端技术&#xff1a;JavaScript、VUE.js&…

鲜花店经营配送小程序商城的作用是什么?

鲜花在人们日常生活中的应用度非常高&#xff0c;在同城场景中也有大量从业者&#xff0c;对商家们来说&#xff0c;其主要生意来源于本地&#xff0c;当然也有批发或同时经营玩具类的商家会有外地配送属性。 所谓客户在哪里&#xff0c;商家就应在哪里&#xff0c;如今互联网…

随笔-服务器运维常用的命令

查询服务器的目录下&#xff0c;所有的文件大小 // 查看当前目录下&#xff0c;深度为3的所有目录内容大小 du -h –max-depth3 *// 查看目录下文件夹大小写&#xff0c;并按照大小排序 du -sh * | sort -rh查看当前目录下文件的大小 // 查看文件大小 ll -h3. 清空文件内容 …

【从0学习Solidity】22. Call函数详解

【从0学习Solidity】22. Call函数详解 博主简介&#xff1a;不写代码没饭吃&#xff0c;一名全栈领域的创作者&#xff0c;专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构&#xff0c;分享一些项目实战经验以及前沿技术的见解。关注我们的主页&#xff0c;探索…

kux转mp4,实测有效,有图有真相

kux转mp4 kux视频格式真坑爹&#xff0c;我在MAC下载优酷&#xff0c;居然无法播放这种格式&#xff0c;这就是优酷专属的下载格式啊&#xff0c;真不是一般烦人。故而切换到window系统&#xff0c;实验了一下午&#xff0c;找了所有单独的第三方软件&#xff0c;下载的时候一个…

倒置字符串(牛客)

一、题目 二、代码 #include <iostream> #include<string> using namespace std;int main() {string s;getline(cin, s);string s2;int i s.length() - 1;int prev i;int next 0;while (i > 0 && prev > 0) { //从字符串的最后往前遍历if (s[pre…

排序子序列(牛客)

目录 一、题目 二、代码 &#xff08;一&#xff09;时间复杂度过高&#xff08;通过70%&#xff09; &#xff08;二&#xff09;改进 一、题目 二、代码 &#xff08;一&#xff09;时间复杂度过高&#xff08;通过70%&#xff09; #include <iostream> #includ…

C语言内功修炼--指针详讲(进阶)

前言&#xff1a; 通过之前的指针初阶讲解&#xff0c;相信大家已经大概明白了指针的概念以及基本用法&#xff0c;这里我再来整理一下&#xff1a; 1.指针就是一个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识着内存的一块空间。 2.指针具有类型&#xff0c;指针…

单元测试的重要性

前言 在日常生活中&#xff0c;单元测试无论是对软件测试人员还是开发人员&#xff0c;都扮演着重要的角色。这主要是因为&#xff0c;单元测试在开发阶段&#xff0c;可以确保每个组件和程序都能够正常的运行。 很多开发人员都讨厌编写单元测试&#xff0c;但是它可以在开发…

变量使用、

六&#xff1a;变量使用 1.语法格式 变量调用语法&#xff1a; {{ var_name }} 案例&#xff1a; 通过命令行传递变量&#xff1a;&#xff08;通过--extra-vars或-e选项来传递keyvalue变量&#xff09; vim var.yaml 传递字典(同时传递多个变量) vim var-1.yaml 案例二…

竞赛 基于深度学习的行人重识别(person reid)

文章目录 0 前言1 技术背景2 技术介绍3 重识别技术实现3.1 数据集3.2 Person REID3.2.1 算法原理3.2.2 算法流程图 4 实现效果5 部分代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的行人重识别 该项目较为新颖&#xff0c;适合…

居家养老一键通的功能

居家养老一键通的功能 居家养老一键通是指为老年人提供全方位的居家养老服务的平台或系统。它通过整合各种资源和服务&#xff0c;为老年人提供便捷、安全、舒适的居家养老环境&#xff0c;帮助他们解决生活中的各种难题。 居家养老一键通的功能通常包括以下几个方面&#xff…

深拷贝与浅拷贝(对象的引用)

可以用赋值 1.对象的引用 代码&#xff1a; <!-- 1.对象的引用 --><script>const info{name:"lucy",age:20}const objinfo;info.name"sam"console.log(obj.name) //sam</script>图解&#xff1a; 等于号的赋值&#xff0c;对象info…

ARMv7-A 那些事 - 4.处理器模式与特权等级

By: Ailson Jack Date: 2023.09.23 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/156.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…

二、iMove-cli 本地开发模式

引言&#xff1a;iMove cli肩负着落库逻辑代码的重要责任&#xff0c;本文主要介绍本地开发模式下cli的工作。 书接上文&#xff0c;iMove主要由3个包组成&#xff1a;cli、compile-code、core&#xff0c;此外还有一个插件库plugin-store&#xff0c;这个后续文章再出&#xf…