目錄
一、了解指針
二、二級指針
三、字元指針
四、野指針
1.未初始化
2.指針越界通路
3.動态記憶體釋放
4.規避野指針
五、指針數組
六、數組指針
七、函數指針
八、函數指針數組
九、指向函數指針數組的指針
十、函數的回調
C語言之是以難,就是因為有指針這個東西,那麼首先我們需要了解指針,這裡基本概念我會簡單略過,主要講解指針的各種運用。
一、了解指針
概念:指針=位址(本篇指針和位址會換着使用,但請讀者了解這兩個是一個東西),而我們平常習慣說的指針,實質上是指針變量,這兩個是不同的概念,千萬别搞混了!
我們用指針來指向位址,例如:
#include<stdio.h>
int main()
{
int a=10;
int* p=&a;
return 0;
}
這裡我要提一下,*号告訴p是指針,p前面的int是告訴我們p指向的對象的類型是int類型
我們調試便可以知道p和a的位址是相同的
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLxQWYyMGOwYWNjdTMxMTM4UTM3QjYmhDO3MDOxEGOlN2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
細心的小夥伴肯定發現p本身也是有位址的,是以有了二級指針。
二、二級指針
#include<stdio.h>
int main()
{
int a=10;
int* p=&a;
int** pp=&p;
return 0;
}
有了指針以後,我們可以用*号去解引用,可以進行修改等一系列操作,例如:
那麼我們要如何了解這裡的操作呢?請看下面的圖例
那麼我們為什麼要加*号呢,我的了解是a的類型是int,p的類型是int *,我們想要類型比對,就需要在加一個*号,這樣我們才能去使用a儲存的值,而不是位址。
三、字元指針
概念:指向字元的指針
#include<stdio.h>
int main()
{
//這裡儲存的并不是整個字元常量,而是字元常量的首位址,即w的位址
char* c="work hard";
printf("%s",c);
return 0;
}
四、野指針
什麼叫野指針?即:指針指向的空間是未知的。
導緻這個問題有幾個原因,我們來一一分析。
1.未初始化
int main()
{
int *p;//p指向一個随機的位置
*p=20;
printf("%d",*p);
return 0;
}
我們調試的時候便可以看到問題:
2.指針越界通路
int main()
{
int arr[5]={1,2,3,4,5};
int* p=arr;
//數組最大是5,我們卻通路了後面的元素,不僅指針不能這麼用,數組也不能這麼用
for(int i=0;i<9;i++)
printf("%d",*(p+i));
return 0;
}
3.動态記憶體釋放
int main()
{
int* a=(int*)malloc(sizeof(int));
free(a);
//free隻是釋放這個空間,單這個空間還可以被使用,為了不被使用,我們需要置空
a=NULL;
return 0;
}
4.規避野指針
(1) 指針初始化
(2)不越界通路
(3)指針指向的空間要釋放
(4)避免傳回局部變量的位址(因為局部變量出了作用域會被銷毀)
(5)指針使用前檢查有效性
五、指針數組
概念:本質上是數組,裡面存放了指針(位址),定義方法為int* p[]
p是一個數組,數組裡存放了3個元素,每個元素的類型是int*
這裡p[3]存放的是數組名,而數組名就是數組首元素的位址,這個用法是幫助我們了解指針數組,實際上指針數組還有一個常用的方法:
#include<stdio.h>
int main()
{
int i=0;
char* p[3]={"I","love","China"};
for(i=0;i<3;i++)
printf("%s ",*(p+i));
return 0;
}
輸出結果為:I love China
注意!很多同學在這裡認為指針數組儲存的是一整個字元串,其實不是,這裡儲存的是常量字元串的首位址。
六、數組指針
概念:指向數組的指針,存放的是數組位址(重點是數組位址),本質上是一個指針,那麼這裡我們這樣定義int* p[]是對的嗎?答案是:完全錯誤!因為p首先和方括号結合,最後就成了數組了,這裡我們需要加括号來指名他是數組指針,例如:int (*p)[],這樣p就會首先和*号結合,牢牢記住!
p的類型是int(*)[],數組指針的基本用法為:
#include<stdio.h>
int main()
{
int i=0;
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int (*p)[10]=&arr;
for(i=0;i<10;i++)
{
//*p=arr,因為p相當于&arr,*p就等于*&arr,這樣就抵消了,相當于獲得了數組名
printf("%d ",(*p)[i]);
}
return 0;
}
輸出結果為:1 2 3 4 5 6 7 8 9 10
這裡一定要注意我的注釋,非常重要,這裡注意是&arr,指針數組用的是arr,這兩個的差別是arr是數組首元素的位址,而&arr是整個數組的位址,實際上我們不會這麼用,我隻是想用這個方法告訴你數組指針的基本概念,我們一般這樣用:
#include<stdio.h>
void print(int (*arr)[4],int row,int col)
{
int i=0;
int j=0;
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
//printf("%d ",arr[i][j]);
printf("%d ",*(*(arr+i)+j));
printf("\n");
}
}
int main()
{
int arr[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}};
print(arr,3,4);
return 0;
}
輸出結果為:1 2 3 4
2 3 4 5
3 4 5 6
二維數組的數組名是首元素的位址,而二維數組的首元素就是第一行的位址!是以arr+i就是第i行的位址*(arr+i)相當于拿到了第i行,這又相當于數組名,因為*(p+i)==p[i],在二維數組中,這就是第i行的數組名,數組名又相當于數組首元素的位址,是以我們可以寫成*(*(arr+i)+j)
七、函數指針
函數指針:指向函數的指針變量,存放函數位址
下面我們來看一個圖:
我們發現&Add和Add的位址一樣的, 那麼我們可以用指針儲存起來,void (*pf)(),以下是代碼
#include<stdio.h>
int Add(int x,int y)
{
return x+y;
}
int main()
{
//這兩個都可以用,因為他們指向的位址是一樣的
//數組&和沒有&是有差別的,但是函數沒有,因為函數沒有首元素取位址的說法
//int (*pf)(int x,int y)=&Add;
int (*pf)(int x,int y)=Add;
//這裡可以沒有*号的原因是Add=pf
int ret=pf(2,3);
//int ret=(*pf)(2,3);
printf("%d \n",ret);
return 0;
}
八、函數指針數組
概念:把函數位址存放到數組中成為函數指針數組,它的本質是數組,是以我們定義為void(*pf[])()
在我們看函數指針數組實際運用之前,我們先做一個加減乘除的電腦,代碼如下:
#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;
}
void menu()
{
printf("**** 0.exit 1.add ****\n");
printf("**** 2.sub 3.mul ****\n");
printf("**** 4.div ****\n");
}
int main()
{
int input=0;
int a=0;
int b=0;
int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
do
{
menu();
printf("請輸入->\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("exit\n");
break;
case 1:
printf("請輸入兩個數字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Add(a,b));
break;
case 2:
printf("請輸入兩個數字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Sub(a,b));
break;
case 3:
printf("請輸入兩個數字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Mul(a,b));
break;
case 4:
printf("請輸入兩個數字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Div(a,b));
break;
default:
break;
}
}while(input);
return 0;
}
這段代碼備援度很高對吧?這時函數指針數組的優勢就展現出來了,我們可以用函數把main函數的代碼改為:
int main()
{
int input=0;
int a=0;
int b=0;
int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
do
{
menu();
printf("請輸入->\n");
scanf("%d",&input);
if(input==0)
{
printf("exit\n");
}
else if(input>=1&&input<=4)
{
printf("輸入兩個數字\n");
scanf("%d %d",&a,&b);
int ret=pfArr[input](a,b);
printf("%d\n",ret);
}
else
{
printf("重新輸入\n");
}
}
while(input);
return 0;
}
九、指向函數指針數組的指針
概念:簡單說就是存放函數指針數組的位址,重心在于指針!
int main()
{
int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
//重心在于指針,是以我們需要先寫成這樣(*ppfArr)
int (*(*ppfArr)[5])(int x,int y)={&pfArr};
}
十、函數的回調
概念:函數的(指針)位址作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們稱為函數的回調。
可能看這個文字描述會讓我們雲裡霧裡的,是以我們直接用代碼去解釋,就用之前的電腦的代碼:
//既然我們傳過來了函數的位址,那麼我們裡面的參數就可以用函數指針來接受
void calc(int (*pf)(int x,int y))
{
printf("請輸入兩個數字->");
int a=0;
int b=0;
scanf("%d %d",&a,&b);
int ret=pf(a,b);
printf("%d\n",ret);
}
int main()
{
int input=0;
do
{
menu();
printf("請輸入->\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("exit");
break;
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
default:
break;
}
}
while(input);
return 0;
}
這裡我們在回過頭來想想是不是很好了解了?我們把函數的位址作為參數,把這個參數給另一個函數,這個指針被用來調用了我們定義的加減乘除函數。
指針的所有用法基本都在這裡介紹了,如果有錯誤希望指正!