指針
- 指針是C語言的精華,同時也是其中的難點和重點,我在近日對這一部分內容進行了重新的研讀,把其中的一些例子自己重新編寫和理解了一遍。此篇博客的內容即是我自己對此書例子的一些理解和總結。
一.大問題:指針是什么?
我的理解:
變量的本質即內存,指針即訪問變量的地址。利用指針來 <間接訪問> 變量。
定義一個指針,p是指針變量名,系統自動為其分配內存,存放的是其指向的變量(內存)的地址。
例如:
1> int a=4;
2> int *p;
3> p=&a;
上述程序定義一個變量a,系統自動為其分配內存,那么這個內存的名稱就是a,其存放的值是4。
再定義一個整形指針p,系統也為其分配內存(根據指針的類型分配不同大小的內存單元,例如此處指針為int類型,計算機為其分配4個字節),該內存里存放的是指向名稱為a的內存的地址。
- 變量的本質是什么?-變量名只是一個代號,變量的本質就是內存。
- 指針保存的是什么?-指針保存的是內存地址。
這樣來看,會不會對指針理解更加清楚一點呢?
草圖:
待解決
二.指針變量
- 使用指針變量
- 定義指針變量
- 引用指針變量
- 指針變量作為函數參數
1.int *p1,*p2; p1-p2是什么?
#include<stdio.h>
int main()
{
int a[4]={3,4};
int *p1=&a[0],*p2=&a[1];
printf("%d %d\n",p1,p2);
printf("%d ",p2-p1);/*p2-p1=地址之差/數組元素的長度
就是p1所指向的元素與p2所指向的元素差之間幾個元素*/
return 0;
}
我的理解:
p1指向數組a的首元素a[0],p2指向數組a的a[1],p1-p2代表的是a[0]與a[1]之間相隔幾個元素。
輸出結果:
計算機自動處理為
p2-p1=地址之差/數組元素的長度.而不是簡單的地址之差=4.
2.輸入三個整數按從大到小的順序輸出.(指針變量作為函數參數)
- 錯誤解法:
#include<stdio.h>
void swap(int *p1,int *p2,int *p3)
{
int *p;
if(*p1<*p2)
{
p=p1;
p1=p2;
p2=p;
}
if(*p1<*p3)
{
p=p1;
p1=p3;
p3=p;
}
if(*p2<*p3)
{
p=p2;
p2=p3;
p3=p;
}
}
int main()
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
printf("%d %d %d\n",a,b,c);
int *point_1=&a,*point_2=&b,*point_3=&c;
swap(point_1,point_2,point_3);
printf("%d %d %d\n",*point_1,*point_2,*point_3);
return 0;
}
輸入樣例:7 8 9
輸出結果:
- 正確解法:
/*輸入三個整數按從大到小的順序輸出*/
#include<stdio.h>
void swap(int *p1,int *p2,int *p3)
{
int p;
if(*p1<*p2)
{
p=*p1;
*p1=*p2;
*p2=p;
}
if(*p1<*p3)
{
p=*p1;
*p1=*p3;
*p3=p;
}
if(*p2<*p3)
{
p=*p2;
*p2=*p3;
*p3=p;
}
}
int main()
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
printf("%d %d %d\n",a,b,c);
int *point_1=&a,*point_2=&b,*point_3=&c;
swap(point_1,point_2,point_3);
printf("%d %d %d\n",a,b,c);
return 0;
}
輸入樣例:7 8 9
輸出結果:
我的理解:
- 函數值是單向傳遞。swap函數中的指針變量p1,p2,p3交換地址,這樣的變化並不會傳遞回原函數使原函數的指針變量的值發生改變。原函數中指針變量point_1/2/3所存儲的內存地址並沒有改變。
- 錯誤樣例的錯誤在於:
原函數傳遞三個變量的地址到swap函數,企圖通過swap交換形參p1,p2,p3這些指針變量所存儲的地址,來交換實參point_1/2/3地址。然而忽略了函數調用時形參和實參是采用單向傳遞的值傳遞方式。在函數中改變了名稱為p1,p2,p3的內存單元所存儲的內存地址,但是主函數中名稱為point_1/2/3的內存單元所存儲的內存地址並沒有改變。- 這樣的一個細節問題導致了程序的錯誤。時刻注重細節是成為一名程序員的基本素養。
- 使用指針來處理的優點:能夠改變多個值。而普通的函數只能有一個返回值。
3.輸入兩個數 按從大到小的順序輸出
#include<stdio.h>
void avoid(int *p1,int *p2)
/*傳遞的是地址,p1,p2儲存的是a,b的地址*/
{
int p;
if(*p1<*p2)
{
p=*p1;
*p1=*p2;
*p2=p;/*實質就是交換a,b*/
}
}
int main()
{
int *p1,*p2;
int a,b;
scanf("%d %d",&a,&b);
p1=&a;
p2=&b;
if(a<b)
avoid(p1,p2);
printf("%d %d",a,b);
return 0;
}
我的理解:
void avoid(int *p1,int *p2)函數接收內存名為a和內存名為b的內存的地址。通過交換指針(函數接收的地址)所指向的內存所存儲的值來達到排序的目的。
輸入樣例:3 4
輸出結果:
三.通過指針引用數組
數組元素的指針
指針的運算
通過指針引用數組元素
數組名作為函數參數
通過指針引用多維數組
探究a+i(p+i)與&a[i]的關系 例一
#include<stdio.h>
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,0};
int *p=a;
int i;
for(i=0;i<=9;i++)
{
printf("%d ",*(p+i));/* *(p+i)與a[i]等價 */
}
return 0;
}
輸出結果:
我的理解:
- 數組名a即是
&a[0](該數組首元素的地址),將a[0]的地址賦值給指針變量p,並利用指針輸出該數組的各元素。- for循環中的
printf("%d ",*(p+i));語句里的*(p+i)與a[i]無條件等價。- 原因:前面的語句,p的值是a數組首元素的地址,而對於
(p+i),計算機系統處理成&a[0]+i*數組元素的長度,也就是說(p+i)是數組元素a[i]的地址(即&a[i])。那么*(p+i)就相當於是a[i]。
基於這一原理,我們來看下一個例子
探究a+i(p+i)與&a[i]的關系 例二
#include<stdio.h>
int main()
{
int a[15];
int i;
int *p;
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(p=a;p<a+10/*地址小於&a[10]*/;p++)
/*a+10等價於&a[10]*/
{
printf("%d ",*p);
}
return 0;
}
輸入樣例:1 2 3 4 5 6 7 8 9 0
輸出結果:
我的理解:
- 在理解完例一之后,來看這個例子:a數組有十個元素,輸入這十個元素,然后要求利用指針輸出這十個元素。
- 與例一不同的是,這里的for循環有些不一樣:
for(p=a;p<a+10;p++);首先進行賦值操作,把a數組首元素的地址賦值給p,然后輸出*p的值(p指向該數組元素),再執行p++(該操作的意義是:p指向該數組的下一元素),循環結束的條件是p<a+10,即當p指向a[9]時不再進行自增操作。- 本例利用p指針的自增來按序輸出不同的數組元素,++自增運算符是C語言的一大特色。
有關於p=a下文會有提及。
探究a+i(p+i)與&a[i]的關系 例三
#include<stdio.h>
int main()
{
int a[15];
int *p=a;
int i;
for(i=1;i<=5;i++)
scanf("%d",p+i);/*輸入地址p+i相當於&a[i]*/
for(i=1;i<=5;i++)
printf("%d ",*(p+i));
return 0;
}
- a數組里有五個元素,輸入這五個元素,並利用指針按序輸出。
有了上述兩例的鋪墊,這個例子現在理解起來是不是比較容易呢?
有關函數參數的應用 例四
題目:有一個含有n個元素的數組,第一行輸入n,第二行輸入這個數組的元素,編寫一個程序使該數組的元素按相反順序存放並輸出。
- 代碼1:
#include<stdio.h>
void inv(int a[],int n)/* void inv(int *p,int n) */
{
int t,i,j;
for(i=1,j=n;i<=j;i++,j--)
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
int main()
{
int n,a[105];
int i,j;
int *p=a;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",p+i);
inv(a,n);
for(i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}
我的理解(代碼1):形參和實參都用數組名
- 首先,在主函數里定義指針p並將數組a首元素的地址(數組名
int *p=a)賦值給它,接着這個例子特別的地方在於:將數組名作為函數實參傳遞給inv函數。通過前面的例子我們可以知道:數組名相當於是數組首元素的地址,也就是說這里的函數實參相當於是數組首元素的地址。- 其次,在我們定義的函數inv中,
void inv(int a[],int n);這里我們的函數形參也用了數組名,它與void inv(int *p,int n);等價,因為這里的形參數組名相當於指針變量,用來接收傳遞自主函數的地址。我比較喜歡這樣的做法,這樣的好處一是易於我們初學者理解,二是不像普通的int,float之類的自定義函數只能有一個返回的return值,它能夠對整個數組元素進行操作。這里我個人並不是很准確的把它稱為:“通過指針,inv函數接收了一個數組”。- 除了上述的有關於函數實參形參的問題,這段程序還有一個大家剛剛接觸過的一個注意點:
scanf("%d",p+i);中的p+i。
- 代碼2:
#include<stdio.h>
int main()
{
void inv(int *p,int n);
int n,a[105],i,j;
int *p=a;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",p+i);
inv(p,n);
/* p=a */
for(p=a;p<a+n;p++) /* p<=p+n||p<=a+n? Error */
printf("%d ",*p);
return 0;
}
void inv(int *p,int n)
{
int *i,*j,t=0;
for(i=p,j=p+n-1;i<=j;i++,j--)
{
t=*i;
*i=*j;
*j=t;
}
}
我的理解(代碼2):形參和實參都用指針變量
與代碼1有所不同的是,代碼2所定義的inv函數實參和形參都使用了指針來傳遞地址(數組首元素的地址),這也驗證了我的理解(代碼1)的說法:函數形參用數組名與函數形參使用指針變量效果和目的是一樣的,都是接受來自主函數的地址。不過代碼2不足的地方也是在這個地方,由於在函數inv里面沒有聲明a數組(或者說,沒有把a數組“傳遞過來”),無法像代碼1一樣直接對a數組的元素進行操作,只能通過函數inv定義的指針p(其值為數組a的首元素地址)來進行操作。
代碼1和代碼2提供了兩種情況,如果有一個實參數組,想在函數中改變此數組中元素的值,實參和形參對應的情況如下:
(1)形參和實參均使用數組名。如代碼1。
(2)形參和實參均使用指針變量。如代碼2。
(3)形參使用數組名,實參使用指針變量。
(4)形參使用指針變量,實參使用數組名。不管是哪一種情況,函數實參和形參傳遞的都是數組a首元素的地址,在本篇文章的開頭我有提到,指針與內存是息息相關的。此時,我們注意到代碼1這里的形參
void inv(int a[],int n);用到了數組名a,但是由於數組名a相當於指針變量,計算機系統並沒有真正給它分配一個數組的空間。可以這樣理解:
inv函數在調用期間,形參數組和實參數組共同使用 同一段內存 ,那么對於代碼1,在函數中的操作就是 直接 對主函數中數組a的各個元素的操作。
- 代碼3:
#include<stdio.h>
void swap(int *p1,int *p2)
{
int t;/*為何此處不能用*t?*/
t=*p1;
*p1=*p2;
*p2=t;
}
int main()
{
int a[105],t,n,i,j;
int *p=a;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",p+i);
for(i=1,j=n;i<=j;i++,j--)
{
swap(&a[i],&a[j]);
}
for(i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}
我的理解(代碼3):有點不同
- 關於代碼3,這是我拿到題目最初的思路和做法,其實如果要快速解題的話,大可不必利用指針這么麻煩,在主函數里面利用for循環即可快速解題。
- 但是我為什么要貼出這一段代碼呢? 原因在於這段代碼的注釋,這個地方也是很令我困擾。
- 理論上,定義一個整形指針p,p指向的是一個未知的整形內存,也可以起到上面代碼swap函數中中間變量t的作用:
void swap(int *p1,int *p2)
{
int *p;
*p=*p1;
*p1=*p2;
*p2=*p;
}
- 輸入樣例:4 1 2 3 4
- 輸出結果:
- 系統報錯。這讓我感到意外,按照上述內容來看,我的想法應該是正確的。但是我忽略了這樣做的危險性。
- 上面我定義的指針准確的名稱叫做野指針,其缺省值(未修改前的初始值)是隨機的,如果剛剛開始定義的指針沒有明確的初始化或者被設置成null指針,它的指向可能是不合法的。如果定義一個野指針,基於其未知的危險性,計算機系統會報錯。
- 正確樣例:
- 代碼3(修改):
#include<stdio.h>
void swap(int *p1,int *p2)
{
int a=4;
int *t=&a;
*t=*p1;
*p1=*p2;
*p2=*t;
}
int main()
{
int a[105],t,n,i,j;
int *p=a;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",p+i);
for(i=1,j=n;i<=j;i++,j--)
{
swap(&a[i],&a[j]);
}
for(i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}







