低价网站建设哪家便宜,wordpress 主题窜改,软件开发网站能做seo吗,做网站必须需要服务器嘛本章介绍以下内容#xff1a; 关键字#xff1a;struct、union、typedef 运算符#xff1a;.、- 什么是C结构#xff0c;如何创建结构模板和结构变量 如何访问结构的成员#xff0c;如何编写处理结构的函数 联合和指向函数的指针 设计程序时#xff0c;最重要的步骤之…本章介绍以下内容 关键字struct、union、typedef 运算符.、- 什么是C结构如何创建结构模板和结构变量 如何访问结构的成员如何编写处理结构的函数 联合和指向函数的指针 设计程序时最重要的步骤之一是选择表示数据的方法。在许多情况下简单变量甚至是数组还不够。为此C提供了结构变量structure variable提高你表示数据的能力它能让你创造新的形式。如果熟悉Pascal的记录record应该很容易理解结构。如果不懂Pascal也没关系本章将详细介绍C结构。我们先通过一个示例来分析为何需要C结构学习如何创建和使用结构。
14.1 示例问题创建图书目录
创建的结构有3部分每个部分都称为成员member或字段field
14.2 建立结构声明
结构声明structure declaration描述了一个结构的组织布局
声明类似下面这样 struct book { char title[MAXTITL]; char author[MAXAUTL]; float value; };
该声明并未创建实际的数据对象只描述了该对象由什么组成。〔有时我们把结构声明称为模板因为它勾勒出结构是如何储存数据的
首先是关键字 struct它表明跟在其后的是一个结构后面是一个可选的标记该例中是 book稍后程序中可以使用该标记引用该结构
struct book library; 这把library声明为一个使用book结构布局的结构变量
在结构声明中用一对花括号括起来的是结构成员列表。每个成员都用自己的声明来描述
成员可以是任意一种C的数据类型甚至可以是其他结构右花括号后面的分号是声明所必需的表示结构布局定义结束
可以把这个声明放在所有函数的外部如本例所示也可以放在一个函数定义的内部。如果把结构声明置于一个函数的内部它的标记就只限于该函数内部使用。如果把结构声明置于函数的外部那么该声明之后的所有函数都能使用它的标记
结构的标记名是可选的。但是以程序示例中的方式建立结构时在一处定义结构布局在另一处定义实际的结构变量必须使用标记
14.3 定义结构变量
结构有两层含义。一层含义是“结构布局”刚才已经讨论过了。结构布局告诉编译器如何表示数据但是它并未让编译器为数据分配空间。下一步是创建一个结构变量即是结构的另一层含义
在结构变量的声明中struct book所起的作用相当于一般声明中的int或float 图14.1 一个结构的内存分配 就计算机而言下面的声明 struct book library; 是以下声明的简化 struct book { char title[MAXTITL]; char author[AXAUTL]; float value; } library; /* 声明的右右花括号后跟变量名*/
声明结构的过程和定义结构变量的过程可以组合成一个步骤。如下所示组合后的结构声明和结构变量定义不需要使用结构标记
struct { /* 无结构标记 */ char title[MAXTITL]; char author[MAXAUTL]; float value; } library;
然而如果打算多次使用结构模板就要使用带标记的形式或者使用本章后面介绍的typedef
14.3.1 初始化结构
初始化一个结构变量ANSI之前不能用自动变量初始化结构ANSI之后可以用任意存储类别与初始化数组的语法类似 struct book library { The Pious Pirate and the Devious Damsel, Renee Vivotte, 1.95 };
我们使用在一对花括号中括起来的初始化列表进行初始化各初始化项用逗号分隔
为了让初始化项与结构中各成员的关联更加明显我们让每个成员的初始化项独占一行。这样做只是为了提高代码的可读性对编译器而言只需要用逗号分隔各成员的初始化项即可
如果初始化静态存储期的变量如静态外部链接、静态内部链接或静态无链接必须使用常量值。这同样适用于结构。如果初始化一个静态存储期的结构初始化列表中的值必须是常量表达式。如果是自动存储期初始化列表中的值可以不是常量
14.3.2 访问结构成员
结构类似于一个“超级数组”
使用结构成员运算符——点.访问结构中的成员
14.3.3 结构的初始化器
C99和C11为结构提供了指定初始化器designated initializer[1]其语法与数组的指定初始化器类似。但是结构的指定初始化器使用点运算符和成员名而不是方括号和下标标识特定的元素
struct book surprise { .value 10.99};
可以按照任意顺序使用指定初始化器
struct book gift { .value 25.99, .author James Broadfool, .title Rue for the Toad};
在指定初始化器后面的普通初始化器为指定成员后面的成员提供初始值。另外对特定成员的最后一次赋值才是它实际获得的值
struct book gift {.value 18.90, .author Philionna Pestle, 0.25}; 赋给value的值是0.25
14.4 结构数组
由于该数组是自动存储类别的对象其中的信息被储存在栈stack中。如此大的数组需要很大一块内存这可能会导致一些问题。如果在运行时出现错误可能抱怨栈大小或栈溢出你的编译器可能使用了一个默认大小的栈这个栈对于该例而言太小。要修正这个问题可以使用编译器选项设置栈大小为10000以容纳这个结构数组或者可以创建静态或外部数组这样编译器就不会把数组放在栈中或者可以减小数组大小为16
14.4.1 声明结构数组
struct book library[MAXBKS]; 以上代码把library声明为一个内含MAXBKS个元素的数组。数组的每个元素都是一个book类型的数组。因此library[0]是第1个book类型的结构变量library[1]是第2个book类型的结构变量以此类推。参看图14.2 可以帮助读者理解。数组名library本身不是结构名它是一个数组名该数组中的每个元素都是struct book类型的结构变量。 图14.2 一个结构数组library[MAXBKS]
14.4.2 标识结构数组的成员
为了标识结构数组中的成员可以采用访问单独结构的规则在结构名后面加一个点运算符再在点运算符后面写上成员名
数组下标紧跟在library后面不是成员名后面 library.value[2] // 错误 library[2].value // 正确
14.4.3 程序讨论
while (getchar() ! \n) continue; /* 清理输入行 */ 前面章节介绍过这段代码弥补了scanf()函数遇到空格和换行符就结束读取的问题
scanf()函数接受1、2、.、5和0但是把\n留在输入序列中。如果没有上面两行清理输入行的代码就会把留在输入序列中的换行符当作空行读入程序以为用户发送了停止输入的信号。我们插入的这两行代码只会在输入序列中查找并删除\n不会处理其他字符。这样s_gets()就可以重新开始下一次输入
14.5 嵌套结构
注意如何在结构声明中创建嵌套结构。和声明int类型变量一样进行简单的声明 struct names handle; 该声明表明handle是一个struct name类型的变量。当然文件中也应包含结构names的声明。 其次注意如何访问嵌套结构的成员这需要使用两次点运算符 printf(Hello, %s!\n, fellow.handle.first); 从左往右解释fellow.handle.first (fellow.handle).first
14.6 指向结构的指针
第一就像指向数组的指针比数组本身更容易操控如排序问题一样指向结构的指针通常比结构本身更容易操控。第二在一些早期的C实现中结构不能作为参数传递给函数但是可以传递指向结构的指针。第三即使能传递一个结构传递指针通常更有效率。第四一些用于表示数据的结构中包含指向其他结构的指针
14.6.1 声明和初始化结构指针
声明结构指针很简单 struct guy * him; 首先是关键字 struct其次是结构标记 guy然后是一个星号*其后跟着指针名。这个语法和其他指针声明一样
和数组不同的是结构名并不是结构的地址因此要在结构名前面加上运算符
him加1相当于him指向的地址加84。在十六进制中874 - 820 54十六进制84十进制因为每个guy结构都占用84字节的内存names.first占用20字节names.last占用20字节favfood占用20字节job占用20字节income占用4字节假设系统中float占用4字节
顺带一提在有些系统中一个结构的大小可能大于它各成员大小之和
14.6.2 用指针访问成员
第1种方法也是最常用的方法使用-运算符。该运算符由一个连接号-后跟一个大于号组成。我们有下面的关系 如果him barney那么him-income 即是 barney.income 如果him fellow[0]那么him-income 即是 fellow[0].income 换句话说-运算符后面的结构指针和.运算符后面的结构名工作方式相同不能写成him.incone因为him不是结构名
第2种方法是以这样的顺序指定结构成员的值如果him fellow[0]那么*him fellow[0]因为和*是一对互逆运算符。因此可以做以下替代
fellow[0].income (*him).income 必须要使用圆括号因为.运算符比*运算符的优先级高。 总之如果him是指向guy类型结构barney的指针下面的关系恒成立 barney.income (*him).income him-income // 假设 him barney
14.7 向函数传递结构的信息
ANSI C允许把结构作为参数使用。所以程序员可以选择是传递结构本身还是传递指向结构的指针。如果你只关心结构中的某一部分也可以把结构的成员作为参数
14.7.1 传递结构成员
如果需要在被调函数中修改主调函数中成员的值就要传递成员的地址 modify(stan.bankfund);
14.7.2 传递结构的地址
必须使用运算符来获取结构的地址。和数组名不同结构名只是其地址的别名
14.7.3 传递结构
14.7.4 其他结构特性
现在的C允许把一个结构赋值给另一个结构但是数组不能这样做。也就是说如果n_data和o_data都是相同类型的结构可以这样做 o_data n_data; // 把一个结构赋值给另一个结构 这条语句把n_data的每个成员的值都赋给o_data的相应成员。即使成员是数组也能完成赋值
还可以把一个结构初始化为相同类型的另一个结构 struct names right_field {Ruthie, George}; struct names captain right_field; // 把一个结构初始化为另一个结构
现在的C包括ANSI C函数不仅能把结构本身作为参数传递还能把结构作为返回值返回。把结构作为函数参数可以把结构的信息传送给函数把结构作为返回值的函数能把结构的信息从被调函数传回主调函数。结构指针也允许这种双向通信
第一为了传递结构本身函数的参数必须是person而不是person。那么相应的形式参数应声明为struct namect而不是指向该类型的指针。第二可以通过返回一个结构把结构的信息返回给main()
14.7.5 结构和结构指针的选择
把指针作为参数有两个优点无论是以前还是现在的C实现都能使用这种方法而且执行起来很快只需要传递一个地址。缺点是无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。不过ANSI C新增的const限定符解决了这个问题。例如如果在程序清单14.8中showinfo()函数中的代码改变了结构的任意成员编译器会捕获这个错误
把结构作为参数传递的优点是函数处理的是原始数据的副本这保护了原始数据。另外代码风格也更清楚
传递结构的两个缺点是较老版本的实现可能无法处理这样的代码而且传递结构浪费时间和存储空间。尤其是把大型结构传递给函数而它只使用结构中的一两个成员时特别浪费。这种情况下传递指针或只传递函数所需的成员更合理。 通常程序员为了追求效率会使用结构指针作为函数参数如需防止原始数据被意外修改使用const限定符。按值传递结构是处理小型结构最常用的方法。
14.7.6 结构中的字符数组和字符指针
因此如果要用结构储存字符串用字符数组作为成员比较简单。用指向 char 的指针也行但是误用会导致严重的问题
14.7.7 结构、指针和malloc()
14.7.8 复合字面量和结构C99
14.7.9 伸缩型数组成员C99
14.7.10 匿名结构C11
匿名结构是一个没有名称的结构成员
在C11中可以用嵌套的匿名成员结构定义person struct person { int id;
struct {char first[20]; char last[20];}; // 匿名结构 }; 初始化ted的方式相同 struct person ted {8483, {Ted, Grass}}; 但是在访问ted时简化了步骤只需把first看作是person的成员那样使用它 puts(ted.first); 当然也可以把first和last直接作为person的成员删除嵌套循环
14.7.11 使用结构数组的函数
可以把数组名作为数组中第1个结构的地址传递给函数。 然后可以用数组表示法访问数组中的其他结构。注意下面的函数调用与使用数组名效果相同 sum(jones[0], N) 因为jones和jones[0]的地址相同使用数组名是传递结构地址的一种间接的方法
14.8 把结构内容保存到文件中
14.8.1 保存结构的程序示例
14.8.2 程序要点
14.9 链式结构
这些形式包括队列、二叉树、堆、哈希表和图表。许多这样的形式都由链式结构linked structure组成。通常每个结构都包含一两个数据项和一两个指向其他同类型结构的指针。这些指针把一个结构和另一个结构链接起来并提供一种路径能遍历整个彼此链接的结构。例如图14.3演示了一个二叉树结构每个单独的结构或节点都和它下面的两个结构或节点相连
14.10 联合简介
联合union是一种数据类型它能在同一个内存空间中储存不同的数据类型不是同时储存
创建联合和创建结构的方式相同需要一个联合模板和联合变量。可以用一个步骤定义联合也可以用联合标记分两步定义
union hold { int digit; double bigfl; char letter; }; 根据以上形式声明的结构可以储存一个int类型、一个double类型和char类型的值
声明的联合只能储存一个int类型的值或一个double类型的值或char类型的值
下面定义了3个与hold类型相关的变量 union hold fit; // hold类型的联合变量 union hold save[10]; // 内含10个联合变量的数组 union hold * pu; // 指向hold类型联合变量的指针
第1个声明创建了一个单独的联合变量fit。编译器分配足够的空间以便它能储存联合声明中占用最大字节的类型
联合只能储存一个值这与结构不同。有 3 种初始化的方法把一个联合初始化为另一个同类型的联合初始化联合的第1个元素或者根据C99标准使用指定初始化器
union hold valA; valA.letter R; union hold valB valA; // 用另一个联合来初始化 union hold valC {88}; // 初始化联合的digit 成员
union hold valD {.bigfl 118.2}; // 指定初始化器
14.10.1 使用联合
点运算符表示正在使用哪种数据类型。在联合中一次只储存一个值。即使有足够的空间也不能同时储存一个char类型值和一个int类型值
和用指针访问结构使用-运算符一样用指针访问联合时也要使用-运算符
用一个成员把值储存在一个联合中然后用另一个成员查看内容这种做法有时很有用
联合的另一种用法是在结构中储存与其成员有从属关系的信息
14.10.2 匿名联合C11
即匿名联合是一个结构或联合的无名联合成员
flits.owncar.socsecurity 代替flits.ownerinfo.owncar.socsecurity
总结结构和联合运算符 成员运算符. 一般注释 该运算符与结构或联合名一起使用指定结构或联合的一个成员。如果name是一个结构的名称 member是该结构模版指定的一个成员名下面标识了该结构的这个成员 name.member name.member的类型就是member的类型。联合使用成员运算符的方式与结构相同。 示例 struct { int code; float cost; } item; item.code 1265; 间接成员运算符- 一般注释 该运算符和指向结构或联合的指针一起使用标识结构或联合的一个成员。假设ptrstr是指向结构的指针member是该结构模版指定的一个成员那么 ptrstr-member 标识了指向结构的成员。联合使用间接成员运算符的方式与结构相同。 示例 struct { int code; float cost; } item, * ptrst; ptrst item; ptrst-code 3451; 最后一条语句把一个int类型的值赋给item的code成员。如下3个表达式是等价的 ptrst-code item.code (*ptrst).code
14.11 枚举类型
可以用枚举类型enumerated type声明符号名称来表示整型常量。使用enum关键字可以创建一个新“类型”并指定它可具有的值实际上enum常量是int类型因此只要能使用int类型的地方就可以使用枚举类型。枚举类型的目的是提高程序的可读性。它的语法与结构的语法相同
enum spectrum {red, orange, yellow, green, blue, violet}; enum spectrum color;
虽然枚举符如red和blue是int类型但是枚举变量可以是任意整数类型前提是该整数类型可以储存枚举常量。例如spectrum的枚举符范围是05所以编译器可以用unsigned char来表示color变量
C枚举的一些特性并不适用于C。例如C允许枚举变量使用运算符但是C标准不允许。所以如果编写的代码将来会并入C程序那么必须把上面例子中的color声明为int类型才能C和C都兼容
14.11.1 enum常量
red成为一个有名称的常量代表整数0。类似地其他标识符都是有名称的常量分别代表15。只要是能使用整型常量的地方就可以使用枚举常量。例如在声明数组时可以用枚举常量表示数组的大小在switch语句中可以把枚举常量作为标签
14.11.2 默认值
默认情况下枚举列表中的常量都被赋予0、1、2等。因此下面的声明中nina的值是3 enum kids {nippy, slats, skippy, nina, liz};
14.11.3 赋值
在枚举声明中可以为枚举常量指定整数值 enum levels {low 100, medium 500, high 2000}; 如果只给一个枚举常量赋值没有对后面的枚举常量赋值那么后面的常量会被赋予后续的值。例如假设有如下的声明 enum feline {cat, lynx 10, puma, tiger}; 那么cat的值是0默认lynx、puma和tiger的值分别是10、11、12。
14.11.4 enum的用法
枚举类型的目的是为了提高程序的可读性和可维护性。如果要处理颜色使用red和blue比使用0和1更直观。注意枚举类型只能在内部使用。如果要输入color中orange的值只能输入1而不是单词orange。或者让程序先读入字符串orange再将其转换为orange代表的值
因为枚举类型是整数类型所以可以在表达式中以使用整数变量的方式使用enum变量。它们用在case语句中很方便
14.11.5 共享名称空间
C语言使用名称空间namespace标识程序中的各部分即通过名称来识别
作用域是名称空间概念的一部分两个不同作用域的同名变量不冲突两个相同作用域的同名变量冲突。名称空间是分类别的。在特定作用域中的结构标记、联合标记和枚举标记都共享相同的名称空间该名称空间与普通变量使用的空间不同。这意味着在相同作用域中变量和标记的名称可以相同不会引起冲突但是不能在相同作用域中声明两个同名标签或同名变量
尽管如此以两种不同的方式使用相同的标识符会造成混乱。另外C不允许这样做因为它把标记名和变量名放在相同的名称空间中
14.12 typedef简介
typedef工具是一个高级数据特性利用typedef可以为某一类型自定义名称
这方面与#define类似但是两者有3处不同 与#define不同typedef创建的符号名只受限于类型不能用于值。 typedef由编译器解释不是预处理器。 在其受限范围内typedef比#define更灵活
该定义的作用域取决于typedef定义所在的位置。如果定义在函数中就具有局部作用域受限于定义所在的函数。如果定义在函数外面就具有文件作用域
通常typedef定义中用大写字母表示被定义的名称以提醒用户这个类型名实际上是一个符号缩写
也可以用小写 typedef unsigned char byte; typedef中使用的名称遵循变量的命名规则
使用typedef还能提高程序的可移植性
typedef的一些特性与#define的功能重合。例如 #define BYTE unsigned char 这使预处理器用BYTE替换unsigned char。但是也有#define没有的功能 typedef char * STRING;
没有typedef关键字编译器将把STRING识别为一个指向char的指针变量。有了typedef关键字编译器则把STRING解释成一个类型的标识符该类型是指向char的指针。因此 STRING name, sign; 相当于 char * name, * sign; 但是如果这样假设 #define STRING char * 然后下面的声明 STRING name, sign; 将被翻译成 char * name, sign; 这导致只有name才是指针
还可以把typedef用于结构 typedef struct complex { float real; float imag; } COMPLEX;
然后便可使用COMPLEX类型代替complex结构来表示复数。使用typedef的第1个原因是为经常出现的类型创建一个方便、易识别的类型名。例如前面的例子中许多人更倾向于使用 STRING 或与其等价的标记
用typedef来命名一个结构类型时可以省略该结构的标签 typedef struct {double x; double y;} rect;
使用typedef的第2个原因是typedef常用于给复杂的类型命名。例如下面的声明
typedef char (* FRPTC ()) [5]; 把FRPTC声明为一个函数类型该函数返回一个指针该指针指向内含5个char类型元素的数组
typedef并没有创建任何新类型它只是为某个已存在的类型增加了一个方便使用的标签
14.13 其他复杂的声明 表14.1 声明时可使用的符号
int board[8][8]; // 声明一个内含int数组的数组 int ** ptr; // 声明一个指向指针的指针被指向的指针指向int int * risks[10]; // 声明一个内含10个元素的数组每个元素都是一个指向int的指针 int (* rusks)[10]; // 声明一个指向数组的指针该数组内含10个int类型的值 int * oof[3][4]; // 声明一个3×4 的二维数组每个元素都是指向int的指针 int (* uuf)[3][4]; // 声明一个指向3×4二维数组的指针该数组中内含int类型值 int (* uof[3])[4]; // 声明一个内含3个指针元素的数组其中每个指针都指向一个内含4个int类型元素的数组 要看懂以上声明关键要理解*、()和[]的优先级。记住下面几条规则。
1.数组名后面的[]和函数名后面的()具有相同的优先级。它们比*解引用运算符的优先级高。因此下面声明的risk是一个指针数组不是指向数组的指针 int * risks[10]; 2.[]和()的优先级相同由于都是从左往右结合所以下面的声明中在应用方括号之前*先与rusks结合。因此rusks是一个指向数组的指针该数组内含10个int类型的元素 int (* rusks)[10]; 3.[]和()都是从左往右结合。因此下面声明的goods是一个由12个内含50个int类型值的数组组成的二维数组不是一个有50个内含12个int类型值的数组组成的二维数组 int goods[12][50];
14.14 函数和指针
函数指针常用作另一个函数的参数告诉该函数要使用哪一个函数
void (*pf)(char *); // pf 是一个指向函数的指针
在声明函数指针时必须把*和指针名括起来。如果省略第1个圆括号会导致完全不同的情况 void *pf(char *); // pf 是一个返回字符指针的函数
要声明一个指向特定类型函数的指针可以先声明一个该类型的函数然后把函数名替换成(*pf)形式的表达式。然后pf就成为指向该类型函数的指针
声明了函数指针后可以把类型匹配的函数地址赋给它。在这种上下文中函数名可以用于表示函数的地址 void ToUpper(char *); void ToLower(char *); int round(double); void (*pf)(char *); pf ToUpper; // 有效ToUpper是该类型函数的地址 pf ToLower; //有效ToUpper是该类型函数的地址 pf round; // 无效round与指针类型不匹配 pf ToLower(); // 无效ToLower()不是地址
也可以用函数指针访问函数。奇怪的是有两种逻辑上不一致的语法可以这样做下面解释 void ToUpper(char *); void ToLower(char *); void (*pf)(char *); char mis[] Nina Metier; pf ToUpper; (*pf)(mis); // 把ToUpper 作用于语法1 pf ToLower; pf(mis); // 把ToLower 作用于语法2
但是为了与现有代码兼容ANSI C认为这两种形式本例中是(*pf)(mis)和pf(mis)等价。后续的标准也延续了这种矛盾的和谐 图14.4 函数名的用法
14.15 关键概念
14.16 本章小结
C 结构提供在相同的数据对象中储存多个不同类型数据项的方法。可以使用标记来标识一个具体的结构模板并声明该类型的变量。通过成员点运算符.可以使用结构模版中的标签来访问结构的各个成员。 如果有一个指向结构的指针可以用该指针和间接成员运算符-代替结构名和点运算符来访问结构的各成员。和数组不同结构名不是结构的地址要在结构名前使用运算符才能获得结构的地址。 一贯以来与结构相关的函数都使用指向结构的指针作为参数。现在的C允许把结构作为参数传递作为返回值和同类型结构之间赋值。然而传递结构的地址通常更有效。 联合使用与结构相同的语法。然而联合的成员共享一个共同的存储空间。联合同一时间内只能储存一个单独的数据项不像结构那样同时储存多种数据类型。也就是说结构可以同时储存一个int类型数据、一个double类型数据和一个char类型数据而相应的联合只能保存一个int类型数据或者一个double类型数据或者一个char类型数据。 通过枚举可以创建一系列代表整型常量枚举常量的符号和定义相关联的枚举类型。 typedef工具可用于建立C标准类型的别名或缩写。 函数名代表函数的地址可以把函数的地址作为参数传递给其他函数然后这些函数就可以使用被指向的函数。如果把特定函数的地址赋给一个名为pf的函数指针可以通过以下两种方式调用该函数 #include math.h /* 提供sin()函数的原型double sin(double) */ ... double (*pdf)(double); double x; pdf sin; x (*pdf)(1.2); // 调用sin(1.2) x pdf(1.2); // 同样调用 sin(1.2)