目錄
一、字符指針
1.1?? 使用場景
1.2 ? 有關字符串筆試題
二、數組指針
2.1 ? 數組指針變量
2.2 ? 數組指針類型
2.3 ? 數組指針的初始化
三、數組指針的使用
3.1 ? 二維數組和數組名的理解
3.2 ? 二維數組傳參
四、函數指針
4.1 ? 函數的地址
4.2 ? 函數指針變量
4.3 ? 函數指針變量的使用
五、函數指針數組
六、轉移表
一、字符指針
字符指針:指向字符的指針?
1.1?? 使用場景
【使用場景一】
#include <stdio.h>int main()
{char c = 'w';char* pc = &c;*pc = 'x';printf("%c\n", *pc);return 0;
}
【使用場景二】
#include <stdio.h>int main()
{char* p = "abcdef";//這里把首字符a的地址賦給了變量pprintf("%s\n", p);return 0;
}
注意
這里printf中%s的格式邏輯是:從給定的地址處開始,逐個向后輸出字符,直到遇見結束標記\0為止。解引用的操作符在printf函數內部造成。
如果由用戶解引用,那printf函數將只能拿到單個字符,反而無法實現功能。
易錯點1:
場景二中如果修改*p的值,代碼就會報錯。報錯類型如下:
原因:
"abcdef"是個常量字符串。
常量字符串的意思是:這個字符串本身是不能被更改的。
而這個*p沒有被限制,它其實是可以去改變后面的字符串的,所以char* p="abcdef"; 報警告是正常的。
防止方法:在char*p 前加const
易錯點2:
不能解引用p,解引用打印的是一個字符,一個字符不能用%s打印。
char* p = "abcdef";
printf("%s", *p);//error
以下是將字符串放在數組里面:
char arr[] = "abcdef";
而數組的內容是可變的。
1.2 ? 有關字符串筆試題
#include <stdio.h>int main()
{char arr1[] = "hello";char arr2[] = "hello";const char* p1 = "hello";const char* p2 = "hello";if (arr1 == arr2)printf("arr1=arr2\n");elseprintf("arr1!=arr2\n");if (p1 == p2)printf("p1=p2\n");elseprintf("p1!=p2\n");return 0;
}
運行結果如下:
原因如下:
arr1!=arr2
- 我們知道:arr1和arr2是數組首元素的地址,這兩個數組是兩塊獨立的內存空間,它們只是存儲的內容相同,都是hello字符串。
p1=p2
- 這里的p1和p2指向的是同一個常量字符串。c/c++會把常量字符串存儲到一個單獨的一個內存區域,當幾個指針指向同一個字符串的時候,它們實際會指向同一塊內存(代碼段中)。
- arr1,arr2,p1,p2都是放在棧區,指向的hello(常量字符串)是放在代碼段。
二、數組指針
整型指針:存放整型變量的地址,能夠指向整型數據的指針
浮點型指針:存放浮點型變量的地址,能夠指向浮點型數據的指針
那數組指針:存放數組的地址,指向數組的指針
2.1 ? 數組指針變量
看下面兩行代碼,p1,p2分別是什么?
int* p1[10];
int(*p2)[10];
- p1是指針數組
p1是數組名,該數組里存放了10個元素,每個元素是int*類型
- p2是數組指針
因為p2先和*結合,說明p是一個指針變量,然后指向一個大小為10個整型的數組,所以p是一個指針,指向一個數組,所以叫數組指針。
注意:
- [ ]的優先級要高于*號,所以必須加上()來保證p先和*結合。
- int(*p2)[10];里面的*不是解引用的意思,這顆星就代表是指針,只有前面不加類型的時候才是解引用。
2.2 ? 數組指針類型
去掉指針變量名就是指針(變量)的類型。
看如下代碼:
它們跳過的字節不同就是因為他們的類型不同導致。
注意:
- 一個指針是否是野指針取決于你是否用它。這個指針雖然指向這里,但是沒有產生壞的結果。只要不使用它就沒關系。
- 雖然它指向的空間不屬于“我”,但是它并不危險。
2.3 ? 數組指針的初始化
數組指針存放的是數組的地址。
所以初始化的時候要給整個數組的地址。代碼如下:
注意:
[ ]里的元素個數不能省略,不然編譯器會自動認為是數組元素個數為0
三、數組指針的使用
3.1 ? 二維數組和數組名的理解
首先我們來理解一下二維數組及其數組名:
在c語言中,只有一維數組(N維數組的元素是數組),數組名作為指針時永遠指向第一個元素。
如:
- 數組a[3];? *a=a[0]
- 數組a[3][4];? *a=a[0]? ?只不過這時候a[0]又是一個數組。
這時候的a[0]又是指向它自己元素的第一個元素,又有 *a[0]=a[0][0]
- 這種方式可以推廣到N維數組,所有數組直接對數組名取地址(如:&a),得到的指針指向該數組,而不是指向第一個元素。注意這點區別。
舉個例子:??int board[3][4];
board:一維數組的地址。
二維數組的數組名,數組名就是首元素地址。我們知道,可以把一維數組看作二維數組的元素。所以,board就是一維數組的地址。
&board:取出的是整個二維數組的地址。
board[0]:第一行第一個元素的地址。
解引用,相當于拿到第一行數組的數組名,也就是首元素地址,即第一行第一個元素的地址。
board[0]=*board=&board[0][0]
&board[0]:第一行的地址。
board=&board[0]
3.2 ? 二維數組傳參
清楚了上面的概念之后,我們來看下面一段代碼:
之前二維數組傳參時,形參部分用的是數組接收。
#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;
}
發現:實參arr是數組名,通過剛才的分析,直到數組名是首元素地址,首元素地址就是第一行的地址,也就是一維數組的地址,那么它的類型就是數組指針類型。
那么實參就可以寫成數組指針的形式,代碼如下:
#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));//指針解引用}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;
}
問題:為什么*(p+i) 跳過的是一行數組?
回答:
數組的類型決定了它+1跳過幾個字節。
p的類型是:int(*)[5];
p是指向一個整型數組的,數組5個元素 int [5]
p+1 :跳過一個5個int元素的數組
四、函數指針
函數指針:存放函數的地址,指向函數的指針
4.1 ? 函數的地址
函數是否有地址呢?我們來測試一下:
由測試結果可知:函數存在地址,取&函數名和函數名拿到的都是函數的地址。
4.2 ? 函數指針變量
我們通過函數指針來存儲函數的地址。
int (*pf) (int x, int y) = &Add;
int (*pf) (int , int ) = Add;//xy可以省略,只寫類型
- 地址要存起來,放到(指針)變量里去。
- pf是變量名,*pf說明是指針,指向的是函數,所以加上括號()。
- 函數的參數是int (參數名寫不寫無所謂,只要類型交代清楚即可),函數的返回值類型也是int。
int (*pf) (int x, int y)
|?? ? ?|?? ?------------
|?? ? ?|?? ??? ?|
| ? ? ?|?? ??? ?pf指向函數的參數類型和個數的交代
|?? ? ? 函數指針變量名
pf指向函數的返回類型int (*) (int x, int y) //pf函數指針變量的類型
4.3 ? 函數指針變量的使用
通過函數指針調用指針指向的函數。
代碼如下:
#include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf)(int, int) = Add;printf("%d\n", (*pf)(2, 3));//輸出結果為5printf("%d\n", pf(2, 3));//輸出結果為5return 0;
}
把函數的地址存到pf里,通過解引用pf找到函數,找到這個函數要調用這個函數,調用函數需要傳參,所以();(傳參),傳2,3。這樣的話它會把2和3相加,得到5。
問題一:為什么*pf必須帶上括號()呢?
回答:因為假如不帶上括號,調用返回5,就會對5進行解引用。
問題二:為什么能寫成pf(2, 3)這種形式?
回答:在C語言里,pf前的*其實是個擺設,可以不寫,也可以寫多個。
這是個技術細節問題,不涉及到語法原則,從不同的思考角度出發,觀點會略有不同,但不影響C語言實踐,初學者也不必過多糾結。
應用:通過函數指針的方式進行調用
#include <stdio.h>int Add(x, y)
{return x + y;}
void cale(int(*pf)(int,int))
{int a = 3;int b = 5;int ret = pf(a, b);printf("%d\n", ret);
}
int main()
{cale(Add);return 0;
}
這里的cale沒有直接調用Add函數,而是通過函數指針的方式進行調用。
五、函數指針數組
函數指針數組:把一個函數的地址存放到一個數組中。
是個函數指針類型的數組。
去掉 函數名+[ ] 就是該數組的類型。
int (*parr[3])( );
解釋:parr先和[ ]結合,說明parr是數組,數組的內容是int(*)()類型的函數指針。
當對函數指針數組進行初始化的時候,后面初始化的可以省略掉數組的大小,它會根據后面初始化的內容來確定數組的大小。
例如:
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
int(*p[])(int x, int y) = { 0, add, sub, mul, div };
六、轉移表
使用了函數指針數組,避免大篇幅地修改內容;也可實現跳轉的功能。
所以函數指針數組也叫:轉移表。
計算機的一般實現
#define _CRT_SECURE_NO_WARNINGS 1#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("%d", &input);if ((input <= 4 && input >= 1)){printf("輸入操作數:");scanf("%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;
}