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

欢迎来到博主的专栏——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.hqwc.cn/news/230699.html

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

相关文章

wvp 视频监控平台抓包分析

抓包时机 下面的抓包时机是抓包文件最新&#xff0c;但是最有用的包 选择网卡开始抓包 如果之前已经选择网卡&#xff0c;直接开始抓包 停止抓包 重新抓包 sip播放过程分析 过滤条件 tcp.port 5060 and sip 可以看到有这些包 选择任何一个 &#xff0c;戍边右键--追踪流--…

操作系统 day14(进程同步、进程互斥、互斥的代码实现、互斥的硬件实现、互斥锁)

进程同步 概念 进程的异步性体现在&#xff0c;例如&#xff1a;当有I/O操作时&#xff0c;进程需要等待I/O操作&#xff0c;而每个I/O操作又是不同的&#xff0c;所以进程没有一个固定的顺序&#xff0c;固定的时间来执行&#xff0c;而这体现了进程的异步性。 进程互斥 …

【LeetCode:907. 子数组的最小值之和 | 贡献法 乘法原理 单调栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

光伏、储能双层优化配置接入配电网研究(附带Matlab代码)

由于能源的日益匮乏&#xff0c;电力需求的不断增长等&#xff0c;配电网中分布式能源渗透率不断提高&#xff0c;且逐渐向主动配电网方向发展。此外&#xff0c;需求响应(demand response&#xff0c;DR)的加入对配电网的规划运行也带来了新的因素。因此&#xff0c;如何综合考…

CodeMeter软件保护及授权管理解决方案(二)

客户端管理工具 CodeMeter Runtime是CodeMeter解决方案中的重要组成部分&#xff0c;其为独立软件包&#xff0c;开发者需要把CodeMeter Runtime和加密后的软件一起发布。CodeMeter Runtim包括以下组件用于实现授权的使用&#xff1a; CodeMeter License Server授权服务器 Co…

Git和Git小乌龟安装

目录 Git简介 Git安装 Git小乌龟简介 Git小乌龟安装 Git简介 Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地进行从很小到非常大的项目的版本管理。它最初是由Linux Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。Git具有速度、…

微信发红包,有哪些测试点

1、功能 1.在红包钱数&#xff0c;和红包个数的输入框中只能输入数字 2.红包里最多和最少可以输入的钱数 200 0.01 3.拼手气红包最多可以发多少个红包 100 3.1超过最大拼手气红包的个数是否有提醒 4.当红包钱数超过最大范围是不是有对应的提示 5.当发送的红包个数超过…

AI - Navmesh 寻路

用cocos2dx引擎简单实现了一下navmesh的多边形划分&#xff0c;然后基于划分多边形的a*寻路。以及路径拐点优化算法 用cocos主要是方便使用一些渲染接口和定时器。重点是实现的原理。 首先画了一个带有孔洞的多边形 //多边形的顶点数据Vec2(100, 100),Vec2(300, 200),Vec2(50…

【Java】泛型的简单使用

文章目录 一、包装类1.基本数据类型和对应的包装类2.自动装箱和自动拆箱3.手动装箱和手动拆箱 二、什么是泛型三、泛型的使用四、裸类型&#xff08;Raw Type&#xff09;五、泛型是如何编译的六、泛型的上界七、泛型方法总结 一、包装类 在了解泛型之前我们先了解什么是包装类…

Fiddler 抓包高级进阶

Fiddler 抓包高级进阶 安装直接下载我附件提供的,中文版的,可以直接使用。如果不能使用就安装.NET 框架。 HTTPS配置工具》》选项: https 都配置上。 配置HTTS证书,然后动作》》 Trust Root Certificate 。 一路确认或者 是,就行了。

WARNING: Access control is not enabled for the database.

MongoDB shell version v3.4.24 WARNING: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted. 1)未启用访问控制 2)读写访问不受限制 D:\MongoDB\Server\3.4\bin>mongo MongoDB shell version v3.4.24 c…

F22服装管理软件系统 前台任意文件上传漏洞复现

0x01 产品简介 F22服装管理软件系统是广州锦铭泰软件科技有限公司一款专为服装行业开发的综合性管理软件。该产品旨在帮助服装企业实现全面、高效的管理&#xff0c;提升生产效率和经营效益。 0x02 漏洞概述 F22服装管理软件系统UploadHandler.ashx接口处存在任意文件上传漏洞…