天天看點

C語言_指針函數、函數指針與回調函數前言指針函數函數指針函數指針實作回調函數注冊函數的實作signal()函數巧妙的寫法

前言

碰到函數與指針的自由組合, 指針函數與函數指針, 總會混淆概念, 區分不清, 在這裡我們會進行一個明确的區分, 讓大家清楚的認識這倆個概念, 以及用法, 區分他們的重點是最後的倆個字, 是指針還是函數, 下面我們具體的來分析這倆者的差別把!

指針函數

定義與聲明格式

指針函數: 看最後的倆個字, 是函數, 它的本質是函數, 函數的傳回類型是某一類型的指針,

聲明形式: 類型辨別符* 函數名(函數參數1, 參數2…);

有好幾種寫法都是正确的(這個看個人喜好):

int *fun(int a, int b);
int * fun(int a, int b);
int* fun(int a, int b);
           

int* fun(int a, int b); //該函數是一個有倆個int 類型的形參, 傳回值是int 類型的指針, 是一個位址;

該函數的傳回值一定是一個相同類型指針來接收;

代碼用例:

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

int* fun(int a, int b)
{
    int *p = NULL;

    p = (int *)malloc(4);
    
    *p = a + b;
    
    printf("p   指向的位址為:%p\n", p); 

    return p;
}


int main (int argc, char **argv)
{
    int *ptr = NULL;

    ptr = fun(3, 4); 
    
    printf("ptr 指向的位址為:%p\n", ptr);
    printf("*ptr = %d\n", *ptr);
    
	free(ptr);
	ptr = NULL;
	
    return 0;
} 
           

運作結果

我們可以看到在函數fun中malloc的位址空間位址為: 0x55e93d8772a0

在調用函數時, 傳回的位址空間也是: 0x55e93d8772a0

這裡一定注意一個問題, 就是不能傳回棧空間中的變量的位址, 棧空間中的變量的生存周期就是函數調用開始到調函傳回, 如果傳回的位址時棧中變量的位址, 那麼在函數結束時, 該位址已經不是合法空間了, 程式可能會正常運作, 但是要知道是因為此時并沒有其他的變量去改變該位址空間, 如果該位址空間被改變, 那麼将發生錯誤;

一定要傳回的是合法空間的位址, 比如在函數中malloc的位址空間, 使用malloc, 要注意free哦! 不然會造成記憶體洩漏哦!

C語言_指針函數、函數指針與回調函數前言指針函數函數指針函數指針實作回調函數注冊函數的實作signal()函數巧妙的寫法

函數指針

定義與聲明格式

函數名字就是函數的入口位址, 這時候可以定義一個指針指向這個函數, 這個指針就是函數指針;

函數指針: 重點在指針, 其本質是指針, 該指針指向一個函數, 不是普通的資料類型或者某一對象;

聲明格式: 類型辨別符 (*指針變量名) (參數1, 參數2…);

int (*fun)(int, int); 
int* (*fun)(int, int);
           

int (*fun)(int a, intb); // 該指針是指向一個 參數為int, int 類型, 傳回值是int 資料類型的變量的一個函數;

int* (*fun)(int, int); //當然該函數的傳回值也可以是指針, 形參是可以省略的, 那麼聲明就變成了這種形式;

代碼用例

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

int add(int a, int b);
int mul(int a, int b);

int main (int argc, char **argv)
{
    int (*ptr)(int, int);
    int     result = -1; 

    ptr = add;
    result = ptr(3, 4); 
    printf("add:%d\n", result);

    ptr = mul;
    result = ptr(3, 4); 
    printf("mul:%d\n", result);

    return 0;
}

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

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

運作結果

這裡可以看到 主函數中的ptr就是一個函數指針, 指向的一個 形參為int, int, 傳回值為int類型的函數, 通過ptr = add; 和 ptr = mul; 可以執行形參和傳回值相同, 但功能不同的函數.

這裡可能有人有些疑問了, 這裡直接調用add和mul不好嗎? 其實是一樣的, 這裡就要引出callback函數了! 他是函數指針的進階使用!

C語言_指針函數、函數指針與回調函數前言指針函數函數指針函數指針實作回調函數注冊函數的實作signal()函數巧妙的寫法

函數指針實作回調函數

什麼是callback函數呢?

如果把某一個函數作為參數傳遞到另一個函數中, 在另一個函數中會調用該函數, 這個函數叫回調函數(案例中的add()函數就是一個回調函數)!

回調函數是由函數指針來接收的; (這裡會在注釋中使用typedef來巧妙的定義, 在下面會介紹這個用法)

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

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

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

/* 
typedef int(*fun_t)(int, int);
void process_data(int a, int b, fun_t fun)
{
	printf("process data : %d\n", fun(a, b));	
}
*/

void process_data(int a, int b, int (*callback)(int, int))
{
    printf("process data : %d\n", callback(a, b));
}

int main (int argc, char **argv)
{

    process_data(3, 4, add);
    process_data(3, 4, mul);

    return 0;
}
           

運作結果

C語言_指針函數、函數指針與回調函數前言指針函數函數指針函數指針實作回調函數注冊函數的實作signal()函數巧妙的寫法

注冊函數的實作

注冊函數注冊之後并不會立刻發生調用, 隻有條件滿足的時候會調用函數. 下面的用例是使用函數指針去實作一個注冊函數

signal()函數就是注冊函數! 下面我們會自己實作一個signal()

#include <stdio.h>
#include <unistd.h>

void print_odd(void)
{
    printf("printf odd data\n");
}

void print_even(void)
{
    printf("printf even data\n");
}

int main (int argc, char **argv)
{
    int     i = 1;
    
	void (*func_reg[2])(void);
	
    func_reg[0] = print_even;
    func_reg[1] = print_odd;

    while(1)
    {   
        func_reg[i%2]();

        i++;
        sleep(3);
    }   

    return 0;
}
           

運作結果

C語言_指針函數、函數指針與回調函數前言指針函數函數指針函數指針實作回調函數注冊函數的實作signal()函數巧妙的寫法

signal()函數巧妙的寫法

我們先看一下man手冊中對signal函數原型的介紹

C語言_指針函數、函數指針與回調函數前言指針函數函數指針函數指針實作回調函數注冊函數的實作signal()函數巧妙的寫法

signal()函數的第一個參數是一個int 類型的變量, 第二個參數是一個 參數為int類型, 傳回為void 的一個函數指針, 它的傳回值是一個注冊成功的函數類型;

即: void (*ptr)(int) signal(int signum, void (*ptr)(int) ptr);

這樣一個函數看起來非常龐大, 傳回值是一個函數指針, 參數一個是int, 一個是函數指針;

這個時候我們可以巧妙的使用typedef;

typedef void (*headler_t)(int)
/*  這時候headler_t就是一個 傳回值為void 參數為int的指針類型*/
headler_t  signal(int signum, headler_t headler);
           

注冊函數改進

這個時候我們回頭看一下之前寫的注冊函數, 使用typedef之後的樣子

typedef void (*funptr_t)(void);
//typedef void (fun_t)(void);	這樣定義是定義一個函數
int main (int argc, char **argv)
{
    int     i = 1;
    
	funptr_t 		func_reg[2];
	//fun_t			  *func_reg[2];		//這裡前面加上*, 也是函數指針
    func_reg[0] = print_even;
    func_reg[1] = print_odd;

    while(1)
    {   
        func_reg[i%2]();

        i++;
        sleep(3);
    }   

    return 0;
}
           

繼續閱讀