




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第八章指針8.1指針的概念與定義8.2指針作函數參數8.3指針與數組8.4指針與函數8.5復雜指針8.6程序設計舉例習題8.1指針的概念與定義8.1.1指針的概念指針就是用來存放地址的變量。某個指針存放了哪個變量的地址,就可以說該指針指向了這個變量。實際上,指針還可以指向數組、字符串等對象,甚至可以用來存放函數的入口地址。為了理解指針的概念,程序員要有關于計算機如何在存儲器中存儲信息的基本知識。以下簡單地介紹個人計算機中存儲器存儲的情況。個人計算機中CPU可以直接訪問的,用來存儲程序和數據的記憶部件稱為內存儲器,內存儲器由成千上萬個順序存儲單元組成,每個單元由一個惟一的地址標識。給定計算機的存儲器地址范圍為從0到所安裝的存儲器數量的最大值。在計算機上運行的每一個程序都要使用存儲器。例如,操作系統要占用一些計算機存儲空間,每個應用程序也要占用計算機存儲空間。按照面向過程的結構化程序設計方法,程序代碼和程序要處理的數據是分開存儲的,所以,一個程序在內存中要占兩部分空間:數據部分和指令代碼部分。本節考察數據段在存儲器中的存儲情況。當C程序中定義一個變量時,編譯器劃分出一定數目的存儲器單元來存儲那個變量,存儲器單元的數目由變量的類型確定。編譯器將這幾個存儲單元與變量名聯系起來,當程序引用這個變量名時,自動地訪問相應的存儲器單元,當然程序也可以通過該變量的地址來訪問這些存儲器單元。程序中的變量所需存儲單元的數目由變量的類型決定。例如,一個int變量占據的存儲單元可能為2字節(16位機)或4字節(32位機),一個float變量為4字節,一個char變量為1字節等。變量所占據的存儲單元的地址就是變量的地址,變量的地址表示為表達式
&變量名
&是單目運算符,稱為取地址運算符。例如:
charc=′A′;則&c是字符變量c的地址,即圖8.1中的FFF3H。圖8.1內存分配表存放變量的地址的變量稱為指針。如果pc是存放字符變量地址的變量,則語句
pc=&c;將c的地址存入指針pc,如圖8.1所示,稱“pc指向c”,或“pc是c的指針”,被pc指向的變量c稱為“pc的對象”。“對象”就是一個有名字的內存區域,即一個變量。
#include<stdio.h>
voidmain()
{
inti=0x3038;
charc=′A′;
int*pi=&i;
char*pc=&c;
printf("i=%x,c=%c,*pi=%d,*pc=%c\n",i,c,*pi,*pc);
printf("&i=%p,&c=%p,&pi=%p,&pc=%p\n",pi,pc,&pi,&pc);
}運行結果:
i=3038,c=A,*pi=3038,*pc=A
&i=FFF4,&c=FFF3,&pi=FFF0,&pc=FFEE該程序在TurboC++3.0下編譯后,各變量的存儲內容和內存分配情況如圖8.1所示。可以看到,在內存中,整型變量i占2個字節,字符型變量c占1個字節,指針變量pi和pc各占2個字節(在這里,我們應該看到,不管指針變量的類型是什么,它在內存中所占的字節數是一定的;16位系統下是2個字節,32位系統下是4個字節),這是因為指針保存的是地址,與具體類型是無關的。從圖8.1中可以看到,既可以通過變量名直接訪問內存的數據,又可以通過變量的地址(即指針)間接訪問該變量。指針類型是對所有類型的指針的總稱,指針的類型是指針所指對象的數據類型。例如,pc是指向字符變量的指針,簡稱字符指針。字符指針是基本類型的指針之一,除各種基本類型之外,允許說明指向數組的指針、指向函數的指針、指向結構體和共用體的指針以及指向各類指針的指針。在C語言中只有指針被允許用來存放地址的值,其它類型的變量只能存放該類型的數據。(很多書中用指針一詞來指地址值,或用指針變量來代表指針,閱讀中應注意其具體含義。)8.1.2指針的定義及使用
1.指針的定義指針是一種存放地址值的變量,像其它變量一樣,必須在使用前定義。指針變量的命名遵守與其它變量相同的規則,即必須是惟一的標識符。指針定義的格式如下:類型名*指針名;其中,“類型名”說明指針所指變量的類型;星號“*”是一個指針運算符,說明“指針名”是一個“類型名”類型的指針而不是一個“類型名”類型的變量。指針可與非指針變量一起說明,如例8.1所示。例8.1
指針與非指針的定義。
char*pcl,*pc2;/*pcl和pc2均為指向char型的指針*/
float*pf,percent;/*pf是float型的指針,而percent為普通的float型變量*/指針的類型是使用指針時必須注意的問題之一。在例8.1中,pc1、pc2和pf都是指針,它們存放的都是內存的地址,但這并不意味著它們的類型相同。實際上pc1和pc2是同類型的,而它們與pf的類型卻不同。在使用中,pc1和pc2只能夠存放字符型變量的地址,而pf只能夠存放浮點型變量的地址。指針的當前指向是使用指針的另一個需特別注意的問題。程序員定義一個指針之后,一般要使指針有明確指向,這一點可以通過對指針初始化或賦值來完成。與常規的變量未賦初值相同,沒有明確指向的指針不會引起編譯器出錯,但對于指針可能導致無法預料的或隱蔽的災難性后果。例8.2
指針的指向。
int*point;
scanf("%d",point);例8.2中指向整型的指針point在定義之后直接使用了,這兩條語句在編譯時不會出現語法錯誤,但在使用時卻幾乎肯定會出問題。表面上看,scanf()函數的參數要求給出的是地址,而point的值就代表的是地址,但是point的值究竟是多少,也就是說point究竟指向哪里,我們無法得知,在這種情況下就對point指向的單元進行輸入操作,將沖掉point指向的單元的原有內容,假如這個單元是操作系統的所在處,就破壞了操作系統,顯然是一件危險的事。
2.指針的有關運算符兩個有關的運算符:
&:取地址運算符。*:指針運算符(或稱“間接訪問”運算符)。例如:&a為變量a的地址,*p為指針p所指向的存儲單元的內容。
&運算符只能作用于變量,包括基本類型變量和數組的元素、結構體類型變量或結構體的成員(具體內容見第九章),不能作用于數組名、常量或寄存器變量。例如:
doubler,a[20];
inti;
registerintk;表達式&r、&a[0]、&a[i]是正確的,而&(2*r)、&a、&k是非法表示。單目運算符*是&的逆運算,它的操作數是對象的地址,*運算的結果是對象本身。單目*稱為間訪運算符,“間訪”就是通過變量的地址而不是變量名存取(或引用)變量。例如,如果pc是指向字符變量c的指針,則*(&c)和*pc表示同一字符對象c。因而賦值語句:*(&c)=′a′;*pc=′a′;
c=′a′;效果相同,都是將′a′存入變量c。指針可以由帶有地址運算符的語句來使之有明確指向,如例8.3所示。例8.3
取地址運算符。
intvariable,*point;
point=&variable;
3.指針的使用在定義了指針并明確了它的指向后,就可以使用指針了。例8.4和例8.5給出了指針使用的簡單例子。例8.4
指針的使用。
#include<stdio.h>
voidmain()
{
inta,b,*p1,*p2;
a=10;b=20;
p1=&a;p2=&b;
printf("%d\t%d\n",*p1,*p2);
p1=&b;p2=&a;
printf("%d\t%d\n",*p1,*p2);
}運行結果:
10
20
20
10說明:
(1)在兩個printf()函數調用語句中的*是指針運算符,這一單目運算符的運算對象應該是指針或地址,它的作用是得到指針指向變量的值。
(2)在第一個printf()函數調用時,可以假設內存的分配如圖8.2所示。假設變量a占用的內存單元為FFF4H和FFF5H,變量b占用的內存單元為FFF2H和FFF3H,指針p1占用的內存單元為FFF0H和FFF1H,指針p2占用的內存單元為FFEFH和FFF0H,則它們的值分別是:10(000AH),20(0014H),FFF2H,FFF4H。*p1和*p2的值分別是20(0014H)和10(000AH),這就是第一個printf()函數調用所得到的結果。圖8.2內存分配表(一)
(3)在第二個printf()函數調用時,可以假設內存的分配如圖8.3所示。圖8.3內存分配表(二)此時各變量所占用的內存單元不變,但p1和p2的值分別為FFF4H和FFF2H,也就是它們此時分別指向b和a。這樣*p1和*p2的值分別是10(000AH)和20(0014H),所以第二個printf()函數調用所得到的結果為10(000AH)和20(0014H)。
例8.5
指針的使用。
#include<stdio.h>
voidmain()
{
inta,*pi;
floatf,*pf;
a=10;f=20.5;
pi=&a;pf=&f;
printf("%d\t%4.1f\n",a,f);
printf("%d\t%4.1f\n",*pi,*pf);
}運行結果:
1020.5
1020.5
說明:
(1)printf()函數調用時,可以假設內存的分配如圖8.4所示。圖8.4內存分配表假設變量a占用的內存單元為FFF4H和FFF5H,浮點型變量f占用的內存單元為FFEEH,FFEFH,FFF0H和FFF1H,指針pi占用的內存單元為FFF2H和FFF3H,指針pf占用的內存單元為FFECH和FFEDH,則它們的值分別是:10(0AH),20.5(3F80FF00H),FFF4H,FFEEH。*pi和*pf的值分別是10(0AH)和20.5(3F80FF00H),這就是printf()函數調用所得到的結果。
(2)printf()函數調用語句中的*pi和*pf雖然都是指針運算,但*pi是將pi的值(2000)開始的兩個內存單元的值(10)作為運算結果,而*pf是將pf的值(2002)開始的四個內存單元的值作為運算結果。這就是指針類型不同所帶來的結果,指針運算符會根據它的運算對象的類型進行相應的運算。
(3)*既可用作指針運算符,也可用作乘號運算符。不必擔心編譯器不能分辨。在*附近總會有足夠的信息使編譯器能分辨是指針運算符還是乘號。
(4)用變量名來訪問變量的內容稱為直接訪問;用指針來訪問變量的內容稱為間接訪問,即間接地從指針中找到地址值,再據此地址訪問變量。直接訪問和間接訪問的結果是一樣的,正如本例中的兩條printf輸出的結果是相同的。由于&和*運算符在使用指針時經常使用,二者優先級相同,結合方向為自右至左,下面進行一些說明。假設已定義了整型變量a和整型指針point,并已執行了“point=&a;”語句,則:
(1)&*point的含義,根據&和*運算符的優先級和結合性可知,先進行*point運算,它就是變量a,再執行&運算。因此&*point和&a相同,也就是point本身。
(2)*&point的含義,根據&和*運算符的優先級和結合性可知,先進行&point運算,得到point的地址值,因為point作為一個變量,在內存中分配單元,所以有確定的地址值。再執行*運算,又得到point的值。
(3)*&a的含義,根據&和*運算符的優先級和結合性可知,先進行&a運算,得到a的地址值,它就是指針point的值,再執行*運算,因此*&a和*point相同,也就是a本身。
(4)&*a的含義,根據&和*運算符的優先級和結合性可知,先進行*a運算,此時將整型變量a作為一個指針來運算,將a的值作為一個地址值對待,這是不允許的,所以&*a不允許使用。
(5)(*point)++相當于a++。如果去掉括號,即成為*point++,根據運算符的優先級和結合性,它相當于*(point++),這時先按point的原值進行*運算,得到a的值。然后使point的值改變,之后point就不再指向a了。8.2指針作函數參數前面已經講過,C在函數調用時參數的傳遞是按照單向值傳遞的方式,因而在被調用函數中形參的變化不能改變實參變量的值。
例8.6
函數參數的傳遞。
#include<stdio.h>
voidswap(intx,inty);
main()
{
inta,b;
a=10;b=20;
swap(a,b);
printf("a=%d,b=%d\n",a,b);
}
voidswap(intx,inty)
{
inttemp;
temp=x;
x=y;
y=temp;
}運行結果:
a=10,b=20可以看到,雖然在swap()函數中交換了x和y的值,但main()函數中a和b的值并未交換。其原因可在圖8.5和圖8.6中看出,在swap()函數調用時,a和b的值傳遞給x和y,而在swap()函數調用結束時雖然x和y的值交換了,但是它們所占用的存儲單元也隨swap()函數調用結束而釋放了,a和b的值并未改變。圖8.5swap()函數被調用時的內存分配圖圖8.6swap()函數調用結束時的內存分配圖為了達到交換主調函數中的變量a和b的目的,需要用指針作參數。通過將主調函數中變量的地址傳給被調用函數的指針形參,這樣,在被調用函數中對形參所指向內容進行交換就是對主調函數中的變量a、b交換,從而達到交換變量a、b的值這一目的。
例8.7
指針作函數參數。
#include<stdio.h>
voidswap(int*x,int*y);
main()
{
inta,b,*p1,*p2;
a=10;b=20;p1=&a;p2=&b;
swap(p1,p2);
printf(“a=%d,b=%d\n”,a,b);
/*或printf("%d%d",*p1,*p2)*/
}
voidswap(int*pa,int*pb)
{
inttemp;
temp=*pa;*pa=*pb;*pb=temp;
}運行結果:
a=20,b=10可以看到,上例中主函數中以整型指針p1、p2作為swap()函數的實參,調用前已用賦值語句使p1指向變量a,p2指向變量b。swap()函數的形參pa和pb也是整型指針。從圖8.7中可以看到,在swap()函數被調用時,實參指針p1和p2分別把它們的值傳遞給形參指針pa和pb,可以認為指針p1和pa都指向變量a,而指針p2和pb都指向變量b。所以*pa就是引用a,*pb就是引用b,交換*pa和*pb就是交換a和b。*pa和*pb既是swap()函數的輸入參數又是輸出參數,進入swap()時,*pa和*pb是交換之前的a、b,從swap()返回時,*pa和*pb是交換后的a、b,因為形參、實參本來就指向同一內存單元,相當于形參所指向內容的改變“返回”給了實參所指的內容(如圖8.8所示)。可見,通過將變量的地址傳遞給被調函數,可在被調函數中改變這些變量的值。圖8.7swap()函數被調用時的內存分配圖圖8.8swap()函數調用結束時的內存分配圖程序運行的結果使a和b的值得以交換。是不是用指針作函數參數時,參數的傳遞就不是單向值傳遞了呢?事實上,用指針作函數參數時,參數的傳遞仍然符合由實參到形參的單向值傳遞。從圖8.7可以看到,在函數swap()調用過程中,交換了pa和pb所指向變量的內容(注意,并未交換pa和pb本身的值),也就是交換了變量a和b的值。形參pa,pb和實參p1,p2本身的值均未改變。
swap()的形參pa和pb都是整型指針。于是,調用swap()時,傳給pa和pb的分別是a的地址和b的地址,為了對指針作函數參數進行充分說明,將例8.7進行一些改變,讓我們來分析下面兩個例子。例8.8
指針作函數參數。
#include<stdio.h>
voidswap(int*,int*);
voidmain()
{
inta,b,*p1,*p2;
a=10;b=20;p1=&a;p2=&b;
swap(p1,p2);
printf("a=%d,b=%d\n",a,b);
}
voidswap(int*pa,int*pb)
{
int*temp;
temp=pa;
pa=pb;
pb=temp;
}運行結果:
a=10,b=20例8.8與例8.7的不同之處在于例8.8的swap()函數中交換的是pa和pb本身的值,而并未交換它們所指向的變量a和b的值,所以最后得到的結果是變量a和b的值并未交換。從圖8.9和圖8.10中可以看到,函數swap()調用結束時指針pa和pb的值進行了交換,但這一交換不會影響到變量a和b,同樣也不會影響到指針p1和p2,這完全符合函數參數的單向值傳遞。圖8.9swap()函數被調用時的內存分配圖圖8.10swap()函數調用結束時的內存分配圖
例8.9
指針作函數參數。
#include<stdio.h>
voidswap(int*,int*);
voidmain()
{
inta,b,*p1,*p2;
a=10;b=20;p1=&a;p2=&b;
swap(p1,p2);
printf("a=%d,b=%d\n",a,b);
}
voidswap(int*pa,int*pb)
{
int*temp;*temp=*pa;*pa=*pb;*pb=*temp;
}對于這個例子我們沒有給出運行結果,這是因為這個例子中有不安全的因素。在swap()函數中的指針temp在沒有明確指向的情況下就試圖對它所指向的變量進行了操作,這是指針使用中所不允許的。當然,也許在某些系統中,這個程序可以得到運行結果,即
a=20,b=10但這個程序還是有問題的,必須要經過改進才可以。可以將swap()函數改變成這樣:
voidswap(int*pa,int*pb)
{
int*temp,t;
temp=&t;*temp=*pa;*pa=*pb;*pb=*temp;
}
此時可以得到運行結果:
a=20,b=10其原因與例8.7一樣,這里不再重復。通過以上例題可以總結出:如果想通過函數調用得到n個要改變的值,可以:
(1)在主調函數中設n個變量,用n個指針指向它們。
(2)用指針作實參,將這n個變量的地址傳給所調用的函數的形參(也可直接用n個變量的地址作實參來簡化以上兩步)。
(3)通過形參指針,改變該n個變量的值。
(4)在主調函數中使用這些改變了值的變量。其它具體示例可參考《〈C程序設計〉學習指導(第二版)》中本章的典型例題。不論是用普通變量還是用指針作函數參數,都不違反函數參數的由實參到形參的單向值傳遞,只是用指針作函數參數時,因為指針的值就是地址,所以傳遞的是地址,此時雖然形參的改變仍無法返回給實參,但利用形參指針對其所指向單元內容的操作,就有可能改變主調函數中的變量的值。這樣往往使函數的功能更為完善和強大,C的許多標準函數都采用這種方式,結構化程序設計方法要求函數間以此種方式來傳遞數據,所以這是指針的一個很重要的應用,需要進行大量的實踐去體會與掌握。8.3指針與數組
C語言中數組與指針有著密切的聯系,在編程時完全可以用指針代替下標引用數組的元素,且使數組的引用更為靈活、有效。當一個數組被定義后,程序會按照其類型和長度在內存中為數組分配一塊連續的存儲單元。數組名成為符號常量,其值為數組在內存中所占用單元的首地址,也就是說數組名就代表了數組的首地址。指針就是用來存放地址的變量,當某個指針存放數組中第一個元素的地址時,可以說該指針指向了這個數組,這樣我們可以通過指針運算間接訪問數組中的元素。8.3.1指向一維數組的指針我們已經知道,如下的語句:
inta[10];定義了a是長度為10的一個整型數組,a[i](i=0,1,…,9)是a的第i個元素。為了用指針表示a的元素,需要定義一個與a的元素同類型的指針,例如:
int*pa;并由賦值語句
pa=&a[0];或pa=a;使pa指向a的第0個元素,習慣上稱為使pa指向數組a,如圖8.11所示。也可在定義時賦初值,即
inta[10],*pa=a;或
inta[10],*pa=&a[0];然后,只要移動指針pa,就可訪問數組a的任一元素。圖8.11指向數組元素的指針如果pa指向a[0],則(pa+i)指向a[i];如果pa指向a[i],則pa+1(pa不變)或++pa(pa增1)指向a[i+1]。如果pa指向a[0],則*pa等價于a[0];如果(pa+i)指向a[i],則*(pa+i)等價于a[i]。同理,如果(pa+1)指向a[i+1],則*(pa+1)等價于a[i+1]。概括地說,指向數組的指針加1等效于數組元素的下標加1。類似的,如果pa指向a[i],則*(++pa)等價于a[++i]。實際上,C語言允許這樣的表達方式:pa[i]和*(a+i),它們等價于*(pa+i)和a[i]。由此可見,引用數組元素有兩種等價的形式:通過下標引用和通過指針引用。以數組a為例,假定pa指向a[0],元素的下標引用和指針引用的對應關系如下(寫在同一行上的表達式是對同一元素的等價引用形式):
a[0] *pa *a或*(a+0) a[1] *(pa+1) *(a+1) a[2] *(pa+2) *(a+2) a[9] *(pa+9) *(a+9)元素地址的對應關系如下:
&a[0] pa a或a+0 &a[1] pa+1 a+1 &a[2] pa+2 a+2 &a[9] pa+9 a+9
注意:指針pa是變量,數組名a是常量,因而
pa=a;pa++;是合法的操作,而
a=pa;a++;pa=&a;都是非法的。說明:
(1)如果數組名a的值為2000,也就是數組a在內存中的首地址為2000。若執行了pa=a,則pa的值也為2000。那么,pa+2的值是多少呢?pa+2的值為2000+2×sizeof(int),假設在我們所使用的系統中int型所占的字節為2,則pa+2的值為2004,即元素a[2]的地址。指針加1,不是簡單地將指針的值加1,而是指向下一個數,系統會根據類型自動地計算地址。
(2)當指針指向數組時,可通過數組名和指針兩種方式來訪問數組元素,因為指針是變量而數組名是常量,所以指針的值可以改變,這就需要特別注意指針的當前指向,是指向了數組的哪個元素?還是已經指向了數組所占內存空間以外的地方?如果已經指向了數組所占內存空間以外的地方,則一般會出問題,這是指針使用中常出錯之處,也是指針使用中最危險之處。
(3)*pa++的意義:*和++的優先級相同,且為右結合性,*pa++則相當于*(pa++),它與(*pa)++是不同的,雖然兩個表達式的值是相同的。前者的意思是先取*pa的值,并作為表達式的值,后使pa加1;后者是先取*pa的值,并作為表達式的值,后使*pa加1。注意,兩者自增的對象是不同的。8.3.2數組作函數參數當我們需要將數組的首地址作為函數參數來傳遞時,可采用的方式有兩種。一種是用指向數組的指針作為函數參數;另一種是直接用數組名作為函數參數。指針和數組名都可以作為形參和實參,它們既可以同時作為形參和實參,也可以分別作為形參和實參。這樣可有四種情況:
(1)形參和實參都是指針時,這種情況與8.2節所討論的問題相似,只是此時實參存放的是某個數組的首地址,并把這個地址傳遞給形參。
(2)實參是數組名,形參是指針時,這種情況是同類型的常量實參傳遞給變量形參,此時,對形參指針所指向內容的訪問就是對數組的訪問。
(3)形參和實參都是數組名時,這時有一些特殊性。因為按照數組的定義,數組名應該是存放數組首地址的常量,而此時形參的值卻會在函數調用時得到實參傳遞的值,這豈不是矛盾了嗎?實際上,雖然有實參數組名和形參數組名兩個數組名,卻只有一個數組,也就是在這種情況下,C語言不會給形參數組再開辟一個數組的內存單,而是認為形參數組名是實參數組的別名,也就是說,對形參數組的操作就是對實參數組的操作。
(4)實參是指針,形參是數組名,這與上一問題有些相似。系統認為實參指針是指向某個數組的,此時形參數組與實參所指向的數組是同一數組,且為該數組的別名。
例8.10
數組名作函數參數。程序功能:用選擇法對10個整數排序。
#include<stdio.h>
voidmain()
{
int*p,i,a[10];
p=a;
for(i=0;i<10;i++)
scanf("%d",p++);
p=a;
sort(p,10);
for(p=a,i=0;i<10;i++)
{
printf("%5d",*p);
p++;}printf("\n");
}
voidsort(intx[],intn)
{
inti,j,k,t;
for(i=0;i<n-1;i++)
{k=i;
for(j=i+1;j<n;j++)
if(x[j]>x[k])k=j;
if(k!=i)
{t=x[i];x[i]=x[k];x[k]=t;}
}
}函數sort()的形參x可以認為是main()函數中數組a的別名,所以在函數sort()中對x的操作就是對a的操作,使得數組a得以排序,完成了程序的要求。注意在main()函數中使用指針p時,指針當前指向的變化。當然,main()函數中調用sort()函數時實參可以是指針,也可以是數組名,在函數sort()中形參可以是數組名,也可以是指針。可將上例的程序改寫如下:
#include<stdio.h>
voidsort(int*,int);
voidmain()
{
inti,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
sort(a,10);
for(i=0;i<10;i++)
printf("%5d",a[i]);
}
voidsort(int*x,intn)
{
inti,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
if(x[j]>x[k])k=j;
if(k!=i)
{t=x[i];x[i]=x[k];x[k]=t;
}
}可以看到在函數sort()中,除了對形參x的類型說明有一點變化,程序的其它部分沒有改變。雖然x此時是指針,用它訪問數組元素時可以用下標法,也可用指針法。如將x[j]寫成*(x+j),x[k]寫成*(x+k),x[i]寫成*(x+i)。另外,此例值得注意之處是對“自頂向下,逐步細化,模塊化”的結構化程序設計思想的典型體現。在第七章的開始,我們已講述了結構化的程序設計思想,強調一個函數應只完成單一的任務。對于本題用選擇法排序便是這樣一個功能單一的最小任務。而其它功能,如數組的輸入/輸出等則在其主調函數中完成,這是高級語言編寫結構化程序的常見模式。8.3.3指針和字符串在C語言中,字符串(例如"Iamastudent")指在內存中存放的一串以′\0′結尾的若干個字符。在沒有學習指針之前,我們已經知道可以用字符數組來表達一個字符串。例如,可以這樣定義和初始化一個字符數組:
charstring[]="Iamastudent";數組長度由字符串長度加1確定。也可以定義一個字符數組,然后用標準輸入函數從外部設備讀入一個字符串。例如:
charstring[20];
scanf("%s",string);數組長度應能足夠存放讀入的最大長度的字符串。利用指針也可以表達字符串,而且比用字符數組更為方便靈活。例如,可以這樣定義和初始化一個字符指針:
char*point="Iamastudent";point是指向字符串"Iamastudent"的指針,即字符串的首地址賦給了字符指針,因此使一個字符指針指向一個字符串。也可以采用下面的方式:
char*point;
point="Iamastudent";賦值語句“point="Iamastudent";”不是串拷貝,實際上,字符串常量"Iamastudent"的值就是該字符串在內存中的首地址,這樣這個賦值語句就很容易理解了,相當于指針point中存放了字符串的首地址,從這一點也可以看出字符串與指針的關系更為密切。下面這種情況同樣是不允許的:
char*point;
scanf("%s",point);其原因是指針沒有明確的指向,其值是任意的,也許所指向的區域不是用戶可以訪問的內存區域,或是根本不存在的地方。雖然字符數組和字符指針都可以用來表達字符串,但它們還是有不同之處。例如:
charstring[]="Iamastudent";
char*point="Iamastudent";string和point的值都是字符串"Iamastudent"的首地址,但string是一個字符數組,名字本身是一個地址常量,而point是值為字符數組首地址(第0個元素的地址)的指針。因而point可以被賦值,而string不能,即
point="Iamastudent";合法
string=“Iamastudent”;非法指向字符串的指針常常出現在函數參數中,下面舉幾個例子。
例8.11
字符串拷貝函數。
voidmy_strcpy(char*t,char*s)
{
while((*t=*s)!=′\0′)
{
s++;
t++;
}
}函數my_strcpy()將串s復制到串t,準確地講是將s指向的字符串復制到由t指向的字符數組。開始時,t和s分別指向兩個實參數組的頭元素,復制一個元素;然后各自的指針移向下一個元素,復制下一個元素;……這一過程進行到字符′\0′被復制為止,此時串s被全部復制到t。調用my_strcpy()時,對應于目的串t的實參可以是字符數組名或指向字符數組的指針;對應于源串s的實參可以是字符串、字符數組名或指向字符串的指針。例如:
chars1[20],s2[20],*ps1,*ps2;下面對my_strcpy()函數的調用都是正確的:
(1)my_strcpy(s1,"Iamastudent");
(2)ps1=&s1[0];
ps2="Iamastudent";
my_strcpy(ps1,ps2);
(3)ps2=&s2[0];
my_strcpy(ps2,"Iamastudent");
my_strcpy(s1,s2);或my_strcpy(s1,ps2);以上三組語句是等效的,都是將串"Iamastudent"復制到字符數組s1。
my_strcpy的定義可以寫成更簡練的形式:
voidmy_strcpy(char*t,char*s)
{
while((*t++=*s++)!=′\0′);
}復制過程繼續的條件是被復制的字符為非0(非′\0′)。由于組成字符串的任何字符(′\0′除外)的值都為非0,因此my_strcpy()還可以進一步簡化為:
voidmy_strcpy(char*t,char*s)
{
while(*t++=*s++);
}例8.12
字符串比較函數。
intmy_strcmp(char*s,char*t){
for(;*s==*t;s++,t++)
if(*s==′\0′)return0;
return(*s-*t);
}函數my_strcmp()按字典順序逐個比較串s和t的每個對應元素,如果s和t的長度相同且所有元素都相等,則s和t相等,my_strcmp()返回值0;否則s和t不相等。當第一次出現不相等元素時,my_strcmp()結束比較,較小的那個字符所在的那個串較小,反之為大。當s<t時,返回值小于0;當s>t時,返回值大于0。8.3.4指向多維數組的指針在研究多維數組的問題時,我們可以將數組僅僅看作是C語言的一個構造類型,其元素可以是C語言的任何類型,包括數組本身。也就是說,數組可以作為另一個數組的數組元素。這樣,就不存在多維數組的問題了。可以說在C語言中,數組在實現方法上只有一維的概念,多維數組被看成以下一級數組為元素的數組。設有一個二維數組的定義為:
staticinta[2][4]={{1,3,5,7},{2,4,6,8}};表面上看,a是一個二維數組名,我們也可以將它看成是一個一維數組名。它包含兩個數組元素,分別為a[0]和a[1]。每個數組元素又包含四個元素,例如,數組a[0]包含四個元素,分別為:a[0][0]、a[0][1]、a[0][2]和a[0][3]。a[0]和a[1]雖然沒有單獨地、顯式地定義,它們卻可以被認為是數組名,是數組在內存中的首地址,這一點與數組名a一樣,與a不同的是類型,也就是數組元素的類型不同。a[0]和a[1]數組的元素類型為整型數,而a數組的元素類型為整型數組。我們可以假設數組a在內存中的分配情況如下:可以看到對于數組a來說,它所占用的內存空間是連續的。如果我們將a視為一維數組的話,那么它的兩個數組元素a[0]和a[1]所占用的內存也是連續的,此時每個數組元素占用8個內存單元。當然如果將a視為二維數組的話,它的8個數組元素所占用的內存也是連續的,此時每個數組元素占用2個內存單元。數組a一旦有了以上的定義后,在C語言的程序中可用的與數組a有關的表示形式有很多。為了更清楚地說明,我們可以把二維數組a看成是一個兩行四列的形式。這樣對于二維數組可以認為a為首行地址(即第0行地址),而a+1為第1行地址;a[0]為首行首列地址,而a[0]+1為第0行第1列地址。詳細區分說明如下:
對行地址進行一次指針運算就成為列地址,而對列地址進行一次取地址運算就成為行地址,這就很容易理解雖然a+1和*(a+1)具有相同的值,但卻表示不同的類型。
例8.13
多維數組。
#include<stdio.h>
voidmain()
{
staticinta[3][4]={{1,3,5,7},{2,4,6,8},{10,20,30,40}};
int*p;
for(p=a[0];p<a[0]+12;p++)/*注1*/
{
if((p-a[0])%4==0)printf("\n");/*注2*/
printf("%4d",*p);
}
}運行結果:
1357
2468
10203040程序中指針p為一個可以存放整型量地址的變量,在程序的第5行將第0行第0列地址賦給它,實際上行地址也是某個整型量的地址,所以可以這樣做。如果將注1行的a[0]改成a,是否可以呢?不行!雖然a和a[0]的值相同,但類型卻不同,這樣做是不合法的。那么是否可以將注2行的a[0]用a替換呢?也不行!因為指針的加減運算結果是受到類型影響的,不同類型之間的運算是無法進行的。既然我們用整型指針來存放列地址,那么如何定義行指針來存放行地址呢?下面就是行指針的定義方式。類型名(*指針名)[數組長度];約束行指針類型的條件有兩個,一是它所指向數組的類型;一是每行的列數。下面用行指針來改寫例8.13。
例8.14
多維數組。
#include<stdio.h>
voidmain()
{
staticinta[3][4]={{1,3,5,7},{2,4,6,8},{10,20,30,40}};
inti,j,(*p)[4];
p=a;
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
printf("%4d",*(*(p+i)+j));
printf("\n");
}
}運行結果:
1357
2468
10203040
注意程序中的表達式*(*(p+i)+j)還可以表示成p[i][j]和(*(p+i))[j]。使用多維數組時一定要注意類型問題,下面以二維數組為例來說明使用多維數組作函數參數時應注意的問題。
(1)形參說明為指向數組元素的指針,實參為數組元素的地址或指向元素的指針。例如:調用函數f(),用數組元素的地址作實參:
inta[2][3];
voidf(int*,int);
f(a[0],2*3);
a[0]是元素a[0][0]的地址,f(a[0],2*3)調用也可以寫成f(&a[0][0],2*3)。2*3是元素的個數。調用函數f(),用指向數組元素的指針作實參:
inta[2][3],*pi;
voidf(int*,int);
pi=a[0];/*或pi=&a[0][0]*/
f(pi,2*3);
pi是指向元素a[0][0]的指針。函數f()的定義:
voidf(int*pi,intsize)
{
}形參pi說明為列指針。
(2)形參說明為行指針,實參為行地址或行指針。例如,調用函數f(),用行指針作實參:
inta[2][3];
voidf(int(*)[3],int);
f(a,2);
實參a是行指針,類型為int(*)[3];實參2是二維數組a的行數。調用函數f(),用行指針作實參:
inta[2][3],(*pa)[3];
voidf(int(*)[3],int);
pa=a;
f(pa,2);
pa是行指針,賦值語句“pa=a;”使pa指向a的第0行,pa的類型與a的類型相同。函數f()的定義:
voidf(int(*pa)[3],intsize)
{
}例8.15
行指針作函數參數。輸入一個用年、月、日表示的日期,定義函數day_of_year()將它轉換成該年的第幾天;輸入某年的第幾天,定義函數month_day()將它轉換成該年的某月某日。
#include<stdio.h>/*day_of_year:從月份和日期計算為一年中的第幾天*/
intday_of_year(intyear,intmonth,intday,int*pi)
{
inti,leap;
leap=year%4==0&&year%100!=0||year%400==0;
for(i=1;i<month;i++)
day+=*(pi+leap*13+i);
return(day);
}/*month_day:從一年中的第幾天計算月份和日期*/
voidmonth_day(intyear,intyday,int(*pdaytab)[13],int*pmonth,int*pday)
{
inti,leap;
leap=year%4==0&&year%100!=0||year%400==0;
for(i=1;yday>*(*(pdaytab+leap)+i);i++)
yday-=*(*(pdaytab+leap)+i);*pmonth=i;*pday=yday;
}
intmain(void)
{
intdaytab[2][13]={
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}
},y,m,d,yd;
printf("inputyear,month,day:\n");
scanf("%d%d%d",&y,&m,&d);
yd=day_of_year(y,m,d,&daytab[0][0]);
printf("dayofyearis%d\n",yd);
printf("inputyear,day_of_year:\n");
scanf("%d%d",&y,&yd);
month_day(y,yd,daytab,&m,&d);
printf("%d,%din%d\n",m,d,y);
return0;
}運行結果:
inputyear,month,day: (輸出)
19951211↙ (輸入)
dayofyearis345 (輸出)
inputyear,day_of_year: (輸出)
1994280↙ (輸入)
10,7in1994 (輸出)
day_of_year()函數的返回值為轉換后的該年第幾天;形參year、month和day是輸入的年、月、日信息;pi是指向一個整型變量的指針,調用時傳給pi的是數組daytab的首地址。局部變量leap根據年號year是平年還是閏年分別置為0和1,leap起著daytab數組行下標的作用:如果year是平年,leap為0,計算時使用第0行元素;否則leap為1,使用第1行元素。*(pi+leap*13+i)根據leap的值引用第0行的第i個元素或第1行的第i個元素,其中leap*13是當year為閏年時指針越過第0行元素。day的初值是調用時傳給它的某月的天數,循環共執行(month-l)次,使day的累加和為年號year的第day天。因而day就是函數day_of_year()的返回值。函數month_day()無返回值。形參year和day是輸入的年號和該年第幾天的信息。
pdaytab是指向含有13個整型元素的數組的指針,調用時傳給它的是daytab數組第0行的首地址,即*pdaytab或*(pdaytab+0)是指向第0行的指針,當year為閏年時(leap為1),*(pdaytab+leap)越過第0行,指向第1行的第0個元素;形參pmonth和pday是指向整型變量的指針,調用時傳給pmonth和pday的分別是變量m和d的地址,返回時變量m和d分別為轉換結果月和日。8.3.5指針數組指針變量可以同其它變量一樣作為數組的元素,由指針變量組成的數組稱為指針數組,組成數組的每個元素都是相同類型的指針。指針數組說明的形式為:
類型名*數組名[常量表達式]:其中“*數組名[常量表達式]”是指針數組說明符。例如:
int*ps[10];說明:ps是含有10個元素的指針數組,每個元素是一個指向int型變量的指針。注意:int*ps[10]不同于int(*ps)[10],后者說明ps是一個指向有10個int型元素的數組的指針。因為[]的優先級高于*,所以int*ps[10]的解釋過程為:ps是一個數組,它含有10個元素;每個元素是一個int指針。指針數組可以與其它同類型對象在一個說明語句中說明。例如:
charc,*pc,*name[5];
floatx,*px[5];c是一個字符變量;pc是一個字符指針;name是含有5個元素的指針數組,每個元素是一個字符指針。x是一個float變量;px是含有5個元素的指針數組,每個元素是一個float指針。指針數組的主要用途是表示二維數組,尤其是表示字符串的數組。用指針數組表示二維數組的優點是:每一個字符串可以具有不同的長度。用指針數組表示字符串數組處理起來十分方便靈活。設有二維數組說明:
inta[4][4];用指針數組表示數組a,就是把a看成4個一維數組,并說明一個有4個元素的指針數組pa,用于集中存放a的每一行元素的首地址,且使指針數組的每個元素pa[i]指向a的相應行。于是可以用指針數組名pa或指針數組元素pa[i]引用數組a的元素。指針數組pa的說明和賦值如下:
int*pa[4],a[4][4];
pa[0]=&a[0][0];或pa[0]=a[0];
pa[1]=&a[1][0];或pa[1]=a[1];
pa[2]=&a[2][0];或pa[2]=a[2];
pa[3]=&a[3][0];或pa[3]=a[3];*(*(pa+i)+0),*pa[i]或*(pa[i]+0)(i=0,1,2,3)引用第i行第0列元素a[i][0];*(*(pa+i)+1)或*(pa[i]+1)引用第i行第1列元素a[i][1];…。用指針數組表示二維數組在效果上與數組的下標表示是相同的,只是表示形式不同。用指針數組表示時,需要額外增加用作指針的存儲開銷;但用指針方式存取數組元素比用下標速度快,而且每個指針所指向的數組元素的個數可以不相同。例如,可用有5個元素的指針數組和5個不同長度的整型數組來描述下面的三角矩陣:
a00
a10
a11
a20
a21
a22
a30
a31
a32
a33
a40
a41
a42
a43
a44存儲三角矩陣的數組和每一行的指針可說明如下:
inta1[1],a2[2],a3[3],a4[4],a5[5],*pa[5];下面的語句使pa的每個元素指向三角矩陣的每一行:
pa[1]=&a1[0];
pa[2]=&a2[0];
pa[3]=&a3[0];
pa[4]=&a4[0];
pa[5]=&a5[0];對照前面所講二維數組的指針表示可以看出,用指針數組表示二維數組其實質就是用指針表示二維數組,只是用指針數組更直觀、更方便而已(主要體現在處理字符串數組上)。用指針數組表示二維數組的方法可以推廣到三維以上數組。例如,對三維數組來說,指針數組元素的個數應與左邊第一維的長度相同,指針數組的每個元素指向的是一個二維數組,而且每個二維數組的大小也可以不同。指針數組不經常用于描述整型、浮點型等多維數組,用得最多的是描述由不同長度的字符串組成的數組。8.4指針與函數指針除了可以作為函數參數外,它與函數本身還有兩方面的關系。一方面,對于指針來說,它可以是指向函數的,也就是說,指針存放的是函數的入口地址。另一方面,對于函數來說,它的返回值可以是一個地址,我們可以將該函數的返回值定義為某種類型的指針。8.4.1指向函數的指針對于函數和數組來說,可以通過函數名和數組名來訪問它們,也可以通過指向它們的指針來訪問,這一點是類似的。
C語言可以定義指向函數的指針,函數型指針的定義形式為:類型標識符(*指針名)();例如:
int(*fp)();說明:fp是指向int類型函數的指針。與指向數組的指針說明類似,說明符中用于改變運算順序的()不能省。如果將(*fp)()寫成*fp(),則fp成為返回值為指針類型的函數。指向函數的指針是存放函數入口地址的變量,一個函數的入口地址由函數名表示,它是函數體內第一個可執行語句的代碼在內存中的地址。如果把函數名賦給一個指向函數的指針,就可以用該函數型指針來調用函數。函數型指針可以被賦值,可以作為數組的元素,可以傳給函數,也可以作為函數的返回值。其中常用的是將函數名傳給另一函數。C語言允許將函數的名字作為函數參數傳給另一函數,由于參數傳遞是單向值傳遞,相當于將函數名賦給形參,因此在被調用函數中,接收函數名的形參是指向函數的指針。被調用函數可以通過函數的指針來調用完成不同功能的具體函數。下面的簡單例子說明函數指針的用法。
例8.16
指向函數的指針。設一個函數operate(),在調用它的時候,每次實現不同的功能。輸入a和b兩個數,第一次調用operate()得到a和b中最大值,第二次得到最小值,第三次得到a與b之和。
#include<stdio.h>
voidmain()
{
voidoperate();
intmax(),min(),sum(),a,b;
/*必須進行函數聲明,否則無法調用*/
printf("Entertwonumber:");
scanf("%d%d",&a,&b);
printf("max=");operate(a,b,max);
printf("min=");operate(a,b,min);
printf("sum=");operate(a,b,sum);
}
intmax(intx,inty)
{
if(x>y)return(x);
elsereturn(y);
}
intmin(intx,inty)
{
if(x<y)return(x);
elsereturn(y);
}
intsum(intx,inty)
{
return(x+y);
}
voidoperate(intx,inty,int(*fun)())
{
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 碳素摻雜劑在鐵合金冶煉中的應用考核試卷
- 森林公園生態旅游市場細分與定位考核試卷
- 農業農業機械產業節能減排配合服務批發考核試卷
- 礦物加工廠職業衛生與員工健康考核試卷
- 漁業資源保護與海洋資源長期可持續發展戰略全面實施考核試卷
- 電信行業區塊鏈技術探索與應用考核試卷
- 紅富士蘋果病蟲害防治考核試卷
- 武漢民政職業學院《描述統計學和概率》2023-2024學年第一學期期末試卷
- 石家莊工程職業學院《環境學導論》2023-2024學年第二學期期末試卷
- 山西體育職業學院《高級應用氣象統計》2023-2024學年第二學期期末試卷
- 消化道畸形課件
- 自來水過戶轉讓協議
- 精神科診療常規及技術操作規范
- 2023年湖北高中學業水平合格性考試生物試卷真題(含答案詳解)
- (醫學課件)SOAP的規范書寫及練習
- 【行業研究報告】2023年中國演出市場年度報告
- 向上管理的藝術(升級版):如何正確匯報工作
- 國開2023春計算機組網技術形考任務一參考答案
- 甘肅省蘭州市成考專升本2023年英語真題及答案
- 推薦如果歷史是一群喵讀書分享會模板
- 全過程跟蹤審計和結算審計服務方案技術標投標方案
評論
0/150
提交評論