【c语言】自定义类型----结构体

结构体是c语言的一种自定义类型,自定义类型对于开发者及其重要的类型,它可以随意由开发者进行谱写功能,而今天的结构体可以用来表示一种变量的单个或多种具体属性,再编写代码时有着不可替代的作用!!!!

目录

前言:自定义类型

一、结构体类型的声明

1.1结构体的面貌

1.2结构体的定义 

1.3特殊的声名-----匿名结构体类型

1.4结构体的自引用

二、结构体的定义和引用

2.2 结构体内存对齐



前言:自定义类型

什么是自定义类型?我们在编写代码的过程中,会遇到许多类型,比如:短整型,整型,字符类型,布尔型,浮点型等多种类型,可这些都是c语言库中自带的,编写者在编写过程中只需要记住就好。

举个例子:你要定义一个变量为整数,就需要定义该变量为int型,写一个字符,就要定义一个char类型。定义时只需对应拿出即可,但是你要定义一个人的基本信息怎么办,如电话,地址,性别,名字,身份等多种属性,在c语言给出的类型中,并没有具体的类型可以去定义,所以这时就需要编写者自己根据具体的需求,去编写自己的类型,我们称为自定义类型.

而结构体类型就是常见的自定义类型的一种。


提示:以下是本篇文章正文内容,下面案例可供参考

一、结构体类型的声明

1.1结构体的面貌

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

例如我们要表示一个人,那么他的名字,电话号码,身份,性别,年龄就是他的成员变量,名字可以是字符型,电话可以是整型,身份,性别都可以是整型,就是这些成员变量构成了一个结构。

1.2结构体的定义 

struct tag

{

成员列表1.;

成员列表2.

}列表变量;

struct 为结构的关键字,tag 则为这个结构的类型名 

为了更进一步了解,我们描述一位学生

struct su
{
	char name[20];//名字
	int age;//年龄
	char sex[];//性别
};

其中“;”(分号)是千万不能丢的。 

1.3特殊的声名-----匿名结构体类型

什么叫匿名结构体类型,顾名思义,就是结构体的类型是匿名的

struct 
{
    char name[20];//名字
    int age;//年龄
    char sex[];//性别
};

对比一下,少了“su”结构体类型,匿名结构体固然很帅,但是也是有很大的弊端的。

先对比两组代码

代码一:

struct 
{
	char name[20];//名字
	int age;//年龄
	char sex[];//性别
}li;

代码二:

struct 
{
	char name[20];//名字
	int age;//年龄
	char sex[];//性别
}*p;

我们在编译器上运行一下

我们会发现:

因为如果我们不写类型名,系统就不知道它们两个各自是什么类型

注意:指针是指向同类型的变量的,而这两个结构没有明确的类型。所以系统不认为他们是同一类型的结构,所以会报错。

就像一个字符指针无法接收一个整型变量。匿名结构体也是一样的,所以是非法的。

1.4结构体的自引用、

在内存中有可能是连续存放的,也有可能是像这样不连续存放的,随机存放的。

这样的1,2,3,4,5的数字我们称作节点,在节点里除了存放数据外,还有可能存放下一个节点的信息,我们将一个节点看作一个结构体,那么,结构体可以自己找到自己吗?

自引用类型:Node;

所以我们可以写出自引用代码

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

为什么这个不行哪???

仔细分析,其实本来就不行,结构体引用了同类型的结构体,结构体的大小会变的无限大,最终可能会导致内存崩溃。

但是如果不能自引用,那么为什么还要说明自引用哪?

所以是有正确的自引用方式的

我们在每个节点存放下一个节点的信息,在一个节点处存放下一个节点的地址,因此可以在结构体中存放一个结构体指针类型,这样既能解决同类型的问题不会导致无限大,还能找到下一个结构体节点的信息。

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

 这次我们发现,编译器不再报错。。。

问题:在结构体⾃引⽤使⽤的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引⼊问题,看看 下⾯的代码,可⾏吗?

解答:这段代码其实是错误的,他将Node的重命名定义在了使用Node的后面,这样会导致系统无法正常识别Node是什么,所以程序错误。

二、结构体的定义和引用

初始化方式:

1.最标准的定义:

 struct su
{
	char name[20];//名字
	int age;//年龄
	char sex[];//性别
};
 int main()
 {
	 struct su  a = { "qingkai",18,"n"};//初始化
	 printf("名字%s\n年龄%d\n性别%c\n", a.name, a.age, a.sex);//引用
	 return 0;

 }

2.最不环保的方式:

#include <stdio.h>
struct student /*声明时直接定义*/
{
int age;  /*年龄*/
float score;  /*分数*/
char sex;   /*性别*/
/*这种方式不环保,只能用一次*/
} a={21,80,'n'};
int main ()
{
 printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );
return 0;
}

3.最烂的方式

2. 

struct   //直接定义结构体变量,没有结构体类型名。这种方式最烂
{
int age;
float score;
char sex;
} t={21,79,'f'};
int main ()
{
printf("年龄:%d 分数:%f 性别:%c\n", t.age, t.score, t.sex);
return 0;
}

2.1.2引用的方式 :

2.1.1.1"操作符引用

"."操作符引用:结构体变量.成员列表

如a.name,a.age,a.sex;

这种直接使用成员列表。

2.2.2.2"->"操作符引用:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct book
{
	char book_name[20];//书名
	char author[20];//作者
	char id[20];//编号
	float price;//价格
};
int main()
{
	struct book s1 = { "c语言","谭浩强","THQ3324",55.5f };
	struct book s2 = { .author = "谭浩强",.book_name = "c语言",.id = "THQ3324",55.5f };//.引用
	struct book* ps = &s2;
	printf("%s %s %s %f\n", s1.author, s1.book_name, s1.id, s1.price);
	printf("%s %s %s %f\n", ps->author, ps->book_name, ps->id, ps->price);//->引用
	return 0;
}

我们可以看出:

我们看到这两种引用方式的结果一模一样,区别是箭头引用只能是指针变量使用。2.

2.2 结构体内存对齐

内存是什么???,为什么会有内存对齐???

我们都知道一个整型占4个字节,一个字符类型占1个字节,而占的字节数,就是所需内存

。但是一个结构体因为定义的类型各有不同,那么结构体占多少内存哪??

别急,我们对比代码

代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct book1
{
	char book_name;//书名
	char author;//作者
	char id;//编号
	int price;//价格
};
struct book2
{
	char bok_na;
	char auy;
	int price;
	char id;
};
int main()
{
	printf("%d\n", sizeof(struct book1));
	printf("%d\n", sizeof(struct book2));
	return 0;
}

不妨先猜一猜:char类型有三个,占3个字节,int类型有一个,占4个字节,最后一定都是占4+3==7个字节吧!!!

运行代码:

????,为什么哪,不要疑惑,不要惊讶,这就是内存对齐!!!!

2.2.1内存对齐

   对齐规则

  • 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
  •  其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 
  • 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值

- VS 中默认的值为 8

- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩

  •  结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
  • .如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

这里我们先介绍一种宏offsetof作用是计算结构体成员相较结构体变量起始位置的偏移量。

偏移量就是相较于起始位置的字节偏移量

偏移量 - C++ 参考 (cplusplus.com)

这里我们以struct book1类型结构体为例,

#include<stdio.h>
#include<stddef.h>//头文件

struct book1
{
	char book_name;//书名
	char author;//作者
	char id;//编号
	int price;//价格
};
int main()
{
	printf("%d\n", offsetof(struct book1, book_name));
	printf("%d\n", offsetof(struct book1, author));
	printf("%d\n", offsetof(struct book1, id));
	printf("%d\n", offsetof(struct book1,price ));
	return 0;
}

运行代码:

接下来我们就一点一点深挖这结果的由来

1.无结构体嵌套

我们已经知道4个成员的偏移量分别是:0,1,2,4

由此我们可以画出

我们可以看出,正好8个字节,与程序运行的结果相同

 中间的画红×的字节就被舍弃了,舍弃的原因我们稍后讲解。 

2.代码2

struct s2
{
	char c1;
	int n;
	char c2;

};

int main()
{
	printf("%zd\n", offsetof(struct s2, c1));
	printf("%zd\n", offsetof(struct s2, c2));
	printf("%zd\n", offsetof(struct s2, n));
	return 0;
}

乍一看没什么不一样,我们运行结果试一试 

三个成员的偏移量为0,4,8

这里的字节最高字节数看着是9,但是还记得对齐规则吗?

 所以在以上struct s2中一开始第一个成员c1的大小为1,比vs默认偏移数8小,所以偏移数就是1,c1大小为1

第二个成员n的大小为4,比vs默认偏移数8小,所以偏移数就是4,所以n从第五个字节开始,n大小为4字节

第三个成员c2的大小为1,比vs默认偏移数8小,所以偏移数就是1,所以n从第九个字节开始,c2大小为1字节

在此还未结束,因为结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍

该结构体struct s2最大对齐数为4,所以结构体大小必须是4的整数倍,

9字节并不是4的整数倍,所以struct s2大小为12字节 

 2.有结构体嵌套的内存对齐


#include<stdio.h>
struct S3
{
	double x;
	char c;
	int n;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
	printf("%zd\n", sizeof(struct S4));
	
	return 0;

}
  •  如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

所以对于嵌套结构体来说,在s4结构体中,s3的结构体成员要对齐到8的整数倍

s4的整体大小是所有最大对齐数的整数倍

  • c1的大小为1个字节,偏移量为0
  •  s3偏移量为8,大小为16字节
  • d的大小为8个字节,偏移量为24

最终结构体struct S4大小就为32个字节 

2.2 为什么存在内存对齐


 1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

例如在以上结构体struct S中如果机器每次内存读取4个字节,在未对齐情况下就要读取两次,而在对齐的情况下就只要读取一次 


总体来说:结构体的内存对齐是拿空间来换取时间的做法

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起

2.3 修改默认对齐数


在以上的学习中我们知道了VS的默认对齐数是8,那么有什么办法可以修改默认对齐数呢?
其实是有办法的,利用#pragma 这个预处理指令,可以改变编译器的默认对齐数。

#include<stdio.h>
#pragma pake(1)//修改默认对齐数为2
struct s1
{
    char c1;
    char c2;
    int n;
};
#pragma pake()//恢复默认对齐数
int main()
{
    printf("%zd", sizeof(struct s1));
    return 0;
}


 使用了#pragma后默认对齐数就被修改为了2,结构体struct s1的大小就变成6字节

结构体传参

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", s.data[i]);
	}
	printf("\n");

	printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", ps->data[i]);
	}
	printf("\n");
	printf("%d\n", ps->num);
}
int main()
{

	print1(s); //传结构体
	print2(&s); //传地址
	return 0;
}

 在函数传参的时候分为传值调用和传址调用,结构体中也有两种调用,如print1是传值调用,print2是传址调用。虽然两种方式都能找到结构体成员,但是对于系统来说,传值调用是很不利的,原因是使用传址调用不用再开辟一块内存空间,新创建的指针就指向了原来的那块内存空间,而使用传值调用时,需要再创建一块新的内存空间,如果这个结构体像以上struct S一样所占内存空间很大的话,再创建新的内存就很浪费内存,还有函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

结论:结构体传参最好用传址调用。

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

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

相关文章

编译原理-词法分析(实验 C语言)

编译原理-词法分析 1. 实验目的 设计、编写并调试一个词法分析程序&#xff0c;加深对词法分析原理的理解 2. 实验要求 2.1 待分析的简单语言的词法 关键字&#xff1a;begin&#xff0c;if&#xff0c;then&#xff0c;while&#xff0c;do&#xff0c;end 所有关键字都是…

Nginx(openresty) 查看连接数和并发送

1 通过浏览器查看 #修改nginx配置文件 location /status {stub_status on;access_log off;allow 192.168.50.0/24;deny all;} #重新加载 sudo /usr/local/openresty/nginx/sbin/nginx -s reloadActive connections //当前 Nginx 当前处理的活动连接数。 server accepts handl…

HPC: perf入门

如果你想查看你的程序在cpu上运行时&#xff0c;耗时时如何分布的&#xff0c;那么perf是一个合理的选择。 准备工作 为了支持使用perf&#xff0c;首先你要安装相关的库 sudo apt install linux-tools-5.15.0-67-generic此外&#xff0c;因为使用perf进行benchmark&#xf…

四种跨域解决方案

文章目录 1.引出跨域1.基本介绍2.具体演示1.启动之前学习过的springboot-furn项目2.浏览器直接访问 [localhost:8081/furns](http://localhost:8081/furns) 可以显示信息3.启动前端项目&#xff0c;取消请求拦截器&#xff0c;这样设置&#xff0c;就会出现跨域4.跨域原因 2.跨…

linux指令--sed

sed 主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。 语法解析 sed [选项] 编辑命令 文件 选项&#xff1a; -n&#xff1a;只显示匹配处理的行-e&#xff1a;执行多个编辑命令时-i&#xff1a;在原文件中进行修改&#xff0c;不输出到屏幕-…

零基础入门学用Arduino 第二部分(一)

重要的内容写在前面&#xff1a; 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。个人把这个教程学完之后&#xff0c;整体感觉是很好的&#xff0c;如果有条件的可以先学习一些相关课程&#xff0c;学起来会更加轻松&#xff0c;相关课程有数字电路…

系统思考—决策

为‮么什‬模型及‮式公‬通常比人‮决类‬策更有效&#xff1f;究‮是竟‬什么让‮些某‬模型和公‮表式‬现出色&#xff1f;事实上&#xff0c;我‮应们‬该探究的‮人是‬类在决策‮程过‬中的不足。关‮在键‬于人类‮策决‬中存在的“噪声”。尽‮模管‬型或公‮不式‬完…

【JavaScript】内置对象 - 字符串对象 ⑤ ( 判断对象中是否有某个属性 | 统计字符串中每个字符出现的次数 )

文章目录 一、判断对象中是否有某个属性1、获取对象属性2、判定对象是否有某个属性 二、统计字符串中每个字符出现的次数1、算法分析2、代码示例 String 字符串对象参考文档 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String 一、判…

【NI国产替代】电池模拟器,快速模拟 3C 产品电池的充放电功能

电池模拟器 快速模拟 3C 产品电池的充放电功能输出灵活可调节的电压/电流内置双向 DC-DC 降压变换器为 3C 产品提供漏电检测 电池模拟器系列包含单节双通道&#xff08;1S&#xff09;、双节双通道&#xff08;2S&#xff09;、三节单通道&#xff08;3S&#xff09;三种规格&…

贪心(不相交的开区间、区间选点、带前导的拼接最小数问题)

目录 1.简单贪心 2.区间贪心 不相交的开区间 1.如何删除&#xff1f; 2.如何比较大小 区间选点问题 3.拼接最小数 1.简单贪心 比如&#xff1a;给你一堆数&#xff0c;你来构成最大的几位数 2.区间贪心 不相交的开区间 思路&#xff1a; 首先&#xff0c;如果有两个…

LeetCode刷题之HOT100之颜色分类

下午好呀&#xff0c;大家&#xff01;昨天估计是喝了假酒&#xff0c;现在没有胃口&#xff0c;喝酒真的没有任何好处。以后尽量避免此活动。今天几乎没睡觉&#xff0c;准备做完这题回宿舍&#xff0c;把电脑也带回去。 1、题目描述 2、逻辑分析 对颜色排序&#xff0c;要求…

数字孪生技术体系和核心能力整理

最近对数字孪生技术进行了跟踪调研学习,整理形成了调研成果,供大家参考。通过学习,发现数字孪生技术的构建过程其实就是数字孪生体的构建与应用过程,数字孪生体的构建是一个体系化的系统工程,数字化转型的最终形态应该就是数实融合互动互联的终极状态。数实融合是每个行业…

自定义类型:结构体+结构体内存对齐+结构体实现位段

结构体内存对齐实现位段 一.结构体1.结构体的声明2.结构体变量成员访问操作符3.结构体传参4.匿名结构体5.结构的自引用 二.结构体内存对齐1.对齐规则2.为什么存在内存对齐&#xff1f;3.修改默认对齐数 三.结构体实现位段1.什么是位段2.位段的内存分配3.位段的跨平台问题4.位段…

flink读取hive写入http接口

目录 0、创建hive数据 1、pom.xml 2、flink代码 3、sink 4、提交任务jar 5、flink-conf.yaml 6、数据接收 flink-1.17.2jdk1.8hive-3.1.3hadoop3.3.6passwordhttp0、创建hive数据 /cluster/hive/bin/beeline !connect jdbc:hive2://ip:10000 create database demo; d…

2024 年最新 Python 基于百度智能云实现短语音识别详细教程

百度智能云语音识别 采用国际领先的流式端到端语音语言一体化建模算法&#xff0c;将语音快速准确识别为文字&#xff0c;支持手机应用语音交互、语音内容分析、机器人对话等场景。百度短语音识别可以将 60 秒以下的音频识别为文字。适用于语音对话、语音控制、语音输入等场景…

HTTP-web服务器

web服务器 web服务器实现了http和相关的tcp连接处理&#xff0c;负责管理web服务器提供的资源&#xff0c;以及对服务器的配置&#xff0c;控制以及拓展等方面的管理 web服务器逻辑实现了http协议&#xff0c;并负责提供web服务器的管理功能&#xff0c;web服务器逻辑和操作系…

skywalking基础使用

skywalking基础使用 找链路追踪Id将链路追踪Id拿到skywalking-ui中筛选对应链路补充说明例如, sql的打印能让我们了解到代码中对应的sql是否符合预期 找链路追踪Id 在接口响应header中复制x-trace-id 这个接口响应正常了, 异常没有暴露到前端, 且调用链路很长, 但我们借助s…

校园外卖系统的技术架构与实现方案

随着校园生活的日益现代化&#xff0c;外卖需求在高校学生群体中迅速增长。为了满足这一需求&#xff0c;校园外卖系统应运而生。本文将详细探讨校园外卖系统的技术架构及其实现方案&#xff0c;帮助读者了解这一系统的核心技术与实现路径。 一、系统概述 校园外卖系统主要包…

【旅行】关于毕业旅行与长期旅行计划(城市、攻略、预算、交通、面基等)

【旅行】关于毕业旅行与长期旅行计划&#xff08;城市、攻略、预算、交通、面基等&#xff09; 文章目录 一、目的地与去哪儿玩1、可能2、人民币3、国家地理4、省份与城市5、环球旅行 二、攻略之怎么玩&#xff08;旅行预算、攻略&#xff09;1、旅行预算之交通、住宿、门票等2…

Linux 35.5 + JetPack v5.1.3@RACER编译安装

Linux 35.5 JetPack v5.1.3RACER编译安装 1. 源由2. 编译&安装Step 1&#xff1a;依赖库安装Step 2&#xff1a;LKH-3安装Step 3&#xff1a;建立工程Step 4&#xff1a;编译工程Step 5&#xff1a;安装工程 3. 问题汇总3.1 组件ros-noetic-multi-map-server问题3.2 swarm…