跳转至内容

C 中的多态数据结构 / C 结构体简介

来自维基教科书,自由的教科书

← 前言 | 指针 →

本书基于对 C 语言中用于创建数据结构的不同构造的根本理解。本章中,我们将考察这些构造、它们的用途以及新 C 程序员可能会遇到的陷阱。

结构体

[编辑 | 编辑源代码]

一个结构体是 C 语言中的一种简单构造,可以同时存储多种类型的数据。在其他编程语言(如 Pascal)中,这些被称为记录或簇。结构体可以存储任何类型的数据,包括另一个结构体。定义结构体的语法很简单

struct tag {
   member declarations
} ;

其中 struct 是定义结构体的关键字,tag 是结构体的名称(可选,但非常有用),成员声明 是正常的变量声明,定义了结构体中“字段”的名称。但是请注意,定义一个结构体不会声明任何变量。请记住,定义一个变量会告诉编译器记录有关新类型的的信息,以便在实际声明变量时使用。为了声明一个新的结构体,可以使用标准的 C 语言语法

struct tag variablename ;

variablename 当然就是新结构体的名称。

当开发人员需要跟踪多个(但相关)数据类型时,结构体很有用。例如,假设开发人员需要跟踪在给定字符串中特定单词出现的次数。他们会定义一个结构体来保存搜索字符串以及单词的计数。

struct word_count {
   char word[MAX_WORDLENGTH] ;
   int frequency ;
} ;

为了声明新的结构体,他们会使用

struct word_count search ;

与 C 语言中的所有类型一样,也可以在同一个语句中声明和定义结构体变量。上面的例子可以写成

struct word_count {
   char word[MAX_WORDLENGTH] ;
   int frequency ;
} search ;

如果结构体只被引用一次,那么在这种情况下,可以省略标签 (word_count)。这种用法在定义嵌套结构体时最有用。例如

struct employee_data {
   char name[16] ;
   struct {
      char street[32] ;
      char city[8] ;
      char state[3] ;
      int zipcode ;
   } address ;
   struct {
      int salary ;
      int years_employed ;
   } misc ;
} ;

结构体操作

[编辑 | 编辑源代码]

对结构体执行的主要操作是成员引用。这可以通过使用成员引用运算符 "."(句点)来实现。这与在面向对象语言(如 C++)中使用的引用方法类似。

variablename.member

为了获得上面声明的搜索词的频率,可以使用代码 search.frequency。单词的第一个字符是 search.word[0]。同样,对于声明

struct employee_data d ;

开发人员会写 d.address.zipcode 来访问员工的邮政编码。

将数据读入结构体必须在成员级别进行,这意味着为了将数据输入结构体,开发人员必须完全引用成员。例如,要在一行中读入上面的结构体 dname、address zipcodesalary,可以使用以下代码

scanf( "%s %d %d" , d.name , &d.address.zipcode , &d.misc.salary ) ;

但是,可以复制整个结构体,而无需单独复制其成员。

struct1 = struct2 ;

联合体

[编辑 | 编辑源代码]

联合体与结构体非常相似,因为它们也可以存储多种类型的数据。但是,联合体在任何给定时间只能保存一个成员的数据。例如

union symbol {
   char name[4] ;
   int value ;
} ;

定义了一个可以保存 一个 四个字符数组 或者 一个整数的类型,但不能同时保存两者。联合体分配的内存量等于最大成员的大小,然后覆盖所有成员的引用点。上面的语句(假设 sizeof ( char ) 为 1,而 sizeof ( int ) 为 2)可以用图表表示如下

当成员使用大约相同的空间量时,或者当较大的成员最常使用时,联合体最实用。否则,联合体在内存管理方面会造成浪费,应避免使用,而应采用更复杂的方法。

联合体不是类型感知的;也就是说,联合体无法准确识别哪个成员正在使用。因此,始终通过外部变量来跟踪正在使用的类型非常有用。一个简单的做法是定义一个结构体,包含两个字段:联合体和一个 type 字段,用于指定联合体中正在使用的类型。

枚举类型

[编辑 | 编辑源代码]

枚举类型是具有多个值(也称为标记)的类型,这些值根据指定的顺序列出。枚举类型使用以下语法定义

enum tag { tokens } ;

其中 enum 是定义枚举类型的关键字,tag 是类型的名称(与结构体不同,这不是可选的),tokens 是用逗号分隔的可能值。例如,要为上面使用的联合体创建一个字段,可以使用以下代码

enum member_type { name , value } ;

然后,可以围绕联合体构建一个结构体,如下所示

struct symbol {
   enum member_type type ;
   union {
      char name[4] ;
      int value ;
   } symbol ;
} ;

使用声明 struct symbol sym ;,程序员可以使用以下代码进行赋值

sym.type = value ;
sym.symbol.value = 6 ;

请记住,typesymbol 字段在语法上是独立的(也就是说,字段的名称彼此之间不依赖),因此由程序员来确保它们之间的语义关系。

枚举类型可以用于需要一组固定值的任何目的。枚举类型的主要问题是程序可能无法正确处理与枚举类型相关的格式化输入和输出(通常以字符串格式)。因此,C 语言中的枚举类型被定义为保存整数值。列表中的第一个标记为 0,后面的每个标记从最后一个标记开始递增 1。也可以通过列出它们,然后是赋值运算符 (=) 和值,为特定标记提供显式值。

enum numbers { zero , two = 2 , three , ten = 10 , eleven } ;

将显式值赋予标记会导致编号在该数字处“重置”;上面的 threeeleven 持有预期值。

枚举类型也可以使用预处理指令(本章后面将讨论)来处理,但这种方法的灵活性要差得多,而且由错误指令引起的错误消息往往更难调试。

预处理指令

[编辑 | 编辑源代码]

预处理指令是 C 语言中的快捷方式。它们允许开发人员在编译时对代码执行“查找和替换”。它们通常放在函数 main() 之上或全局头文件中(在多文件项目中)。它们使用以下语法定义

#directive_type shortcut( parameters ) actual_expression

parameters 是可选的,如果不需要将参数传递给指令,则可以省略。actual_expression 只需要在某些指令中包含。最常用的预处理指令之一是 #include 语句。它获取头文件(存储在默认库目录或本地目录中),并将它插入到程序中的该位置。代码

#include <stdio.h>

会导致从 C 库目录(在基于 Unix 的系统上为 /usr/include/)获取文件 stdio.h,并将其插入到程序的顶部。请注意定义末尾没有分号。下面是一个将在本书后面用到的用户创建指令的示例

#define DATA( L )   ( ( L ) -> datapointer )

在本例中,我们使用语句 **DATA( L )** 来替换常规语句 **( ( L ) -> datapointer )**。此系统可以使许多复杂的语句更容易创建,因为它们只需要创建一次。预处理器(C 编译器的预处理器)会对所有程序员的代码进行“盲目搜索”,并根据 **#define** 规则替换代码。因此,由编译器产生的与预处理指令相关的错误消息可能会有些晦涩难懂。

类型定义

[编辑 | 编辑源代码]

C 提供了一种使用 **typedef** 语句来定义新类型的工具。与预处理指令不同,合适的 C 编译器会识别类型定义中使用的名称,并可以执行更复杂的替换。类型定义通常用于为复杂结构(如结构体和联合体)创建类型别名,以及用于预定大小的数组。较不常见的用法是重新定义特定类型的名称(尽管这通常被认为是不好的做法)。

例如,考虑以下结构

struct student {
   char name[64] ;
   int id ;
} ;

通常,定义使用上面声明的结构的变量将使用以下语句

struct student student_data ;

相反,使用类型定义,我们可以创建一个名为student的类型,使变量定义更容易。

typedef struct student student ;

现在,程序员只需输入 **student student_data**,而无需输入 **struct student student_data**。类型定义可以帮助开发人员克服 C 固有的某些弱点,例如缺乏 **string** 类型。

typedef char string[128] ;

以上语句将 **string** 类型定义为一个包含 127 个字符(加上一个空字节)的数组。由于 C 中的字符串被视为以空字节结尾的字符数组,因此这种字符串实现是可接受的。

华夏公益教科书