C语言进阶指南(16)(自定义数据类型——结构体)

news2025/1/21 12:02:39

欢迎来到博主的专栏——C语言进阶指南
博主id:reverie.ly

文章目录

    • 结构体类型
      • 结构体类型的声明
      • 结构体变量的声明
    • 结构体变量的初始化
    • 结构体变量
      • 结构体变量的赋值
      • 结构体变量的成员
      • 结构体变量的使用
      • 结构体变量的内存存储

前面使用的变量都是简单类型的变量,以及这些相同类型变量的集合——数组。
但是在日常生活中,很多物体都是复杂类型变量的集合体,只使用单一类型的话是很难概括出这个物体的。

结构体类型

以书本为例,一个书本通常具有以下要素:(1)、价格(2)、书名(3)、作者(4)、页数
以生活经验来看,这些要素要用C语言的类型来描述的话,价格是一个float类型变量,书名和作者是一串字符串,页数是一个int类型的变量。

由此可见,书本是由一个int类型的变量,两个字符串和一个float类型的变量组合在一起的集合,我们能从C语言规定的类型中找到描述书本的声明吗?

显然是不能的,因此为了解决这个问题,C语言推出了三种自定义类型的方式来帮助程序员实现复杂类型变量的声明。分别是结构体,枚举和联合体

结构体类型的声明

声明一个结构体类型需要用到结构体关键字struct
struct tag
{
struct number;
}struct variable;
比如创建一个用于声明书本的结构体。

struct book
{
	float price;
	char bookname[20];
	char writer[20];
	int page;
};

这个结构体类型名是book
price,bookname,writer,page是book类型的结构体的结构体成员。这些结构体成员构成一个不同类型的变量的集合。这个集合就是结构体变量

结构体变量的声明

结构体变量的声明和一般变量的声明规则是一致的,既然int i;是声明一个int类型的i。那么我要声明一个book类型的结构体变量,就需要用结构体声明来声明结构体。

struct book
{
	float price;
	char bookname[20];
	char writer[20];
	int page;
};
struct book book1;

除此之外,变量还可以声明在结构体声明创建的后面

struct book
{
	float price;
	char bookname[20];
	char writer[20];
	int page;
}book1,book2;

结构体变量的初始化

完成变量的声明之后,就可以给结构体的变量进行初始化了,结构体变量的初始化与数组的初始化类似,以上述的book1变量为例

	struct book book1 = { 22.5,"myreverie","LY",199 };

结构体变量的内容需要按照成员顺序来进行初始化的。以book类型的结构体的成员顺序为例

	float price;
	char bookname[20];
	char writer[20];
	int page;

那么变量初始化的内容就要按照这个顺序依次给成员进行赋值。比如book1中的初始化就是先将price赋值成22.5,bookname初始化成“myreverie”,writer初始化成“LY”,page初始化成199

没有被初始化的部分被初始化成0

	struct book book2 = { 33.5,"myreverie","LY"};

这里book2只被初始化了price,bookname和writer.而page是未被初始化的成员,因此page被初始化成0。

结构体变量可以指定成员进行初始化

struct book book3 = { .page = 199,.writer = "hello,world",
.price = 258.5,.name = "brother" };

按照这种方式进行初始化时可以忽略成员的顺序,未被初始化的成员默认是0.

结构体变量

结构体变量会在内存中按照成员的顺序以及类型为各个成员开辟一个空间
在这里插入图片描述
所以为结构体变量赋值不能以一整个结构体变量的方式进行赋值。正确的赋值方式是采用访问结构体内部成员的方式进行赋值。

访问结构体成员的符号有两个,一个是 ‘.’ (点),一个是“->”(一个减号‘-’加上一个大于‘>’组成的箭头符号)。这两个符号虽然都起到访问结构体成员的作用,但是使用的场景却不一样

'.'用于结构体类型的变量

struct book book3 = { .page = 199,.writer = "hello,world",
book3.name

而‘->’用于结构体指针类型的变量

struct book* pbook = &book3;
pbook->name;

结构体变量的赋值

当成功访问了结构体变量的成员之后,就能对这个变量进行赋值了,赋值的表达式需要符合这个结构体变量的成员的类型
以book类型的结构体为例

struct book book4 = { 0 };
book4.page=199;
book4.price = 256.5;
book4.name = "myreverie";//err
strcpy(&(book4.name), "myreverie");//right

book4.name是一个字符数组,而数组是不能对整体进行赋值的,因此想要给book4.name赋值一个字符串,需要用到字符串函数了。
或者对book4.name这个字符数组里的元素进行单个赋值

	book4.name[2] = 'w';

如果是对结构体指针变量里的成员进行赋值则要用(->)对成员先进行访问,再对访问后的结构体变量的成员进行赋值。

struct book book4 = { 0 };
struct book *book5 =&book4;
book5->name[4] = 'w';
book5->page = 1024;
book5->price = 3.14;
return 0;

C语言可以让两个相同类型结构体变量进行赋值计算。

struct book book6 = { 74.4,"myreverie","LY",114514 };
struct book book7;
book7 = book6;

此时结构体变量book7的成员与结构体变量book6的成员的值是一致的。
在这里插入图片描述

结构体变量的成员

结构体变量的成员被访问后,就变成了和成员一样类型的变量了。比如

book5.name;
book5.page;

book5.name是一个char类型的数组,那么这个成员被使用时就应该被当成一个char[20]类型的数组使用。

	scanf("%c", &book5.name[4]);
	printf("%c", book5.name[4]);
	printf("%s", book5.name);

以此类推,book5.page的使用也被当成int类型的变量来使用

	printf("%d", book5.page);
	scanf("%d", &book5.page);

包括作为函数的实参以及算术计算时,都会被当做声明时的成员的类型来使用。

结构体变量的使用

以book这个结构体类型为例,我们来写一个记录书本数据的程序。
首先考虑一个书本都有哪些数据,这些数据又是什么类型的。首先是价格,书名和作者名,以及页数。再考虑这些数据分别是什么类型的:价格是小数,float类型,书名和作者名是字符串,页数是整数,用int。那么创建出一个书本的结构体

struct book 
{
	float price;
	char name[20];
	char writer[20];
	int page;
};

接着就是对这个程序的实现了,这个程序的作用是将一本书的数据记录下来,那么就是将这个书的内容存进变量中,因此需要用到输入数据的程序。记录在书中的数据需要将其展示出来,这就需要用到输出数据的程序,因此我们将程序分为两部分,一个是用作输入的程序,一个是用作输出的程序。而这个程序不止用到一个书籍,所以需要将这个书籍的数据存放在一个结构体数组中

struct book booklib[10];

创建一个头文件library.h,用来声明函数原型

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#define book_max 10
int book_num;
struct book
{
	float price;
	char name[20];
	char writer[20];
	int page;
}booklib[book_max];
void menu(void);
void read_date(void);
void delite_date(void);
void print_date(void);

创建一个源文件library.c用来定义函数

#include"library.h"
extern book_num;
static void initname(char* name)
{
	for (int i = 0; i < 20; i++)
	{
		name[i] = 0;
	}
	return;
}
void menu(void)
{
	printf("1.   增加数据    2.删除数据    3.打印数据     0退出\n");
	printf("请选择你要进行的操作:");
}
void read_date(void)
{
	if (book_num == book_max)
	{
		printf("数据已满\n");
		return;
	}
	printf("请输入书名:");
	scanf("%s", booklib[book_num].name);
	printf("请输入作者名:");
	scanf("%s", booklib[book_num].writer);
	printf("请输入价格:");
	scanf("%g", &booklib[book_num].price);
	printf("请输入页数:");
	scanf("%d", &booklib[book_num].page);
	book_num++;
}
void delite_date(void)
{
	int No = -1;
	printf("请选择要删除的序号");
	scanf("%d", &No);
	if (No >= 0 && No < book_num)
	{
		initname(booklib[No].name);
		initname(booklib[No].writer);
		booklib[No].page = 0;
		booklib[No].price = 0.0;
		for (int i = No; i < book_num; i++)
		{
			if (No == book_num - 1)
				break;
			booklib[i] = booklib[i + 1];
		}
		book_num--;
	}
	else
	{
		printf("你选择的书籍不存在\n");
	}
	return;
}
void print_date(void)
{
	if (book_num == 0)
	{
		printf("数据还未输入\n");
		return;
	}
	int i = 0;
	for (i = 0; i < book_num; i++)
	{
		printf("%d\n", i);
		printf("%s\n", booklib[i].name);
		printf("%s\n", booklib[i].writer);
		printf("%f\n", booklib[i].price);
		printf("%d\n", booklib[i].page);
		printf("\n");
	}
	return ;
}

再创建一个源文件main.c,用来输出主菜单

#include"library.h"
int main()
{
	int input = 1;
	void (*pf[4])(void) = {NULL,read_date,delite_date,print_date};
	while (input)
	{
		menu();
		scanf("%d", &input);
		if(input)
		pf[input]();
	}
	return 0;
}

运行结结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结构体变量的内存存储

结构体类型的内存存储是具有以下对其规则的

结构体的内存对齐规则如下:
1)第一个成员在与结构体变量偏移量为0的地址处。
2)每个成员变量都需要对齐到对其数的整数倍偏移量的地址处去。
对齐数是每个变量类型的大小与编译器默认对齐数的较小值。 vc中的默认对齐数位8,gcc没有设置默认对齐数,因此对齐数位该成员的大小。
3)结构体的总大小为最大对齐数的整数倍。
4)如果嵌套了一个结构体成员变量,那么嵌套的结构体的偏移量为该结构体中最大的对齐数的倍数。该结构体的大小为该结构体中最大的对齐数的整数倍。且符合上述规

结构体的内存计算。如
在这里插入图片描述

对齐数设置的原因为:
1)平台方面:某些硬件不支持访问任意地址上的数据,因此设置对齐数来圈定访问的空间。
2)数据结构应该尽可能的对齐。原因在于当访问未对齐的数据时,处理器需要进行二次访问才能得到数据。而对其的数据只需要访问1次。
因此对齐数的设定是牺牲内存空间来换取读取的便利性。

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

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

相关文章

赴日开发做什么?日本签证很难拿?

日本的IT行业历史比较悠久&#xff0c;业务以上层前端业务为主&#xff0c;如设计和构建软件。日本IT公司组织庞大&#xff0c;行业内部有着严格的分工和部署&#xff0c;工作会被细分化。分配给个人的工作量不会太大&#xff0c;难度也不会很高。 在日本IT公司就业&#xff0…

XUbuntu22.04之隐藏顶部任务栏(一百九十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Java常见CodeReview及编码规范

鉴于自己的开发经验,以及常见容易产生bug及性能问题的点做个记录. 1.数据库 如果开发人员的经验不足,Java通过ORM(Mybatis)对数据库的操作的性能问题比较隐蔽.因为不压测或者异常case没发生的时候一般发现不了问题.特别是异常case发生的时候. 除配置表以外的sql都要经过expl…

悠络客荣获“供应链服务之星”“供应链服务之星最佳人气奖”两项殊荣

11月28日&#xff0c;由AC汽车主办的第八届汽车后市场连锁百强&TOP品牌颁奖典礼在上海盛大举行。超300家企业欢聚一堂&#xff0c;共同见证一年一度的荣耀时刻&#xff01; 经过线上征集、评选组提名、网络投票和专家评审等多轮评选&#xff0c;悠络客在全国众多参赛品牌中…

jenkins使用nexus插件

nexus介绍 Nexus 是一个强大的仓库管理工具&#xff0c;用于管理和分发 Maven、npm、Docker 等软件包。它提供了一个集中的存储库&#xff0c;用于存储和管理软件包&#xff0c;并提供了版本控制、访问控制、构建和部署等功能。 Nexus 可以帮助开发团队提高软件包管理的效率和…

二叉树(判断是否为对称二叉树)

题目&#xff08;力扣&#xff09;&#xff1a; 观察题目&#xff0c;只需判断该二叉树是否对称。 判断二叉树是否对称&#xff0c;就可以换位去判断该二叉树的左子树和右子树是否对称。 这时就可以写一个辅助函数来方便判断。 该函数是判断两颗树是否镜像对称&#xff0c;这…

分布式文件系统之HDFS

前言 一、HDFS简介 1.1 HDFS产出背景及定义 1&#xff09;HDFS产生背景 先给大家介绍一下什么叫HDFS&#xff0c;我们生活在信息爆炸的时代&#xff0c;随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&a…

C++中用于动态内存的new和delete操作符

文章目录 1、动态分配内存的应用2、动态分配内存与分配给普通变量的内存有什么不同?3、C 中如何分配/释放内存4、new 操作符4.1 使用new的语法4.2 初始化内存4.3 分配内存块4.4 普通数组声明 Vs 使用new4.5 如果运行时没有足够内存可用怎么办&#xff1f; 5、delete 操作符 C/…

【知识】简单理解为何GCN层数越多越能覆盖多跳邻居聚合信息范围更广

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 背景说明 大多数博客在介绍GCN层数时候&#xff0c;都会提到如下几点(经总结)&#xff1a; 在第一层&#xff0c;节点聚合来自其直接邻居的信息。在第二层&#xff0c;由于每个节点现在包含了其直接邻居的信息&a…

flink源码分析之功能组件(四)-slotpool组件I

简介 本系列是flink源码分析的第二个系列&#xff0c;上一个《flink源码分析之集群与资源》分析集群与资源&#xff0c;本系列分析功能组件&#xff0c;kubeclient&#xff0c;rpc&#xff0c;心跳&#xff0c;高可用&#xff0c;slotpool&#xff0c;rest&#xff0c;metrics&…

树与二叉树堆:链式二叉树的实现

目录 链式二叉树的实现&#xff1a; 前提须知&#xff1a; 前序&#xff1a; 中序&#xff1a; 后序&#xff1a; 链式二叉树的构建&#xff1a; 定义结构体&#xff1a; 初始化&#xff1a; 构建左右子树的指针指向&#xff1a; 前序遍历的实现&#xff1a; 中序…

J-Flash工具的使用---擦除、烧录及校验

文章目录 前言一、打开J-Flash工具二、使用步骤1.创建工程&#xff0c;选择MCU&#xff0c;配置端口2.打开要烧录的文件3.连接J-Link4.擦除Flash5. 烧录固件 总结 前言 不使用IDE&#xff08;如keil、Iar&#xff09;如何来烧录固件。当我们的程序需要保密&#xff0c;不需要被…

汽车悬架底盘部件自动化生产线3d检测蓝光三维测量自动化设备-CASAIM-IS(2ND)

随着汽车工业的不断发展&#xff0c;对于汽车零部件的制造质量和精度要求也在不断提高。汽车悬架底盘部件作为汽车的重要组成部分&#xff0c;其制造质量和精度直接影响到整车的性能和安全性。因此&#xff0c;采用CASAIM-IS&#xff08;2ND&#xff09;蓝光三维测量自动化设备…

滴滴也崩了!

昨日&#xff0c;据国内多家媒体报道&#xff0c;从11月27日晚上开始&#xff0c;滴滴因系统故障导致App服务异常&#xff0c;不显示定位且无法打车。 这次瘫痪事件持续了12个小时&#xff0c;根据滴滴公布的2023年第三季度财报&#xff0c;滴滴出行日均单量达到3130万单&#…

【老文新发】Otsu大津法详解及python实现

原文&#xff1a;A Threshold Selection Method from Gray-Level Histograms A Fast Algorithm for Multilevel Thresholding 前言 大津法包含两个重要的概念&#xff1a;类间方差&#xff08;between-class variance&#xff09;和类内方差&#xff08;within-class varianc…

Linux地址空间随机化

ASLR(Address Space Layout Randomization)在2005年被引入到Linux的内核 kernel 2.6.12 中&#xff0c;早在2004年就以补丁的形式引入。内存地址的随机化&#xff0c;意味着同一应用多次执行所使用内存空间完全不同&#xff0c;也意味着简单的缓冲区溢出攻击无法达到目的。 1.…

LeetCode(39)赎金信【哈希表】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 赎金信 1.题目 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。 magazine 中的每个字…

深度学习毕设项目 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model 1 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&am…

哈希_快乐数

//编写一个算法来判断一个数 n 是不是快乐数。 // // 「快乐数」 定义为&#xff1a; // // // 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 // 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 // 如果…