
目錄
- 一、前言
- 1.1進入主題
- 1.2牛刀小試
- 二、指標陣列
- 1.1指標陣列的初始化
- 1.2指標陣列存放一維陣列的地址
- 三、陣列指標
- 1.1&陣列名VS陣列名
- 1.2陣列指標的使用
- 1.2.1陣列指標的錯誤示范
- 1.2.2陣列指標的正確示范示范
- 四、陣列引數、指標引數
- 1.1一維陣列傳參
- 1.2二維陣列傳參
- 1.3一級指標傳參
- 1.4二級指標傳參
- 五、函式指標
- 1.1函式的地址和陣列的地址有哪些地方不太一樣
- 1.2函式的地址又要存放到哪里去
- 1.3函式指標的使用
- 1.4閱讀兩段有趣的代碼:
- 1.4.1代碼二的簡化寫法
- 六、函式指標陣列
- 1.1函式指標陣列的初始化
- 1.2運用之前的知識
- 1.3通過回呼函式的方式實作另外一種方案
- 1.4何為回呼函式呢?
- 七、指向函式指標陣列的指標
- 總結
一、前言
在上一篇博客中作者已將指標的基礎知識歸納在這篇博客中,《 指標和結構體(初級)》,而今天我們要進入指標的下一個環節,指標進階!!為了怕大家忘記之前講過的知識,本人再次梳理出一些知識,請各位耐心觀看,以下又是老調重彈
- 指標就是個變數,用來存放地址,地址唯一標識一塊記憶體空間,
- 指標的大小是固定的4/8個位元組(32位平臺/64位平臺),
- 指標是有型別,指標的型別決定了指標的±整數的步長,指標解參考操作的時候的權限,
- 指標的運算,
1.1進入主題
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<assert.h>
#include <stdio.h>
int main()
{
char *p = NULL;
p = "hello word";
return 0;
}

進入正題,我們都知道指標的大小(所占用的位元組),是跟作業系統有關的,指標的大小是固定的4/8個位元組(32位平臺/64位平臺),而字符在記憶體中是占用一個位元組的,那么上面的代碼是將整個的 hello word 存放到字符指標中去,還是只存放字串的第一個字符呢?
在記憶體視窗中我們可以看到指標變數p存放的恰好就是字串的首字符的地址,直到后面的 00 00 ,前面的都是有效字符,試想字符在記憶體中是占一個位元組的,如果要把整個字串存放到一個指標變數中去,那不就得溢位了嗎,所以存放的是首字符的地址,字串在記憶體中是挨著存的,有了首字符的地址不難找到其他字串內容,另外值得一提的一點就是字串是存放在字串常量區的,既然是一個常量,那么就不可以通過別的方式對其修改,否則就會引發程式出錯,如果不小心修改了怎么辦呢,建議在前面加const,這樣即使你想改,編譯器也會編譯不過去
1.2牛刀小試
你見過這樣的一道題嗎?
#include <stdio.h>
int main()
{
char str1[] = "hello word.";
char str2[] = "hello word.";
const char *str3 = "hello word.";
const char *str4 = "hello word.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
請把你心目中的答案,在心里默默的講一句
沒錯我想你應該知道了,但是這里還是得再說一遍
第一個if陳述句中比較的是兩個地址,因為陣列名是字符首地址,那么既然是不同的陣列名,想必在記憶體中的存放位置也是不一樣的,即使在記憶體中存放的值都是 hello word ,但是在比較的時候還是比較的地址,試想不是在同一塊記憶體開辟的同一塊空間,他們的地址會一樣嗎?顯然不是,而在 str3 和 str4比較的時候比較的也是地址,并且這兩個指標變數指向的都是一塊空間,既然是指向同一塊空間,那么指向的就是字符首地址,因為在記憶體中壓根就不會存放兩份一樣的資料,我們都知道字串是常量不能被修改,既然是不可變的那么被兩個指標去指向這一份不會被修改的內容又有什么關系呢而且還不會浪費記憶體,所以在這個地方比較的是同一個地址,當我們的程式執行起來,可以看到和我們的預期結果是一樣的
結論:
這里str3和str4指向的是一個同一個常量字串,C/C++會把常量字串存盤到單獨的一個記憶體區域,當幾個指標,指向同一個字串的時候,他們實際會指向同一塊記憶體,但是用相同的常量字串去初始
化不同的陣列的時候就會開辟出不同的記憶體塊,所以str1和str2不同,str3和str4不同
二、指標陣列
指標陣列的主體是一個陣列,陣列中存放的每一個元素是一個指標
int* arr1[10]; //整形指標的陣列
char *arr2[4]; //一級字符指標的陣列
char **arr3[5];//二級字符指標的陣列
這么理解指標陣列的語法:
就拿arr1來說,[ ]的優先級是最高的,所以[ ]會先和arr1結合,那么arr1表示的是一個陣列,而陣列的元素型別是什么,int * 表示的就是陣列的元素型別,所以arr1是一個每個元素都是int *的指標陣列
1.1指標陣列的初始化
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int *arr[4] = {&a,&b,&c,&d};
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
陣列是連續存盤的,而指標陣列的每一個元素都是一個指標變數,并且這個指標變數指向的元素型別是int型別,所以在初始化的時候是將變數的地址存入到陣列中,以下就是指標陣列在記憶體中的布局,而列印的程序也很簡單,通過陣列下標索引陣列中的每一個元素,元素對應變數的地址,對地址解參考就能找到此地址對應的變數值

1.2指標陣列存放一維陣列的地址
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int *parr[3] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ",parr[i][j]);
//另外一種寫法 printf("%d ",*(*(parr + i) + j));
}
printf("\n");
}
return 0;
}
parr是一個指標陣列,這個指標陣列存放的元素是一維陣列的地址,每一個元素型別是int ,因為陣列名是陣列首元素的地址(陣列首地址),而陣列首地址的型別是int ,所以指標陣列的每一個元素都是int ,而指標陣列的型別是int [3]
printf("%d ",((parr + i) + j)); 如何理解這句代碼,(parr + i)是找到下標為i的那個元素,而這個元素是一個陣列首地址啊,(parr + i) + j,找到了一維陣列的首地址再偏移j個長度,不是就找到了一維陣列中的第j個元素的地址了嗎,((parr + i) + j),再通過()解參考就能找到地址上對應的數值,其實(*(parr + i) + j) 和 parr[i][j]這兩種訪問陣列元素的原理是差不多的

int main()
{
char *arr[] = { "春眠不覺曉","處處聞啼鳥","夜來風雨聲","花落知多少" };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%s\n",arr[i]);
}
return 0;
}
字符指標陣列的記憶體布局

指標陣列中存放的每一個元素都是一個指標變數,這個指標變數的型別char*,每一個元素都指向著字串起始地址,在以%s列印的時候會通過指標指向的起始地址找到‘\0’出現的前面字符,依次列印
三、陣列指標
陣列指標的主體是指標,該指標指向一個陣列首地址(陣列名)
int (*parr)[10]
這么理解陣列指標的語法,()的優先級是最高的,所以* 會先和
parr結合,這么一看是一個指標,往后一看[10],表示的是指標指向的陣列的元素個數是10,前面的int 表示陣列指標指向的陣列的每一個元素都是int ,而陣列指標的型別是 int (*)[10]
1.1&陣列名VS陣列名
前提概念:1、陣列名是陣列首地址,陣列名表示陣列首元素的地址
2、&arr取出的是整個陣列的地址
3、&arr的型別是陣列指標 int (*)[10]
int mian()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}

從列印結果可以看出陣列首元素的地址 + 1會跳過一個整形,而&陣列名會跳過40個整形,原因是因為&arr的型別是 int(*)[10],對它 + 1會跳過40個位元組,這里的地址不是陣列首元素的地址,而是陣列的地址,在這里要有一個概念性的了解
1.2陣列指標的使用
1.2.1陣列指標的錯誤示范
void func(int (*arr)[10],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);//err
printf("%d ", *(arr + i));//err
}
}
int main()
{
int arr[10] = {1,2,3,4,5,67,8,9,10};
int len = sizeof(arr) / sizeof(arr[0]);
func(&arr,len);
return 0;
}

根據前面的邏輯我們知道&arr取出的是陣列的地址,int (*arr)[10] 是個陣列指標,本質上還是一個指標,訪問的是整個陣列的地址,而陣列的地址 + 1會偏移40個位元組,隨后訪問的都是隨機值,
1.2.2陣列指標的正確示范示范
void func(int (*arr)[10],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
//以下幾種方式均可以
printf("%d ", arr[0][i]);
printf("%d ",(*arr)[i]);
printf("%d ",*(*arr + i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,67,8,9,10};
int len = sizeof(arr) / sizeof(arr[0]);
func(&arr,len);
return 0;
}
解讀:
arr [0][i] 有了第一個元素的下標向后訪問i下標的元素,因為陣列是連續存盤的,有了第一個元素的下標,自然也能找到其他下標位置處的元素
(*arr)[i],陣列指標是指向陣列的地址,對指標解參考就能找到指標指向的內容,*arr表示的是陣列的首元素地址,有了首元素的地址就可以向后偏移i位,其實等價于arr[i]
*(*arr + i),陣列指標是指向陣列的地址,對指標解參考就能找到指標指向的內容,*arr表示的是數組的首元素地址,對首元素的地址(陣列首地址),偏移i個長度,會跳過4 * i個位元組,再將偏移后的指標解參考找到指標指向的內容
希望以上的解釋對大家的理解有所幫助,這也是博主對這些知識在一定程度上的理解,對這些語法的解釋
接下來再看看另外一種用法
/* 常規寫法 */
void func(int arr[3][5],int row,int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
/* 使用陣列指標 */
void func(int (*arr)[5],int row,int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
/* 以下兩種方式都可以 */
printf("%d ",arr[i][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};
int row = sizeof(arr) / sizeof(arr[0]);
int col = sizeof(arr[0]) / sizeof(arr[0][0]);
func(&arr,row,col);
return 0;
}
主要還是說一下陣列指標的使用方式
首先我們來看這是35的二維陣列
當使用陣列指標的時候獲取的是它的下標為【0】的一維陣列的地址,陣列指標里面存放的就是它的地址
假設現在要找處arr[1][4]的元素,下面我們直接看圖
在這里是通過讓陣列指標指向下標【1】的位置處,有了陣列的起始地址向后偏移,方便找到其他的元素,不過這種int()[5]型別的陣列指標只是針對一維陣列的,存放的是一維陣列的地址,這一點請大家務必注意
有了上面知識的理解,再來看一組,你現在知道它們代表著什么嗎?

1、int arr[5]; --》 表示的是一個陣列,陣列的每一個元素都是一個int
2、int *parr1[10]; --》表示的是指標陣列,陣列中的每個元素都是int *型別的指標變數
3、 int (*parr2)[10]; --》陣列指標,本質上是一個指標,指向的是一個陣列,陣列的元素個數是10,陣列的元素型別是int
4、int (*parr3[10])[5]; 重點來了,先看運算子的優先級,()的優先級是最高的,所以先看()里的內容,其次是[ ]優先級是第二,arr3先和[ ] 結合就是一個陣列,陣列里面存放著10個元素,每一個元素的型別是 int( * )[5],所以每一個元素都是一個陣列指標,該陣列指標指向的陣列是【5】個元素,每一個元素是int型別,
四、陣列引數、指標引數
1.1一維陣列傳參
#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
1.2二維陣列傳參
void test(int arr[3][5])//ok
{}
void test(int arr[][])//err
{}
void test(int arr[][5])//ok
{}
//總結:二維陣列傳參,函式形參的設計只能省略第一個[]的數字,
//因為對一個二維陣列,可以不知道有多少行,但是必須知道一行多少元素,
//這樣才方便運算,
void test(int *arr)//err
{}
void test(int* arr[5])//err
{}
void test(int (*arr)[5])//ok
{}
void test(int **arr)//err
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
1.3一級指標傳參
#include <stdio.h>
void print(int *p, int sz) {
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一級指標p,傳給函式
print(p, sz);
return 0; }
1.4二級指標傳參
#include <stdio.h>
void test(int** ptr) {
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
int *arr[10];//指標陣列
test(pp);//ok,二級指標傳參給,二級指標接收
test(&p);//ok,二級指標存放一級指標的地址
test(arr);//ok,指標陣列傳參,傳遞的是首元素的地址,元素型別是int,元素地址是int *,二級指標能存放一級指標的地址
return 0; }
五、函式指標
我們都知道函式指標變數是用來存放函式的地址的,那么函式的地址長什么樣呢

1.1函式的地址和陣列的地址有哪些地方不太一樣
&陣列名 - 陣列的地址
陣列名 - 陣列首元素的地址函式名 - 函式的地址
&函式名 - 函式的地址
1.2函式的地址又要存放到哪里去
void test()
{
printf("hello word\n");
}
//下面pfun1和pfun2哪個有能力存放test函式的地址?
void (*pfun1)();
void *pfun2();
pfun1可以存放,pfun1先和*結合,說明pfun1是指標,指標指向的是一個函式,指向的函式無引數,回傳值型別為void,函式指標的型別是int ( *)(),
1.3函式指標的使用
int Add(int x,int y)
{
return x + y;
}
int main()
{
int ret = Add(10,20);
printf("%d\n", ret);
int (*pf)(int x, int y) = NULL;//定義函式指標變數
pf = Add;//函式指標變數指向函式地址
//pf = &Add; 可以寫成這種方式
ret = pf(20,30);//有了函式的地址,就可以呼叫該函式
//ret = (*pf)(20,30); 可以寫成這種形式,便于初學者理解
//ret = (******pf)(20,30); 多加幾顆*也沒有影響
printf("%d\n",ret);
}
1.4閱讀兩段有趣的代碼:
代碼來自《C陷阱和缺陷》

代碼一
先看代碼一,0是整形,型別是int,(void () ())外面的一層小括號表示的是強制型別轉換,而void()()表示的是一個型別,函式指標型別(void(*) ()) 0拼接起來的意思不就是將int型的0強制型別轉換為
void( * ) ()型別的函式指標嗎,有了了函式地址,再對指標解參考就能呼叫該函式,被呼叫的函式是一個無參,回傳值型別為void的函式
以上代碼確實符合語法,因為0地址處是被作業系統呼叫的,而程式員無法呼叫
總結:這是一次函式呼叫
代碼二
這是一次函式宣告,宣告的函式名是signal,signal函式有兩個引數,第一個是int型別,第二個是void( * )(int)的函式指標型別,signal函式的回傳型別是void( * )(int)的函式指標型別
1.4.1代碼二的簡化寫法
typedef void(*pfunc_t)(int);//給函式指標型別取別名為pfunc_t
pfunc_t signal(int,pfunc_t);
這樣子寫起來是不是很簡明,方便理解,熟練的使用typedef也可以解決這一類的問題
六、函式指標陣列
函式指標陣列的主體是一個陣列,該陣列存放的每一個元素是一個函式指標型別
1.1函式指標陣列的初始化
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul (int x, int y)
{
return x * y;
}
int main()
{
int(*pfArr[4])(int x, int y) = { Add ,Sub ,Mul ,Div};
return 0;
}
怎么理解函式指標陣列的語法呢?
int(*pfArr[4])(int x, int y)
首先【】的優先級是最高的,所以會先和pfArr結合,這一看就是一個陣列嘛,陣列的元素個數是4,陣列的每一個元素型別就是int ( * )(int,int)型別的,那么我們可以知道陣列的每一個元素都是一個函式指標,
這種初始化方式結合之前所學的函式指標和指標陣列的知識相信螢屏前的你可以看懂,沒錯就是一個陣列里面存放著的每一個元素都是一個函式指標,陣列的元素個數是4,陣列的每一個元素型別是int(*)(int,int)
1.2運用之前的知識
假設現在要實作一個簡單的計算器程式
先按常規的寫法實作一個計算器
void Menu()
{
printf("****************************\n");
printf("*******1、Add 2、Sub *****\n");
printf("*******3、Mul 4、Div *****\n");
printf("******* 0、exit *****\n");
printf("****************************\n");
}
int main()
{
int input = 0;
do
{
int x = 0;
int y = 0;
int ret = 0;
Menu();
printf("請輸入你的選擇\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("退出計算器\n");
break;
case 1:
printf("請輸入兩個運算元\n");
scanf("%d %d", &x, &y);
ret = Add(x,y);
printf("ret = %d\n",ret);
break;
case 2:
printf("請輸入兩個運算元\n");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret = %d\n",ret);
break;
case 3:
printf("請輸入兩個運算元\n");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret = %d\n",ret);
break;
case 4:
printf("請輸入兩個運算元\n");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("ret = %d\n",ret);
break;
default:
printf("輸入錯誤請重新輸入\n");
break;
}
} while (input);
return 0;
}
簡化版本
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul (int x, int y)
{
return x * y;
}
void Menu()
{
printf("****************************\n");
printf("*******1、Add 2、Sub *****\n");
printf("*******3、Mul 4、Div *****\n");
printf("******* 0、exit *****\n");
printf("****************************\n");
}
int main()
{
int input = 0;
do
{
int x = 0;
int y = 0;
int ret = 0;
Menu();
printf("請輸入你的選擇\n");
scanf("%d",&input);
int(*pfArr[5])(int x, int y) = { 0,Add ,Sub ,Mul ,Div };
if (!input)
{
printf("退出計算器\n");
break;
}
else if (input >= 1 && input < 5)
{
printf("請輸入兩個運算元\n");
scanf("%d %d",&x,&y);
ret = pfArr[input](x, y);
printf("ret = %d\n",ret);
}
else
{
printf("輸入錯誤\n");
}
} while (input);
return 0;
}

相比于之前的常規寫法,這樣的組織代碼的方式,只讓相同功能的代碼只保存一份
可以看到函式指標陣列的使用極大的降低了代碼的冗余,而且可擴展性也能有所提升,將來要實作其他的功能,比如 >> 、<<、^、&=、這些計算都是可以的,只需要在函式指標陣列初始化的時候再添加一個函式進去,再把else if分走陳述句的條件判斷范圍再給擴大那就ok,這種函式指標陣列的使用方法在專業術語上叫做轉移表
1.3通過回呼函式的方式實作另外一種方案
1.4何為回呼函式呢?
回呼函式就是一個通過函式指標呼叫的函式,.
如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用為呼叫它所指向的函式時,我們就說這是回呼函式,.
回呼函式不是由該函式的實作方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用于對該事件或條件進行回應,
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul (int x, int y)
{
return x * y;
}
void Menu()
{
printf("****************************\n");
printf("*******1、Add 2、Sub *****\n");
printf("*******3、Mul 4、Div *****\n");
printf("******* 0、exit *****\n");
printf("****************************\n");
}
void Calc(int(*pf)(int x,int y))
{
int x = 0;
int y = 0;
int ret = 0;
printf("請輸入兩個運算元\n");
scanf("%d %d", &x, &y);
ret = (*pf)(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 0;
do
{
int x = 0;
int y = 0;
int ret = 0;
Menu();
printf("請輸入你的選擇\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("退出計算器\n");
break;
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
default:
printf("輸入錯誤請重新輸入\n");
break;
}
} while (input);
return 0;
}
這樣的代碼組織方式也讓相同功能的代碼只保存一份
七、指向函式指標陣列的指標
指向函式指標陣列的指標是一個 指標, 指標指向一個 陣列 ,陣列的元素都是 函式指標 ;
怎么定義呢?
int(*arr)[5] = {0};//陣列指標
int(*pf)(int ,int) = NULL;//函式指標
int(*pfArr[5])(int, int) = {0};//函式指標陣列
int(*(*ppfArr)[5])(int, int) = &pfArr;//指向函式指標陣列的指標
怎么理解指向函式指標陣列的指標的語法呢?
int(* (* ppfArr ) [5] ) (int, int)
首先ppfArr 是先和 * 結合的,那么他就是一個指標變數,指標變數指向一個什么呢,向后一看【5】,原來指標變數指向的是一個陣列,陣列的元素型別是什么呢,int ( * )(int,int),原來是函式指標
總結
總結:指向函式指標陣列的指標 是一個指標,該指標變數指向的陣列中存放的元素個數是5,陣列的每一個元素型別是 int ( * )(int,int)
指標進階還有部分內容將在下一章講解,如果想進一步了解指標變數請關注博主,如果覺得本章內容復雜建議收藏反復觀看,不要停止學習的步伐,畢竟欲戴王冠必承其重,祝你拿到好offer,感謝各位看官老爺

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/294513.html
標籤:其他







