理解C语言——从小菜到大神的晋级之路(10)——结构体、联合体

2023-11-23 04:38

本文主要是介绍理解C语言——从小菜到大神的晋级之路(10)——结构体、联合体,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本节视频链接:点击这里


        上篇中讲述的数组是复合数据类型中最简单的一种,一个数组使用一段连续的内存保存了若干个类型相同的数据元素。由于类型和长度相同,数组的每个元素通过数组下标和指针变量访问。如果我们希望一个结构保存多个不同类型的数据元素,那么数组将无能为力。为了实现这样的功能,C语言提供了结构体和联合体。



1、结构体基本概念


(1)结构体的定义


        假设我们需要定义一个图形中的点的概念。在一个使用笛卡尔坐标系表示图像的系统中,点的位置使用两个坐标分量表示,即横坐标x和纵坐标y。那么为了定义点这个变量,我们需要将坐标的两个分量定义于一起。将坐标x和坐标y定义在结构体中的方法为:
struct Point
{int x;int y;
};

        定义一个结构体使用关键字struct实现。关键字struct定义了结构体名称,并在其后用大括号{ }指明了结构体的成员。每一个结构体成员是该结构体实例中所包含的数据。如我们定义的Point结构中就包含了x和y两个int型数据成员。在定义结构体的同时可以定义结构体的实例,如:

struct Point
{int x;int y;
} pt1, pt2, *ppt1;

        另一种方法也可以将定义结构体类型和定义结构体实例分开:

struct Point
{int x;int y;
};
struct Point pt1, pt2, *ppt1;

        在定义结构体时,更常用是使用typedef定义一种新的类型,这样后面再定义新的结构体实例时,便不需要再添加关键字struct,使得我们定义的结构体更像C语言提供的其他基本数据类型一样,使用更加简洁。

typedef struct _Point
{int x;int y;
} Point;
Point pt1, pt2, *ppt1;


(2)结构体成员的访问


        当我们定义了结构体实例后,可以轻松访问包含在结构体中的各种变量。例如我们定义了以下的Point变量:
Point pt1 = {1,3}, pt2 = {4, 8}, *ppt1 = &pt1;

        使用结构体成员运算符“.”,可以按照结构体成员的名称获取其数值:

printf(“Point 1: (%d, %d), Point 2: (%d, %d)\n”, pt1.x, pt1.y, pt2.x, pt2.y);
float distance = sqrt(pow(pt1.x-pt2.x, 2) + pow(pt1.y-pt2.y, 2));
printf(“Distance of two points: %f”, distance);

        对于结构体实例,可以直接访问使用其内部成员,而使用指向结构体实例的指针也可以间接访问结构体的成员,其方法不再是使用”.”而是”->”。例如:

printf(“Point 1: (%d, %d)\n”, ppt1->x, pt1->y);

        对于使用指针访问结构体成员,其效果同使用实例访问结构体成员是一致的。在实际使用时,使用指针的情况还更加频繁。


(3)结构体成员的初始化


        从前面的程序中可以看出,结构体的初始化方法同数组的初始化方法比较类似,使用的是一对大括号所包括的、由逗号分隔的多个初始值。同初始化数组不同的是,由于结构体中的成员可以是不同的数据类型,初始化结构体的数据也可以依据结构体成员的定义类型不同而不同。结构体成员类型相同时的初始化方法已在上一节有所体现,现在我们来定义一个更复杂的结构体:
typedef struct _Student
{long long student_id;char *student_name;char student_gender;int student_age;float student_height;float student_weight;
} Student;

        定义两个结构体实例,并将其初始化:

Student Jack = {201601001, “Jack”, ‘F',17, 180.5, 190.0}, Alice = {201601002, “Alice”, ‘M’, 16, 165.0, 140.8};

        在这两个实例进行初始化时,我们根据定义的数据不同,分别包含了整型、字符串、字符型和浮点型等数据类型,并在初始化时按照不同类型对其成员变量进行了赋初值。如果我们只初始化了前面一部分的成员,那么剩余的结构体成员将被初始化为缺省值。


2、结构体的存储结构



        当我们定义了一个结构体时,有时需要对其存储结构有一定了解。通常情况下,一个结构体的实例的大小等同于结构体内部各个成员大小的总和,因为程序编译时编译器会按照成员列表的顺序一个一个给每一个成员分配内存,但是这只有在满足内存边界对其时才是这种情况。如果定义的结构体成员之间大小关系不满足边界对其要求,那么成员之间可能会出现用于填充的额外内存空间。

        例如我们定义一个结构体:
typedef struct  
{char   ChrVal1;int      IntVal;char   ChrVal2;
} MemAlign;
MemAlign ma1 = {10, ‘A’, ‘b'};

        结构体实例ma1在内存中的实际大小因机器不同而异。如果某个机器的的整型值长度诶4 Byte,且起始存储位置必须能被4整除,则这一结构在内存中的大小为12,而不是1+4+1=6。


        结构体成员的存储必须满足几个原则:
  1. 某一个结构体对象的起始存储位置必须是最大的成员大小的整数倍;
  2. 每个结构体成员相对于本结构体起始位置的偏移量必须是自身大小的整数倍;
  3. 结构体对象所占据的总大小必须是最大数据成员体积的整数倍。如果以上三个条件有任何一个不满足,都将会在相应位置补充填充字节使之满足条件。

        鉴于此,如果不涉及到程序可读性和可维护性的前提下,我们可以重新排列结构体成员的顺序来提高内存的利用效率。如MemAlign结构按照如下方式声明,将只占据8个字节,相对于上文的声明方法存储效率提高了1/3。
typedef struct  
{int      IntVal;char   ChrVal1;char   ChrVal2;
} MemAlign;



3、位段



        位段是结构体中一种特殊的成员变量类型。位段的声明方式同普通的结构体成员类似,但是位段所代表的是一个或多个位(bit)的数据。所有的位段成员必须声明为int/signed/unsigned int类型。合适地使用位段可以根据需要更高效地利用内存,但是可能会降低程序的可移植性。这主要是由于以下情况根据系统的不同而不同:
  • int位段被作为有符号还是无符号;
  • 一个位段中允许的最大bit数;高位系统上允许的程序在低位系统上可能无法运行;
  • 位段成员在内存中分配的顺序(从左到右/从右到左);
  • 体积较大的后续位段的存放位置(直接紧邻前一个/从下一个字开始);
        例如,一个使用位段的结构体声明如下:
typedef struct {unsigned ch: 7;unsigned font: 6;unsigned size: 19;
} CHAR;
CHAR ch1;


4、联合体


(1)联合体的概念和使用


        联合体是C语言中提供的另一种结构。联合体的声明方式同结构体类似,但是它的结构和行为方式同结构体完全不同。结构体中使用不同的内存位置包含了不同的成员,而联合体则是使用相同的内存位置代表不同的联合体成员,而其中内存实际的数据都是相同的。例如,定义一个简单的联合体:
union {float f;int    i;
} fi;

        该联合体实例只占用1个32位的内存空间,其中的f和i成员指代的都是同一段内存。如果我们对其中的一个成员赋值,那么联合体其他成员所指代的含义通常是用其他的格式表示内存中相同的位:

fi.f = 3.14159;
int x = fi.i;

        如果一个联合体中各成员的长度不同,那么联合体的长度是其最长的成员的长度。

(2)联合体实例如何初始化:


        初始化一个联合体实例必须采用联合体第一个成员的类型,而且该值必须位于大括号内部,例如:
union {float f;int    i;
} fi = {3.14};

这篇关于理解C语言——从小菜到大神的晋级之路(10)——结构体、联合体的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/415397

相关文章

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

深入理解Apache Airflow 调度器(最新推荐)

《深入理解ApacheAirflow调度器(最新推荐)》ApacheAirflow调度器是数据管道管理系统的关键组件,负责编排dag中任务的执行,通过理解调度器的角色和工作方式,正确配置调度器,并... 目录什么是Airflow 调度器?Airflow 调度器工作机制配置Airflow调度器调优及优化建议最

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端