?? 10.txt
字號:
10 指針 1
10.1 地址指針的基本概念 1
10.2 變量的指針和指向變量的指針變量 2
10.2.1 定義一個指針變量 3
10.2.2 指針變量的引用 3
10.2.3 指針變量作為函數參數 7
10.2.4 指針變量幾個問題的進一步說明 10
10.3 數組指針和指向數組的指針變量 13
10.3.1 指向數組元素的指針 13
10.3.2 通過指針引用數組元素 14
10.3.3 數組名作函數參數 16
10.3.4 指向多維數組的指針和指針變量 22
10.4 字符串的指針指向字符串的針指變量 25
10.4.1 字符串的表示形式 25
10.4.2 使用字符串指針變量與字符數組的區別 28
10.5 函數指針變量 29
10.6 指針型函數 30
10.7 指針數組和指向指針的指針 31
10.7.1 指針數組的概念 31
10.7.2 指向指針的指針 34
10.7.3 main函數的參數 36
10.8 有關指針的數據類型和指針運算的小結 37
10.8.1 有關指針的數據類型的小結 37
10.8.2 指針運算的小結 37
10.8.3 void指針類型 38
10 指針
指針是C語言中廣泛使用的一種數據類型。運用指針編程是C語言最主要的風格之一。利用指針變量可以表示各種數據結構;能很方便地使用數組和字符串;并能象匯編語言一樣處理內存地址,從而編出精練而高效的程序。指針極大地豐富了C語言的功能。學習指針是學習C語言中最重要的一環,能否正確理解和使用指針是我們是否掌握C語言的一個標志。同時,指針也是C語言中最為困難的一部分,在學習中除了要正確理解基本概念,還必須要多編程,上機調試。只要作到這些,指針也是不難掌握的。
10.1 地址指針的基本概念
在計算機中,所有的數據都是存放在存儲器中的。一般把存儲器中的一個字節稱為一個內存單元,不同的數據類型所占用的內存單元數不等,如整型量占2個單元,字符量占1個單元等,在前面已有詳細的介紹。為了正確地訪問這些內存單元,必須為每個內存單元編上號。根據一個內存單元的編號即可準確地找到該內存單元。內存單元的編號也叫做地址。 既然根據內存單元的編號或地址就可以找到所需的內存單元,所以通常也把這個地址稱為指針。 內存單元的指針和內存單元的內容是兩個不同的概念。 可以用一個通俗的例子來說明它們之間的關系。我們到銀行去存取款時, 銀行工作人員將根據我們的帳號去找我們的存款單, 找到之后在存單上寫入存款、取款的金額。在這里,帳號就是存單的指針, 存款數是存單的內容。對于一個內存單元來說,單元的地址即為指針,其中存放的數據才是該單元的內容。在C語言中,允許用一個變量來存放指針,這種變量稱為指針變量。因此,一個指針變量的值就是某個內存單元的地址或稱為某內存單元的指針。
圖中,設有字符變量C,其內容為“K”(ASCII碼為十進制數 75),C占用了011A號單元(地址用十六進數表示)。設有指針變量P,內容為011A,這種情況我們稱為P指向變量C,或說P是指向變量C的指針。
嚴格地說,一個指針是一個地址,是一個常量。而一個指針變量卻可以被賦予不同的指針值,是變量。但常把指針變量簡稱為指針。為了避免混淆,我們中約定:“指針”是指地址,是常量,“指針變量”是指取值為地址的變量。定義指針的目的是為了通過指針去訪問內存單元。
既然指針變量的值是一個地址,那么這個地址不僅可以是變量的地址,也可以是其它數據結構的地址。在一個指針變量中存放一個數組或一個函數的首地址有何意義呢? 因為數組或函數都是連續存放的。通過訪問指針變量取得了數組或函數的首地址,也就找到了該數組或函數。這樣一來,凡是出現數組,函數的地方都可以用一個指針變量來表示,只要該指針變量中賦予數組或函數的首地址即可。這樣做,將會使程序的概念十分清楚,程序本身也精練,高效。在C語言中,一種數據類型或數據結構往往都占有一組連續的內存單元。 用“地址”這個概念并不能很好地描述一種數據類型或數據結構,而“指針”雖然實際上也是一個地址,但它卻是一個數據結構的首地址,它是“指向”一個數據結構的,因而概念更為清楚,表示更為明確。 這也是引入“指針”概念的一個重要原因。
10.2 變量的指針和指向變量的指針變量
變量的指針就是變量的地址。存放變量地址的變量是指針變量。即在C語言中,允許用一個變量來存放指針,這種變量稱為指針變量。因此,一個指針變量的值就是某個變量的地址或稱為某變量的指針。
為了表示指針變量和它所指向的變量之間的關系,在程序中用“*”符號表示“指向”,例如,i_pointer代表指針變量,而*i_pointer是i_pointer所指向的變量。
因此,下面兩個語句作用相同:
i=3;
*i_pointer=3;
第二個語句的含義是將3賦給指針變量i_pointer所指向的變量。
10.2.1 定義一個指針變量
對指針變量的定義包括三個內容:
(1) 指針類型說明,即定義變量為一個指針變量;
(2) 指針變量名;
(3) 變量值(指針)所指向的變量的數據類型。
其一般形式為:
類型說明符 *變量名;
其中,*表示這是一個指針變量,變量名即為定義的指針變量名,類型說明符表示本指針變量所指向的變量的數據類型。
例如: int *p1;
表示p1是一個指針變量,它的值是某個整型變量的地址。或者說p1指向一個整型變量。至于p1究竟指向哪一個整型變量,應由向p1賦予的地址來決定。
再如:
int *p2; /*p2是指向整型變量的指針變量*/
float *p3; /*p3是指向浮點變量的指針變量*/
char *p4; /*p4是指向字符變量的指針變量*/
應該注意的是,一個指針變量只能指向同類型的變量,如P3 只能指向浮點變量,不能時而指向一個浮點變量,時而又指向一個字符變量。
10.2.2 指針變量的引用
指針變量同普通變量一樣,使用之前不僅要定義說明,而且必須賦予具體的值。未經賦值的指針變量不能使用,否則將造成系統混亂,甚至死機。指針變量的賦值只能賦予地址, 決不能賦予任何其它數據,否則將引起錯誤。在C語言中,變量的地址是由編譯系統分配的,對用戶完全透明,用戶不知道變量的具體地址。
兩個有關的運算符:
1) &:取地址運算符。
2) *:指針運算符(或稱“間接訪問” 運算符)。
C語言中提供了地址運算符&來表示變量的地址。
其一般形式為:
&變量名;
如&a表示變量a的地址,&b表示變量b的地址。變量本身必須預先說明。
設有指向整型變量的指針變量p,如要把整型變量a 的地址賦予p可以有以下兩種方式:
(1) 指針變量初始化的方法
int a;
int *p=&a;
(2) 賦值語句的方法
int a;
int *p;
p=&a;
不允許把一個數賦予指針變量,故下面的賦值是錯誤的:
int *p;
p=1000;
被賦值的指針變量前不能再加“*”說明符,如寫為*p=&a 也是錯誤的。
假設:
int i=200, x;
int *ip;
我們定義了兩個整型變量i,x,還定義了一個指向整型數的指針變量ip。i,x中可存放整數,而ip中只能存放整型變量的地址。我們可以把i的地址賦給ip:
ip=&i;
此時指針變量ip指向整型變量i,假設變量i的地址為1800,這個賦值可形象理解為下圖所示的聯系。
以后我們便可以通過指針變量ip間接訪問變量i,例如:
x=*ip;
運算符*訪問以ip為地址的存貯區域,而ip中存放的是變量i的地址,因此,*ip訪問的是地址為1800的存貯區域(因為是整數,實際上是從1800開始的兩個字節),它就是i所占用的存貯區域, 所以上面的賦值表達式等價于
x=i;
另外,指針變量和一般變量一樣,存放在它們之中的值是可以改變的,也就是說可以改變它們的指向,假設
int i,j,*p1,*p2;
i='a';
j='b';
p1=&i;
p2=&j;
則建立如下圖所示的聯系:
這時賦值表達式:
p2=p1
就使p2與p1指向同一對象i,此時*p2就等價于i,而不是j,圖所示:
如果執行如下表達式:
*p2=*p1;
則表示把p1指向的內容賦給p2所指的區域, 此時就變成圖所示
通過指針訪問它所指向的一個變量是以間接訪問的形式進行的,所以比直接訪問一個變量要費時間,而且不直觀,因為通過指針要訪問哪一個變量,取決于指針的值(即指向),例如"*p2=*p1;"實際上就是"j=i;",前者不僅速度慢而且目的不明。但由于指針是變量,我們可以通過改變它們的指向,以間接訪問不同的變量,這給程序員帶來靈活性,也使程序代碼編寫得更為簡潔和有效。
指針變量可出現在表達式中, 設
int x,y,*px=&x;
指針變量px指向整數x,則*px可出現在x能出現的任何地方。例如:
y=*px+5; /*表示把x的內容加5并賦給y*/
y=++*px; /*px的內容加上1之后賦給y,++*px相當于++(*px)*/
y=*px++; /*相當于y=*px; px++*/
【例10.1】
main()
{ int a,b;
int *pointer_1, *pointer_2;
a=100;b=10;
pointer_1=&a;
pointer_2=&b;
printf("%d,%d\n",a,b);
printf("%d,%d\n",*pointer_1, *pointer_2);
}
對程序的說明:
1) 在開頭處雖然定義了兩個指針變量pointer_1和pointer_2,擔它們并未指向任何一個整型變量。只是提供兩個指針變量,規定它們可以指向整型變量。程序第5、6行的作用就是使pointer_1指向a,pointer_2指向b。
2) 最后一行的*pointer_1和*pointer_2就是變量a和b。最后兩個printf函數作用是相同的。
3) 程序中有兩處出現*pointer_1和*pointer_2,請區分它們的不同含義。
4) 程序第5、6行的“pointer_1=&a”和 “pointer_2=&b”不能寫成“*pointer_1=&a”和 “*pointer_2=&b”。
請對下面再的關于“&”和“*”的問題進行考慮:
1) 如果已經執行了“pointer_1=&a;”語句,則&*pointer_1是什么含義?
2) *&a含義是什么?
3) (pointer_1)++和pointer_1++的區別?
【例10.2】輸入a和b兩個整數,按先大后小的順序輸出a和b。
main()
{ int *p1,*p2,*p,a,b;
scanf("%d,%d",&a,&b);
p1=&a;p2=&b;
if(a<b)
{p=p1;p1=p2;p2=p;}
printf("\na=%d,b=%d\n",a,b);
printf("max=%d,min=%d\n",*p1, *p2);
}
10.2.3 指針變量作為函數參數
函數的參數不僅可以是整型、實型、字符型等數據,還可以是指針類型。它的作用是將一個變量的地址傳送到另一個函數中。
【例10.3】題目同例10.2,即輸入的兩個整數按大小順序輸出。今用函數處理,而且用指針類型的數據作函數參數。
swap(int *p1,int *p2)
{int temp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
main()
{
int a,b;
int *pointer_1,*pointer_2;
scanf("%d,%d",&a,&b);
pointer_1=&a;pointer_2=&b;
if(a<b) swap(pointer_1,pointer_2);
printf("\n%d,%d\n",a,b);
}
對程序的說明:
swap是用戶定義的函數,它的作用是交換兩個變量(a和b)的值。swap函數的形參p1、p2是指針變量。程序運行時,先執行main函數,輸入a和b的值。然后將a和b的地址分別賦給指針變量pointer_1和pointer_2,使pointer_1指向a,pointer_2指向b。
接著執行if語句,由于a〈b,因此執行swap函數。注意實參pointer_1和pointer_2是指針變量,在函數調用時,將實參變量的值傳遞給形參變量。采取的依然是“值傳遞”方式。因此虛實結合后形參p1的值為&a,p2的值為&b。這時p1和pointer_1指向變量a,p2和pointer_2指向變量b。
接著執行執行swap函數的函數體使*p1和*p2的值互換,也就是使a和b的值互換。
函數調用結束后,p1和p2不復存在(已釋放)如圖。
最后在main函數中輸出的a和b的值是已經過交換的值。
請注意交換*p1和*p2的值是如何實現的。請找出下列程序段的錯誤:
swap(int *p1,int *p2)
{int *temp;
*temp=*p1; /*此語句有問題*/
*p1=*p2;
*p2=temp;
}
請考慮下面的函數能否實現實現a和b互換。
swap(int x,int y)
{int temp;
temp=x;
x=y;
y=temp;
}
如果在main函數中用“swap(a,b);”調用swap函數,會有什么結果呢?請看下圖所示。
【例10.4】請注意,不能企圖通過改變指針形參的值而使指針實參的值改變。
swap(int *p1,int *p2)
{int *p;
p=p1;
p1=p2;
p2=p;
}
main()
{
int a,b;
int *pointer_1,*pointer_2;
scanf("%d,%d",&a,&b);
pointer_1=&a;pointer_2=&b;
if(a<b) swap(pointer_1,pointer_2);
printf("\n%d,%d\n",*pointer_1,*pointer_2);
}
其中的問題在于不能實現如圖所示的第四步(d)。
【例10.5】輸入a、b、c3個整數,按大小順序輸出。
swap(int *pt1,int *pt2)
{int temp;
temp=*pt1;
*pt1=*pt2;
*pt2=temp;
}
exchange(int *q1,int *q2,int *q3)
{ if(*q1<*q2)swap(q1,q2);
if(*q1<*q3)swap(q1,q3);
if(*q2<*q3)swap(q2,q3);
}
main()
{
int a,b,c,*p1,*p2,*p3;
scanf("%d,%d,%d",&a,&b,&c);
p1=&a;p2=&b; p3=&c;
exchange(p1,p2,p3);
printf("\n%d,%d,%d \n",a,b,c);
}
10.2.4 指針變量幾個問題的進一步說明
指針變量可以進行某些運算,但其運算的種類是有限的。它只能進行賦值運算和部分算術運算及關系運算。
1. 指針運算符
1) 取地址運算符&:取地址運算符&是單目運算符,其結合性為自右至左,其功能是取變量的地址。在scanf函數及前面介紹指針變量賦值中,我們已經了解并使用了&運算符。
2) 取內容運算符*:取內容運算符*是單目運算符,其結合性為自右至左,用來表示指針變量所指的變量。在*運算符之后跟的變量必須是指針變量。
需要注意的是指針運算符*和指針變量說明中的指針說明符*不是一回事。在指針變量說明中,“*”是類型說明符,表示其后的變量是指針類型。而表達式中出現的“*”則是一個運算符用以表示指針變量所指的變量。
【例10.6】
main(){
int a=5,*p=&a;
printf ("%d",*p);
}
表示指針變量p取得了整型變量a的地址。printf("%d",*p)語句表示輸出變量a的值。
2. 指針變量的運算
1) 賦值運算:指針變量的賦值運算有以下幾種形式。
① 指針變量初始化賦值,前面已作介紹。
② 把一個變量的地址賦予指向相同數據類型的指針變量。
例如:
int a,*pa;
pa=&a; /*把整型變量a的地址賦予整型指針變量pa*/
③ 把一個指針變量的值賦予指向相同類型變量的另一個指針變量。
如:
int a,*pa=&a,*pb;
pb=pa; /*把a的地址賦予指針變量pb*/
由于pa,pb均為指向整型變量的指針變量,因此可以相互賦值。
④ 把數組的首地址賦予指向數組的指針變量。
例如:
int a[5],*pa;
pa=a;
(數組名表示數組的首地址,故可賦予指向數組的指針變量pa)
也可寫為:
pa=&a[0]; /*數組第一個元素的地址也是整個數組的首地址, 也可賦予pa*/
當然也可采取初始化賦值的方法:
int a[5],*pa=a;
⑤ 把字符串的首地址賦予指向字符類型的指針變量。
例如:
char *pc;
pc="C Language";
或用初始化賦值的方法寫為:
char *pc="C Language";
這里應說明的是并不是把整個字符串裝入指針變量,而是把存放該字符串的字符數組的首地址裝入指針變量。在后面還將詳細介紹。
⑥ 把函數的入口地址賦予指向函數的指針變量。
例如:
int (*pf)();
pf=f; /*f為函數名*/
2) 加減算術運算
對于指向數組的指針變量,可以加上或減去一個整數n。設pa是指向數組a的指針變量,則pa+n,pa-n,pa++,++pa,pa--,--pa運算都是合法的。指針變量加或減一個整數n的意義是把指針指向的當前位置(指向某數組元素)向前或向后移動n個位置。應該注意,數組指針變量向前或向后移動一個位置和地址加1或減1在概念上是不同的。因為數組可以有不同的類型,各種類型的數組元素所占的字節長度是不同的。如指針變量加1,即向后移動1 個位置表示指針變量指向下一個數據元素的首地址。而不是在原地址基礎上加1。例如:
int a[5],*pa;
pa=a; /*pa指向數組a,也是指向a[0]*/
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -