此文是在看到《剑指Offer》第2版p129面的面试题21:调整数组顺序使奇数位于偶数前面学到的一种函数扩展方法,这种思想是很让人受用的。
题目:输入一个整数数组,实现一个函数来调整该数组中的数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
题解:可以使用简单的双指针思想,一个在首,一个在尾。然后首向右移动,直到遇到偶数num[i] & 0x1 == 0
,尾向左移动,直到遇到奇数num[i] & 0x1 != 0
,再将首尾指针所指元素进行互换。进行以上操作直至首尾指针相遇。代码如下:
void RecorderOddEven(int *pData, int length) {
if(pData == nullptr || length == 0) return;
int *pBegin = pData;
int *pEnd = pData + length - 1;
while(pBegin < pEnd) {
//向后移动pBegin,直到指向偶数
while(pBegin < pEnd && (*pBegin & 0x1) != 0)
pBegin++;
//向前移动pEnd,直到指向奇数
while(pEnd > pEnd && (*pEnd & 0x1) == 0)
pEnd--;
if(pBegin < pEnd) {
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
}
以上代码只能解决将奇数置于偶数之前的问题,若是需要将所有负数放在非负数之前,或是能被3整除的放在不能被3整除的前面,这是该怎么办?会发现,只要更改上述代码中的(*pBegin & 0x1) != 0
和(*pEnd & 0x1) == 0
两个判断条件即可。
此时就要用到c++中的函数指针转函数参数的方式,把判断的标准变成一个函数指针,具体形式为函数参数列表中的bool (*func)(int)
,也就是用一单独的函数来判断数字是否符合标准。这样就将函数解耦成两部分,一部分是判断数字的位置标准,另一部分是拆分转换数组的操作。代码如下:
//添加一函数指针作为函数中的形式参数,作用为对数值位置的判断标准
//其中bool为函数指针形参的返回类型,*func表示是函数指针,int表示函数指针参数的形参列表
void Recorder(int *pData, int length, bool (*func)(int)) {
if(pData == nullptr || length == 0) return;
int *pBegin = pData;
int *pEnd = pData + length - 1;
while(pBegin < pEnd) {
//向后移动pBegin,直到指向偶数
while(pBegin < pEnd && !func(*pBegin))
pBegin++;
//向前移动pEnd,直到指向奇数
while(pEnd > pEnd && !func(*pEnd))
pEnd--;
if(pBegin < pEnd) {
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
}
bool isEven(int n) {
//是否是偶数
return n & 0x1;
}
bool divideByThree(int n) {
//是否能被3整除
return n % 3;
}
bool isMinusZero(int n) {
//是否是负数
return n < 0;
}
对于原问题,就可以上述代码进行组装后解决:
void RecorderOddEven(int *pData, int length) {
//奇数在偶数前
Recorder(pData, length, isEven);
}
void RecorderDivideByThree(int *pData, int length) {
//能被3整除在前
Recorder(pData, length, divideByThree);
}
void RecorderMinusZero(int *pData, int length) {
//负数在前
Recorder(pData, length, isMinusZero);
}
可以看到解决类似问题的时候,都只需定义新的函数来确定分组的标准,而函数Recorder不需要进行任何改动,提高了代码的重用性,为功能扩展提供了便利。
也即是将标准和操作分开,当不同厂商需要对自己函数的业务进行改变时,只需要将业务函数中的函数指针参数改变为满足新业务的函数的函数指针即可,各厂商不需要做很大的改变即可完成业务更替。