周立功教你学C语言编程:结构体,使程序设计更方便——内置函数指针和嵌套结构体
第二章为程序设计技术,本文为2.2.3 内置函数指针和2.2.4 嵌套结构体。
我们知道,数组和指针是相同类型有序数据的集合,但很多时候需要将不同类型的数据捆绑在一起作为一个整体来对待,使程序设计更方便。在C语言中,这样的一组数据被称为结构体。
>>> 2.2.3 内置函数指针
面对一系列数据,真正重要的不是如何存储数据,而是如何使用数据。实际上,一个结构体的成员可以是数据,还可以是包含操作数据的函数指针。为了支持这种风格,在这里不妨引入一个新的概念——方法是作为某个结构体的一部分声明的,有了方法就可以操作存储在结构体中的数据。
1. 类型与变量
当函数指针作为结构体的成员时,即将校验参数和调用校验器的函数指针封装在一起,形成了一个新的结构体类型。有了类型就可以定义一个该类型的变量,然后就可以用这个变量引用校验参数和调用校验器函数。
为了支持这种风格,C允许将方法作为某个结构体的一部分来声明,那么操作存储在结构体中的数据就很容易了,详见程序清单 2.18。
程序清单 2.18 范围值校验器接口

接下来需要设计一个判断value值是否符合范围值要求的validateRange()接口函数,其具体的实现详见程序清单 2.19。
程序清单 2.19 范围值校验器接口函数的实现

同理,偶校验器OddEvenValidator和变量oddEvenValidator的定义详见程序清单 2.20。
程序清单 2.20 偶校验器接口

接下来同样需要设计一个判断value值是否符合偶校验要求的validateOddEven()接口函数,其具体的实现详见程序清单 2.21。
程序清单 2.21 偶校验器接口函数的实现

显然,无论是什么校验器,其共性是value值合法性判断,因此可以共用一个函数指针,即特殊的函数指针类型RangeValidate和OddEvenValidate被泛化成了一般的函数指针类型Validate。其次,由于每个函数都有一个指向当前对象的pThis指针,因此特殊的结构体类型struct _RangeValidator *和struct _OddEvenValidator *被泛化成了void *类型,即可接受任何类型数据的实参。比如:

这就是范型编程,校验器泛化接口的实现详见程序清单 2.22。由于pRangeValidator与pThis的类型不同,因此必须对pThis指针强制类型转换才能引用相应结构体的成员。
程序清单 2.22 通用校验器接口的实现(validator.c)

由此可见,当将方法作为结构体的一部分声明时,就直接将方法和数据打包成为了一个新的数据类型RangeValidator。有了RangeValidator类型,就可以创建一个该类型的变量rangeValidator,即可通过rangeValidator引用该结构体的数据,并调用相应的处理函数。真正想强化的是由方法定义结构体的思想,而不是实现结构体时碰巧用到的那些数据。
2. 初始化
使用名为newRangeValidator的宏将结构体初始化:

其中,validateRange为范围值校验器的函数名,使用方法如下:

宏展开后如下:

其相当于:

如果有以下定义:

即可通过pValidator引用RangeValidator的min和max。校验函数的调用方式如下:

以上调用形式的前提是已知pValidator指向了确定的结构体类型,如果pValidator将指向未知的校验器,显然以上调用形式无法做到通用,那么将如何调用?
虽然pValidator与&rangeValidator.validate的类型不一样,但它们的值相等,因此可以利用这一特性获取validateRange()函数的地址。比如:

其调用形式如下:

3. 接口与实现
为了便于阅读,如程序清单 2.23所示详细地展示了通用校验器的接口。
程序清单 2.23 通用校验器接口(validator.h)

以范围值校验器为例,调用validateRange()的rangeCheck()函数的实现如下:

rangeCheck()函数的调用形式如下:

由此可见,rangeCheck()函数的实现不依赖任何具体校验器。 注意,在这里,作者并没有提供完整的代码,请读者补充完善。
>>> 2.2.4 嵌套结构体
1. 重构
随着添加一个又一个功能,处理一个又一个错误,代码的结构会逐渐退化。如果对此置之不理,这种退化最终会导致纠结不清,难以维护的混乱代码,因此需要经常性地重构代码扭转这种退化。
重构就是在不改变代码行为的前提下,对其进行一系列小的改进,旨在改进系统结构的实践活动。虽然每个改进都是微不足道的,甚至几乎不值得去做,但如果将所有的改造叠加在一起时,对系统设计和架构的改进效果是十分明显的。
- 电源软启动的实用设计技巧(07-16)
- 周立功:动态分布内存——malloc()函数与calloc()函数(07-22)
- 周立功“程序设计与数据结构”:深度解剖动态分布内存的free()函数与realloc()函数(07-25)
- 周立功教你学程序设计技术:做好软件模块的分层设计,回调函数要这样写(07-30)
- 周立功教你学C语言编程:教你数组是如何保存指针的(07-31)
- 算法的泛化问题,这些坑你可能都经历过!|周立功教你学软件设计(08-01)
