C语言的抽象与函数指针2

我们现在可以定义一个函数来计算一个整型数的立方,并且把它传递给这个print_int_fun(),就可以打印出这个数的立方了。

     换句话说,print_int_fun()不仅处理变量,同时也处理函数,它具备了第三层抽象的能力。


三、主角——函数指针

     使C语言具备第三层抽象能力的,是C语言中的函数指针。使用函数指针,我们可以实现模拟把函数作为一个参数传递进另一个函数中以供后者调用,使得调用者有一种模板的性质。

     作为一个练习,观众们不妨看一下下面几个函数的作用:

T* map(T (*fun)(T), T arr[], int len)
{
     for (int i = 0; i < len; ++i)
     {
         arr[i] = fun(arr[i]);
     }
    
     return arr;
}

R reduce(R (*fun)(R, T), R init, T arr[], int len)
{
     R res = init;     // 最终要返回的结果
     for (int i = 0; i < len; ++i)
     {
         res = fun(res, arr[i]);
     }
    
     return res;
}

int filter(bool (*fun)(T), T arr[], int len)
{
     int res = 0;     // 在arr中能使fun()返回真值的元素个数
     for (int i = 0; i < len; ++i)
     {
         if (fun(arr[i]))
         {
             arr[res++] = arr[i];
         }
     }
    
     return res;
}

T* range(T arr[], T init, int len)
{
     for (int i = 0; i < len; ++i)
     {
         arr[i] = init + i;
     }
    
     return arr;
}

     在C++的STL中和Python,它们有对应的泛型算法,但是名字有些不同。在这里,我更喜欢用Python的术语,因为我不懂C++。-_-!

     这四个函数都很简单易懂,这里就不作解释了。

四、例子
     如果我们要写一个程序来计算1到100的各个数的和,我们可能会这样写:
int res = 0;
for (int i = 1; i <= 100; i++)
{
     res += i;
}

printf("%d\n", res);

     如果我们想要计算1到100之间的数的平方和的话,我们可能会这样写:
int res = 0;
for (int i = 1; i <= 100; i++)
{
     res += i * i;
}

printf("%d\n", res);

     如果要计算1/(1*3) + 1/(5*7) + 1/(9*11) + ... + 1/(397 * 399),我们可能会这样写:
double res = 0.0;
for (int i = 1; i <= 100; i++)

{
     res += 1.0 / ((4 * i - 3) * (4 * i - 1));
}

printf("%lf\n", res);

     如果要计算((2 * 4) / (3 * 3)) * ((4 * 6) / (5 * 5)) * ... * ((22 * 24) / (23 * 23)),我们可能会这样写:
double res = 1.0;
for (int i = 1; i <= 10; i++)
{
     res += ((2.0 * i) * (2.0 * i + 2.0)) / ((2.0 * i + 1) * (2.0 * i + 1));
}

printf("%lf\n", res);


     很明显,这四个程序具有相同的结构,只是在以下几个方面不同:结果的初值不同,每次的增量不同,增加增量的方法不同,项数长度不同。如果我们把这四个不同给提取出来作为参数,则可以把这个四个程序合并为一个:

#include <stdio.h>

double delta1(double n)
{
     return n;
}

double delta2(double n)
{
     return n * n;
}

double delta3(double n)
{
     return 1.0 / ((4 * n - 3) * (4 * n - 1));
}

double delta4(double n)
{
     return (((2 * n) * (2 * n + 2)) / ((2 * n + 1) * (2 * n + 1)));
}

double add(double x, double y)
{
     return x + y;
}

double multi(double x, double y)
{
     return x * y;
}

double sum(double (*fun)(double), double init, int len, double (*attach)(double, double))
{
     double res = init;
     for (int i = 1; i <= len; i++)
     {
         res = attach(res, fun(i));
     }
    
     return res;
}

int main(void)
{
     double res1 = sum(delta1, 0.0, 100, add);
     printf("%lf\n", res1);
    
     double res2 = sum(delta2, 0.0, 100, add);
     printf("%lf\n", res2);
    
     double res3 = sum(delta3, 0.0, 100, add);
     printf("%lf\n", res3);
    
     double res4 = sum(delta4, 1.0, 10, multi);
     printf("%lf\n", res4);
    
     return 0;
}
这样是不是简单很多?


计算一个数的阶乘的程序大家都写过,但是大家写过这样的阶乘程序没有?
reduce(multi, 1, range(arr, 1, LEN), LEN);
其中,multi是把两个整型相乘的函数,arr是一个整型数组,LEN是它的长度,为10。而如果要计算1到1000的数中,平方数的个位数为4的数的立方和,则可以这样写:
int len = filter(square_end_with_4, range(arr, 1, 1000));
R res = reduce(add, 0.0, map(cube, arr, len), len);

     利用上面的map(),reduce(),filter(),range()函数也可以把上面的程序改写:
int main(void)
{
#define LEN 100
     R arr[LEN];
    
     R res1 = reduce(add, 0.0, range(arr, 1.0, LEN), LEN);

printf("%lf\n", res1);
    
     R res2 = reduce(add, 0.0, map(delta2, range(arr, 1.0, LEN), LEN), LEN);
     printf("%lf\n", res2);
    
     R res3 = reduce(add, 0.0, map(delta3, range(arr, 1.0, LEN), LEN), LEN);
     printf("%lf\n", res3);
    
     R res4 = reduce(multi, 1.0, map(delta4, range(arr, 1.0, 10), 10), 10);
     printf("%lf\n", res4);
    
     return 0;
}

     最后,以一个求二叉树中的子结点的例子来结束这篇文章。

     如果把二叉树的类型定义为Tree,并且定义Tree*的类型为PTree,并且已经定义好以下几个函数:

// 建立二叉树结点
PTree make_tree(PTree t, PTree l, PTree r, int val)
{
     t->value = val;
     t->left = l;
     t->right = r;
    
     return t;
}

// 获得二叉树的左子树
PTree get_left(PTree t)
{
     return t == NULL ? NULL : t->left;
}

// 获得二叉树的右子树
PTree get_right(PTree t)
{
     return t == NULL ? NULL : t->right;
}

// 获得二叉树的结点值
int get_value(PTree t)
{
     return t == NULL ? -1 : t->value;
}

     假如我们以结点N来表示根结点的右子树的右子树的左子树的右子树的左子树的左子树这个结点,计算N的结点的值,如果N不存在则返回-1。我们会怎么计算呢?
     有些人可能会这样计算:
PTree n = root;
if (n->right)
{
     n = root->right;
     if (n != NULL)
     {
     n = root->right;
     ....
     }
}
return get_value(n);
这个办法虽然可行,但是很笨重。或者有人会这样计算:
typedef PTree (*F_T)(PTree);

PTree n = root;
F_T arr[6] = {get_right, get_right, get_left, get_right, get_left, get_left};
for (int i = 0; i < 6; i++)
{
     n = arr[i](n);
}
return get_value(n);

     但是如果换了是我,我会这样计算:
typedef R PTree*;
typedef PTree (*T)(PTree);

R cat(R res, T n)
{
     return n(res);
}
F_T fs[LEN] = {get_right, get_right, get_left, get_right, get_left, get_left};
    
Tree res = reduce(cat, &t01, fs, LEN);
return get_value(res);

     别看这四个函数很小,但是它们在处理列表的时候非常有用,因为它们通过函数指针的方式,把列表的生成,遍历,筛选,求和都抽象起来了,所以它们能用于许多列表操作里面去!

原文地址:https://www.cnblogs.com/crazyxu/p/2171047.html