- 前言:
- 正文
- 1. 字符指針變量
- 2. 數組指針變量
- 2.1 數組指針變量是什么?
- 2.2 數組指針變量怎么初始化
- 3. 二維數組傳參的本質
- 4. 函數指針變量
- 4.1 函數指針變量的創建
- 4.2 函數指針變量的使用
- 4.3 兩段有趣的代碼
- 4.3.1 typedef關鍵字
- 5. 函數指針數組
- 6. 轉移表
- 總結
前言:
這是指針第四篇,主要介紹:字符指針變量、數組指針變量、二維數組傳參的本質、函數指針變量、函數指針數組以及函數指針數組的應用——轉移表
正文
1. 字符指針變量
在指針的類型中有一種指針類型為字符指針char*
。
一般使用方式如下:
int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}
還有一種使用方式如下:
int main()
{const char* ps = "hello C.";printf("%s\n", ps);return 0;
}
代碼const char* ps = "hello C";
容易讓人以為是把字符串hello C
放到字符指針ps
里了,但是本質是把字符串hello C.
首字符的地址放到了ps
中。實際是把一個常量字符串的首字符h
的地址存放到指針變量ps
中。
《劍指offer》中又一道與之相關的題目,代碼如下:
#include <stdio.h>
int main()
{char str1[] = "hello C.";char str2[] = "hello C.";const char *str3 = "hello C.";const char *str4 = "hello C.";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;
}
為什么會有這樣的結果呢?const修飾常量字符串,str3和strr4是指向同一個字符串的,在C語言中會把常量字符串單獨存儲到一個內存區域,雖然str3和str4變量名不同,但實際上指向同一片內存空間。然而,用同樣的字符串初始化不同數組時會形成不同空間,即不同的內存塊。所以str1和str2不同,str3和str4相同。
2. 數組指針變量
2.1 數組指針變量是什么?
先區分一下,之前學過指針數組
,它是一種數組,用來存放指針(也就是地址)
而數組指針變量
,和·指針數組·
不同,它是指針變量
舉個栗子:
int* pint 表示整型指針變量用來存放整型變量的地址
float* pf表示浮點型指針變量 , 用來存放浮點型數據的地址
數組指針變量
存放的是數組的地址,能夠指向數組的指針變量。
這里用兩個代碼經常混淆
int *p1[10];
int(*p2)[10];
哪一個是數組指針變量呢?答案是p2即int(*p2)[10];
p1是指針數組,p2先和結合,說明p2是一個指針變量,然后指針指向的是一個大小為 10 個整型的數組,所以p2是一個指針,指向一個數組,叫數組指針。
注意:[]的優先級是高于的,所以必須加上()保證*和p是一起的
2.2 數組指針變量怎么初始化
數組指針變量是用來存放數組地址的,通過&數組名
可以獲得數組的地址。例如:
int arr[10] = {0};
&arr;
如果要存放數組的地址,就得存放在數組指針變量中,如下:
int(*p)[10] = &arr;
調試可以看到&arr
和p
的類型是完全一致的。
數組指針類型解析:
3. 二維數組傳參的本質
有了數組指針的基礎理解,就能夠講一下二維數組傳參的本質。
過去二維數組傳參給一個函數時,寫法如下:
#include <stdio.h>
void test(int a[3][5], int a, int b)
{int i = 0;int j = 0;for(i=0; i<a; i++){for(j=0; j<b; 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;
}
0 0 0 | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | |
---|---|---|---|---|---|
0 0 0 | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 |
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 |
2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 |
arr數組 內容如上
實參是二維數組,形參也是二維數組,但是前面說過二維數組其實是一維數組的數組,二維數組的首元素是第一行,是一維數組,所以我們可以這樣理解:二維數組數組名是第一行的地址,是一維數組的地址(指針),第一行的一維數組類型就是int [5]
,so,第一行的地址類型就是數組指針類型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));}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;
}
總結:二維數組傳參,形參的部分可以寫成數組,也可以寫成指針形式。
4. 函數指針變量
4.1 函數指針變量的創建
函數指針變量應該是用來存放函數地址的,未來通過地址能夠調用函數。
函數是有地址的,通過以下測試可以證明:
#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}
輸出結果中test
和&test
的地址相同,函數名就是函數的地址,也可以通過&函數名
的方式獲得函數的地址。
如果要將函數的地址存放起來,就得創建函數指針變量,函數指針變量的寫法和數組指針非常類似。例如:
void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{return x+y;
}
int(*pf3)(int x, int y) = &Add;
int(*pf3)(int, int) = Add;
函數指針類型解析:
int (*pf3) ( int x, int y)
//( int x, int y)——指向函數的參數類型和個數的交代
//(*pf3) ——函數指針變量名
//int ——pf3指向函數的返回類型int (*) (int x, int y) //pf3函數指針變量的類型
4.2 函數指針變量的使用
現在寫一個加法的函數,你可能會這樣寫:
int add(int x,int y)
{return x + y;
}
int main()
{int r = add(2,5);printf("%d",r);return 0;
}
但我們學過上述內容后,通過函數指針調用指針指向的函數,示例代碼如下:
#include <stdio.h>
int Add(int x, int y)
{return x+y;
}
int main()
{int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));printf("%d\n", pf3(3, 5));return 0;
}
4.3 兩段有趣的代碼
(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);
兩段代碼均出自《C陷阱和缺陷》這本書。
可以自己寫出來理解一下,正常情況我們不會這樣寫,實在理解不了也沒關系,(也可以問問deepseek).
4.3.1 typedef關鍵字
typedef
是用來類型重命名的,可以將復雜的類型簡單化。比如將unsigned int
重命名為uint
:
typedef unsigned int uint;
對于指針類型也能重命名,將int*
重命名為ptr_t
:
typedef int* ptr_t;
對于數組指針和函數指針重命名稍有區別。將數組指針類型int(*)[5]
重命名為parr_t
:
typedef int(*parr_t)[5];
將函數指針類型void(*)(int)
重命名為pf_t
:
typedef void(*pfun_t)(int);
簡化代碼2可以這樣寫:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
5. 函數指針數組
數組是一個存放相同類型數據的存儲空間,已經學習了指針數組,例如int * arr[10];
,數組的每個元素是int*
。
把函數的地址存到一個數組中,這個數組就叫函數指針數組。函數指針數組定義為int (*parr1[3])();
,parr1
先和[]
結合,說明parr1
是數組,數組的內容是int (*)()
類型的函數指針。
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("%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;
}
總結
本文就到這里了,如有誤,歡迎指正,指針很難,但堅持下去,終有收獲,朋友,把這件事堅持下去吧。
敬,不完美的明天。