理解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

相关文章

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

Go语言中使用JWT进行身份验证的几种方式

《Go语言中使用JWT进行身份验证的几种方式》本文主要介绍了Go语言中使用JWT进行身份验证的几种方式,包括dgrijalva/jwt-go、golang-jwt/jwt、lestrrat-go/jw... 目录简介1. github.com/dgrijalva/jwt-go安装:使用示例:解释:2. gi

Go 语言中的 Struct Tag 的用法详解

《Go语言中的StructTag的用法详解》在Go语言中,结构体字段标签(StructTag)是一种用于给字段添加元信息(metadata)的机制,常用于序列化(如JSON、XML)、ORM映... 目录一、结构体标签的基本语法二、json:"token"的具体含义三、常见的标签格式变体四、使用示例五、使用

Python+PyQt5实现文件夹结构映射工具

《Python+PyQt5实现文件夹结构映射工具》在日常工作中,我们经常需要对文件夹结构进行复制和备份,本文将带来一款基于PyQt5开发的文件夹结构映射工具,感兴趣的小伙伴可以跟随小编一起学习一下... 目录概述功能亮点展示效果软件使用步骤代码解析1. 主窗口设计(FolderCopyApp)2. 拖拽路径

Go语言使用slices包轻松实现排序功能

《Go语言使用slices包轻松实现排序功能》在Go语言开发中,对数据进行排序是常见的需求,Go1.18版本引入的slices包提供了简洁高效的排序解决方案,支持内置类型和用户自定义类型的排序操作,本... 目录一、内置类型排序:字符串与整数的应用1. 字符串切片排序2. 整数切片排序二、检查切片排序状态:

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

如何合理管控Java语言的异常

《如何合理管控Java语言的异常》:本文主要介绍如何合理管控Java语言的异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、Thorwable类3、Error4、Exception类4.1、检查异常4.2、运行时异常5、处理方式5.1. 捕获异常

C语言中的常见进制转换详解(从二进制到十六进制)

《C语言中的常见进制转换详解(从二进制到十六进制)》进制转换是计算机编程中的一个常见任务,特别是在处理低级别的数据操作时,C语言作为一门底层编程语言,在进制转换方面提供了灵活的操作方式,今天,我们将深... 目录1、进制基础2、C语言中的进制转换2.1 从十进制转换为其他进制十进制转二进制十进制转八进制十进