C语言之结构体

一.前言引入.

我们知道在C语言中有内置类型,如:整型,浮点型等。但是只有这些内置类 型还是不够的,假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要名字、年龄、学号、⾝⾼、体重等;描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问 题,增加了结构体,共用体等⾃定义的数据类型,让程序员可以⾃⼰创造适合的类型。今天我们来讲讲结构体。

二.结构体介绍.

定义:结构体是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体

结构体的声明:

struct tag
{member-list;
}variable-list;

例如:描述一个学生

struct Student
{char name[20];//名字int age;//年龄char sex[5];//性别char id[15];//学号
}s1;//s1为全局变量
int main()
{struct Student s2;//局部变量return 0;
}

结构体特殊声明

在声明结构的时候,可以不完全的声明
//在声明结构的时候,可以不完全的声明,即匿名结构体类型
struct
{int a;char b;float c;
}s1;

请判断下面代码是否正确:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//在声明结构的时候,可以不完全的声明,即匿名结构体类型
struct
{int a;char b;float c;
}s1;
struct
{int a;char b;float c;
}s2[20], * p;
int main()
{p = &s1;return 0;
}
警告:
编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次

结构体变量的定义:

struct Point
{int x; int y;
}s1;//结构体变量的定义
struct Point s2;//结构体变量的定义
int main()
{struct Point s3;//结构体变量的定义return 0;
}

结构体变量的初始化:

struct Stu
{char name[20];int age;
};
int main()
{struct Stu s1 = { "zhangsan",18 };///结构体变量初始化	return 0;
}

 镶嵌初始化:

struct Point
{int x;int y;
};
struct Stu
{char name[20];int age;struct Point s2;
};
int main()
{struct Stu s1 = { "zhangsan",18,{2,3} };///结构体变量镶嵌初始化	return 0;
}

三.结构成员访问操作符.

3.1.结构体成员的直接访问

使⽤⽅式:结构体变量.成员名

结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数.
struct Point
{int x;int y;
}s1 = {3,5};
int main()
{struct Point s2 = { 2,4 };printf("%d %d\n", s1.x, s1.y);printf("%d %d\n", s2.x, s2.y);return 0;
}

3.2.结构体成员的间接访问

使⽤⽅式:结构体指针->成员名

有时候我们得到的不是⼀个结构体变量,⽽是得到了⼀个指向结构体的指针.

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct Point
{int x;int y;
}s1 = { 3,5 };
int main()
{struct Point s2 = { 2,3 };struct Point* pi1 = &s1;struct Point* pi2 = &s2;printf("%d %d\n", pi1->x, pi1->y);printf("%d %d\n", pi2->x, pi2->y);pi1->x = 10;pi1->y = 20;printf("%d %d\n", pi1->x, pi1->y);return 0;
}

综合使用结构体用例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
struct Stu
{char name[10];int age;
};
void Print(struct Stu s2)
{printf("%s %d\n", s2.name, s2.age);
}
void Set(struct Stu* s3)
{s3->age = 15;strcpy(s3->name, "李四");
}
int main()
{struct Stu s1 = { "zhangsan",18 };Print(s1);Set(&s1);Print(s1);return 0;
}

结果:

四.结构体的⾃引⽤.

五.结构体的内存对齐.

计算结构体的⼤⼩

例如:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
struct s1
{char a;int b;char c;
};
struct s2
{char a;char b;int c;
};
int main()
{printf("%zd\n", sizeof(struct s1));printf("%zd\n", sizeof(struct s2));return 0;
}

如果我给你一份这样的代码,请问你认为结果如何呢?两者一样吗?

答案如下:

和你想的一样吗?

如果不一样,不妨来看看我的解读:

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

例题1:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct s1
{//1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处char a;//对齐偏移量为0位置处,char一个字节//其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。//对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。  //对齐数VS 中默认的值为 8 char b;//对齐偏移量为1位置处,char一个字节,即 1 8 1(第一个为成员变量的对齐数,第二个为VS对齐数,第三个为该行对齐数)int c;//对齐偏移量为4位置处,int四个字节,即 4 8 4
};	
int main()
{//结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍,即1 1 4 8 ->8printf("%zd\n", sizeof(struct s1));//结果为8return 0;
}

结果为:

例题二:

struct s2
{//1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处char a;//对齐偏移量为0位置处,char一个字节//其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。//对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。  //对齐数VS 中默认的值为 8 int b;//对齐偏移量为4位置处,四个字节,int: 4 8 4(第一个为成员变量的对齐数,第二个为VS对齐数,第三个为该行对齐数)char c;//对齐偏移量为9位置处,char 1个字节;1 8 1};
int main()
{//结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍,即1 4 1  ->4的整数倍为12(原来的数据已经达到偏移量为9了)printf("%zd\n", sizeof(struct s2));//结果为12return 0;
}

结果:

例题三:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct s3
{//下面简写//前三个为成员大小 VS大小 该行大小,偏移量的位置double d;//8 8 8,0-7char c;//1 8 1,8int i;//4 8 4.12-15
};
int main()
{printf("%zd\n", sizeof(struct s3));//结果8 1 4->8 15->16return 0;
}

结果:

例题四:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
练习4-结构体嵌套问题
struct s3
{//下面简写//前三个为成员大小 VS大小 该行大小,偏移量的位置double d;char c;int i;
};
struct s4
{char c1;//1 8 1,0//4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍struct s3 a;//16 8 8,8-23double d;//8 8 8,24-31
};
int main()
{printf("%zd\n", sizeof(struct s4));//1 16 8->16 31->32return 0;
}

你是否想过这样一个问题,为什么存在内存对⻬呢?
平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,
重点:如何做到: 让占⽤空间⼩的成员尽量集中在⼀起!!!
重点:如何做到: 让占⽤空间⼩的成员尽量集中在⼀起!!!
重点:如何做到: 让占⽤空间⼩的成员尽量集中在⼀起!!!

说三遍!

相信你现在一定会开头的那题了!

那么我们可以自己设置对齐数吗?答案是当然可以啦!

#pragma 这个预处理指令,可以改变编译器的默认对⻬数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#pragma pack(1)
练习4-结构体嵌套问题
struct s3
{//下面简写//前三个为成员大小 VS大小 该行大小,偏移量的位置double d;char c;int i;
};
struct s4
{char c1;//1 8 1,0//4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍struct s3 a;//16 8 8,8-23double d;//8 8 8,24-31
};
int main()
{printf("%zd\n", sizeof(struct s4));//1 16 8->16 31->32return 0;
}

修改之后结果为:

#pragma pack()//取消设置的对⻬数,还原为默认

这样,当结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对齐数。

六.结构体传参和对比

1.传数据

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct S
{int data[1000];int num;
};
void Print(struct S s1)
{printf("%d\n", s1.num);
}
int main()
{struct S s1 = { {1,2,3,4}, 1000 };Print(s1);return 0;
}

结果:

2.传地址

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct S
{int data[1000];int num;
};
void Print(struct S* s1)
{printf("%d\n", s1->num);
}
int main()
{struct S s1 = { {1,2,3,4}, 1000 };Print(&s1);return 0;
}

结果:

对比两者,你认为哪个好呢?

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

最后,学习进步!!!

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

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

相关文章

Spark经典案例分享

Spark经典案例 链接操作案例二次排序案例 链接操作案例 案例需求 数据介绍 代码如下&#xff1a; package base.charpter7import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FileSystem, Path} import org.apache.spark.SparkContext import org.a…

四、Zookeeper节点类型

目录 1、临时节点 2、永久节点 Znode有两种,分别为临时节点和永久节点。 节点的类型在创建时即被确定,并且不能改变。 1、临时节点 临时节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,

OpenCV-Python:计算机视觉介绍

目录 1.背景 2.计算机视觉发展历史 3.计算机视觉主要任务 4.计算机视觉应用场景 5.知识笔记 1.背景 OpenCV是计算机视觉的一个框架&#xff0c;想要学习OpenCV&#xff0c;需要对计算机视觉有一个大致的了解。计算机视觉是指通过计算机技术和算法来模拟人类视觉系统的能力…

Redis高效缓存:加速应用性能的利器

目录 引言 1. Redis概述 1.1 什么是Redis&#xff1f; 1.2 Redis的特点 2. Redis在缓存中的应用 2.1 缓存的重要性 2.2 Redis作为缓存的优势 2.3 缓存使用场景 3. Redis在实时应用中的应用 3.1 实时数据处理的挑战 3.2 Redis的实时数据处理优势 3.3 实时应用中的Red…

mediapipe+opencv实现保存图像中的人脸,抹去其他信息

mediapipeopencv MediaPipe本身不提供图像处理功能&#xff0c;它主要用于检测和跟踪人脸、手势、姿势等。如果您想要从图像中仅提取人脸主要信息并去除其他信息. # codingutf-8 """project: teatAuthor&#xff1a;念卿 刘file&#xff1a; test.pydate&…

Kubernetes学习笔记-Part.09 K8s集群构建

目录 Part.01 Kubernets与docker Part.02 Docker版本 Part.03 Kubernetes原理 Part.04 资源规划 Part.05 基础环境准备 Part.06 Docker安装 Part.07 Harbor搭建 Part.08 K8s环境安装 Part.09 K8s集群构建 Part.10 容器回退 第九章 K8s集群构建 9.1.集群初始化 集群初始化是首…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《余电上网/制氢方式下微电网系统全生命周期经济性评估》

该标题涉及到对微电网系统的全生命周期经济性进行评估&#xff0c;其重点关注两种运营方式&#xff1a;余电上网和制氢。以下是对标题的解读&#xff1a; 微电网系统&#xff1a; 微电网是指一种小规模的电力系统&#xff0c;通常包括分布式能源资源&#xff08;如太阳能、风能…

ES通过抽样agg聚合性能提升3-5倍

一直以来&#xff0c;es的agg聚合分析性能都比较差&#xff08;对应sql的 group by&#xff09;。特别是在超多数据中做聚合&#xff0c;在搜索的条件命中特别多结果的情况下&#xff0c;聚合分析会非常非常的慢。 一个聚合条件&#xff1a;聚合分析请求的时间 search time a…

部署springboot项目到GKE(Google Kubernetes Engine)

GKE是 Google Cloud Platform 提供的托管 Kubernetes 服务&#xff0c;允许用户在 Google 的基础设施上部署、管理和扩展容器。本文介绍如何部署一个简单的springboot项目到GKE. 本文使用podman. 如果你用的是docker, 只需要把本文中所有命令中的podman替换成docker即可 非H…

java+springboot物资连锁仓库经营商业管理系统+jsp

主要任务&#xff1a;通过网络搜集与本课题相关的素材资料&#xff0c;认真分析连锁经营商业管理系统的可行性和要实现的功能&#xff0c;做好需求分析&#xff0c;确定该系统的主要功能模块&#xff0c;依据数据库设计的原则对数据库进行设计。最后通过编码实现本系统功能并测…

Linux周期任务

我自己博客网站里的文章 Linux周期任务&#xff1a;at和crontab 每个人或多或少都有一些约会或者是工作&#xff0c;有的工作是长期周期性的&#xff0c; 例如&#xff1a; 每个月一次的工作报告每周一次的午餐会报每天需要的打卡…… 有的工作则是一次性临时的&#xff0…

Prometheus+Grafana搭建日志采集

介绍 一、什么是日志数据采集 日志数据采集是指通过各种手段获取应用程序运行时产生的各类日志信息&#xff0c;并将这些信息存储到特定的地方&#xff0c;以便后续分析和使用。通常情况下&#xff0c;这些日志信息包括系统运行状态、错误信息、用户操作记录等等。通过对这些…

牛客算法题 【HJ97 记负均正】 golang实现

题目 HJ97 记负均正 描述 首先输入要输入的整数个数n&#xff0c;然后输入n个整数。输出为n个整数中负数的个数&#xff0c;和所有正整数的平均值&#xff0c;结果保留一位小数。 0即不是正整数&#xff0c;也不是负数&#xff0c;不计入计算。如果没有正数&#xff0c;则平均…

大文件分片上传、分片进度以及整体进度、断点续传(一)

大文件分片上传 效果展示 前端 思路 前端的思路&#xff1a;将大文件切分成多个小文件&#xff0c;然后并发给后端。 页面构建 先在页面上写几个组件用来获取文件。 <body><input type"file" id"file" /><button id"uploadButton…

动态规划学习——回文串

目录 一&#xff0c;回文子串 1.题目 2.题目接口 3&#xff0c;解题代码及其思路 解题代码&#xff1a; 二&#xff0c; 分割回文串II 1&#xff0c;题目 2&#xff0c;题目接口 3&#xff0c;解题思路及其代码 一&#xff0c;回文子串 1.题目 给你一个字符串 s &…

模板初阶(2):函数模板的匹配原则,类模板的实例化

一、函数模板的匹配原则 int Add(const int& x, const int& y) {return x y; }template <class T> T Add(const T& x, const T& y) {return x y; }int main() {int a1 1, a2 2;Add(a1, a2);double d1 1.1, d2 2.2;Add(d1, d2);return 0; }一个非模…

【搭建网站】搭建一个自己的网站

【搭建网站】搭建一个自己的网站 传送门&#xff1a;搭建一个自己的网站&#xff1f;看这个就够了&#xff01; P1&#xff0c;建站准备 P2&#xff0c;创建站点

ZooKeeper 如何保证数据一致性?

在分布式场景中&#xff0c;ZooKeeper 的应用非常广泛&#xff0c;比如数据发布和订阅、命名服务、配置中心、注册中心、分布式锁等。 ZooKeeper 提供了一个类似于 Linux 文件系统的数据模型&#xff0c;和基于 Watcher 机制的分布式事件通知&#xff0c;这些特性都依赖 ZooKee…

【开源】基于JAVA语言的桃花峪滑雪场租赁系统

项目编号&#xff1a; S 036 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S036&#xff0c;文末获取源码。} 项目编号&#xff1a;S036&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设…