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

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

文章目录

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

结构体类型

以书本为例,一个书本通常具有以下要素:(1)、价格(2)、书名(3)、作者(4)、页数

以生活经验来看,这些要素要用C语言的类型来描述的话,价格是一个float类型变量,书名和作者是一串字符串,页数是一个int类型的变量。

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

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

结构体类型的声明

声明一个结构体类型需要用到结构体关键字struct。

struct tag

{

struct number;

}struct variable;

比如创建一个用于声明书本的结构体。

c 复制代码
struct book
{
	float price;
	char bookname[20];
	char writer[20];
	int page;
};

这个结构体类型名是book

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

结构体变量的声明

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

c 复制代码
struct book
{
	float price;
	char bookname[20];
	char writer[20];
	int page;
};
struct book book1;

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

c 复制代码
struct book
{
	float price;
	char bookname[20];
	char writer[20];
	int page;
}book1,book2;

结构体变量的初始化

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

c 复制代码
	struct book book1 = { 22.5,"myreverie","LY",199 };

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

c 复制代码
	float price;
	char bookname[20];
	char writer[20];
	int page;

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

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

c 复制代码
	struct book book2 = { 33.5,"myreverie","LY"};

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

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

c 复制代码
struct book book3 = { .page = 199,.writer = "hello,world",
.price = 258.5,.name = "brother" };

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

结构体变量

结构体变量会在内存中按照成员的顺序以及类型为各个成员开辟一个空间

所以为结构体变量赋值不能以一整个结构体变量的方式进行赋值。正确的赋值方式是采用访问结构体内部成员的方式进行赋值。

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

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

c 复制代码
struct book book3 = { .page = 199,.writer = "hello,world",
book3.name

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

c 复制代码
struct book* pbook = &book3;
pbook->name;

结构体变量的赋值

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

以book类型的结构体为例

c 复制代码
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这个字符数组里的元素进行单个赋值

c 复制代码
	book4.name[2] = 'w';

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

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

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

c 复制代码
struct book book6 = { 74.4,"myreverie","LY",114514 };
struct book book7;
book7 = book6;

此时结构体变量book7的成员与结构体变量book6的成员的值是一致的。

结构体变量的成员

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

c 复制代码
book5.name;
book5.page;

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

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

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

c 复制代码
	printf("%d", book5.page);
	scanf("%d", &book5.page);

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

结构体变量的使用

以book这个结构体类型为例,我们来写一个记录书本数据的程序。

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

c 复制代码
struct book 
{
	float price;
	char name[20];
	char writer[20];
	int page;
};

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

c 复制代码
struct book booklib[10];

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

c 复制代码
#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用来定义函数

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,用来输出主菜单

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次。

因此对齐数的设定是牺牲内存空间来换取读取的便利性。

相关推荐
一点媛艺2 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生2 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程3 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue4 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man4 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang