目錄
九、函數指針數組
1、字符指針變量
2、數組指針變量
3、二維數組傳參的本質
?4、函數指針變量
4.1 分析《C陷阱和缺陷》中的兩端代碼
4.2?typedef關鍵字
5、函數指針數組
6、函數指針數組的用途---轉移表
九、函數指針數組
1、字符指針變量
在指針的類型中我們知道有一種指針類型為字符指針 char* ;一般使用:
int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}
? ? ? ?還有?種使用方式如下:代碼 const char* pstr = "hello bit."; 特別容易讓同學以為是把字符串 hello bit 放到字符指針 pstr 里了,但是本質是把字符串 hello bit. 首字符的地址放到了pstr中。
int main()
{const char* pstr = "hello bit.";//這?是把?個字符地址存放在p中printf("%s\n", pstr);return 0;
}
?所以可以把字符串想象成一個數組:
注:①當常量字符串常量出現在表達式中的時候,他的值是第一個字符的地址。? ? ? ? ? ? ? ? ? ? ? ? ? ? ②雖然可以把字符串看成一個字符數組,但是這個數組是不能修改的,因為它是常量不是變量。?
《劍指offer》中的一道和字符串相關的筆試題:
#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}
? ? ? ? 這里 str3 和 str4 指向的是一個同一個常量字符串。C/C++會把常量字符串存儲到單獨的?個內存區域,當幾個指針指向同一個字符串的時候,他們實際會指向同一塊內存。但是用相同的常量字符串去初始化不同的數組的時候就會開辟出不同的內存塊。所以str1和str2不同,str3和str4相同。
2、數組指針變量
注意:指針數組是一種數組,數組中存放的是地址(指針)。
數組指針變量是指針變量,而不是數組。
#include <stdio.h>
int main()
{int n = 100;int* pn = &n;char ch = 'w';char* pc = &ch;float f = 3.14;float* pf = &f;int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int(*parr)[10] = &arr;//取出的是數組的地址//parr就是數組指針return 0;
}
元素地址與數組地址的區別:?
?數組指針如何應用:(打印數組元素為例)(因為數組指針的特點,一維數組基本不會使用)?
#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//指針訪問數組int(*p)[10] = &arr; //p3是數組地址int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", (*p)[i]);// p == &arr// *p == *&arr == arr}return 0;
}
3、二維數組傳參的本質
#include <stdio.h>
void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", a[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}
? ? ? ? 所以,根據數組名是數組首元素的地址這個規則,二維數組的數組名表示的就是第一行的地址,是一維數組的地址。根據上面的例子,第一行的一維數組的類型就是 int [5] ,所以第一行的地址的類型就是數組指針類型 int(*)[5] 。那就意味著二維數組傳參本質上也是傳遞了地址,傳遞的是第一行這個一維數組的地址,那么形參也是可以寫成指針形式的。如下:
#include <stdio.h>方式一:
void test(int(*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", *(*(p + i) + j)); //p = &arr,p+i=&arr+i,*(p+i)=arr[i]}printf("\n");}
}方式二:
void test(int(*arr)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", arr[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}
通俗理解:由于二維數組由若干個一維數組構成,那么二維數組的數組名即為第一行的一維數組地址,?地址+整數=地址,所以 +?i 表示指到第 i 行的地址。當拿到第 i 行數組名(即地址),解引用后( *(p+i) )則得到了該行首元素的地址,此時與一維數組原理相似。
?4、函數指針變量
? ? ? ?函數指針變量也是指針變量,其存儲的地址指向函數而不是普通變量。在C語言和C++中,函數指針變量可以用來存儲函數的地址,從而可以通過該指針調用相應的函數。 下面做個測試:
? ? ? ?可以發現函數的確有地址。函數名就是函數的地址,當然也可以通過 &函數名 的方式獲得函數的地址。
數組名:數組首元素的地址
&數組名:整個數組的地址
函數名==&函數名:函數的地址
函數指針變量的聲明通常形式為:
return_type (*function_pointer_name)(parameter_list);
? ? ? ?其中?return_type?
表示函數返回類型,function_pointer_name?
是函數指針變量的名稱,parameter_list
是函數的參數列表。?如果我們要將函數的地址存放起來,就得創建函數指針變量,函數指針變量的寫法其實和數組指針非常類似。
void test()
{printf("hehe\n");
}void (*pf1)() = &test;//&test等價于test
void (*pf2)() = test;int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y寫上或者省略都是可以
函數指針變量的使用:
? ? ? ?聲明了一個指向函數的指針 pf3
,它接受兩個整型參數并返回一個整型結果。在這里,pf3
被賦值為 Add
函數的地址,也就是說,它指向了 Add
函數的代碼塊。
pf3
是一個指向函數的指針,它里面包含了函數的地址。*pf3
表示解引用這個指針,也就是得到指針所指向的函數。(*pf3)(2, 3)
表示調用這個函數,傳遞參數 2 和 3 給它。
? ? ? ? 而當寫 pf3(2, 3)
時,因為函數名實際上就是函數的地址,所以直接使用 pf3
(函數指針)并傳遞參數給它,就相當于直接調用了 Add
函數,所以 (*pf3)(2,3) 和 pf3(2,3) 結果相同。
4.1 分析《C陷阱和缺陷》中的兩端代碼
( *( void (*)() )0 )();
?void(*)( ) --- 函數指針類型,? ? (int)3.14 --- 強制類型轉換
上面的代碼是一次函數調用:
1.把0這個整數值強制類型轉換成一個函數地址,這個函數沒有參數,返回類型是void
2.去調用0地址處的函數
void (*signal(int , void(*)(int) ) )(int);
signal沒有和指針括在一起,由于括號的優先級更高,所以signal是函數名。
4.2?typedef關鍵字
? ? ? ?typedef是用來類型重命名的,可以將復雜的類型簡單化。比如,你覺得 unsigned int 寫起來不方便,如果能寫成 uint 就方便多了,那么我們可以使用:?
typedef unsigned int uint;
//將unsigned int 重命名為uint
如果是指針類型,能否重命名呢?其實也是可以的,比如,將 int* 重命名為 ptr_t ,這樣寫:
typedef int* ptr_t;
但是對于數組指針和函數指針稍微有點區別:比如我們有數組指針類型 int(*)[5] ,需要重命名為 parr_t ,那可以這樣寫:
typedef int(*parr_t)[5]; //新的類型名必須在*的右邊
函數指針類型的重命名也是一樣的,比如,將 void(*)(int,int) 類型重命名為 pf_t ,就可以這樣寫:
typedef void(*pfun_t)(int,int);//新的類型名必須在*的右邊
#include<stdio.h>typedef unsigned int uint;typedef int(*pArr_t)[10]; //對于數組、函數指針時不能將名字放在后面,而是*的右邊void (*pf_t)(int, int);int main()
{unsigned int num;uint num2;int(*pb)[10];pArr_t pa;void(*pf1)(int,int);pf_t pf;return 0;
}
?利用 tepedef 對 void (*signal(int , void(*)(int) ) )(int); 進行簡化:
void (*signal(int, void(*)(int)))(int);typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
5、函數指針數組
?指針數組 --- 是數組,每個元素是指針,char* arr[5], double* arr[5]
?數組指針 --- 是指針,指向數組的指針,int(*pa)[10]=&arr, char(*pa)[5]=&arr
? ? ? ?數組是一個存放相同類型數據的存儲空間,那要把函數的地址存到一個數組中,那這個數組就叫函數指針數組,是數組,里面的元素是函數指針。
再次理解函數指針:
#include<stdio.h>
int test(char* c,int n)
{//...return 0;
}
int main()
{int (*pf)(char*, int) = &test;(*pf)("abcdef", 10); pf("abcdef", 10);test("abcdef", 10)return 0;
}
函數指針數組實例:
#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
int main()
{int (*pf)(int, int) = Add; //pf是函數指針int (*pf1[4])(int,int) = { Add,Sub,Mul,Div };//存放函數指針的數組// 0 1 2 3int i = 0;for (i = 0; i < 4; i++){int result=pf1[i](6, 2);/*int result = (*pf1[i])(6, 2);*/printf("%d\n", result);}return 0;
}
6、函數指針數組的用途---轉移表
正常加減乘除運算代碼:
#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input){case 1:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}
使用函數指針數組進行加減乘除運算:?
#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //轉移表do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf_s("%d", &input);if ((input <= 4 && input >= 1)){printf("輸?操作數:");scanf_s("%d %d", &x, &y);ret = (*p[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出計算器\n");}else{printf("輸?有誤\n");}} while (input);return 0;
}
?
?
?