🍏 指向结构的指针
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
喜欢使用指针的人一定很高兴能使用指向结构的指针。至少有 4 个理由可以解释为何要使用指向结构的指针。
  • 第一,就像指向数组的指针比数组本身更容易操控(如,排序问题)一样,指向结构的指针通常比结构本身更容 易操控。
  • 第二,在一些早期的C实现中,结构不能作为参数传递给函数,但是可以传递指向结构的指针。
  • 第三,即使能传递一个结构,传递指针通常更有效率。
  • 第四,一些用于表示数据的结构中包含指向其他结构的指针。
notion image
 

声明和初始化结构指针

声明结构指针很简单:
🍏 向函数传递结构的信息
type
status
date
slug
summary
tags
category
icon
password
Property
 
函数的参数把值传递给函数。每个值都是一个数字——可能是int类型、float类型,可能是ASCII字符码,或者是一个地址。然而,一个结构比一个单独的值复杂,所以难怪以前的C实现不允许把结构作为参数传递给函数。 当前的实现已经移除了这个限制,ANSI C允许把结构作为参数使用。所以程序员可以选择是传递结构本身,还是传递指向结构的指针。如果只关心结构中的某一部分,也可以把结构的成员作为参数。

传递结构成员

只要结构成员是一个具有单个值的数据类型(即,int及其相关类型、char、float、double或指针),便可把它作为参数传递给接受该特定类型的函数
当然,如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址。
 

传递结构的地址

这次把结构的地址作为参数
sum()函数使用指向funds结构的指针(money)作为它的参数。把地址&stan传递给该函数,使得指针money指向结构stan。然后通过->运算符获取stan.bankfundstan.savefund的值。由于该函数不能改变指针所指向值的内容,所以把money声明为一个指向const的指针。 虽然该函数并未使用其他成员,但是也可以访问它们。注意,必须使用&运算符来获取结构的地址。
 
🍏 把结构内容保存到文件中
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
由于结构可以储存不同类型的信息,所以它是构建数据库的重要工具。 例如,可以用一个结构储存雇员或汽车零件的相关信息。最终,我们要把这些信息储存在文件中,并且能再次检索。数据库文件可以包含任意数量的此类数据对象。储存在一个结构中的整套信息被称为记录(record),单独的项被称为字段(field)。
 
储存记录最没效率的方法是用fprintf()
如果pbook标识一个文件流,那么通过下面这条语句可以把信息储存在struct book类型的结构变量primer中:
对于一些结构(如,有 30 个成员的结构),这个方法用起来很不方便。另外,在检索时还存在问题,因为程序要知道一个字段结束和另一个字段开始的位置。虽然用固定字段宽度的格式可以解决这个问题,但是这个方法仍然很笨拙。
 
更好的方案是使用fread()和fwrite()函数读写结构大小的单元,这两个函数使用与程序相同的二进制表示法。例如:
🍏 链式结构
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
结构的多种用途之一:创建新的数据形式。
计算机用户已经开发出的一些数据形式比我们提到过的数组和简单结构更有效地解决特定的问题。这些形式包括队列、二叉树、堆、哈希表和图表。许多这样的形式都由链式结构组成。通常,每个结构都包含一两个数据项和一两个指向其他同类型结构的指针。这些指针把一个结构和另一个结构链接起来,并提供一种路径能遍历整个彼此链接的结构。例如,下图演示了一个二叉树结构,每个单独的结构(或节点)都和它下面的两个结构(或节点)相连。
notion image
图中显示的分级或树状的结构是否比数组高效?考虑一个有10级节点的树的情况。它有210−1(或1023)个节点,可以储存1023个单词。如果这些单词以某种规则排列,那么可以从最顶层开始,逐级向下移动查找单词,最多只需移动9次便可找到任意单词。如果把这些单词都放在一个数组中,最多要查找1023个元素才能找出所需的单词。
🍏 联合
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
联合(union)是一种数据类型,它能在同一个内存空间中储存不同的数据类型(不是同时储存)。其典型的用法是,设计一种表以储存既无规律、事先也不知道顺序的混合类型。使用联合类型的数组,其中的联合都大小相等,每个联合可以储存各种数据类型。 创建联合和创建结构的方式相同,需要一个联合模板和联合变量。可以用一个步骤定义联合,也可以用联合标记分两步定义。下面是一个带标记的联合模板:
根据以上形式声明的结构可以储存一个int类型、一个double类型和char类型的值。然而,声明的联合只能储存一个int类型的值或一个double类型的值或char类型的值。
 
下面定义了3个与hold类型相关的变量:
第1个声明创建了一个单独的联合变量fit。编译器分配足够的空间以便它能储存联合声明中占用最大字节的类型。在本例中,占用空间最大的是double类型的数据。在我们的系统中,double类型占64位,即8字节。第2个声明创建了一个数组save,内含10个元素,每个元素都是8字节。第3个声明创建了一个指针,该指针变量储存hold类型联合变量的地址。 可以初始化联合。需要注意的是,联合只能储存一个值,这与结构不同。有 3 种初始化的方法:把一个联合初始化为另一个同类型的联合;初始化联合的第1个元素;或者根据C99标准,使用指定初始化器:
 

使用联合

🍏 枚举类型
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
可以用枚举类型(enumerated type)声明符号名称来表示整型常量。使用enum关键字,可以创建一个新“类型”并指定它可具有的值(实际上,enum常量是int类型,因此,只要能使用int类型的地方就可以使用枚举类型)。枚举类型的目的是提高程序的可读性。它的语法与结构的语法相同。例如,可以这样声明:
第1个声明创建了spetrum作为标记名,允许把enum spetrum作为一个类型名使用。第2个声明使color作为该类型的变量。第1个声明中花括号内的标识符枚举了spectrum变量可能有的值。因此, color 可能的值是 red、orange、yellow 等。这些符号常量被称为枚举符(enumerator)。然后,便可这样用:
虽然枚举符(如red和blue)是int类型,但是枚举变量可以是任意整数类型,前提是该整数类型可以储存枚举常量。例如,spectrum的枚举符范围是0~5,所以编译器可以用unsigned char来表示color变量。
顺带一提,C枚举的一些特性并不适用于C++。例如,C允许枚举变量使用++运算符,但是C++标准不允许。所以,如果编写的代码将来会并入C++程序,那么必须把上面例子中的color声明为int类型,才能C和C++都兼容。
 

enum常量

blue和red到底是什么?从技术层面看,它们是int类型的常量。例如,假定有前面的枚举声明,可以这样写:
🍏 typedef
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
typedef工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。这方面与#define类似,但是两者有3处不同:
  • 与#define不同,typedef创建的符号名只受限于类型,不能用于值
  • typedef由编译器解释,不是预处理器
  • 在其受限范围内,typedef比#define更灵活
 
假设要用BYTE表示1字节的数组。只需像定义个char类型变量一样定义BYTE,然后在定义前面加上关键字typedef即 可:
随后,便可使用BYTE来定义变量:
该定义的作用域取决于typedef定义所在的位置。如果定义在函数中,就具有局部作用域,受限于定义所在的函数。如果定义在函数外面,就具有文件作用域。
🍏 其他复杂的声明
type
status
date
slug
summary
tags
category
icon
password
Property
 
一些较复杂的声明示例:
数组名后面的[]和函数名后面的()具有相同的优先级。它们比*(解引用运算符)的优先级高。
 
可以使用typedef建立一系列相关类型:
🍏 函数指针
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
什么是函数指针?假设有一个指向int类型变量的指针,该指针储存着这个int类型变量储存在内存位置的地址。同样,函数也有地址,因为函数的机器语言实现由载入内存的代码组成。指向函数的指针中储存着函数代码的起始处的地址。
其次,声明一个数据指针时,必须声明指针所指向的数据类型。声明一个函数指针时,必须声明指针指向的函数类型。为了指明函数类型,要指明函数签名,即函数的返回类型和形参类型。例如,考虑下面的函数原型:
ToUpper()函数的类型是“带char * 类型参数、返回类型是void的函数”。下面声明了一个指针pf指向该函数类型:
从该声明可以看出,第1对圆括号把*和pf括起来,表明pf是一个指向函数的指针。因此,(*pf)是一个参数列表为(char *)、返回类型为void的函数。 注意,把函数名ToUpper替换为表达式(*pf)是创建指向函数指针最简单的方式。所以,如果想声明一个指向某类型函数的指针,可以写出该函数的原型后把函数名替换成(*pf)形式的表达式,创建函数指针声明。前面提到过,由于运算符优先级的规则,在声明函数指针时必须把 *和指针名括起来。如果省略第1个圆括号会导致完全不同的情况:
要声明一个指向特定类型函数的指针,可以先声明一个该类型的函数,然后把函数名替换成(*pf)形式的表达式。然后,pf就成为指向该类型函数的指针。 声明了函数指针后,可以把类型匹配的函数地址赋给它。在这种上下文中,函数名可以用于表示函数的地址:
最后一条语句是无效的,不仅因为 ToLower()不是地址,而且ToLower()的返回类型是 void,它没有返回值,不能在赋值语句中进行赋值。注意,指针pf可以指向其他带char *类型参数、返回类型是void的函数,不能指向其他类型的函数。 既然可以用数据指针访问数据,也可以用函数指针访问函数。奇怪的是,有两种逻辑上不一致的语法可以这样做,下面解释:
🍐 位操作
type
status
date
slug
summary
tags
category
icon
password
Property

 

二进制数、位和字节

用二进制系统可以把任意整数(如果有足够的位)表示为0和1的组合。由于数字计算机通过关闭和打开状态的组合来表示信息,这两种状态分别用0和1来表示,所以使用这套数制系统非常方便

二进制整数

通常,1字节包含8位。C语言用字节(byte)表示储存系统字符集所需的大小,所以C字节可能是8位、9位、16位或其他值。不过,描述存储器芯片和数据传输率中所用的字节指的是8位字节。为了简化起见,假设1字节是8位(计算机界通常用八位组这个术语特指8位字节)。可以从左往右给这8位分别编号为7~0。在1字节中,编号是7的位被称为高阶位,编号是0的位被称为低阶位。每 1位的编号对应2的相应指数。
notion image
这里,128是2的7次幂,以此类推。该字节能表示的最大数字是把所有位都设置为1:11111111。这个二进制数的值是:
🍑 C预处理器
type
status
date
slug
summary
tags
category
icon
password
Property
 
🍑

翻译处理

在预处理之前,编译器必须对该程序进行一些翻译处理。
  1. 编译器把源代码中出现的字符映射到源字符集。该过程处理多字节字符和三字符序列——字符扩展让C更加国际化。
  1. 编译器定位每个反斜杠后面跟着换行符的实例,并删除它们
    1. 注意,在这种场合中,“换行符”的意思是通过按下Enter键在源代码文件中换行所生成的字符,而不是指符号表征\n。
      由于预处理表达式的长度必须是一个逻辑行,所以这一步为预处理器做好了准备工作。一个逻辑行可以是多个物理行。
🍄 C库
type
status
date
slug
summary
tags
category
icon
password
Property
 
🍄

 
最初,并没有官方的C库。后来,基于UNIX的C实现成为了标准。ANSIC委员会主要以这个标准为基础,开发了一个官方的标准库。在意识到C语言的应用范围不断扩大后,该委员会重新定义了这个库,使之可以应用于其他系统。
 

访问C库

何访问C库取决于实现,因此要了解当前系统的一般情况。首先,可以在多个不同的位置找到库函数。例如,getchar()函数通常作为宏定义在stdio.h头文件中,而strlen()通常在库文件中。其次,不同的系统搜索这些函数的方法不同。下面介绍3种可能的方法。
  • 自动访问
    • 在一些系统中,只需编译程序,就可使用一些常用的库函数。 在使用函数之前必须先声明函数的类型,通过包含合适的头文件即可完成。在描述库函数的用户手册中,会指出使用某函数时应包含哪个头文件。但是在一些旧系统上,可能必须自己输入函数声明。 过去,不同的实现使用的头文件名不同。ANSI C标准把库函数分为多个系列,每个系列的函数原型都放在一个特定的头文件中。