C語言指針函數
C語言指針函數就是函數中用到了指針的函數,主要是有以下兩種方式
- 以指針為參數的函數
- 以指針為傳回值的函數
指針做函數參數
學習函數的時候,講了函數的參數都是值拷貝,在函數裡面改變形參的值,實參并不會發生改變。如下圖:
每個函數都有一個獨立的棧區,在函數傳參的過程中,是把實參的值拷貝給形參,修改形參的值并不能作用到實參。如果想要通過形參改變實參的值,就需要傳入實參的位址,可以通過尋址方式作用到實參上,如下圖:
想要修改實參的值,需要傳入實參的位址,故想要修改該指針變量的指向需要傳入指針變量的位址,也就是二級指針。多級指針中也是依次類推,資料結構中常有二級指針傳參。
示例程式| 傳參的方式動态申請一維數組
傳參的方式修改一級指針的值,需要傳入二級指針,通過尋址的方式修改一級指針,如下測試代碼:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
void createArray(int** parray, int arrayNum)
{
*parray = (int*)calloc(arrayNum,sizeof(int));
assert(parray);
}
int main()
{
int* p = NULL;
createArray(&p, 3);
for (int i = 0; i < 3; i++)
{
printf("%d\t", p[i]);
}
printf("\n");
return 0;
}
運作結果如下:
示例程式| 封裝函數操作數組
通常在封裝函數操作數字類(int ,float,double,…)數組一定要傳入數組長度,操作字元串類通常不需要,因為字元串存在字元串結束标記。例如封裝周遊數組函數和字元串比較函數,代碼如下:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
//等效void printArray(int array[], int arrayNum)
void printArray(int* array, int arrayNum)
{
for (int i = 0; i < arrayNum; i++)
{
printf("%d\t", array[i]);
}
printf("\n");
}
int myStrcmp(const char* str1, const char* str2)
{
int i = 0;
int j = 0;
//字元串比較從左往右比,找到不同的字元即可得到比較結果
while (str1[i] == str2[j]&&str1[i] != '\0')
{
i++;
j++;
}
return str1[i] - str2[j];
}
int main()
{
int array[5] = { 1,2,3,4,5 };
printArray(array, 5);
printf("%d\n", myStrcmp("string1", "string")>0);
printf("%d\n", myStrcmp("string", "string")==0);
printf("%d\n", myStrcmp("string", "string1")<0);
return 0;
}
運作結果如下:
當然比較函數你也可以傳回0,-1,1,隻需要在字元串比較函數中分類讨論下即可。
指針做函數傳回值
指針當做函數傳回值和普通函數一樣,隻是傳回值類型不同而已,既然傳回是一個指針,*指針等效變量,故*函數調用也可以等效變量。把指針當做函數傳回值注意項:
- 不要傳回臨時變量的位址
- 可以傳回動态申請的空間的位址
- 可以傳回靜态變量和全局變量的位址
當函數傳回臨時變量的位址時,位址中存儲的資料随着函數調用完會被回收掉,導緻擷取垃圾值。如下測試代碼:
#include <stdio.h>
int* testFunc()
{
int number = 1314;
return &number;
}
int main()
{
int* result=testFunc();
//第一次資料做了保留
printf("%d\n", *result);
//後續資料被回收了,垃圾值
printf("%d\n", *result);
printf("%d\n", *result);
return 0;
}
運作結果如下:
在vs開發工具中會友善給予提醒,希望看到這類提醒當做錯誤處理,及時改善,友善提醒如下:
示例程式| 傳回值的方式動态申請一維數組
可以傳回動态申請的空間的位址,堆區記憶體需要調用free函數手動釋放,如下測試代碼:
#include <stdio.h>
#include <stdlib.h>
int* createArray(int arrayNum)
{
int* p = (int *)calloc(arrayNum, sizeof(int));
return p;
}
int main()
{
int* p = NULL;
p = createArray(3);
for (int i = 0; i < 3; i++)
{
printf("%d\t", p[i]);
}
free(p);
p = NULL;
return 0;
}
運作結果如下:
示例程式| 用字元串初始化堆區記憶體并傳回首位址
其實和數字類的操作沒什麼太大差別,唯一要注意的是字元串申請統計長度用strlen,申請是可見長度加1,拷貝指派用strcpy完成,如下測試代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* createArray(const char* str)
{
//申請長度是可見度長度+1
unsigned int length = strlen(str)+1;
char* p = (char *)calloc(length, sizeof(int));
assert(p);
//不能直接 p=str,文法沒問題但是意義不同
strcpy(p, str);
return p;
}
int main()
{
char* pstr = NULL;
pstr = createArray("coolmoying");
puts(pstr);
free(pstr);
pstr = NULL;
return 0;
}
運作結果如下:
C語言函數指針
什麼是函數指針
如果在程式中定義了一個函數,那麼在運作時系統就會為這個函數代碼配置設定一段存儲空間,這段存儲空間的首位址稱為這個函數的位址。擷取函數位址有以下兩種方式:
- 函數名
- &函數名
既然是位址我們就可以定義一個指針變量來存放,這個指針變量就叫作函數指針變量,簡稱函數指針。函數指針的唯一作用就是調用函數,函數指針沒有++和 –運算
如何建立函數指針
函數傳回值類型 (*指針變量名) (函數參數清單);
簡單來說一句話,用(*變量名) 替換函數名,剩下的照抄即可,形參名可寫可不寫就是函數指針變量。如下函數的函數指針建立:
如何通過函數指針調用函數
函數指針可以通過不同的初始化方式,調用除了函數名不同,其他類型相同的所有函數。調用方式有以下兩種:
- 直接函數指針名替換函數名去調用函數
- (*函數指針)替換函數名的方式去調用函數
推薦使用第一種方式,代碼看起來比較簡單。如下測試代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
void test()
{
printf("Test\n");
}
void test2()
{
printf("Test2\n");
}
int Max(int a, int b)
{
return a > b ? a : b;
}
void printArray(int(*p)[3], int row, int cols)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < cols; j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main()
{
//建立函數指針變量
void (*pTest)() = NULL;
int(*pMax)(int a, int b) = NULL;
//參數名可省略
void (*pprint)(int(*)[3], int, int) = NULL;
//函數指針指派
//兩種方式即可
pTest = test;
pTest = &test;
pMax = Max;
pprint = printArray;
//函數指針變量調用函數
//兩種方式即可
pTest();
(*pTest)();
printf("%d\n",pMax(1, 2));
int array[2][3] = { 1,2,3,4,5,6 };
pprint(array, 2, 3);
//調用除了函數名不同,其他類型相同的所有函數
pTest = &test2;
pTest();
return 0;
}
運作結果如下:
回調函數
回調函數就是以函數指針作為某個函數的參數,函數指針比較重要的應用就是回調函數,在Windows SDK,多線程,事件進行中大量用到回調函數。函數指針變量可以作為某個函數的參數來使用的,回調函數就是一個通過函數指針調用的函數。簡單講:回調函數是由别人的函數執行時調用你實作的函數。通俗的講:你到一個商店買東西,沒有貨,留給店員電話,有貨了,打電話給你,然後你去取貨。在這個例子裡,你的電話号碼就叫回調函數,你把電話留給店員就叫登記回調函數,店裡後來有貨了叫做觸發了回調關聯的事件,店員給你打電話叫做調用回調函數,你到店裡去取貨叫做響應回調事件。如下測試代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
void get()
{
printf("取貨成功!!!\n");
}
void wait()
{
printf("等待售貨員電話!...\n");
}
void salesperson(bool flag, void(*Doing)())
{
if (flag == true) //有貨
{
printf("通知取貨\n");
Doing();
}
else //無貨
{
printf("無貨\n");
Doing();
}
}
int main()
{
//通常回調函數有關聯的事件
//這裡簡單用有無貨物來做
salesperson(false, wait);
salesperson(true, get);
return 0;
}
通常salesperson是第三方封裝好的,我們隻需要實作salesperson函數指針,通過salesperson去調用自己的函數,通常别人設計的回調函數都會綁定事件,目前初步接觸了解下。運作結果如下:
C語言萬能指針充當函數指針
萬能指針充當函數指針使用前必須要強制類型轉換,函數指針的類型就是去掉變量名即可 ,如下測試代碼:
#include <stdio.h>
#include <stdlib.h>
void test()
{
printf("調用成功!!!\n");
}
int main()
{
void* p = test;
//正常指針調用:p();
//test類型: void(*)()
//強轉文法: (類型)(表達式)
((void(*)())p)();
return 0;
}
運作結果如下:
複雜函數指針解析
右左法則
首先找到辨別符,然後往右看,再往左看,每當遇到圓括号時,就應該調轉閱讀方向,一旦解析完圓括号裡面的所有東西,就跳出圓括号,重複這個過程直到整個聲明解析完畢。
示例1| int (*func)(int *p)
首先找到那個辨別符,就是func,它的外面有一對圓括号,而且左邊是一個*号,這說明func是一個指針,然後跳出這個圓括号,先看右邊,也是一個圓括号,這說明(*func)是一個函數,而func是一個指向這類函數的指針,就是一個函數指針,這類函數具有int*類型的形參,傳回值類型是 int。
示例2| int (*func)(int *p, int (*f)(int*))
func被一對括号包含,且左邊有一個*号,說明func是一個指針,跳出括号,右邊也有個括号,那麼func是一個指向函數的指針,這類函數具有int *和int (*)(int*)這樣的形參,傳回值為int類型。再來看一看func的形參int (*f)(int*),類似前面的解釋,f也是一個函數指針,指向的函數具有int*類型的形參,傳回值為int。
示例3| int (*func[5])(int *p)
func右邊是一個[]運算符,說明func是一個具有5個元素的數組,func的左邊有一個*,說明func的元素是指針,要注意這裡的*不是修飾 func的,而是修飾func[5]的,原因是[]運算符優先級比*高,func先跟[]結合,是以*修飾的是func[5]。跳出這個括号,看右邊,也是一對圓括号,說明func數組的元素是函數類型的指針,它所指向的函數具有int*類型的形參,傳回值類型為int。
示例4| int (*(*func)[5])(int *p)
func被一個圓括号包含,左邊又有一個*,那麼func是一個指針,跳出括号,右邊是一個[]運算符号,說明func是一個指向數組的指針,現在往左看,左邊有一個*号,說明這個數組的元素是指針,再跳出括号,右邊又有一個括号,說明這個數組的元素是指向函數的指針。總結一下就是:func是一個指向數組的指針,這個數組的元素是函數指針,這些指針指向具有int*形參,傳回值為int類型的函數。
示例5| int (*(*func)(int *p))[5]
func是一個函數指針,這類函數具有int*類型的形參,傳回值是指向數組的指針,所指向的數組的元素是具有5個int元素的數組。
示例6| int (*(*(*func)(int *))[5])(int *)
func是一個函數指針,這類函數的傳回值是一個指向數組的指針,所指向數組的元素也是函數指針,指向的函數具有int*形參,傳回值為int。
實際當中,需要聲明一個複雜指針時,如果把整個聲明寫成上面所示的形式,對程式可讀性是一大損害。應該用typedef來對聲明逐層分解,增強可讀性,如果對typedef不懂的,後續講解。
客觀請留步
如果閣下正好在學習C/C++,看文章比較無聊,不妨關注下關注下小編的視訊教程,通俗易懂,深入淺出,一個視訊隻講一個知識點。視訊不深奧,不需要鑽研,在公交、在地鐵、在廁所都可以觀看,随時随地漲姿勢。