前言
碰到函數與指針的自由組合, 指針函數與函數指針, 總會混淆概念, 區分不清, 在這裡我們會進行一個明确的區分, 讓大家清楚的認識這倆個概念, 以及用法, 區分他們的重點是最後的倆個字, 是指針還是函數, 下面我們具體的來分析這倆者的差別把!
指針函數
定義與聲明格式
指針函數: 看最後的倆個字, 是函數, 它的本質是函數, 函數的傳回類型是某一類型的指針,
聲明形式: 類型辨別符* 函數名(函數參數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哦! 不然會造成記憶體洩漏哦!
函數指針
定義與聲明格式
函數名字就是函數的入口位址, 這時候可以定義一個指針指向這個函數, 這個指針就是函數指針;
函數指針: 重點在指針, 其本質是指針, 該指針指向一個函數, 不是普通的資料類型或者某一對象;
聲明格式: 類型辨別符 (*指針變量名) (參數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函數了! 他是函數指針的進階使用!
函數指針實作回調函數
什麼是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;
}
運作結果
注冊函數的實作
注冊函數注冊之後并不會立刻發生調用, 隻有條件滿足的時候會調用函數. 下面的用例是使用函數指針去實作一個注冊函數
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;
}
運作結果
signal()函數巧妙的寫法
我們先看一下man手冊中對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;
}