C 语言 —— 结构

编程入门 行业动态 更新时间:2024-10-16 02:22:08

C <a href=https://www.elefans.com/category/jswz/34/1770116.html style=语言 —— 结构"/>

C 语言 —— 结构

结构简介

  什么是结构为什么要用结构
  编写代码时,最重要的步骤之一是选择表示数据的方法。在许多情况下,基本的变量类型和数组还步够,为此 C 提供了结构变量来表示复杂的数据。例如,对于一本书,可能需要书名、作者、价格等属性。

  使用结构需要掌握的三个技巧:

  • 为结构建立一个格式或样式;
  • 声明一个合适该样式的变量;
  • 访问结构变量的各个部分。

建立结构声明

  结构声明描述了一个结构的组织布局。

struct book {char title[100];char author[80];float value;
};

  如上所示的声明描述了一个由两个字符数组和一个 float 类型变量组成的结构。注意,该声明仅仅只是描述了该结构由什么组成,并未创建实际的数据对象
  接下来分析一下结构声明的细节。
  struct 是声明结构的关键字,该关键字表明跟在其后面的是一个结构。
  book 是一个可选的标记,该标记表示结构的名称(该例中是 book)。关于标记,会在定义结构变量处讲解。
  { }; 花括号括起来的是结构成员列表,每个成员都是可以是任意一种 C 的数据类型,甚至可以是其他结构。注意,右括号后面一定要有 ; !!

  结构声明可以放在所有函数的外部,也可以放在一个函数定义的内部。如果把结构声明置于一个函数的内部,它的标记(如本例中的 book),只能限于该函数的内部使用。如果把结构声明置于函数的外部,那么该声明之后的所有函数都可以使用它的标记(如本例中的 book)。

定义结构变量

声明结构变量

  结构有两层含义,一层是上面讲过的结构布局,另一层则是这里要讲的定义结构变量。以上面声明的结构 book 为例,定义一个变量 library 的代码如下所示:

struct book library;

  这行代码表明,用 book 结构来为 library 变量分配空间,包括了一个还有 100 个元素的 char 数组,一个包含 80 个元素的 char 数组,和一个 float 类型的变量。

  在结构变量 library 的声明中,struct book 的作用相当于 int 或者 float 。和基本类型变量的声明一样,可以定义多个结构变量,甚至是结构指针。

struct book doyle, panshin, *ptbook;

  从本质上看,book 结构声明创建了一个名为 struct book 的新类型。就计算机而言,struct book library; 是以下声明的简化:

struct book {char title[100];char author[80];float value;
} library;

  所以,建立结构声明的过程和定义结构变量的过程可以组合成一个步骤。组合后的结构声明和结构变量的定义可以不使用结构标记。

struct {char title[100];char author[80];float value;
} library;

  关于可选的结构标记是否可以省略的问题。 如果是只需要定义有限个变量,可以将结构声明和变量定义合并到一起,这样结构模板只会被使用一次,此时可以省略结构标记。然而,如果打算多次使用结构模板,就必须使用带标记的形式,或者使用 typedef (这里不做介绍)。

初始化结构变量

  和定义基本数据类型变量一样,声明结构变量的同时可以对其进行初始化。初始化结构变量使用花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔。

struct book library = {"The Pious Pirate and the Devious Damsel","Renee Vivotte",1.95
};

访问结构成员

  对于结构变量来说,使用结构成员运算符 —— 点(.)访问结构中的成员。例如使用 library.value 访问 library 中的 value 部分。
  对于指向结构变量的指针来说,使用 -> 运算符访问结构中的成员,或者(*指针变量).成员

struct book *ptbook = &library;
ptbook->value;
(*ptbook).value;

结构体的大小(笔试考点)

之前学过的基本数据类型,如 char、int、double,它们的大小都是固定的,在 64 位系统中,char 占 1 字节,int 占 4 字节,double 占 8 字节。而结构体的大小并不固定,因为结构体的成员类型和数目是程序员自定义的。和基本数据类型一样,我们可以用 sizeof 这个运算符来获取结构体占据的内存大小。

struct Cookie
{int a;short b;short c;    
} cookie;
int main()
{printf("%d\n", sizeof cookie);return 0;
}

上面代码输出的结果是 8,正好是 1 个 int 类型,2 个 short 类型的大小之和。
那么结构体的大小是各成员大小之和吗?我们把 Cookie 稍微变化一下。

struct Cookie
{int a;short b;char c;    
} cookie;

将 Cookie 的第3个成员变为 char,运行程序发现输出的结果依旧是 8,而不是 4+3+1 = 7。由此可知结构体的大小并不是简单的各成员大小之和。
在计算结构体大小的时候,需要满足以下规则。

对齐原则

  1. 结构体成员的地址需要根据对齐数进行字节对齐。对齐数=编译器默认的对齐数与该成员类型的大小中的较小值。其中编辑器默认的对齐数由编辑器决定,Linux 环境下是 4,这个值也可以由程序员指定。
  2. 结构体总大小为其最大成员大小的整数倍。

示例1:编译器默认对齐数

注:以下程序是在默认对齐数为 4 的编译器下运行。如果是在像 VS 这种默认对齐数为 8 的编译器下运行结果会有所不同。

struct Cookie1
{int a;short b;char c[10];    
} cookie1;
struct Cookie2
{short b;int a;char c[10];    
} cookie2;

首先看 Cookie1 的大小。Cookie1 的成员中 int 占4字节,short 占2字节,char 占1字节,因此最大成员是占4字节的 int(对于数组,考虑数组元素的大小而不是数组的大小),因此最后计算的 Cookie1 的大小一定是4的倍数。对于第一个成员 a,占4字节。对于第二个成员 b,大小是2字节,小于编译器默认的4字节,因此 b 的对齐数为 2,而 b 前面的成员已经占据的内存大小为 4,是2字节的倍数,因此 b 不需要字节补齐,此时 Cookie1 的内存大小是 4+2=6 字节。对于第三个成员 c,对齐数是 1,前面成员以及占据的内存是 6,是对齐数1的倍数,因此不需要字节补齐。此时 Cookie1 的内存大小是 4+2+10=16,正好是其最大成员 a 大小的倍数,因此 Cookie1 的内存大小是 16。
再来看 Cookie2 的大小。Cookie2 的成员中依旧是 int 为最大成员,因此最后计算的 Cookie2 的大小也一定是 4 的倍数。对于第一个成员 b,占2字节。对于第二个成员 a,对齐数是 4,而 a 前面已占据的内存是 2,不是 4 的倍数,因此需要对前面的内存进行补齐,空出 2 字节空间,此时 Cookie2 的大小为 2(存储 b) + 2(空出) + 4(存储 a) = 8 字节。对于成员 c,不需要字节补齐。此时 Cookie2 的大小为 2+2+4+10 = 18,不是 4 的倍数,因此最后还需要空出 2 字节进行字节补齐,使得 Cookie2 的大小为 4 的倍数,故 Cookie2 的大小为 2+2(空出)+4+10+2(空出)=20。

struct Cookie3
{int a;double b;    short c;
} cookie3;

Cookie3 最大成员为 double 类型,因此 Cookie 的大小一定是 8 的倍数。第一个成员 a,占 4 字节内存。第二个成员 b,对齐数为 min(编译器默认对齐数(4), double 大小(8)),即 b 的对齐数为 4,而 b 前面已占据的内存大小为 4,不需要补齐字节。第三个成员 c,对齐数为 2,之前的成员占据的内存为 4 + 8 = 12,是 2 的倍数,不需要补齐。此时 Cookie3 的大小为 4+8+2 = 14,不是 8 的倍数,需要补齐2字节使其成为 8 的倍数,故 Cookie3 的大小为 16 字节。

示例2:程序员指定对齐数

可以通过 #param pack(n) 来指定 n 为编译器默认的对齐数。
PS:n 的值可以是 1、2、4、8。

#pragma pack(4)
struct Cookie3
{int a;double c;    short b;
} cookie3;
// Cookie3 的大小为 16
#pragma pack(8)
struct Cookie4
{int a;double c;    short b;
} cookie4;
// Cookie4 的大小为 24

为什么要字节对齐?

  1. 为了 CPU 访问数据的高效率。如果变量的地址不对齐,那么 CPU 读取结构体就需要对结构体成员进行重复的访问,然后组合得到数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。
  2. 在有的硬件平台中,计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取。因此字节对齐尤为重要。

为什么要结构体大小?

通过示例1,我们可以看到存储相同的成员的结构体,在结构体声明中成员的顺序不同,结构体的大小是不同的。了解了结构体大小的计算,我们可以在声明结构体的时候声明一个占内存最小的结构体,这样可以减少内存开销。

指向结构的指针

  为什么使用指向结构的指针?

  • 指向结构的指针通常比结构本身容易操控。
  • 一些早期的 C 视线中,结构不能作为参数传递给函数,但是可以传递指向结构的指针。
  • 传递指针通常更有效率。
  • 一些用于表示数据的结构中包含指向其他结构的指针。

向函数传递结构

  和基本类型数据相同,可以向函数传递结构变量和指向结构的指针。

  结构和结构指针作为函数参数的优缺点:

结构作为参数指针作为参数
优点1. 函数处理的是原始数据的副本,保护了原始数组;
2. 代码风格更为清晰。
1. 无论是以前还是现在的实现都可以使用这种方法;
2. 执行起来很快,只需要传递一个地址。
缺点1. 较老版本的实现可能无法处理这样的代码;
2. 传递结构浪费时间和存储空间。
无法保护原始数据,但 ANSI C 新增的 const 限定符解决了这个问题。

  结构和结构指针作为函数参数的选择:
  通常,我们为了追求效率会使用结构指针作为函数参数,如需防止原始数据被意外修改,可以使用 const 限定符。而按值传递结构是处理小型结构最常用的方法。

更多推荐

C 语言 —— 结构

本文发布于:2024-02-13 01:02:45,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1690085.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:语言   结构

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!