珠海高端網站制作公司怎么做網頁設計的頁面
關注小莊 頓頓解饞(。・?・)ノ゙
歡迎回到我們的大型紀錄片《那些年與指針的愛恨情仇》,在本篇博客中我們將繼續(xù)了解指針的小秘密:二級指針,指針與數組的關系以及函數指針。請放心食用!
文章目錄
- 一. 二級指針
- 二. 數組與指針的那點事兒
- 1.🏠 數組名的理解
- 1.1 數組名本質理解
- 1.2 sizeof數組名和取地址數組名
- 2. 🏠 指針數組
- 3.🏠 字符串常量
- 4.🏠 數組指針
- 5.🏠 數組傳參
- 三. 函數指針
一. 二級指針
前面我們講到了指針變量是個存儲指針(地址)的變量,我們知道變量在創(chuàng)建的時候操作系統會給他分配內存空間同時給他編號(地址),那么指針變量的指針(地址)能否被存儲呢?這里就引入我們二級指針的概念了
二級指針:存儲指針變量地址的指針
地址 | 變量 |
---|---|
0x0012ffaa | a |
0x00134455 | *p = &a |
0x0012aaff | **pp=&p |
*在上面表格中pp變量其實就是對應的二級指針,我們可以通過雙重解引用pp來找到a,*pp = p, *(*pp)=a
注:二級指針類型定義時的*理解邏輯可以類比我們之前理解指針變量類型的定義,二級指針變量前的*說明這是個指針(存儲地址的小子),而前面的int*說明它指向的對象是int *類型的,他的變量類型是int **
二. 數組與指針的那點事兒
1.🏠 數組名的理解
1.1 數組名本質理解
int arr[10] = {1,2,3,4,6,7,8,9,10};
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
輸出結果
&arr[0] = 004F9CC
arr = 004F9CC
這里我們可以得出數組名本質是首元素的地址
,明白這個后我們以后訪問數組就可以用指針的方式le
int arr[10]={1,23,4,5,6,7,8,9,10};
int* p = arr;
for(int i = 0;i < 10;i++)
{printf("%d ",*(p+i));
}//p+i指針加減法順藤摸瓜到下個元素
//
延伸:
*此時p與arr等價->arr[i] = *(p+i)= *(arr+i) *
那是否所有情況下都是這個樣子呢?嘿嘿,我們看下面的代碼
1.2 sizeof數組名和取地址數組名
Sizeof數組名
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
輸出結果 : 40
看到這里有同學會疑惑,數組名本質不是首元素地址嗎?
小莊如是說:沒錯,但是這是一種特殊情況也就是特例,sizeof中單獨放數組名,這?的數組名表?整個數組
,計算的是整個數組的??,單位是字節(jié)
&數組名
//代碼1
int arr[10] = {1,2,3,4,5,6,78,9,10};
printf("%p\n",&arr[0]);
printf("%p\n",&arr);
printf("%p\n",arr);
//代碼2
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1 = %p\n", &arr+1);
代碼1輸出結果:
0x004FC550 //&arr[0]
0x004FC550 //&arr
0x004FC550 //arr
代碼2輸出結果:
0077F824 //&arr[0] +1
0077F824 //arr+1
0077F848 //&arr+1
我們可以發(fā)現代碼1輸出結果相同,代碼2就出現不同了其中&arr[0]和arr的結果相同這就再次驗證了數組名的本質是首元素的地址,但對于&數組名+1,&arr和&arr+1相差40個字節(jié),這就是因為&arr是數組的地址,+1操作是跳過整個數組的!
因此我們可以得出&數組名,這里的數組名表示整個數組,取出的是整個數組的地址,只不過數組都是從首個元素開始的。相當于你拿一箱水果只能一個一個搬,而比你壯的可以整箱搬起來
總結:對于數組名的運用,sizeof+數組名和&+數組名是特例表示整個數組地址,其他地方都是首元素地址
2. 🏠 指針數組
拋磚引玉:前面我們學習了數組的相關知識,整形數組是存儲整型數據的數組,字符數組是存儲字符型數據的數組… 那指針數組呢?
指針數組:存儲指針的數組,數組每個元素都是來存儲指針(地址)的,主體是數組
.
- 語法形式
Datatype
* arr[size]
這里的Datatype*指的是數組存儲元素的類型,也就是arr是一個存儲整型指針的數組,大小為size。
- 指針數組模擬二維數組
int arr1[] = {1,2,3,4,5};
int arr2[] = {6,7,8,9,10};
int arr3[] = {11,12,13,14,15};
int* arr[3] = {arr1,arr2,arr3};
int i,j;
for(i=0;i<3;i++)
{for(j=0;j<5;j++){printf("%d ",arr[i][j]);}printf("\n");
}
//我們取每個整形數組首元素地址存儲到指針數組中,就可以依靠這個首元素地址順藤摸瓜到整個數組
//從而打印出每行實現二維數組的效果
延伸:
arr[i][j] = * (*(arr+i)+j) ,arr[i]相當于是數組名,以每行為一個元素。
3.🏠 字符串常量
字符串我們學過下面兩種表達式
1 char arr1[5] = "good";
2 char arr2[5] = {'g','o','o','d','\0'};
還有另外一種
const char* p = "good";
這里const修飾在*的前面說明p指向內容不可變,我們這時稱這個字符串為常量字符串,這里是否表示把整個字符串放進p指針呢?不是的,這里意思是把字符串首元素地址放進p里,我們來段代碼測試下。
char * p = "hello"
printf("%s",p);
printf("%c",*p);
輸出結果為hello h,也就是說字符串打印是通過首地址來找到位置打印的跟字符數組類似
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");
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"
輸出結果:
str1 and str2 are not same
str1 and str2 are same
結論:1.c語言會把常量字符串存儲到單獨的一個內存區(qū)域,幾個指針指向同一個字符串時指向的是同一塊內存空間2.用他們初始化不同數組會開辟不同相獨立的內存空間。
4.🏠 數組指針
拋磚引玉:整形指針是指向整形數據的指針,字符型指針是指向字符型的指針…那數組指針呢?
數組指針:指向對象為數組的指針,也就是存儲的數組的地址
- 數組指針變量
1 int (*p)[10];
解釋:這里的先與p結合表示p是個指針變量(存地址的小子),p指向的是一個大小為10存儲元素為整形數據的數組,指針類型為int()[10]
注:【】的優(yōu)先級高于*,所以要加上()來讓*與p先結合表示他是個指針變量
- 數組指針變量初始化
int(*p)[10] = &arr; //取地址數組名取的是整個數組地址
5.🏠 數組傳參
- 一維數組傳參
void test(int arr[])
{
int sz2 = sizeof(arr)/sizeof(arr[0]);
printf("sz2 = %d\n", sz2);
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz1 = sizeof(arr)/sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
輸出結果 sz1 = 10 sz2 = 1;
這里我們可以看到在函數內部和外部求數組長度得出的結果是不同的,原因就是數組傳參時傳的是數組名,也就是說數組傳參本質上傳的是首元素地址,此時函數內部sizeof(arr) == sizeof(arr[0]),因此我們不能在函數內部求數組長度
明白一維數組傳參本質我們可以這樣傳數組
void test(int* p);
總結:一維數組傳參可以寫成數組的形式也可以寫成指針的形式
- 二維數組傳參
類比一維數組傳參的本質 二維數組傳參傳的也是首元素地址,那二維數組的首元素是什么?
我們知道二維數組是一維數組的數組
,也就是說二維數組的第一行元素
就是它的首元素!那第一行元素地址怎么表示呢?這就與我們之前的數組指針串聯起來了,也就是int(*p)[size];
void test(int arr[3][5]);
void test(int(*p)[5]);
總結:二維數組傳參傳的是第一行一維數組的地址,傳參既可以寫成二維數組的形式也可以寫成數組指針的形式。
三. 函數指針
- 函數指針變量
經過前面的類比我們不難得出:
函數指針變量:存儲函數地址的指針變量
函數是否有地址呢?我們寫代碼測試下
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
輸出結果
test: 005913CA
&test: 005913CA
所以函數是有地址的,函數名就是函數的地址,當然也可以通過 &函數名的方式獲得函數的地址
延伸:函數指針調用(*p)(x,y),由于函數名等價于&函數名,故函數指針調用也可以p(x,y)
- 函數指針變量的創(chuàng)建
int (*pf3) (int x, int y);
//函數指針類型為 int(*)(int x ,int y)
解釋:這里的*表示pf3是個指針變量用來存儲指針,前面的int表示指向函數的返回值是int,后面的(int x,int y)表示的是指向函數參數的類型和個數(參數變量名可寫可不寫),可以類型數組指針變量的定義
- 函數指針數組
類比指針數組,把幾個函數的地址存到一個數組,那這個數組就叫函數指針數組
那函數指針數組如何定義呢?
int (*parr1[3])();
數組特征是【】在函數指針變量基礎上加上【】讓parr1先與【】結合就是數組了,數組存儲元素的類型為int(*)()
- typedef重新定義
typedef int(*parr_t)[5]; //新的類型名必須在*的右邊
typedef void(*pfun_t)(int);//新的類型名必須在*的右邊
- 轉移表
知道函數指針數組后我們可以實現轉移表
#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;
}
這里將函數數組作為一個跳板將函數們放進一個表中方便使用,這里的函數指針數組就叫轉移表
本次分享到這就結束了,喜歡的話給小莊三連吧!