1. 程式人生 > >深入淺出剖析C語言函式指標與回撥函式(一)

深入淺出剖析C語言函式指標與回撥函式(一)

關於靜態庫和動態庫的使用和製作方法。

http://blog.csdn.net/morixinguan/article/details/52451612

今天我們要搞明白的一個概念叫回調函式。

什麼是回撥函式?

百度的權威解釋如下:

回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應

那麼我們可以來看一個例子:

#include <stdio.h>
void print();
int main(void)
{
	void (*fuc)(); 
	fuc = print ; 
	fuc(); 	
} 
void print()
{
	printf("hello world!\n");
}

從這個例子可以看到,我們首先定義了一個函式指標fuc ,這個函式指標的返回值為void型,然後我們給函式指標賦值,賦值為print,也就是print函式的首地址,此時fuc獲得了print的地址,fuc的地址等於print的地址,所以最終呼叫fuc();也就相當於呼叫了print();

那麼我寫的這個例子明顯和百度解釋的不符合啊?定義是如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式,確實,有所不同,但道理是一樣的,我們接下來再來看一個例子。

#include <stdio.h>

int add_ret() ;

int add(int a , int b , int (*add_value)())
{
	return (*add_value)(a,b);
}

int main(void)
{
	int sum = add(3,4,add_ret);
	printf("sum:%d\n",sum);
	return 0 ;
} 

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

從這個例子裡,我們看到:

這樣子不就符合我們的定義了嘛?我們把函式的指標(地址),這裡也就是add_ret,作為引數int add(int a , int b , int (*add_value)()) , 這裡的引數就是int(*add_value)() , 這個名字可以隨便取,但是要符合C語言的命名規範。當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。我們看到add函式內部,return (*add_value)(a,b) ; 這個(*add_value)(a,b)相當於對指標進行了簡引用,我們在main函式中,傳入具體要實現功能的函式,add_ret,這個函式很簡單,就是實現兩數相加並返回,這裡剛剛好,簡引用,相當於取出指標返回地址裡的值,這個值就是return a+b,也就是我們傳入a和b兩數相加的結果。

 那麼,回撥函式究竟有什麼作用呢?

     說到這裡,就有了使用者和開發者之間的概念,假設,使用者是實現add_ret這個函式,而開發者是實現add這個函式,現在的需求是,使用者將add_ret這個函式以引數的形式傳入開發者實現的add函式,add函式就會返回一個數字給使用者,開發者沒必要告訴使用者他實現了什麼東西,使用者也並不知道開發者是怎麼實現的,使用者只需要傳入自己寫的函式,便可以得到開發者實現的函式的返回值,開發者可以將內容封裝起來,將標頭檔案以及庫檔案提供給使用者。

接下來,我們用Linux來演示下這個結果,我們在目錄下建立三個檔案main.c,vendor.c,vendor.h

main.c是使用者開發的。

vendor.c和vendor.h是開發者實現的。

在main.c中,程式碼如下:

#include <stdio.h>
#include "vendor.h"

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

int main(void)
{
    int sum = add(3,4,add_ret);
    printf("sum:%d\n",sum);
    return 0 ;
}

vendor.c,程式碼如下:

#include "vendor.h"
int add(int a , int b , int (*add_value)())
{
        return (*add_value)(a,b);
}

vendor.h,程式碼如下:

#ifndef __VENDOR_H
#define __VENDOR_H

int add(int a , int b , int (*add_value)());

#endif 

 接下來,我們製作一個動態連結庫,最終開發者把vendor.c的內容封起來,把vendor.h提供給使用者使用。

#include <stdio.h>
#include "vendor.h"

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

int main(void)
{
    int sum = add(3,4,add_ret);
    printf("sum:%d\n",sum);
    return 0 ;
}


在linux下製作動態連結庫,將vendor.c和vendor.h打包成一個動態連結庫

先明白以下幾個命令是什麼意思:

生成動態庫:

gcc -shared -fPIC dvendor.c -o libvendor.so    

-shared : 生成動態庫;

-fPIC  : 生成與位置無關程式碼;

-o               :指定生成的目標檔案;

使用動態庫:

gcc main.c -L . –lvendor -o main

-L : 指定庫的路徑(編譯時); 不指定就使用預設路徑(/usr/lib/lib)

-lvendor : 指定需要動態連結的庫是誰;

程式碼執行時需要載入動態庫:

./main 載入動態庫 (預設載入路徑:/usr/lib /lib ./ ...)

./main

我們將編譯動態庫生成的libvendor.so拷貝到/usr/lib後,現在就不需要vendor.c了,此時我們將vendor.c移除,也可以正常的編譯並且執行main函式的結果,這就是回撥函式的作用之一。