《C和指针》笔记35:结构体

news2024/11/15 19:33:28

本文整理一下结构体的相关知识,记录是为了更好地加深理解。

1. 结构体声明

下面两个声明语句:

struct {
	int a;
	char b;
	float c;
} x;

struct {
	int a;
	char b;
	float c;
} y[20], *z;

这两个声明被编译器当作两种截然不同的类型,即使它们的成员列表完全相同。因此,变量y和z的类型和x的类型不同,所以下面这条语句是非法的。

z=&x;

为了能让上面的表达式不报错,我们需要这样书写:

struct SIMPLE {
	int a;
	char b;
	float c;
};

这个声明把标签SIMPLE和这个成员列表联系在一起。该声明并没有提供变量列表,所以它并未创建任何变量。

标签标识了一种模式,用于声明未来的变量,但无论是标签还是模式本身都不是变量。

struct SIMPLE x;
struct SIMPLE y[20], *z;

这些声明使用标签来创建变量。它们创建和最初两个例子一样的变量,但存在一个重要的区别——现在x、y和z都是同一种类型的结构变量。

声明结构时可以使用的另一种良好技巧是用typedef创建一种新的类型,如下面的例子所示。

typedef struct {
	int a;
	char b;
	float c;
} SIMPLE ;

这个技巧和声明一个结构标签的效果几乎相同。区别在于Simple现在是个类型名而不是个结构标签,所以后续的声明可能像下面这个样子:

Simple x;
Simple y[20], *z;

如果你想在多个源文件中使用同一种类型的结构,你应该把标签声明或typedef形式的声明放在一个头文件中。当源文件需要这个声明时可以使用#include指令把那个头文件包含进来。

2. 结构体成员

一个结构体的成员的名字可以和其他结构的成员的名字相同:

struct COMPLEX {
	float f;
	int a[20];
	long *lp;
	struct SIMPLE s;
	struct SIMPLE sa[10];
	struct SIMPLE *sp;
};

一个结构体的成员的名字可以和其他结构体的成员的名字相同,所以这个结构体的成员a并不会与struct SIMPLE s的成员a冲突。

访问结构体成员可以用.(操作对象是结构体)和->(操作对象是结构体指针)。

struct COMPLEX comp;
comp.f
comp.a
comp.s

struct COMPLEX *cp;
cp->f
cp->a
cp->s

3. 结构体的自引用

struct SELF_REF1{
	int a;
	struct SELF_REF1 b;
	int c;
};

这种类型的自引用是非法的,因为成员b是另一个完整的结构,其内部还将包含它自己的成员b。这第2个成员又是另一个完整的结构,它还将包括它自己的成员b。这样重复下去永无止境。这有点像永远不会终止的递归程序。但下面这个声明却是合法的,你能看出其中的区别吗?

struct SELF_REF2{
	int a;
	struct SELF_REF2 *b;
	int c;
};

这个声明和前面那个声明的区别在于b现在是一个指针而不是结构体。编译器在结构的长度确定之前就已经知道指针的长度,所以这种类型的自引用是合法的。更加高级的数据结构体,如链表和树,都是用这种技巧实现的。每个结构体指向链表的下一个元素或树的下一个分枝。

警惕下面这个陷阱:

typedef struct {
	int a;
	SELF_REF3 *b;
	int c;
} SELF_REF3;

这个声明的目的是为这个结构体创建类型名SELF_REF3。但是,它失败了。类型名直到声明的末尾才定义,所以在结构声明的内部它尚未定义。解决方案是定义一个结构标签来声明b,如下所示:

typedef struct SELF_REF3_TAG {
	int a;
	struct SELF_REF3_TAG *b;
	int c;
} SELF_REF3;

4. 结构体不完整声明

有这样一种情况:有多个结构体,其中一个结构包含了另一个结构的一个或多个成员。和自引用结构一样,至少有一个结构必须在另一个结构内部以指针的形式存在。问题在于声明部分:如果每个结构都引用了其他结构的标签,哪个结构应该首先声明呢?

这个问题的解决方案是使用不完整声明,它声明一个作为结构标签的标识符。然后,我们可以把这个标签用在不需要知道这个结构的长度的声明中,如声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。

考虑下面这个例子,两个不同类型的结构内部都有一个指向另一个结构的指针。

struct B;
struct A {
	struct B *partner;
};
struct B {
	struct A *partner;
};

在A的成员列表中需要标签B的不完整的声明。一旦A被声明之后,B的成员列表也可以被声明。

5. 结构体的初始化

结构的初始化方式和数组的初始化很相似。一个位于一对花括号内部、由逗号分隔的初始值列表可用于结构各个成员的初始化。这些值根据结构成员列表的顺序写出。如果初始列表的值不够,剩余的结构成员将使用缺省值进行初始化

结构中如果包含数组或结构成员,其初始化方式类似于多维数组的初始化。一个完整的聚合类型成员的初始值列表可以嵌套于结构的初始值列表内部。这里有一个例子:

struct INIT_EX {
	int a;
	short b[10];
	Simple c;
} x = {
	10,
	{1,2,3,4,5},
	{25,'x',1.9}
}

6. 结构体和指针

这里举了一个例子,我们先定义一个复杂的结构体

typedef struct {
	int a;
	short b[2];
} Ex2;
typedef struct EX {
	int a;
	char b[3];
	Ex2 c;
	struct Ex *d;
} Ex;

Ex的结构可以这样表示:

在这里插入图片描述
初始化:

Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
Ex *px = &x;

在这里插入图片描述

  • int *pi = &px->a;

->的优先级要高于&,所以下面对结构体内部的f取地址不用加括号:

int *pi = &px->a;

在这里插入图片描述

  • px->b

下面的表达式的值是一个指针常量,因为b是一个数组。这个表达式不是一个合法的左值。它的右值是有意义的:

px->b

在这里插入图片描述

  • px->b[1]

如果我们对这个表达式执行间接访问操作,它将访问数组的第1个元素。使用下标引用或指针运算,我们还可以访问数组的其他元素。下面的表达式访问数组的第2个元素:

px->b[1]

在这里插入图片描述

  • px->c

为了访问本身也是结构的成员c,我们可以使用下面的表达式。它的左值是整个结构:

px->c

在这里插入图片描述

  • px->c.a

这个表达式可以使用点操作符访问c的特定成员。例如下面表达式的右值是有意义的,这个表达式既包含了点操作符,也包含了箭头操作符。之所以使用箭头操作符,是因为px并不是一个结构体,而是一个指向结构体的指针。接下来之所以要使用点操作符是因为px->s的结果并不是一个指针,而是一个结构体。

px->c.a

在这里插入图片描述

  • *px->c.b

这里有一个更为复杂的表达式:

*px->c.b

它有三个操作符,首先执行的是箭头操作符。px->c的结果是结构c。在表达式中增加.b访问结构c的成员b。b是一个数组,所以px->b.c的结果是一个(常量)指针,它指向数组的第1个元素。最后对这个指针执行间接访问,所以表达式的最终结果是数组的第1个元素。(*的优先级是最后的)这个表达式可以图解如下:

在这里插入图片描述

  • px->d

来看这样一个表达式:

px->d

表达式的结果正如你所料——它的右值是0,它的左值是它本身的内存位置

  • *px->d

如果对上面的式子进行解引用呢?

`*px->d`

这里间接访问操作符作用于成员d所存储的指针值。但d包含了一个NULL指针,所以它不指向任何东西。对一个NULL指针进行解引用操作是个错误,但正如我们以前讨论的那样,有些环境不会在运行时捕捉到这个错误。在这些机器上,程序将访问内存位置零的内容,把它也当作是结构体成员之一,如果系统未发现错误,它还将高高兴兴地继续下去。这个例子说明了对指针进行解引用操作之前检查一下它是否有效是非常重要的。

我们创建另一个结构,并把x.d设置为指向它。

Ex y;
x.d = &y;

现在我们可以对表达式*px->d求值。成员d指向一个结构,所以对它执行间接访问操作的结果是整个结构。这个新的结构并没有显式地初始化,所以在图中并没有显示它的成员的值。

正如你可能预料的那样,这个新结构的成员可以通过在表达式中增加更多的操作符进行访问。我们使用箭头操作符,因为d是一个指向结构的指针。下面这些表达式是执行什么任务的呢?

在这里插入图片描述

  • px->d->c.b[1]
px->d->c.b[1]

在这里插入图片描述

参考:

  1. 《C和指针》

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

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

相关文章

数字电路与逻辑设计 之 组合电路的设计(多输出电路,全加器,乘法器)

一些例子 多输出的电路设计 全加器 我们尝试不去弄到最简 乘法器 要分析有几个输入,几个输出

代码签名证书续费

代码签名证书的有效周期是1-3年,这种情况下证书到期了就要重新申请办理,最开始同样的申请验证步骤还要再走一遍,尤其是Ukey还是要CA机构重新颁发,还是要等待快递配送。OV代码签名证书、EV代码签名证书目前行业内统一采取Ukey存储&…

链动2+1全新9.0版本 无限链动收益

一个平台能否长期存活取决于它是否有一个支撑其持续发展的商业模式。蜂群精选深谙用户心理,对链动21模式进行改造,创新出一种同时具备裂变能力和高效吸引用户留存的新玩法。 链动21模式在整个架构上都是完整的,可以说是一个非常出色的营销模式…

【JAVA】我们该如何规避代码中可能出现的错误?(三)

个人主页:【😊个人主页】 系列专栏:【❤️初识JAVA】 文章目录 前言throws/throw 关键字throw 关键字throws 关键字 finally关键字 前言 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误00有时候是可以避…

统计学习方法 决策树

文章目录 统计学习方法 决策树决策树模型与学习特征选择决策树的生成ID3 算法C4.5 的生成算法 决策树的剪枝CART 算法CART 回归树的生成CART 分类树的生成CART 剪枝 统计学习方法 决策树 阅读李航的《统计学习方法》时,关于决策树的笔记。 决策树模型与学习 决策…

C++学习笔记之四(标准库、标准模板库、vector类)

C 1、C标准库2、C标准模板库2.1、vector2.1.1、vector与array2.1.2、vector与函数对象2.1.3、vector与迭代器2.1.4、vector与算法 1、C标准库 C C C标准库指的是标准程序库( S t a n d a r d Standard Standard L i b a r a y Libaray Libaray),它定义了十个大类…

编码,解码

一.标准ASCll字符集 标准ASCll 字符集使用一个字节存储一个字符,首尾是0 二.GBK字符集 GBK中一个中文字符编码成两个字节的形式存储,一个英文字母编码成一个字节的形式存储 对于 汉字中夹英文的,GBK规定:汉字的第一个字节的第一位…

MySQL安装多个实例——批处理脚本一键配置MySQL服务

1.下载mysql的免安装压缩包 官网:https://downloads.mysql.com/archives/community/ 2.解压并新增批处理脚本 echo off chcp 65001 setlocal enabledelayedexpansionecho MySQL版本为8.0.34REM 使用set /p命令获取用户输入的端口号 set /p "port请输入端口号…

C++标准模板(STL)- 类型支持 (类型特性,is_pointer,is_lvalue_reference,is_rvalue_reference)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实…

【git】git使用教程

1、版本管理工具 如果有一个软件能记录我们对文档的所有修改&#xff0c;所有版本&#xff0c;这类软件我们一般叫做版本控制工具。 特性“ 能够记录历史版本&#xff0c;回退历史版本团队开发&#xff0c;方便代码合并 2、版本管理工具介绍 svn、git svn是集中式版本控制…

mac版本 Adobe总是弹窗提示验证问题如何解决

来自&#xff1a; mac软件下载macsc站 mac电脑使用过程中总是弹出Adobe 的弹窗提示&#xff0c;尤其是打开Adobe的软件&#xff0c;更是频繁的弹出提示&#xff1a; Your Adobe app is not genuine. Adobe reserves the right to disable this software after a 0 grace period…

Ubuntu 22.04 开机闪logo后卡在/dev/sda3: clean

环境 Vmware 17.0.0&#xff0c;CPU 2&#xff0c;内存4G&#xff0c;硬盘50G Ubuntu 22.04 问题描述 开机 --> 显示两行代码 --> 显示ubuntu logo --> 左上显示两个代码卡住不动 原因分析 1、网上大多说显卡驱动&#xff0c;最近没安装相关软件&#xff0c;也没…

ARM汇编指令之数据操作指令

数据搬移指令&#xff1a;立即数&#xff1a;在待判断的32位数&#xff08;以十六进制展开&#xff09;中&#xff0c;寻找一个0~255&#xff08;即0x00~0xff&#xff09;之间的数值&#xff0c;然后将这个数值循环右移偶数个位置&#xff0c;可以得到待判断的数&#xff0c;即…

Vue $nextTick

我们用一个例子来说明$nextTick的作用&#xff1a; 我们用一个变量showIpt来控制input框的显示和隐藏&#xff0c;默认是隐藏。 我们点击一个按钮后显示这个输入框的同时&#xff0c;input还要自动获取焦点。 但是我们点击按钮过后并没有生效。 为什么&#xff1f;this.show…

C++标准模板(STL)- 类型支持 (类型特性,is_union,is_class,is_function)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

Ubuntu中查看电脑有多少个核——lscpu

1. 使用lscpu命令: 打开终端并输入以下命令: lscpu你会看到与CPU相关的详细信息。查找"CPU(s)"这一行来看总的核心数。另外&#xff0c;“Core(s) per socket”表示每个插槽或每个物理CPU的核数&#xff0c;“Socket(s)”表示物理CPU的数量。将这两个值相乘即得到总…

MyBatis开启二级缓存

MyBatis开启二级缓存 前言 MyBatis-Plus&#xff08;简称MP&#xff09;是一个基于MyBatis的增强工具&#xff0c;提供了更便捷的CRUD操作和其他功能。与MyBatis相比&#xff0c;MyBatis-Plus并没有引入自己的缓存机制&#xff0c;而是直接使用了MyBatis的缓存机制。 在MyBati…

【Linux】第五站:Linux权限

文章目录 一、shell命令以及运行原理二、Linux下用户的分类1.root用户和普通用户的切换2.对一条指令的提权 三、什么叫做权限1.权限2.文件的属性3.文件类型4.权限属性 四、更改权限1. chmod 更改文件的属性2. chown 更改拥有者3. chgrp更改所属组4.chown一次性更改拥有者和所属…

12种常见的恶意软件类型与防范建议

1、病毒 病毒是迄今为止最常见的恶意软件类型之一。它是一种能够感染、破坏计算机设备&#xff0c;并在其运行系统上自我复制的程序。由于病毒是自我复制的&#xff0c;一旦安装并运行&#xff0c;它们就可以在同一网络上自动从一台设备传播到另一台设备&#xff0c;无需人为干…

网络(番外篇)can网络知识

通常ECU发出的网络管理报文ID Base Address Node ID Mifa项目向外发的网络管理报文0x418&#xff0c;就是DBC根据基地址加上节点ID定义的。 报文属性是 NmAsrMessage即应答网络报文。 DBC里关于整个网络管理的参数定义&#xff0c;确定好后导入达芬奇&#xff0c;就是直接…