C语言结构体详解

前言:

        何为结构体,结构体又是什么呢,相信有很多小伙伴对结构体还没有一个清楚的概念,今天咱也一起来探讨一下何为结构体,在C语言当中有着许多的数据类型,如char,int,long,double等等这些类型分别用来存放着相对应的数据类型,而在日常的需求当中,很多时候我们所要用到的类型都不止一个,这个时候多种数据类型,放在代码当中很容易就搞混了,此时C语言为了应对这种情况的发生,引进了一个名为结构体的概念,专门用来存储多种数据类型,将这些数据类型封装在一起,统一使用

举个例子:一个学校要存储一个学生的信息,那用代码应该如何存储呢

int main()
{char name[20] = "wangwu";    //姓名int age = 20;   //年龄char speciality[20] = "yunjisuan";   //专业int ID = 5;     //学号return 0;}

        没学结构体前,只能一个个赋值,这样写虽然没有错误,但是数据一旦多起来,会显得很麻烦,下边我们来看一下结构体又是如何存储这些数据的呢

struct Student
{char name[20];    //姓名int age;   //年龄char speciality[20];   //专业int ID;     //学号
};int main()
{struct Student s = { "wangwu",20,"yunjisuan",5 };return 0;
}

        此时关于学生相关信息的赋值一条语句即可搞定,后续这个结构体还可重复调用,比起前边没有运用结构体的语句是不是简单明了很多呢

        下边我们来了解一下结构体在C语言当中应该如何正确的调用,赋值等

结构体的声明:

struct tag
{member-list;  //数据列表
}variable-list;   //变量列表

        举个例子(这边还是以上边的代码举例):

struct Student
{char name[20];    //姓名int age;   //年龄char speciality[20];   //专业int ID;     //学号
}zhangsan,lisi,wangwu;   //可以省略到后边主函数当中定义变量int main()
{struct Student s = { "wangwu",20,"yunjisuan",5};return 0;
}

        相比之前的代码,这里在结构体结尾多了一个变量的初始化,但是上边并没有初始化变量,所以这里的variable-list是可以省略的,等后续需要的时候再去初始化

结构体的不完全声明:

        相比上边的完全声明情况下,还有一组特殊的声明方式,下边我们一起来看看它特殊在哪里:

//完全声明
struct Student
{char name[20];    //姓名int age;   //年龄char speciality[20];   //专业int ID;     //学号
}zhangsan,lisi,wangwu;//不完全声明
struct
{char name[20];    //姓名int age;   //年龄char speciality[20];   //专业int ID;     //学号
}a,a1;

        上边两段代码,有着一些细微的差异,我们仔细观察会发现,它们不同的地方,是struct后边一个有标签(tag)Student,一个后边啥也没有,而这个啥也没有的,就是我们要讲的不完全声明,那么问题来了,居然它没有标签(tag)那么我们在后续调用的时候又该如何调用呢,如果有多个不完全声明呢,这个时候我们就要注意,当它有多个的时候,编译器也不知道该调用哪一个,所以在使用不完全声明的时候,要确保你只会使用一次该声明的情况下去使用,否则可能程序会发生错误

结构体变量的创建及初始化:

        变量的创建无非就是在主函数当中以结构体类型 ,创建一个变量,初始化则是在创建的同时给它赋值,下边我们来看一组例子:

struct Student
{char name[20];    //姓名int age;   //年龄char speciality[20];   //专业int ID;     //学号
}zhangsan,lisi,wangwu;   //可以省略到后边主函数当中定义变量int main()
{struct Student s = { "wangwu",20,"yunjisuan",5 };  //变量的创建及初始化struct Student s1 = { "lisi",19,"dashuju",10 };struct Student s1 = { "zhangsan",18,"yuweng",18 };return 0;
}

结构体的自引用:

        在结构体当中可以引用自己嘛?答案是可以的,但需要注意以下几点:

1. 结构体自引用时,不可直接使用变量调用本身

2. 自引用时应避免使用typedef函数,否则可能会引起不必要的错误

(注:typedef函数作用是将要更改的函数重新命名,后续使用命名以后的名称起到同样的效果)

举例:

//错误引用方式
struct W
{char q[20];struct W s;  //很显然这个引用方式是错误的,如此引用,一层套一层,结构体只会无限变大,这不是我们想要的
};//正确引用方式
struct W
{char q[20];struct W* s;//使用指针引用该结构体的地址
};//typedef错误演示
typedef struct W
{char q[20];N* s; // C语言程序执行是由上往下执行的,此时并没有执行到将结构体命名为N,// 使用程序中的N是什么结构体并不知道,所以错误
}N;

结构体内存对齐:

对齐规则:

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

举例:

struct S1
{char c1;  //1与8比,1小,所以此处字节+1int i;    //4与8比,4小,但由于1不是4的倍数,所以字节+3来到4字节的位置上,然后+4,来到9字节的位置上char c2;  //1与8比,1小,所以此处字节+1,来到9字节的位置上
//最后比较结构体中的最大对齐数 1=1《4,所以最大对齐数为4,9不是4的倍数,所以9+3,来到12字节的位置上
?、所以最后输出的结果为12};int main()
{printf("%d\n", sizeof(struct S1));
}

  题解分析:

          

为什么会有内存对齐注意说法呢,博主找了一些资料供兄弟们参考:

修改默认对齐数:

        前边我们了解到在VS当中默认对齐数为8,那我们有没有方法去修改它呢,答案是有,使用#pragme pack(对齐数)即可,下边我们来看一组例子:

#pragma pack(2)  //将默认对齐数修改为2
struct S1
{char c1;   //1和2,1小,字节+1,到1字节位置处int i;     //2和4,2小,由于1不是2的倍数,所以先+1,来到2字节位置处,然后+4,到6字节处char c2;   //1和2,1小,6是2的倍数,所以+1,到7,7不是最后结构体中最大对齐数4的倍数,所以+1到8
};
#pragma pack()  //重新恢复为默认对齐数8
int main()
{printf("%d\n", sizeof(struct S1));
}//前边我们这段代码的执行结果为12
//现在再执行的话执行结果为8

 结构体传参:

        前边我们已经了解完了结构体如何创建及使用,那函数调用结构体时,又应该如何传参呢,下边我们一起来看一下:

(注:当变量为结构体变量时,采取(变量名.参数)的形式访问,当变量为指针的时候,采取(指针名->参数)的形式访问)

struct Mode
{char name[10];int age;double ID;
}s;void Test(struct Mode s)  //此处传的是整个结构体,传入整个结构体导致调用函数时,内存又得重新开辟一个相同大小的空间,用来专门存储该变量
{s.age = 20;s.name[10] = "zhangsna";s.age = 3.14;
}void Test(struct Mode* s)	//传的是结构体的地址,地址在内存当中占4/8个字节,所以相对上边大大减少需要申请的空间
{s->age = 20;s->name[10] = "zhangsna";s->age = 3.14;
}

        如此看来,在结构体传参的时候,我们的应该传的是地址,而不是结构体本身

结构体位段的实现:

 什么是位段:

举例:

struct Mode
{int a:2;  //代表一个int类型中访问两个bit,后续元素访问访问这个bit,直到该类型空间访问完以后,在开辟下一个int型空间int age:3;int ID:4;
}s;

位段的内存分配:

由于位段的特殊性,可能一个字节有多个元素在使用它,使用位段是不能用(&元素名)取地址的

(注:测试环境为VS2022,该平台位段存储顺序是从左往右存储,当剩余存储空间不够存储下一个元素时,舍弃该空间,重新开辟新的空间)

举例:


struct Mode
{char a:2;char age:3;char ID:4;char a2 : 5;
}s;int main()
{s.a = 4;s.age = 6;s.ID = 15;s.a2 = 33;printf("%zd", sizeof(s));
}

题解分析:

        总结:由于位段的不确定性,所以一般不支持跨平台使用,因为每个平台对应位段的设定可能是不一样的,用得好,可以节省内存空间,但不清楚规则的情况下使用,可能会达不到你想要的效果,所以关于位段请谨慎使用

(今日分享到此结束,感谢支持,如有高见,欢迎评论区留言,兄弟们一起探讨,Thanks♪(・ω・)ノ)

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

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

相关文章

成都欣丰洪泰文化传媒有限公司电商服务的行业先锋

在当今电商行业风起云涌的时代,成都欣丰洪泰文化传媒有限公司凭借其深厚的行业积淀和敏锐的市场洞察力,已经崭露头角,成为众多电商企业争相合作的对象。欣丰洪泰不仅专注于电商服务,更是以其专业的服务理念和创新的营销策略&#…

软考102-上午题-【信息安全】-杂题+小结

一、杂题 真题1: 真题2: 真题3: 真题4: 真题5: 真题6:

【JavaEE】Thread类中run和start的区别

文章目录 先说结论Run方法Start方法 先说结论 当你想要创建一个新的线程并执行某些任务时,你应该重写run方法以提供任务的具体实现,并通过调用start方法来启动新线程 run方法包含了线程应该执行的代码,但直接调用它并不会启动新的线程。 s…

yolov5目标检测可视化界面pyside6源码(无登录版)

这个是yolov5pyside6实现目标检测可视化的代码,本套项目没有用户登录的功能,如需用户登录版,看另一篇文章:yolov5pyside6登录用户管理目标检测可视化源码_yolov5用户登入功能-CSDN博客

java入门学习Day02

本文介绍的内容主要有:java的注释(样式)、关键字、字面量。 一、java中的注释 1、基本语法 ① 单行注释 //注释信息 ② 多行注释 /* 注释信息1 注释信息2, */ ③ 文档注释 /** 注释信息1 注释信息2, */ public class…

vue3封装Element分页

配置当前页 配置每页条数 页面改变、每页条数改变都触发回调 封装分页 Pagination.vue <template><el-paginationbackgroundv-bind"$attrs":page-sizes"pageSizes"v-model:current-page"page"v-model:page-size"pageSize":t…

C#预处理器指令(巨细版)

文章目录 一、预处理器指令的基本概念二、预处理器指令的基本规则三、C# 预处理器指令详解3.1 #define 和 #undef3.2 #if、#else、#elif 和 #endif3.3 #line3.4 #error 和 #warning3.5 #region 和 #endregion 四、高级应用&#xff1a;预处理器指令的最佳实践4.1 条件编译的最佳…

量化交易软件开发定制的步骤

量化交易软件的定制开发是一个复杂而精细的过程&#xff0c;需要经过一系列步骤来确保最终交付的软件符合客户的需求并具有高度的可靠性和效率。以下是量化交易软件开发定制的主要步骤&#xff1a; 1. 需求分析与规划 在开始开发之前&#xff0c;首先需要与客户深入沟通&…

k8s安装traefik作为ingress

一、先来介绍下Ingress Ingress 这个东西是 1.2 后才出现的&#xff0c;通过 Ingress 用户可以实现使用 nginx 等开源的反向代理负载均衡器实现对外暴露服务&#xff0c;以下详细说一下 Ingress&#xff0c;毕竟 traefik 用的就是 Ingress 使用 Ingress 时一般会有三个组件: …

AI新工具 小模型也有大智慧Qwen1.5-MoE;大模型动态排行榜;马斯克更新Grok-1.5

✨ 1: Qwen1.5-MoE 阿里巴巴一款小型 MoE 模型&#xff0c;只有 27 亿个激活参数&#xff0c;但性能与最先进的 7B 模型&#xff08;如 Mistral 7B 和 Qwen1.5-7B&#xff09;相匹配。 Qwen1.5-MoE是一个使用混合专家模型&#xff08;Mixture-of-Experts&#xff0c;MoE&…

每日一练 两数相加问题(leetcode)

原题如下&#xff1a; 这道题目是一道链表题&#xff0c;我们对于这种链表类&#xff0c;很显然我们最后输出的是初始节点&#xff0c;所以我们要保留我们的初始头指针&#xff0c;那么我们的第一步一定是把头指针保留一份&#xff0c;然后再让头指针往后进行操作。那么我们进行…

java子集(力扣Leetcode78)

子集 力扣原题链接 问题描述 给定一个整数数组 nums&#xff0c;数组中的元素互不相同。返回该数组所有可能的子集&#xff08;幂集&#xff09;。解集不能包含重复的子集。可以按任意顺序返回解集。 示例 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#x…