




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
函
數2.1函數的定義與使用
在編輯一個大型程式時,即使各個函數的前後順序不同,程式執行的開始點永遠是主函數。主函數按照調用與被調用關係調用子函數。子函數如果與其它子函數又存在調用與被調用關係,當然還可以再調用其他子函數。
在一對調用與被調用關係中,我們把調用其他函數的函數稱為主調函數,被其他函數調用的函數稱為被調函數。在一個較為複雜的大型程式中,一個函數很可能同時扮演兩種不同的角色——主調函數與被調函數,即既調用別的函數(被調函數)又被另外的函數(主調函數)調用。函數一般應遵守先定義後調用的原則,否則應在調用函數中先進行原形說明。2.1.1函數的定義一個完整的函數定義由兩部分組成,即函數頭與函數體。1.函數定義的一般語法形式<類型識別字><函數名說明符>(形式參數表){
說明性語句序列;實現函數功能的語句系列;}
函數頭是指上述格式中的<類型識別字><函數名說明符>(形式參數表)。其中函數名可由函數設計者命名,可以是任何一個不重複的合法的識別字(唯一的例外是,主函數必須命名為main)。函數體是指上述格式中被一對大括弧括起的複合語句部分。該函數所應實現的功能由相應的複合語句完成。2.函數的類型和返回值函數頭部分的類型識別字規定了函數的返回值類型。函數的返回值是返回給主調函數的處理結果,由函數體部分的return語句帶回。例如,returnvalue1。無返回值的函數其類型識別字為void,不必有return語句。3.形式參數函數頭部分的形式參數(簡稱形參)表的內容如下:類型l形參名1,類型2形參名2,...,類型n形參名n其中類型1、類型2、...、類型n是類型識別字,表示形參的數據類型(int、double、float、char、bool等);形參名1、形參名2、...、形參名n是形參名(合法的自定義識別字)。形參是用來實現主調函數與被調函數之間的數據聯繫,通常將函數所處理的數據、影響函數功能的因素或者函數處理的結果作為形參。對於無形參的函數,其形參表的內容應該為空,但代表函數的小括弧對不能省略。
函數在沒有被調用的時候其形參只是一個符號,它標誌著在形參出現的位置應該有一個什麼類型的數據。函數在被調用時才由主調函數將實際參數(簡稱實參)賦予形參。從這一點上說,C++中的函數與數學中的函數概念極其相似。例如,我們都熟悉的如下數學中的函數形式:
f(x)=3x2+5x-2
這個函數只有當引數x被賦以確定的值以後,才能計算出函數的值。2.1.2函數的調用如果沒有遵守先定義後調用的原則,調用函數之前先要在主調函數中聲明函數原型。在主調函數中,或所有函數之前,按如下形式進行函數原型聲明:
<類型識別字><被調函數名>(含類型說明的形參表);
如果是在所有函數之前聲明了函數原型,那麼該函數原型在本程式檔中任何地方都有效,也就是說,在本程式檔中任何地方都可以依照該原型調用相應的函數。如果是在某個主調函數內部聲明了被調函數原型,那麼該原型就只能在這個函數內部有效。聲明了函數原型之後,便可以按如下形式調用子函數:
<函數名>(實參1,實參2,…,實參n)
實參列表中應給出與函數原型中形參個數相同、類型相符的實參,每個實參都可以是常量、變數或運算式三者之一。實參與實參之間用逗號作為分隔符號。注意,這裏的逗號不是順序求值運算符。函數調用可以作為一條語句,這時函數可以沒有返回值。函數調用也可以出現在運算式中,這時就必須有一個明確的返回值。函數調用示例如下。【例2-1】編寫一個函數,把華氏溫度轉換為攝氏溫度,公式為C=(F-32)*5/9,公式中F代表華氏溫度,C代表攝氏溫度。在主函數中提示用戶輸入一個華氏溫度,並完成輸入及輸出,由函數完成轉化功能。程式代碼如下:
#include<iostream.h>floathstoss(floatfHuashi);//原型說明
voidmain(){floatfHuashi; cout<<endl<<"輸入一個華氏溫度值:"; cin>>fHuashi; cout<<"華氏"<<fHuashi <<"度對應攝氏溫度"<<hstoss(fHuashi)<<"度"<<endl; //函數調用作為一個運算式出現在輸出語句中}floathstoss(floatfHuashi){ floatfSheshi; fSheshi=(fHuashi-32)*5/9; return(fSheshi);}程式運行結果為輸入一個華氏溫度值:68華氏68度對應攝氏20度【例2-2】編寫一個求x的n次方的函數。分析:求x的n次方,實際是求x自乘n次的乘積。程式代碼如下:#include<iostream.h>doublepower(doubledDishu,intiMi); //原型說明voidmain(){ cout<<"底數1.8的3次冪是"<<power(1.8,3)<<endl; //函數調用作為一個運算式出現在輸出語句中}doublepower(doubledDishu,intiMi){ intiCount; doubledResult=1.0; for(iCount=1;iCount<=iMi;iCount++) dResult=dResult*dDishu; return(dResult);}程式運行結果為底數1.8的3次冪是5.832【例2-3】輸入一個8位的二進位數,將其轉換為十進位數後再輸出。對於非法輸入(除0和1以外的任何字元)應給出提示資訊。分析:將二進位數轉換為十進位數,只要將二進位數的每一位乘以該位的權,然後相加。例如,110100112=1(27)+1(26)+0(25)+1(24)+0(23)+0(22)+1(21)+l(20)=21110,所以,如果輸入00001101,則應輸出13。可以直接引用例2-2中的函數power來求2的各次方。程式代碼如下:#include<iostream.h>doublepower(doubledDishu,intiMi); //函數原型說明voidmain(){ intiCount=8; intiValue=0; charcChar; boolbFlag=true;cout<<"輸入一個8位的二進位數:"; while(iCount>0) { cin>>cChar; if(cChar!='1'&&cChar!='0') { cout<<"這不是一個二進位數!不能正確轉換"<<endl; bFlag=false; } if(cChar=='1') iValue=iValue+int(power(2,iCount-1));iCount--; } if(bFlag) cout<<"十進位值為:"<<iValue<<endl;}doublepower(doubledDishu,intiMi){ intiCount; doubledResult=1.0; for(iCount=1;iCount<=iMi;iCount++) dResult=dResult*dDishu; return(dResult);}
輸入符合要求時(僅有字元0和1)的程式運行結果為輸入一個8位的二進位數:11010011
十進位值:211
輸入不符合要求時(含有除0和1以外的任何字元)的程式運行結果為輸入一個8位的二進位數:110lao11
這不是一個二進位數!不能正確轉換【例2-4】編寫一個函數可用來判斷任給的一個正整數是否為素數(或質數)。再編寫主程序完成輸入、調用和輸出。素數是指只能被1和它自身整除的數。分析:素數的逆定義就是,一但某數n能被2、3、...、n-1中的任何一個數除盡(只要除法中有一次餘數為零),則n肯定不是一個素數;某數n若依次除以2、3、...、n-1,結果都除不盡(有餘數),則n肯定是一個素數。程式代碼如下:#include<iostream.h>intiIsprime(intiNum);voidmain(){ intiNum; cout<<"請輸入一個正整數:"; cin>>iNum; if(iIsprime(iNum)==1) cout<<iNum<<"是一個素數."<<endl;else cout<<iNum<<"不是一個素數."<<endl;}intiIsprime(intiNum){ intiChushu; boolbFlag=false; for(iChushu=2;iChushu<=iNum-1;iChushu++) { if(iNum%iChushu==0) { bFlag=true; break; } } if(bFlag==false) return1; else return0;}第一次程式運行結果為請輸入一個正整數:3131是一個素數第二次程式運行結果為請輸入一個正整數:119119不是一個素數2.1.3函數的參數傳遞函數的參數用於在調用函數與被調用函數之間進行數據傳遞。在函數定義時,函數名後面括弧內的參數稱為形式參數(簡稱形參)。在函數被調用時,函數名後面括弧內的參數稱為實際參數(簡稱實參)。
當函數未被調用時,C++編譯系統並沒有給函數的形參分配相應的記憶體空間,函數的形參更不會有實際的值。只有在函數被調用時,C++編譯系統這時才為形參分配實際的存儲單元,並將實參與形參結合。實參可以是常量、變數或運算式,其類型必須與形參相符。函數的參數傳遞,指的就是形參與實參結合(簡稱形實結合)的過程。形實結合的方式有值調用和引用調用兩種。1.值調用值調用是指當發生函數調用時,編譯系統為形參分配相應的存儲空間並且直接將實參的值複製給形參,這樣形參和實參就各自擁有不同的存儲單元,且形參是實參的副本。因此,值調用過程是參數值的單向傳遞過程,一旦形參獲得了與實參相同的值就與實參脫離關係,以後不論形參發生多大的改變,都決不會反過來影響到實參。前面2.1.2節中的四道例題均屬於值調用方式。【例2-5】從鍵盤輸入兩個整數,交換位置後輸出(交換未成功)。#include<iostream.h>voidswap(inta,intb);voidmain(){ intx,y; x=5; y=10; cout<<"x="<<x<<"y="<<y<<endl; swap(x,y);//交換x,y的值cout<<"afterswap"<<endl; cout<<"x="<<x<<"y="<<y<<endl;}voidswap(inta,intb){ intt; t=a; a=b; b=t;}
程式運行結果為
x=5y=10afterswapx=5y=10
分析:從上面的程式運行結果可以看出,並沒有達到交換的目的。這是因為採用的傳遞方式不合乎問題的要求。在單向值傳遞方式中,形參值雖確實進行了交換,但這些改變對實參不起任何作用。
執行主調函數中的函數調用語句swap(x、y)後,編譯系統將實參x中的值5傳遞給虛參a,將實參y中的值10傳遞給虛參b;在swap函數中,a、b中的值完成互換;返回主函數時,實參x、y中的值不受虛參a、b的影響,並未進行交換。2.引用調用顯而易見,值調用時參數的傳遞方式是實參單向複製其值給虛參,如果我們想使子函數中對形參所做的任何更改也能及時反映給主函數中的實參(即希望形參與實參的影響是互相的或稱是雙向的),又該怎麼辦呢?這就需要改變調用方式,即採用第二種參數傳遞方式——引用調用。引用是一種特殊類型的變數,可以被認為是某一個變數的別名。通過引用名與通過被引用的變數名訪問變數的效果是一樣的。這就是說,對形參的任何操作也就直接作用於實參。
例如:
inta,b;int&ra=a; //建立一個int型的引用ra,並將其初始化為變數a的一個別名
b=10;ri=b; //相當於a=b;
注意:①聲明一個引用時,必須同時對它進行初始化,使它與一個已存在的對象關聯。②一旦一個引用被初始化後,就不能改變關聯對象。換言之,一個引用從它被聲明之後,就必須確定是哪個變數的別名,而且自始至終只能作為這一個變數的別名,不能另作他用。形參也可以引用的方式出現在形參表中。引用作為形參的情況與變數的引用稍有不同。這是因為,形參的初始化不在類型說明時進行,而是在執行主調函數中的調用語句時,才為形參分配記憶體空間,同時用實參來初始化形參。【例2-6】使用引用調用改寫例2-5的程式,使兩實參中的數真正進行互換。#include<iostream.h>voidswap(int&a,int&b);voidmain(){ intx,y; x=5; y=10; cout<<"x="<<x<<"y="<<y<<endl; swap(x,y);//交換x,y的值
cout<<"afterswap"<<endl; cout<<"x="<<x<<"y="<<y<<endl;}voidswap(int&a,int&b){ intt; t=a; a=b; b=t;}程式運行結果為x=5y=10afterswapx=10y=5
分析:子函數swap的兩個參數都是引用,當被調用時,它們分別被初始化成為a和b的別名。因此,在子函數swap中將兩個參數的值進行交換後,交換結果可以返回主函數main。2.2函數調用機制
一個C++的根源程式經過編譯以後形成與根源程式主名相同但尾碼為.exe的可執行檔,且存放在外存儲器中。當該.exe的可執行程式被運行時,首先從外存將程式代碼裝載到記憶體的代碼區,然後從main函數的起始處開始執行。程式在執行過程中,如果遇到了對其他函數的調用,則暫停當前函數的執行,
保存下一條指令的地址(即返回地址,作為從子函數返回後繼續執行的入口點),並保存現場(主要是一些寄存器的內容),然後轉到子函數的入口地址,執行子函數。當遇到return語句或者子函數結束時,則恢復先前保存的現場,並從先前保存的返回地址開始繼續執行。圖2-1說明了函數調用和返回的過程,圖中標號標明了執行順序。圖2-1函數調用和返回的示意圖【例2-7】求
設N=10,X=2、4、6、8,即求N事件中每次取2、4、6、8的組合數。分析:這個問題需要反復利用兩個公式:①N!②N!/X!/(N-X)!
設計兩個函數:一個求整數階乘的函數lJiecheng和一個求組合數的函數lComb。由主函數main調用lComb,lComb又調用lJiecheng。程式代碼如下:#include<iostream.h>longlJiecheng(intn){ longrt=1; inti; for(i=1;i<=n;i++) rt=rt*i; returnrt;}longlComb(intN,intX){ returnlJiecheng(N)/lJiecheng(X)/lJiecheng(N-X);}voidmain(){ longlJiecheng(intn); longlComb(intN,intX); intiNum,x; do {cout<<"請輸入事件數(大於等於8):"; cin>>iNum; }while(iNum<10); for(x=2;x<10;x+=2) cout<<"C("<<iNum<<","<<x<<")="<<lComb(iNum,x)<<endl;}程式運行結果為請輸入事件數(大於等於8):11C(11,2)=55C(11,4)=330C(11,6)=462C(11,8)=1652.3遞歸函數
遞歸函數又稱為自調用函數,其特點是,在函數內部直接或間接地自己調用自己。所謂直接調用自身,就是指在一個函數的函數體中出現了對自身的調用語句。例如:
voidfunc1(void){...func1(); //func1調用func1自身
...}
所謂間接調用自身,就是一個函數func1調用另一個函數func2,而函數func2中又調用了函數func1,於是構成間接遞歸。下麵的例子屬於間接調用情況。
voidfunc1(void){...func2(); //func1調用func2...}voidfunc2(void){...func1(); //func2調用func1...}func1函數就是通過func2實現間接遞歸。遞歸演算法的實質是將原有的問題分解為新的問題,而解決新問題時又用到了原有問題的解法。按照這一原則分解下去,每次出現的新問題都是原有問題的簡化的子集,而最終分解出來的問題,是一個已知解的問題。這便是有限的遞歸調用。【例2-8】編寫函數,用遞歸的方法求n!的值。在主程序中實現任意輸入n值並輸出計算結果。分析:計算n!的公式如下。
1(n=0)n!=n(n-1)!(n>0)
這是一個遞歸形式的公式,在描述階乘演算法時,又用到了階乘,只不過求階乘的數在逐次減1,因而編程時也自然採用遞歸演算法。遞歸的結束條件是n=0。程式代碼如下:#include<iostream.h>longjc(intn){ longrt; if(n<0) cout<<"Dataerror!"<<endl; elseif(n==0)rt=1; //遞歸的結束條件
elsert=n*jc(n-1); //以參數減1的方式繼續遞歸
returnrt;}voidmain(){ longjc(intn); intn; longresult; do { cout<<"輸入一個正整數:"; cin>>n; }while(n<=0);result=jc(n); //首次調用處
cout<<n<<"!="<<result<<endl;}
程式運行結果為輸入一個正整數:66!=720【例2-9】有5個人坐在一起,問第1個人多少歲,他說比第2個人大2歲。問第2個人多少歲,他說比第3個人大2歲。問第3個人多少歲,他說比第4個人大2歲。問第4個人多少歲,他說比第5個人大2歲。最後問第5個人,他說是12歲。請問第1個人多少歲?分析:這是一個遞歸問題。每一個人的年齡都比其後那個人的年齡大2,即age(1)=age(2)+2age(2)=age(3)+2age(3)=age(4)+2age(4)=age(5)+2age(5)=12可以用公式表示如下:12(n=5)age(n)=age(n+1)+2(n<5)程式代碼如下:#include<iostream.h>intage(intn){ intss; if(n==5)ss=12; elsess=age(n+1)+2; return(ss);}voidmain(){ intage(intn); cout<<"第一個人的年齡為"<<age(1)<<"歲"<<endl;}程式運行結果為第一個人的年齡為20歲2.4默認參數的函數
在函數定義中通過賦值運算就可指定默認參數值。一旦程式在調用該函數時,如果給出實參,則用實參初始化形參;如果沒有給出實參,則C++編譯系統自動以預先賦值的默認參數值作為傳入數值。一般情況下都將調用該函數時經常用到的常數作為默認參數值,這樣在調用時就無需每次都寫出該值了。指定默認參數值可以使函數的使用更為簡單,同時也增強了函數的可重用性。【例2-10】帶默認形參值的函數例題。#include<iostream.h>intmult(intn,intk=2) //第二個形參具有默認值{ if(k==2) return(n*n); else return(mult(n,k-1)*n);}voidmain(){ cout<<endl<<mult(5)<<endl; /*形參n用實參來初始化為5,形參k採用默認值2,實現5*5*/ cout<<mult(5,3)<<endl; /*用實參來初始化形參,n為5,k為3,實現5*5*5*/}程式運行結果為25125
默認形參值必須按從右向左的順序定義。在有默認值的形參右面,不能出現無默認值的形參。因為在調用時,實參初始化形參是按從左向右的順序。例如:voidtry(intj=3,intk) //非法voidtry(intj,intk=2,intm) //非法voidtry(intj,intk=7) //合法voidtry(intj,intk=2,intm=3) //合法voidtry(intj=3,intk=2,intm=3) //合法默認形參值應該在函數原型中給出。例如:intmulti(intx=2,inty=5); //默認形參值在函數原型中給出voidmain(){multi(); //並非無參調用,而是採用默認值,x取值2,y取值5}intmulti(intx,inty){return(x*y);}
在相同的作用域內,默認形參值的說明應保持唯一。但如果在不同的作用域內,允許說明不同的默認形參。這裏的作用域是指直接包含著函數原型說明的大括弧所界定的範圍。對作用域概念的詳細介紹請參閱第5章。例如:
intadd(intx=2,inty=5); //全局默認形參值
voidmain(){intadd(intx=1,inty=9); //局部默認形參值add();//此處調用時,採用局部默認形參值,x取值1,y取值9}voidfunc(void){add()//此處調用時,採用全局默認形參值,x取值2,y取值5}2.5內聯函數
內聯函數(也稱線上函數)是在C++中為提高程式運行效率而引入的。所有函數調用時都會產生一些額外的開銷,主要是系統棧的保護、代碼的傳遞、系統棧的恢復以及參數傳遞等。對於一些函數體很小但又經常使用的函數,由於被調用的頻率非常高,這種額外開銷也就很可觀,有時甚至會對運行效率產生本質的影響。
使用內聯函數正是解決這一問題的手段。內聯函數不是在調用時發生轉移,而是在編譯時將函數體嵌入在每一個調用語句處。這樣就相對節省了參數傳遞、系統棧的保護與恢復等的開銷。內聯函數在定義時使用關鍵字inline區別於一般函數,其語法形式如下:
<inline><類型識別字><被調函數名>(含類型說明的形參表){
函數體
}
例如:
inlineintmul(inta,intb){returna*b;
}
當程式中出現mul(2+3,4)的函數調用時,編譯程序就會將其擴展為(2+3)*4。關鍵字inline是一個編譯命令,編譯程序在遇到這個命令時將記錄下來,在處理內聯函數的調用時,編譯程序就試圖產生擴展碼。這樣從使用者的角度來看,內聯函數在語法上與一般函數沒有什麼區別,只是在編譯程式生成目標代碼時才區別處理。
注意:①內聯函數體內一般不能有迴圈語句和switch語句。②內聯函數的定義必須出現在第一次被調用之前。③對內聯函數不能進行異常介面聲明。如果違背了上述注意事項中的任一項,編譯程序就會無視關鍵字inline的存在,像處理一般函數一樣處理,不生成擴展代碼。因此,只有很簡單而使用頻率很高的函數才被說明為內聯函數。內聯函數會擴大目標代碼,使用時要謹慎。【例2-11】內聯函數例題。#include<iostream.h>#include<iomanip.h>inlineintmax(inta,intb){ if(a>b) returna; else returnb;}voidmain(){ inta,b,c,d; a=210; b=150; c=20; d=max(a,b); d=max(d,c); //編譯時兩個調用處均被替換為max函數體語句。
cout<<"Thebiggestof" <<setw(5)<<a <<setw(5)<<b<<setw(5)<<c<<"is"<<d<<endl;}程式運行結果為Thebiggestof21015020is2102.6函數重載
函數的重載也稱多態函數。C++編譯系統允許為兩個或兩個以上的函數取相同的函數名,但是形參的個數或者形參的類型不應相同,編譯系統會根據實參和形參的類型及個數的最佳匹配,自動確定調用哪一個函數,這就是所謂的函數重載。
對於沒有重載機制的C語言,每個函數必須有其不同於其他函數的名稱,即使操作是相同的,僅僅數據的類型不相同,也需要定義名稱完全不同的函數,這樣就顯得重複且效率低下。例如,定義求平方函數,就必須對整數的平方、浮點數的平方以及雙精度數的平方分別用不同的函數名:
intisq(intx,inty);floatfsq(floatx,floaty);doubledsq(doublex,doubley);
程式在調用這三個不同類型的函數時,是以名字加以區別的,需要記住並區別它們的名稱。顯然,這樣就造成了代碼的重複,使用起來也不方便,更不利於代碼的維護。對於具有重載機制的C++語言,允許功能相近的函數在相同的作用域內以相同函數名定義,因而使函數方便使用,便於記憶,也使程式設計更加靈活。仍以上例而言,在C++中只要用一個函數名即可,如square(),然後以賦給此函數的參數類型來決定是要計算int型、float型,還是double型的數的平方。上例在C++中的定義形式如下:intsquare(intx);
floatsquare(floatx);
doublesquare(doublex);要計算square(3)時,C++自動使用第一種形式;計算square(3.25)時,C++自動使用第三種形式;計算square(3.25f)時,C++自動使用第二種形式。但是決不可以定義兩個具有相同名稱、相同參數類型和相同參數個數,只是函數返回值不同的重載函數。
例如,以下定義是C++不允許的:
intfunc(intx);
floatfunc(intx);由此可見,C++是按函數的參數表分辨相同名稱的函數。如果參數表相同,則認為是錯誤的說明。
C++允許重載函數有數量不同的參數個數。當函數名相同而參數個數不同時,C++會自動按參數個數定向到正確的要調用的函數。下例說明了C++的這一特性。
【例2-12】重載函數應用例題。#include<iostream.h>intadd(intx,inty){ intsum; sum=x+y; return(sum);}intadd(intx,inty,intz){intsum; sum=x+y+z; return(sum);}voidmain(){ inta,b; a=add(5,10); b=add(5,10,20); cout<<"a="<<a<<"b="<<b<<endl;}程式運行結果為a=15b=352.7函數模板
有很多時候,我們希望所設計的演算法可以處理多種數據類型。但是,即使這一演算法被設計為重載函數,也只是使用相同的函數名,函數體仍然要分別定義。如下面兩個求較小值的函數:
intsmall(intx,inty){returnx<y?x:y;
}doublesmall(doublex,doubley){returnx<y?x:y;
}
考察以上兩個函數,有如下特點:只有參數類型不同,返回值類型不同,功能則完全一樣。類似這樣的情況,可以使用函數範本,從而避免函數體的重複定義。
函數範本可以用來創建一個通用功能的函數,以支持多種不同形參,簡化重載函數的函數體設計。它的最大特點是把函數所使用的數據類型作為參數。函數範本的定義形式如下:
<template><typename識別字>
函數定義
【例2-13】定義一個能交換兩個變數值的函數,要求用範本函數實現。#include<iostream.h>template<typenameT>voidswap(T&x,T&y){ Tz; z=y;y=x;x=z;}voidmain(){ intm=1,n=8; doubleu=-5.5,v=99.3; cout<<"m="<<m<<"n="<<n<<endl; cout<<"u="<<u<<"v="<<v<<endl; swap(m,n); //整型
swap(u,v); //雙精度型
cout<<"m與n,u與v交換以後:"<<endl; cout<<"m="<<m<<"n="<<n<<endl; cout<<"u="<<u<<"v="<<v<<endl;}
程式運行結果為
m=1n=8u=-5.5v=99.3m與n,u與v交換以後:
m=8n=1u=99.3v=-5.5
分析:編譯系統從調用swap()時,根據實參的類型推導出函數範本的類型參數。
於調用swap(m,n),由於實參m及n為int類型,所以,推導出範本中類型參數T為int。當類型參數的含義確定後,編譯器將以函數範本為樣板生成一個函數:
intswap(int&x,int&y){intz;
z=y;y=x;x=z;
}
同樣,對於調用swap(u,v),由於實參u、v為double類型,所以,推導出範本中類型參數T為double。
接著,編譯器將以函數範本為樣板,生成如下函數:
doubleswap(double&x,double&y){doublez;
z=y;y=x;x=z;
}
因此,當主函數第一次調用swap()時,執行的實際上是由函數範本生成的函數:intswap(int&x,int&y)當主函數第二次調用swap()時,執行的實際上是由函數範本生成的函數:doubleswap(double&x,double&y)2.8使用C++系統函數C++不僅允許我們根據需要自定義函數,而且C++的系統庫中還提供了幾百個常用函數可供程式員使用。如求平方根函數(sqrt)、求浮點數或雙精度數的絕對值函數(fabs)、對數函數(log)、指數函數(exp)、三角函數等都屬於數學函數。輸入/輸出格式控制函數有setw()、setprecision()等。
由前面已學習的知識可知,調用函數之前必須先聲明函數原型。系統函數的原型聲明已經全部由系統提供了,並且已分類存在於不同的頭檔中。程式員需要做的事情,就是用include指令嵌入相應的頭檔,然後便可以使用系統函數了。例如,要使用基本輸入/輸出流函數cin()/cout()函數,就必須嵌入頭檔iostream.h。
若要使用數學函數,如求絕對值函數abs()、fabs(),三角函數sin()、cos()、tan(),開平方函數sqrt(),對數值函數log(),以e為底的指數函數exp(),就要嵌入頭檔math.h。同樣,要使用輸入/輸出格式控制函數setw()/setprecision(),就要嵌入頭檔iomanip.h。【例2-14】系統函數應用例題。從鍵盤輸入一個角度值,求出該角度的正弦值、余弦值和正切值。
分析:系統函數中提供了求正弦值、余弦值和正切值的函數——sin()、cos()及tan()函數的說明在頭檔math.h中。同樣,系統函數中也提供了輸入/輸出格式控制函數,如輸出域寬控制函數setw()、輸出精度控制函數setprecision(),函數的說明在頭檔iomanip.h中。因此,需要用到這些系統函數時,就必須將該函數所屬的頭檔以#include<頭檔案名>或#include“頭檔案名”的形式寫在程式代碼開始部分。程式代碼如下:#include<iostream.h>#include<math.h>#include<iomanip.h>constdoublepi=3.14159265;voidmain(){ doublea,b; cin>>a; b=a*pi/180;cout<<"sin("<<a<<")="<<setw(10)<<sin(b)<<endl; cout<<"cos("<<a<<")="<<setw(10)<<cos(b)<<endl; cout<<"tan("<<a<<")="<<setw(10)<<tan(b)<<endl;}程式運行結果為輸入:30sin(30)=0.5cos(30)=0.866025tan(30)=0.57735
充分利用系統函數,可以大大減少編程的工作量,提高程式的運行效率和可靠性。要使用系統函數,應該注意以下兩點:①瞭解所使用的C++開發環境提供了哪些系統函數。不同的編譯系統提供的系統函數有所不同。②確定要使用的系統函數的聲明在哪個頭檔中。這也可以在庫函數參考手冊或聯機幫助中查到。
例如,在MSDNLibraryVisualStudio6.0中查找VC++6.0系統函數的分類列表:首先在“活動子集”欄選擇VisualC++Documentation,然後按如下路徑選擇:VisualC++Documentation→UsingVisualC++→VisualC++Programmer'sGuide→Run-TimeLibraryReference→Run-TimeRoutinesbyCategory→Run-TimeRoutinesbyCategory,如圖2-2。該幫助系統中將函數按如下分類列出:獲取參數(Argumentaccess)浮點支持(Floating-pointsupport)緩衝區操作(Buffermanipulation)輸入與輸出(Inputandoutput)位元組分類(Byteclassification)國際化(Internationalization)字元分類(Characterclassification)記憶體分配(Memoryallocation)數據轉換(Dataconversion)處理機與環境控制(Processandenvironmentcontrol)調試(Debug)查找與排序(Searchingandsorting)目錄控制(Directorycontrol)字串操作(Stringmanipulation)錯誤處理(Errorhandling)系統調用(Systemcalls)異常處理(Exceptionhandling)時間管理(Timemanagement)檔處理(Filehandling)圖2-2MSDNLibraryVisualStudio6.0窗口第3章數組3.1數組的基本概念3.2一維數組3.3多維數組3.4數組作為函數的參數3.5數組與字串3.6數組應用舉例3.7構造數據類型3.1數組的基本概念
數組是一種構造數據類型,是具有統一名稱和相同類型的一組數據元素的集合,它佔用連續記憶體單元進行存儲。要引用數組中的特定位置或元素,就要指定數組中的特定位置或元素的位置號(positionnumber)。
圖3-1顯示了整型數組c的數組元素存儲分配。這個數組包含7個元素。可以用數組名加上方括號([])中該元素的位置號來引用該元素。數組中的第一個元素稱為第0個元素。這樣,c數組中的第一個元素為c[0],c數組中的第二個元素為c[1],…,c數組中的第七個元素為c[6]。一般來說,c數組中的第i個元素為c[i-1]。圖3-1整型數組c的數組元素存儲分配示意
方括號中的位置號通常稱為下標(subscript)。下標應為整數或整型運算式。注意:帶下標的數組名一般稱為下標變數,可用於賦值語句的左邊。例如:
c[3]=72;3.2一維數組3.2.1一維數組的聲明數組在使用前必須先聲明。聲明一個一維數組的形式如下:
<類型識別字><數組名>[數組長度]其中:①數組名必須遵循C++語言對識別字的要求,其命名規則與其它變數名的相同。②數組長度是個常量運算式,它規定了數組的大小,即所聲明的數組由多少個數據類型相同的存儲空間組成。
組成數組的對象稱為該數組的元素。數組元素可存儲的數據類型由聲明時指定的C++語言數據類型決定。數組的聲明為以後使用數組分配了存儲空間,數組中每個元素在內存中是依次排列的。例如,intScore[50];定義了名稱為Score的一維數組,該數組有50個元素,是int類型的。在聲明數組時,要注意數組的長度只能由常量運算式來決定,不能是變數。即,數組的長度必須是確定的。例如:
intnMonth;floatfSales[nMonth*12];是錯誤的。這個聲明在編譯時編譯器會給出錯誤資訊。3.2.2一維數組中的元素訪問數組中的每個元素可以當成普通的變數使用。訪問一維數組元素的形式如下:
<數組名>[下標]
下標就是元素索引值,它代表了要被訪問的數組元素在內存中的相對位置。下標值的允許範圍從0開始到數組長度-1。下標等於0代表要訪問的元素在數組的第1個位置上,下標等於1代表要訪問的元素在數組的第2個位置上,依次類推。
例如,聲明一個長度為20的整型數組,並將數組中的各個元素按順序賦予從50到70以1遞增的數,即賦予數組的第0個元素的值為50,賦予數組的第1個元素的值為51,依此類推。寫出相應的程式段。注意,必須有一個值從0開始,以1為增量,遞增到19的變數作為訪問數組時的下標,這樣才能訪問到數組中所有的元素。
用一個簡單的for迴圈就可以做到這一點,而迴圈控制變數就是最好的下標值。要按順序給數組元素賦予從50到70的值,也就是讓每個數組元素的值等於其下標值加上50,所以只要將迴圈控制變數加上50賦予相應的數組元素即可。程式段代碼如下:
intnData[20];for(intnIndex=0;nIndex<20;nIndex++)nData[nIndex]=nIndex+50;【例3-1】生成一個長度為10的連續偶數序列,該偶數序列從2開始。要求將此序列保存在數組中,並輸出此數組的每個元素的值。分析:顯然,先要聲明一個長度為10的數組。模仿上面的做法,可以用一個迴圈來解決對所有數組元素的訪問,關鍵在於生成數列。偶數列是個很簡單的數列,如果從0開始,則規律為:第i個數等於i*2(i=0,1,2,…);如果從2開始,則規律為:第i個數等於i*2+2(i=0,1,2,…)。程式代碼如下:#include<iostream.h>voidmain(){ intnEven[10];//定義用於存放10個偶數的數組
intnIndex; for(nIndex=0;nIndex<10;nIndex++) nEven[nIndex]=nIndex*2+2; for(nIndex=0;nIndex<10;nIndex++) { cout<<nEven[nIndex]<<""; } cout<<endl;}程式運行結果為2 4 6 8 10 12 14 16 18 203.2.3一維數組的初始化變數可以在聲明時賦初值,數組也可以在聲明時給所有或部分數組元素賦初始值。要給一維數組元素賦初始值,有如下兩種形式。形式1:
<類型識別字><數組名>[數組長度]={第0個元素值,第1個元素值,…,第n-1個元素值}
形式2:
<類型識別字><數組名>[]={第0個元素值,第1個元素值,…,第n個元素值}
第一種形式將聲明一個長度為“數組長度”的值的數組,然後將花括弧內的值依次賦予數組的各個元素。花括弧內只能是常量運算式。如果花括弧中的常量運算式的個數小於數組長度,則剩餘的數組元素就不被賦予初始值;如果花括弧中的常量運算式的個數大於數組長度,則編譯器會給出錯誤資訊。第二種形式將聲明一個長度為n的數組,並將花括弧內的n個值依次賦給數組的各個元素。【例3-2】一個班級有20名學生,所有學生的英語考試成績保存在一個一維數組中。編寫程式,求出該班學生的英語考試平均成績,並統計考試成績在90分以上(包括90分)的學生人數和不及格的學生人數。分析:為了統計平均成績,需要有個變數保存所有學生的成績總和。求總和的方法就是在對每個數組元素訪問時,將其值累加到這個變數中,然後由此變數除以學生人數即得平均成績。要統計考試成績90分以上的學生人數和不及格的學生人數,
需要分別設置兩個變數。在對每個數組元素進行訪問時,如果元素值大於等於90,就將保存考試成績在90分以上的學生人數的變數值加1;如果元素值小於60,就將保存考試成績不及格的學生人數的變數值加1。程式代碼如下:#include<iostream.h>voidmain(){ intnScore[20]={90,88,45,92,76,59,89,93,60,51,91,65,82,74,92,35,66,78,62,91}; //用數組初始化的方法將成績存於數組nScore中
intnUnPassedCount=0; //定義記錄不及格人數的變數
intnHighScoreCount=0;//定義記錄90分以上人數的變數
intnSum=0; //定義求和的變數for(intni=0;ni<20;ni++) { nSum+=nScore[ni]; if(nScore[ni]<60) nUnPassedCount++; if(nScore[ni]>=90) nHighScoreCount++; } cout<<"平均分數為:"<<(float)nSum/20<<endl; //將總和除以20就是平均成績。注意這裏為了得到精確的結果,//使用了強制類型轉換將nSum從int型轉換成float型
cout<<"90分以上人數為:"<<nHighScoreCount<<endl; cout<<"不及格人數為:"<<nUnPassedCount<<endl;}程式運行結果為平均分數:73.9590分以上人數為:6不及格人數為:43.3多維數組3.3.1多維數組的聲明聲明一個多維數組的形式如下:
<類型識別字><數組名>[長度1][長度2]…[長度n]
同一維數組相同,多維數組的數組名必須遵循C++語言識別字的命名規則,常量運算式中不能有任何變數出現。為了更直觀地介紹多維數組,下麵以二維數組為例。
聲明一個二維數組的形式如下:
<類型識別字><數組名>[第1維長度][第2維長度]
例如,語句floatfMatrix[3][4]將聲明一個數組名為fMatix且第1維長度為3、第2維長度為4的二維數組。在二維數組中,第1維常常稱為行,第2維常常稱為列。這樣,一個二維數組就可同一個二維表格對應起來,如表3-1所示。
表3-1二維數組與表格
列行01230fMatrix[0][0]fMatrix[0][1]fMatrix[0][2]fMatrix[0][3]1fMatrix[1][0]fMatrix[1][1]fMatrix[1][2]fMatrix[1][3]2fMatrix[2][0]fMatrix[2][1]fMatrix[2][2]fMatrix[2][3]
可以這樣理解二維數組:如果只給出二維數組的第1維下標,以一維數組來看二維數組,則這樣的數組中每個元素所代表的是另一個一維數組。例如,fMatrix[0]代表由4個f1oat類型的元素組成的另一個一維數組(數組名為fMatrix[0],元素為fMatrix[0][0]、fMatrix[0][1]、fMatrix[0][2]、fMatrix[0][3])。不難算出,fMatrix中共有3*4=12個fIoat型元素。這12個元素在內存中其實也是按順序存放的:先存放fMatrix[0]的4個元素,緊接著存放fMatrix[1]的4個元素,最後存放fMatrix[2]的4個元素。3.3.2訪問多維數組中的元素要訪問多維數組中的元素,同樣需要指定要訪問的元素的下標。多維數組的元素有多個下標,其書寫形式如下:
<數組名>[第1維下標][第2維下標]…[第n維下標]
下標的值也是從0開始,不能超過該維的長度減1。下標的值可以是任意運算式的值,只要其值在該下標的有效範圍內即可。
要訪問二維數組中的某個元素,必須給出該元素所在的行和列。例如,fMatrix[2][1]代表數組名為fMatrix的二維數組中位於第2(從0開始)行、第1(從0開始)列的元素。同一維數組一樣,二維數組的元素也可以當成變數進行賦值或參與各種運算式的計算。3.3.3二維數組的初始化同一維數組一樣,二維數組也可以在聲明時賦初始值,其形式如下。形式1:
<類型識別字><數組名>[第1維長度][第2維長度]={第0個第2維數據組},
{第1個第2維數據組},…,{第n-1個第2維數據組}}
其中,n等於第1維長度。
形式2:
<類型識別字><數組名>[第1維長度][第2維長度]={第0個元素值,第1個元素值,…,第m個元素值}
其中,m小於或等於第1維長度乘以第2維長度。在兩種形式中,如果花括弧中給出的元素個數少於實際的元素個數,則剩餘的元素就不會被賦予初始值;如果花括弧中給出的元素個數大於實際的元素個數,則編譯器會給出錯誤資訊。【例3-3】生成如下格式的方陣,將其存入二維數組中,並輸出這個二維數組所有元素的值。
12345109876111213141520191817162122232425
分析:注意到這個方陣的規律在於,偶數行中的元素按昇冪排列,奇數行中的元素按降序排列,只要逐行處理方陣中的元素,即可得到這種方陣。為了訪問二維數組中的所有元素,應使用二重嵌套迴圈。外層迴圈的迴圈控制變數作為當前行,內層迴圈的迴圈控制變數作為當前列。在顯示這個二維數組時,為了得到理想的顯示效果,要對不同的元素指定不同的顯示位置。程式代碼如下:#include<iostream.h>voidmain(){ intnRow; //控制行的變數
intnCol; //控制列的變數
intnMatrix[5][5]; //聲明二維數組
for(nRow=0;nRow<5;nRow++){ for(nCol=0;nCol<5;nCol++) { if(nRow%2==0) nMatrix[nRow][nCol]=nRow*5+nCol+1; else nMatrix[nRow][4-nCol]=nRow*5+nCol+1; } } for(nRow=0;nRow<5;nRow++) {for(nCol=0;nCol<5;nCol++) { cout<<nMatrix[nRow][nCol]; if(nMatrix[nRow][nCol]<10) //控制輸出1位數與2位數時的不同間隔
cout<<""; else cout<<""; } cout<<endl; //每輸出一行後換行
}}程式運行結果為
1 23 4 510 9 8 7 611 12 13 14 1520 19 18 17 162122 23 24 253.4數組作為函數的參數
在C++語言中,將整個數組作為參數傳遞給函數要涉及到指針的概念。有關內容將在本書的第6章進一步討論,這裏只結合數組參數的傳遞過程做簡單介紹。要將數組作為參數傳遞給函數,可由不帶方括號的數組名進行。例如,如果數組聲明如下:intnMatrix[20];則將數組傳遞給函數,可用下列函數調用語句:myArray(nMatrix,20);要傳遞數組,大體上有以下兩種形式。形式1:<類型識別字><函數名>(類型識別字數組名[],int長度)形式2:<類型識別字><函數名>(類型識別字數組名[長度])
第一種形式適於處理不同長度的數組,數組的實際長度通過另一個參數傳遞給函數;而第二種形式只可用於傳遞長度固定的數組。不管哪一種形式,傳遞給函數的都不是數組本身,而是保存數組第0個元素的記憶體單元的地址(即存儲數組的起始地址)。通過傳遞數組的開始地址,被調用函數可得到實際數組的準確存放位置。因此,被調用函數在函數體中修改數組元素時,實際上是修改原記憶體地址中的數組元素。
數組名的值實際上就是保存數組中第1個元素的記憶體地址的變數,這樣的變數稱為指針型變數。當進行數組傳遞時,實際參數將此地址值傳遞給形式參數,使形式參數同實際參數指向同一記憶體地址。這樣在函數中如果改變數組中某個元素的值,在函數外數組中該元素的值也發生改變。
儘管函數調用按地址傳遞整個數組,但各個數組元素和簡單變數一樣是按值傳遞。這種簡單的單個數據稱為下標變數或標量(scalar)。要將數組元素傳遞給函數,可用數組元素的下標名作為函數調用中的參數。數組元素、數組名與作為函數形式參數的數組名的這種關係可用圖3-2來表示。
鑒於上述原因,必須考慮在函數體內對作為形式參數傳遞而來的數組的操作。如果函數體內所有對作為形式參數傳遞而來的數組的操作只有讀操作沒有寫操作,則不會有任何問題。但如果要改變數組元素的值,就必須考慮函數的調用者是否允許函數改變數組元素的值。如果不允許,在函數內部必須將數組複製,所有的更改操作只能對複製品進行。
【例3-4】某次歌唱比賽有5名選手參加,有6名評委分別為選手打分,得分如表3-2。
表3-2歌唱比賽記分表評委號
選手號12345619.319.209.009.409.359.2029.719.529.509.669.499.5738.898.809.109.258.909.0049.389.509.409.209.908.9059.308.849.409.459.108.89
規定的積分規則是:每位選手去掉一個最高分,再去掉一個最低分,然後取剩下的得分的平均分。編寫程式計算各選手的成績,並在窗口輸出選手號和成績。分析:由於二維數組同二維表格有對應關係,可以用一個二維數組保存所有評委給所有選手評出的成績。這樣,數組下標的第1維(行)代表的就是選手號——i,第2維(列)代表的是評委號——j。
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 海丰地基加固施工方案
- 防水的施工方案
- 自拌混凝土施工方案
- 河源顶管施工方案
- 泥浆护壁施工方案
- 软件培训方案
- 二零二五年度果树种植土地托管承包与农村金融创新合作协议
- 2025年度汽车维修行业安全生产责任简易合同
- 二零二五年度高科技研发项目劳务合同风险评估书
- 二零二五年度健康医疗合伙投资公司股权合作协议
- 七年级数学新北师大版(2024)下册第一章《整式的乘除》单元检测习题(含简单答案)
- 《工程热力学》课件-11 理想气体热力学能、焓和熵的计算
- 发票知识培训课件
- 《英国小说家罗琳》课件
- 《综合办岗位职责》课件
- 学校与家庭在学生心理健康中的协同作用
- 大学英语翻译课件
- 薄膜电容项目立项申请报告
- 《中医望闻问切》课件
- 教师师德师风考核细则
- 声带肿物的护理教学查房
评论
0/150
提交评论