周立功教你学程序设计结构体:内存对齐和基本数据类型
、double或指针,便可将它作为参数传递给接受该特定类型的函数,rangeCheck()的实现详见程序清单 2.11。
程序清单 2.11 rangeCheck()函数的实现(2)
其调用形式如下:
rangeCheck()既不知道也不关心实参是否是结构体的成员,它只要求传入的数据是int类型。如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址。
(2)传递结构体
虽然传递一个结构体比一个单独的值复杂,但标准C同样允许将结构体作为参数使用,rangeCheck()函数的实现详见程序清单 2.11。
程序清单 2.12 rangeCheck()函数的实现(3)
其调用形式如下:
虽然通过这种方法能够得到正确的结果,但它的效率很低,因为C语言的参数传址调用方式要求将参数的一份拷贝传递给函数。假设结构体的成员是一个占用128字节的数组,甚至更大的数组。如果要将它作为参数进行传递,则必须将所占用的字节数复制到堆栈中,以后再丢弃。
(3)传递结构体的地址
假设有一组这样的数据,存储在结构体成员数组中。其数据结构如下:
显然,只要将结构体的地址(int *)&st作为实参传递给iMax()的形参,即可求出数组中元素的最大值,详见程序清单 2.13。
程序清单 2.13 求数组中元素的最大值范例程序
下面还是以范围值校验器为例,定义一个指向该结构体的指针变量pRange,其初始化、赋值与普通指针变量是一样的:
和数组不一样,结构名并不是结构体的地址,因此要在结构名前加上&运算符,因此这里的pRange为指向Range结构体变量range的指针变量。虽然pRange、&range和&range.min的类型不一样,但它们的值相等,那么下面的关系恒成立:
由于.运算符比*运算符的优先级高,因此必须使用圆括号。这里着重理解pRange是一个指针,pRange->min表示pRange指向结构体的首成员,所以pRange->min是一个int类型的变量,rangeCheck()函数的实现详见程序清单 2.14。
程序清单 2.14 rangeCheck()函数的实现(4)
rangeCheck()使用指向Range的指针pRange作为它的参数,将地址&range传递给该函数,使得指针pRange指向range,然后通过->运算符获取range.min和range.max的值。注意,必须使用&运算符获取结构体的地址,和数组名不同,结构体名只是其地址的别名。
其调用形式如下:
(4)用函数指针调用
如果需要增加一个奇偶校验器对value值进行偶校验,其数据结构如下:
oddEvenCheck()函数的实现详见程序清单 2.15。
程序清单 2.15 oddEvenCheck()函数的实现
当系统需要多个校验器后,在运行时调用者将根据实际情况决定调用哪个函数,根据依赖倒置原则,最好的方法是用函数指针隔离变化。无论什么校验器,其相同的处理部分是value值的合法性判断,因此将其抽象为模块。而可变的是value值和校验参数,由外部传入的参数应对。由于各种校验器的类型不一样,因此必须使用"void *pData"作为形参才能接受任意类型的数据,即将Range *pRange和OddEven *pOddEven泛化成了void *pData。Validate类型的定义如下:
其中,pData为指向任意校验器参数的指针,value为待校验的值,通用校验器的接口详见程序清单 2.16。
程序清单 2.16 通用校验器接口(validator.h)
以范围值校验器为例,其调用形式如下:
这次传递给函数的是一个指向结构体的指针,指针比整个结构体要小得多,所以将它压到堆栈上的效率要高很多,validator接口的实现详见程序清单 2.17。
程序清单 2.17 validator接口的实现(validator.c)
由于pRange、pOddEven与pData的类型不同,因此需要对pData强制类型转换,才能引用相应结构体的成员。注意,在这里,作者并没有提供完整的代码,请读者补充完善。
- 电源软启动的实用设计技巧(07-16)
- 周立功:动态分布内存——malloc()函数与calloc()函数(07-22)
- 周立功“程序设计与数据结构”:深度解剖动态分布内存的free()函数与realloc()函数(07-25)
- 周立功教你学程序设计技术:做好软件模块的分层设计,回调函数要这样写(07-30)
- 周立功教你学C语言编程:教你数组是如何保存指针的(07-31)
- 算法的泛化问题,这些坑你可能都经历过!|周立功教你学软件设计(08-01)