函數指針

指針一直都是c語言中的難點,但是指針在使用中,更多的情況是指向數組、變量等類型。而函數指針一般比較少直接運用。對於函數指針,最常見的兩個用途就是轉移表和作爲參數傳遞給另一個函數。函數指針也和其他指針一樣,對函數指針進行訪問前必須把它初始化爲指向某個函數。
首先講一下回調函數,回調函數是指用戶把一個函數指針作爲參數傳遞給其他函數,後者將“回調”用戶所傳遞的函數,使用這樣技巧的函數稱回調函數。舉個例子來說,在某個時刻,需要比較兩個整形值的大小,從而進行相應的操作,這樣的情況下,當然可以通過編寫一個參數爲整形的比較函數來實現,在需要調用的地方進行調用,但是如果在代碼中另一處又想比較浮點型的參數的大小呢,是不是又要編寫一個浮點型參數的比較函數,假如還要比較字符串呢(當然了字符串的話,有庫函數可以調用,這裏只是舉例說明情況而已),可以看到不同類型的比較,都需要相應的接口函數實現,那麼能不能實現一個函數,只管傳遞參數,不用管參數的類型,也能得到正確的比較結果,答案是可以的。這樣一來就可以統一對外提供的接口,做到便於代碼的管理,所使用的技巧就是回調函數。下面就以實現比較函數的回調函數來作爲例子,來說明如何實現回調函數的用法。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int compare_type_int(void const *va, void const *vb)
{
    int a = *(int *)va;
    int b = *(int *)vb;
    if (a == b)
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

int compare_type_str(void const *va, void const *vb)
{
    char *a = (char *)va;
    char *b = (char *)vb;
    return strcmp(a,b);
}

int compare_function(void const *ta, void const *tb, int (*compare)(void const *a, void const *b))
{
    return compare(ta, tb);
}

int main(void)
{
    int val_a = 5;
    int val_b = 5;
    char *str_a = "abc";
    char *str_b = "abd";

    if (compare_function((void const *)&val_a, (void const *)&val_b, compare_type_int) == 0)
        printf("val_a == val_b\n");
    else 
        printf("val_a != val_b\n");

    if (compare_function((void const *)&str_a, (void const *)&str_b, compare_type_str) == 0)
        printf("str_a == str_b\n");
    else
        printf("str_a != str_b\n");
    system("pause");
    return 0;
}

函數指針的第二用法就是實現轉移表。以整形數的加減乘除操作爲例來說明轉移表的實現方式(這裏旨在說明用法,不作函數健壯性判斷)。

#include <stdio.h>
#include <stdlib.h>

int ope_add(int a, int b)
{
    return a + b;
}

int ope_sub(int a, int b)
{
    return a - b;
}

int ope_mul(int a, int b)
{
    return a * b;
}

int ope_div(int a, int b)
{
    return a / b;   
}

int main(void)
{
    int a;
    int b;
    int oper_type;
    int result;

    scanf("%d%d%d",&a, &b, &oper_type);
    switch(oper_type)
    {
    case 1:
            result = ope_add(a,b);
            break;
    case 2:
            result = ope_sub(a, b);
            break;
    case 3:
            result = ope_mul(a, b);
            break;
    case 4:
            result = ope_div(a, b);
            break;
    }
    printf("result = %d\n",result);
    system("pause");
    return 0;
}

可以看到,對於指定的操作類型,在實現中調用相應的函數。但是如果這樣的操作類型遠不止例子中的4個,則switch中的case語句將會是相當多的,整個函數的代碼會也因此而變得很長,不利於程序閱讀和維護。這時就可以用轉移表來作替代實現。代碼如下:

#include <stdio.h>
#include <stdlib.h>

int ope_add(int a, int b)
{
    return a + b;
}

int ope_sub(int a, int b)
{
    return a - b;
}

int ope_mul(int a, int b)
{
    return a * b;
}

int ope_div(int a, int b)
{
    return a / b;   
}

int (*oper_fun[])(int a, int b) = {ope_add, ope_sub, ope_mul, ope_div};
int main(void)
{
    int a;
    int b;
    int oper_type;
    int result;

    scanf("%d%d%d",&a, &b, &oper_type);

    if (oper_type>= 0 && oper_type <= 3)
        result = oper_fun[oper_type](a, b);
    else
    {
        printf("no such operation\n");
        return -1;
    }
    printf("result = %d\n",result);
    system("pause");
    return 0;
}

定義一個函數指針數組,這樣當需要添加新的操作類型的函數時,只需要定義該類型的函數,並添加數組成員,就可以了。對於操作類型oper_type要做一個安全性判斷,如果發生下標越界訪問,在一個複雜的程序代碼中,將會是很難調節的,因爲其結果是未定義的,並不能知道程序什麼時候會運行失敗,所以在寫代碼時,做好這一步健壯性判斷是必須的。但是假如某程序中使用了轉移表,而在實現調試過程中,懷疑轉移表訪問出問題了,可以在那個函數調用前後各打印一條信息。因爲如果轉移表訪問越界了,函數調用是不會返回的。用這種辦法就可以判斷是不是轉移表訪問越界了。
轉移表雖然方便,但也是也有限制,就是轉移表內的函數類型要相同。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章