官术网_书友最值得收藏!

1.5 函數(shù)和預(yù)處理

在面向過程的結(jié)構(gòu)化程序設(shè)計中,通常需要若干個模塊實現(xiàn)較復(fù)雜的功能,而每一個模塊自成結(jié)構(gòu),用來解決一些子問題。這種能完成某一獨立功能的子程序模塊,稱為函數(shù)。一個較為復(fù)雜的程序一般是由一個主函數(shù)main與若干個子函數(shù)組合而成的。但C++是一種面向?qū)ο蟮某绦蛟O(shè)計語言,它與面向過程設(shè)計方法的最大不同是引入了“類和對象”的概念,而此時函數(shù)是構(gòu)造“類”成員的一種手段。

1.5.1 函數(shù)的定義和調(diào)用

前面已提過,一個程序開始運行時,系統(tǒng)自動調(diào)用main主函數(shù)。主函數(shù)可以調(diào)用子函數(shù),子函數(shù)還可以調(diào)用其他子函數(shù)。調(diào)用其他函數(shù)的函數(shù)稱為“主調(diào)函數(shù)”,被其他函數(shù)調(diào)用的函數(shù)稱為“被調(diào)函數(shù)”。

一般來說,C++程序中除主函數(shù)main外,其他函數(shù)可以是庫函數(shù)或自定義函數(shù)。庫函數(shù)又稱標準函數(shù),是ANSI/ISO C++編譯系統(tǒng)預(yù)先定義好的函數(shù),程序設(shè)計時可根據(jù)實際需要,直接使用這類函數(shù),而不必重新定義。自定義函數(shù)是用戶根據(jù)程序的需要,將某一個功能相對獨立的程序定義成的一個函數(shù),或?qū)⒔鉀Q某個問題的算法用一個函數(shù)來組織。在C++程序中,與變量的使用規(guī)則相同,自定義函數(shù)一定要先說明并定義,然后才能被調(diào)用。

1.函數(shù)的定義

在C++程序中,定義一個函數(shù)的格式如下:

<函數(shù)類型> <函數(shù)名>( <形式參數(shù)表> )
{
    <若干語句>
}

可以看出,一個函數(shù)的定義是由函數(shù)名、函數(shù)類型、形式參數(shù)表和函數(shù)體四部分組成的。函數(shù)類型決定了函數(shù)所需要的返回值類型,它可以是函數(shù)或數(shù)組之外任何有效的C++數(shù)據(jù)類型,包括構(gòu)造的數(shù)據(jù)類型、指針等。如果不需要函數(shù)有返回值,只要定義函數(shù)的類型為void即可。

函數(shù)名必須是一個有效的C++標識符(注意命名規(guī)則),函數(shù)名后面必須跟一對圓括號“( )”,以區(qū)別于變量名及其他用戶定義的標識名。

函數(shù)的形式參數(shù)寫在括號內(nèi),參數(shù)表中的參數(shù)個數(shù)可以是0,表示沒有參數(shù),但圓括號不能省略,也可以是一個或多個參數(shù),但多個參數(shù)間要用逗號分隔。

函數(shù)的函數(shù)體由在一對花括號中的若干條語句組成,用于實現(xiàn)這個函數(shù)執(zhí)行的動作。C++不允許在一個函數(shù)體中再定義函數(shù)。

根據(jù)上述定義,可以編寫一個函數(shù)。例如,下列函數(shù)的作用是計算兩個整數(shù)的絕對值之和:

int  sum(int x,int y)
{
    if(x<0)  x=-x;
    if(y<0)  y=-y;
    int z = x + y;
    return z;
}

其中,x和y是此函數(shù)的形式參數(shù),簡稱形參。所謂形參,是指調(diào)用此函數(shù)所需要的參數(shù)個數(shù)和類型。一般地,只有當(dāng)函數(shù)被調(diào)用時,系統(tǒng)才會給形參分配內(nèi)存單元,而當(dāng)調(diào)用結(jié)束后,形參所占的內(nèi)存單元又會被釋放。

上述函數(shù)定義中,int可以省略,因為C++規(guī)定凡不加類型說明的函數(shù),一律自動按整型(int)處理。由于sum的類型是整型,因此必須要有返回值,且返回值的類型應(yīng)與函數(shù)類型相同,也是整型;若返回值的類型與函數(shù)類型不相同,則按類型自動轉(zhuǎn)換方式轉(zhuǎn)換成函數(shù)的類型。關(guān)鍵字return負責(zé)將后面的值作為函數(shù)的返回值,并將流程返回到調(diào)用此函數(shù)的位置處。

由于return的后面可以是常量、變量或任何合法的表達式,因此函數(shù)sum也可簡化為:

int  sum(int x,int y)
{
    if(x<0)  x=-x;
    if(y<0)  y=-y;
    return  (x+y);                  // 括號可以省略,即return  x+y;
}

若函數(shù)類型是void,函數(shù)體就不需要return語句或return的后面只有一個分號。需要注意的是,因為return是返回語句,它將退出函數(shù)體,所以一旦執(zhí)行return語句,在函數(shù)體內(nèi)return后面的語句便不再被執(zhí)行。例如:

void  f1(int a)
{
    if(a>10) return;
    // …
}

在這里,return語句起了一個改變語句順序的作用。

2.函數(shù)的調(diào)用

定義一個函數(shù)就是為了以后的調(diào)用。調(diào)用函數(shù)時,先寫函數(shù)名,然后緊跟括號,括號里是實際調(diào)用該函數(shù)時所給定的參數(shù),稱為實際參數(shù),簡稱實參,并與形參相對應(yīng)。函數(shù)調(diào)用的一般形式為:

<函數(shù)名>( <實際參數(shù)表> );

調(diào)用函數(shù)時要注意:實參與形參的個數(shù)應(yīng)相等,類型應(yīng)一致,且按順序?qū)?yīng),一一傳遞數(shù)據(jù)。例如,下面的示例用來輸出一個三角形圖案。

例Ex_Call】 函數(shù)的調(diào)用

#include <iostream.h>
void  printline(char ch,  int n)
{
    for (int i = 0 ; i<n ; i++)
        cout<<ch;
    cout<<endl ;
}
int  main()
{
    int  row=5;
    for (int i = 0; i<row; i++)
        printline('*',i+1);                //A語句
    return 0;
}

程序運行結(jié)果如下:

*

**

***

****

*****

代碼中,main函數(shù)的for循環(huán)語句共調(diào)用了5次printline函數(shù)(A語句),每次調(diào)用時因?qū)崊+1的值不斷改變,從而使函數(shù)printline打印出來的星號個數(shù)也隨之改變。

printline函數(shù)由于沒有返回值,因此它作為一個語句來調(diào)用。事實上,對于有返回值的函數(shù)也可進行這種方式的調(diào)用,只是此時不使用返回值,僅要求函數(shù)完成一定的操作。實際上,在C++中,一個函數(shù)的調(diào)用方式還有很多。例如,對于前面的sum函數(shù)還可有下列調(diào)用方式:

sum(3,4);                      //B語句
int  c=2*sum(4,5);             //C語句
c=sum(c,sum(c,4));             //D語句

其中,B語句是將函數(shù)作為一個語句,不使用返回值,只要求函數(shù)完成一定的操作;C語句把函數(shù)作為表達式的一部分,將返回值參與運算,結(jié)果為c = 18;D語句是將函數(shù)作為函數(shù)的實參,等價于“c = sum(18, sum(18,4));”,執(zhí)行函數(shù)參數(shù)內(nèi)的sum(18,4)后,等價于“c = sum(18,22) ;”,最后結(jié)果為c = 40。

3.函數(shù)的聲明

在【例Ex_Call】中,由于函數(shù)printline的定義代碼位置在調(diào)用A語句(在main函數(shù)中)之前,因而A語句執(zhí)行不會有問題。但若將函數(shù)printline的定義代碼位置放在調(diào)用A語句之后,即函數(shù)定義在后,而調(diào)用在前,就會產(chǎn)生“printline標識符未定義”的編譯錯誤。此時必須在調(diào)用前進行函數(shù)聲明

函數(shù)聲明消除了函數(shù)定義的位置的影響,也就是說,不管函數(shù)是在何處定義的,只要在調(diào)用前進行了函數(shù)的聲明就可保證函數(shù)調(diào)用的合法性。雖然,函數(shù)不一定在程序的開始就聲明,但為了提高程序的可讀性,保證簡潔的程序結(jié)構(gòu),最好將主函數(shù)main放在程序的開頭,而將函數(shù)聲明放在主函數(shù)main之前。

聲明一個函數(shù)按下列格式進行:

<函數(shù)類型> <函數(shù)名>( <形式參數(shù)表> );

可見,函數(shù)聲明的格式是在函數(shù)頭的后面加上分號“;”。但要注意,函數(shù)聲明的內(nèi)容應(yīng)和函數(shù)的定義相同。例如,對于前面遇到的sum函數(shù)和printline函數(shù)可有如下聲明:

int  sum(int x,  int y);
void  printline(char ch,  int n);

由于函數(shù)的聲明僅是對函數(shù)的原型進行說明,即函數(shù)原型聲明,其聲明的形參變量名在聲明語句中并沒有任何語句操作它,因此這里的形參名和函數(shù)定義時的形參名可以不同,且函數(shù)聲明時的形參名還可以省略,但函數(shù)名、函數(shù)類型、形參類型及個數(shù)應(yīng)與定義時相同。例如,下面幾種形式都是對sum函數(shù)原型的合法聲明:

int sum(int a,int b);               // 允許原型聲明時的形參名與定義時不同
int sum(int,int);                 // 省略全部形參名
int sum(int a,int);                // 省略部分形參名
int sum(int,int b);                // 省略部分形參名

不過,從程序的可讀性考慮,在聲明函數(shù)原型時,為每一個形參指定有意義的標識符,并且和函數(shù)定義時的參數(shù)名相同,是一個非常好的習(xí)慣。

1.5.2 函數(shù)的參數(shù)傳遞

在討論函數(shù)的參數(shù)傳遞前先簡單介紹全局變量和局部變量的概念。

C++中每一個變量必須先定義后使用,若變量是在函數(shù)體內(nèi)使用變量前定義的,則此變量就是一個局部變量,它只能在函數(shù)體內(nèi)使用,在函數(shù)體外則不能使用它。若變量是在函數(shù)外部(如在main主函數(shù)前)定義的,它能被后面的所有函數(shù)或語句引用,這樣的變量就是全局變量。但如果一個函數(shù)試圖修改一個全局變量的值,也會引起結(jié)構(gòu)不清晰、容易混淆等副作用。因此許多函數(shù)都盡量使用局部變量,而將形參和函數(shù)類型作為公共接口,以保證函數(shù)的獨立性。

C++中函數(shù)的參數(shù)傳遞有兩種方式,一種是按值傳遞,另一種是地址傳遞或引用傳遞。這里先來說明按值傳遞的參數(shù)傳遞方法。

所謂按值傳遞(簡稱值傳遞),是指當(dāng)一個函數(shù)被調(diào)用時,C++根據(jù)實參和形參的對應(yīng)關(guān)系將實際參數(shù)的值一一傳遞給形參,供函數(shù)執(zhí)行時使用。函數(shù)本身不對實參進行操作,也就是說,即使形參的值在函數(shù)中發(fā)生了變化,實參的值也不會受到影響。

例Ex_SwapValue】 交換函數(shù)兩個參數(shù)的值

#include <iostream.h>
void swap(float x, float y)
{
     float temp;
     temp=x;x=y;y=temp;
     cout<<"x="<<x<<",y="<<y<<"\n";
}
int  main()
{
     float a=20,b=40;
     cout<<"a="<<a<<",b="<<b<<"\n";
     swap(a,b);
     cout<<"a="<<a<<",b="<<b<<"\n";
     return 0;
}

程序運行結(jié)果如下:

a = 20, b = 40

x = 40, y = 20

a = 20, b = 40

可以看出,雖然函數(shù)swap中交換了兩個形參x和y的值,但交換的結(jié)果并不能改變實參的值,所以調(diào)用該函數(shù)后,變量a和b的值仍然為原來的值。

所以,當(dāng)函數(shù)的形參是一般變量時,由于其參數(shù)傳遞方式是值傳遞,因此函數(shù)調(diào)用時所指定的實參可以是常量、變量、函數(shù)或表達式等,總之只要有確定的值就可以。例如前面的“printline('*', i+1);”、“c = sum(c, sum(c,4));”等。函數(shù)值傳遞方式的最大好處是保持函數(shù)的獨立性。在值傳遞方式下,函數(shù)只能通過指定函數(shù)類型并在函數(shù)體中使用return來返回某一類型的數(shù)值。

1.5.3 帶默認形參值的函數(shù)

在C++中,允許在函數(shù)的聲明或定義時給一個或多個參數(shù)指定默認值。這樣在調(diào)用時,可以不給出參數(shù),而按指定的默認值進行工作。例如:

void delay(int loops=1000);             // 函數(shù)聲明
//…
void delay(int loops)                   // 函數(shù)定義
{
    if (loops==0) return;
    for(int i=0;i<loops;i++);          // 空循環(huán),起延時作用
}

這樣,當(dāng)調(diào)用

delay();                          // 和delay(1000)等效

時,程序都會自動將loops當(dāng)成1000的值來進行處理。當(dāng)然,也可重新指定相應(yīng)參數(shù)值,例如:

delay(2000);

在設(shè)置函數(shù)的默認參數(shù)值時要注意:

(1)當(dāng)函數(shù)既有原型聲明又有定義時,默認參數(shù)只能在原型聲明中指定,而不能在函數(shù)定義中指定。例如:

void delay(int loops);                 // 函數(shù)原型聲明
//…
void delay(int loops=1000)            // 錯誤:此時不能在函數(shù)定義中指定默認參數(shù)
{   // …     }

(2)當(dāng)一個函數(shù)中需要有多個默認參數(shù)時,則形參分布中,默認參數(shù)應(yīng)嚴格從右到左逐次定義和指定,中間不能跳開。例如:

void display(int a,int b,int c=3);        // 合法
void display(int a,int b=2,int c=3);      // 合法
void display(int a=1,int b=2,int c=3);    // 合法:可以對所有的參數(shù)設(shè)置默認值
void display(int a,int b=2,int c);        // 錯誤:默認參數(shù)應(yīng)從最右邊開始
void display(int a=1,int b,int c=3);      // 錯誤:多個默認參數(shù)中間不能有非默認參數(shù)

(3)當(dāng)帶有默認參數(shù)的函數(shù)調(diào)用時,系統(tǒng)按從左到右的順序?qū)崊⑴c形參結(jié)合,當(dāng)實參的數(shù)目不足時,系統(tǒng)將按同樣的順序用聲明或定義中的默認值來補齊所缺少的參數(shù)。

例Ex_Default】 在函數(shù)定義中設(shè)置多個默認參數(shù)

#include <iostream.h>
void display(int a,int b=2,int c=3)      // 在函數(shù)定義中設(shè)置默認參數(shù)
{
    cout<<"a = "<<a<<", b = "<<b<<", c = "<<c<<"\n";
}
int  main()
{
    display(1);
    display(1, 5);
    display(1, 7, 9);
    return 0;
}

程序運行結(jié)果如下:

a = 1, b = 2, c = 3

a = 1, b = 5, c = 3

a = 1, b = 7, c = 9

(4)由于對同一個函數(shù)的原型可作多次聲明,因此在函數(shù)聲明中指定多個默認參數(shù)時,可用多條函數(shù)原型聲明語句來指定,但同一個參數(shù)的默認值只能指定一次。例如,例Ex_Default可改寫為:

    #include <iostream>
    using namespace std;
// 下面兩條函數(shù)說明語句等效于void display(int a,int b=2,int c=3);
    void display(int a,int b,int c=3);         // 指定c為默認參數(shù)
    void display(int a,int b=2,int c);         // 指定b為默認參數(shù)
    //…

默認參數(shù)值可以是全局變量、全局常量,甚至是一個函數(shù)。但不可以是局部變量,因為默認參數(shù)的函數(shù)調(diào)用是在編譯時確定的,而局部變量的值在編譯時無法確定。

1.5.4 函數(shù)的遞歸調(diào)用

C++允許在調(diào)用一個函數(shù)的過程中出現(xiàn)直接地或間接地調(diào)用函數(shù)本身的情況,稱為函數(shù)的遞歸調(diào)用。遞歸(Recursion)是一種常用的程序方法(算法),相應(yīng)的函數(shù)稱為遞歸函數(shù)

例如,用遞歸函數(shù)編程求n的階乘n!。(n!=n*(n-1)*(n-2)*…*2*1)。它也可用下式表示:

由于n!和(n-1)!都是同一個問題的求解,因此可將n!用遞歸函數(shù)long factorial(int n)來描述,程序代碼如下。

例Ex_Factorial】 求n的階乘n!

#include <iostream.h>
long factorial(int n);
 int  main()
{
    cout<<factorial(4)<<endl;              // 結(jié)果為24
     return 0;
}
long factorial(int n)
{
     long  result=0;
     if(0==n)
        result = 1;
    else
        result=n*factorial(n-1);           // 進行自身調(diào)用
     return  result;
}

主函數(shù)main調(diào)用了求階乘的函數(shù)factorial,而函數(shù)factorial中的語句“result = n *factorial(n-1);”又調(diào)用了函數(shù)自身,因此函數(shù)factorial是一個遞歸函數(shù)。

程序運行結(jié)果如下:

24

下面來分析main函數(shù)中“factorial(4);”語句的執(zhí)行過程,這一過程用圖1.8來表示:

圖1.8 factorial(4)遞歸函數(shù)執(zhí)行過程

① 因n = 4,不等于0,故執(zhí)行“result = 4*factorial(3);”,因語句中有函數(shù)factorial(3)調(diào)用,故進行下一步操作。

② 因n = 3,不等于0,故執(zhí)行“result = 4*factorial(2);”,因語句中有函數(shù)factorial(2)調(diào)用,故進行下一步操作。

③ 因n = 2,不等于0,故執(zhí)行“result = 4*factorial(1);”,因語句中有函數(shù)factorial(1)調(diào)用,故進行下一步操作。

④ 因n = 1,不等于0,故執(zhí)行“result = 4*factorial(0);”,因語句中有函數(shù)factorial(0)調(diào)用,故進行下一步操作。

⑤ 因n = 0,故執(zhí)行result = 1。然后執(zhí)行函數(shù)后面的語句。

⑥ 當(dāng)執(zhí)行“return result;”后,factorial(0)函數(shù)返回到主調(diào)函數(shù)factorial(1)。在主調(diào)函數(shù)factorial(1)中,result = 1*1=1,然后執(zhí)行函數(shù)后面的語句。

⑦ 當(dāng)執(zhí)行“return result;”后,factorial(1)函數(shù)返回到主調(diào)函數(shù)factorial(2)。在主調(diào)函數(shù)factorial(2)中,result = 2*1=2,然后執(zhí)行函數(shù)后面的語句。

⑧ 當(dāng)執(zhí)行“return result;”后,factorial(2)函數(shù)返回到主調(diào)函數(shù)factorial(3)。在主調(diào)函數(shù)factorial(3)中,result = 3*2=6,然后執(zhí)行函數(shù)后面的語句。

⑨ 當(dāng)執(zhí)行“return result;”后,factorial(3)函數(shù)返回到主調(diào)函數(shù)factorial(4)。在主調(diào)函數(shù)factorial(4)中,result = 4*6=24,然后執(zhí)行函數(shù)后面的語句。

⑩ 當(dāng)執(zhí)行“return result;”后,factorial(4)函數(shù)返回到主調(diào)函數(shù)main。在主調(diào)函數(shù)main中,執(zhí)行下一條指令,輸出結(jié)果24。

可以看出,遞歸函數(shù)實際上是同名函數(shù)的多級調(diào)用。但要注意,遞歸函數(shù)中必須要有結(jié)束遞歸過程的條件,即函數(shù)不再進行自身調(diào)用,否則遞歸會無限制地進行下去。

1.5.5 內(nèi)聯(lián)函數(shù)

函數(shù)調(diào)用時,內(nèi)部過程需要進行調(diào)用初始化,執(zhí)行函數(shù)代碼,調(diào)用后處理等步驟。當(dāng)函數(shù)體比較小,且執(zhí)行的功能比較簡單時,這種函數(shù)調(diào)用方式的系統(tǒng)開銷相對較大。為了解決這一問題, C++引入了內(nèi)聯(lián)函數(shù)的概念,它把函數(shù)體的代碼直接插入到調(diào)用處,將調(diào)用函數(shù)的方式改為順序執(zhí)行直接插入的程序代碼,這樣可以減少程序的執(zhí)行時間,但同時增加了代碼的實際長度。

內(nèi)聯(lián)函數(shù)的使用方法與一般函數(shù)相同,只是在內(nèi)聯(lián)函數(shù)定義時,需在函數(shù)的類型前面加上inline關(guān)鍵字。

例Ex_Inline】 用內(nèi)聯(lián)函數(shù)實現(xiàn)求兩個實數(shù)的最大值

#include <iostream.h>
inline float fmax(float x, float y)
{
    return x>y?x:y;
}
int  main()
{
    float a;
    a=fmax(5,10);                     //A語句
    cout<<"最大的數(shù)為:"<<a<<"\n";
    return 0;
}

這樣,當(dāng)程序編譯時,A語句就變成了:

a = 5>10 ? 5 : 10;

程序運行結(jié)果如下:

最大的數(shù)為:10

要注意使用內(nèi)聯(lián)函數(shù)的一些限制:

(1)內(nèi)聯(lián)函數(shù)中不能有數(shù)組定義,也不能有任何靜態(tài)類型(后面會討論)的定義。

(2)內(nèi)聯(lián)函數(shù)中不能含有循環(huán)、switch和復(fù)雜嵌套的if語句。

(3)內(nèi)聯(lián)函數(shù)不能是遞歸函數(shù)。

總之,內(nèi)聯(lián)函數(shù)一般是比較小的、經(jīng)常被調(diào)用的、大多可在一行寫完的函數(shù),并常用來代替以后要討論的帶參數(shù)的宏定義。

1.5.6 函數(shù)重載

函數(shù)重載是指C++允許多個同名的函數(shù)存在,但同名的各個函數(shù)的形參必須有區(qū)別:要么形參的個數(shù)不同,要么形參的個數(shù)相同,但參數(shù)類型有所不同。

例Ex_OverLoad】 編程求兩個或三個操作數(shù)之和

#include <iostream.h>
int sum(int x, int y);
int sum(int x, int y, int z);
double sum(double x, double y);
double sum(double x, double y, double z);
int  main()
{
    cout<<sum(2,5)<<endl;                  // 結(jié)果為7
    cout<<sum(2,5,7)<<endl;                // 結(jié)果為14
    cout<<sum(1.2,5.0,7.5)<<endl;          // 結(jié)果為13.7
    return 0;
}
int sum(int x, int y)
{
    return x+y;
}
int sum(int x, int y, int z)
{
    return x+y+z;
}
double sum(double x, double y)
    return x+y;
}
double sum(double x, double y, double z)
{
    return x+y+z;
}

程序運行結(jié)果如下:

7

14

13.7

從上面的例子可以看出,由于使用了函數(shù)的重載,因而不僅方便函數(shù)名的記憶,更主要的是完善了同一個函數(shù)的代碼功能,給調(diào)用帶來了許多方便。程序中各種形式的sum函數(shù)都稱為sum的重載函數(shù)。需要說明的是,重載函數(shù)必須具有不同的參數(shù)個數(shù)或不同的參數(shù)類型,只有返回值的類型不同是不行的。例如:

void fun(int a, int b);
int fun(int a, int b);

是錯誤的。因為如果有函數(shù)調(diào)用fun(2,3)時,編譯器無法準確地確定應(yīng)調(diào)用哪個函數(shù)。

同樣,當(dāng)函數(shù)的重載帶有默認參數(shù)時,也應(yīng)該注意避免上述的二義性情況。例如:

int fun(int a, int b = 0);
int fun(int a);

是錯誤的。因為如果有函數(shù)調(diào)用fun(2)時,編譯器也無法準確地確定應(yīng)調(diào)用哪個函數(shù)。

1.5.7 作用域和可見性

作用域又稱作用范圍,是指程序中標識符(變量名、函數(shù)名、數(shù)組名、類名、對象名等)的有效范圍。一個標識符是否可以被引用,稱為標識符的可見性。在一個C++程序項目中,一個標識符只能在聲明或定義它的范圍內(nèi)可見,在此之外是不可見的。根據(jù)標識符的作用范圍,可將其作用域分為5種:函數(shù)原型作用域、函數(shù)作用域、塊作用域、類作用域和文件作用域。其中,類作用域?qū)⒃诘?章介紹,這里介紹其他幾種。

1.塊作用域

這里的塊就是前面已提到過的塊語句(復(fù)合語句)。在塊中聲明的標識符,其作用域從聲明處開始,一直到結(jié)束塊的花括號為止。塊作用域也稱局部作用域,具有塊作用域的變量是局部變量。即在塊中定義的變量僅在塊中有效,塊執(zhí)行后,變量被釋放。例如:

void fun(void)                  // 在形參表中指定void,表示沒有形參,void可不要
{
    int a;                      //a的作用域起始處
    cin>>a;
    if (a<0) {
            a = -a;
            int b;              //b的作用域起始處
            //…
    }                           //b的作用域終止處
}                               //a的作用域終止處

代碼中,聲明的局部變量a和b處在不同的塊中。其中變量a是在fun函數(shù)的函數(shù)體塊中,因此在函數(shù)體這個范圍內(nèi),該變量是可見的。而b是在if語句塊中聲明的,故它的作用域是從聲明處開始到if語句結(jié)束處終止。

需要說明的是:

(1)當(dāng)標識符的作用域完全相同時,不允許出現(xiàn)相同的標識符名。而當(dāng)標識符具有不同的作用域時,允許標識符同名。

(2)在多層次塊(塊的嵌套)中,外層塊與內(nèi)層塊之間具有不同的作用域。外層塊的變量可在內(nèi)層塊中使用,但內(nèi)層塊中的變量僅能在內(nèi)層塊中使用。當(dāng)外層塊和內(nèi)層塊中有同名變量定義時,外層塊的同名變量在內(nèi)層塊中不起作用。

(3)在Visual C++中,for語句聲明的標識符的作用域是包含for語句的那個內(nèi)層塊,而不是僅僅作用于for語句,這與標準C++是不一樣的。

2.函數(shù)原型作用域

函數(shù)原型作用域指的是在聲明函數(shù)原型時所指定的參數(shù)標識符的作用范圍。這個作用范圍在函數(shù)原型聲明中的左、右圓括號之間。正因為如此,在函數(shù)原型中聲明的標識符可以與函數(shù)定義中說明的標識符名稱不同。由于所聲明的標識符與該函數(shù)的定義及調(diào)用無關(guān),所以可以在函數(shù)原型聲明中只作參數(shù)的類型聲明,而省略參數(shù)名。例如:

double  max(double x,double y);

double  max(double,double);

是等價的。不過,從程序的可讀性考慮,在聲明函數(shù)原型時,為每一個形參指定有意義的標識符,并且和函數(shù)定義時的參數(shù)名相同,是一個非常好的習(xí)慣。

3.函數(shù)作用域

具有函數(shù)作用域的標識符在聲明它的函數(shù)內(nèi)可見,但在此函數(shù)之外是不可見的。在C++語言中,只有g(shù)oto語句中的標號具有函數(shù)作用域。goto語句的濫用會導(dǎo)致程序流程無規(guī)則、可讀性差,因此現(xiàn)代程序設(shè)計方法不主張使用goto語句。

4.文件作用域

在函數(shù)外定義的標識符或用extern說明的標識符稱為全局標識符。全局標識符的作用域稱為文件作用域,它從聲明之處開始,直到文件結(jié)束一直是可見的。需要說明的是:

(1)全局的常量或變量的作用域是文件作用域,它從定義開始到源程序文件結(jié)束。例如:

const float PI=3.14;                  // 全局常量PI,其作用域從此開始到文件結(jié)束
int a;                                // 全局變量a,其作用域從此開始到文件結(jié)束
void main( )
{   //…
}
void funA(int x)
{   // …
}

其中,全局常量PI和全局變量a的作用域是文件作用域。

(2)若函數(shù)定義在后,調(diào)用在前,必須進行函數(shù)原型聲明。若函數(shù)定義在前,調(diào)用在后,函數(shù)定義包含了函數(shù)的原型聲明。一旦聲明了函數(shù)原型,函數(shù)標識符的作用域是文件作用域,它從定義開始到源程序文件結(jié)束。例如:

void funA(int x);                    // 函數(shù)funA的作用域從此開始到文件結(jié)束
void funB()                          // 函數(shù)funB的作用域從此開始到文件結(jié)束
{   // …
}
void main( )
{   // …
}
void funA(int x)
{   // …
}

(3)在C++中,若在塊作用域內(nèi)使用與局部標識符同名的塊外標識符時,則須使用域運算符“::”來引用,且該標識符一定要是全局標識符,即它具有文件作用域。

例Ex_Process】 在塊作用域內(nèi)引用文件作用域的同名變量

#include <iostream.h>
int  i=10;                                 //A
int  main()
{
    int  i=20;                             //B
    {
            int  i=5;                      //C
            int  j;
            ::i=::i+4;                     //::i是引用A定義的變量i,不是B中的i
            j=::i+i;                       // 這里不加::的i是C中定義的變量
            cout<<"::i = "<<::i<<", j = "<<j<<"\n";
    }
    cout<<"::i="<<::i<<",i="<<i<<"\n";     // 這里不加::的i是B中定義的變量
    return 0;
}

程序運行結(jié)果如下:

::i = 14, j = 19

::i = 14, i = 20

1.5.8 存儲類型

存儲類型是針對變量而言的,它規(guī)定了變量的生存期。無論是全局變量還是局部變量,編譯系統(tǒng)往往根據(jù)其存儲方式定義、分配和釋放相應(yīng)的內(nèi)存空間。變量的存儲類型反映了變量在哪里開辟內(nèi)存空間,以及占用內(nèi)存空間的有效期限。

在C++中,變量有4種存儲類型:自動類型、靜態(tài)類型、寄存器類型和外部類型,這些存儲類型是在變量定義時指定的,其一般格式如下:

<存儲類型> <數(shù)據(jù)類型> <變量名表>;

1.自動類型(auto)

一般來說,用自動存儲類型聲明的變量都限制在某個程序范圍內(nèi)使用,即為局部變量。從系統(tǒng)角度來說,自動存儲類型變量是采用動態(tài)分配方式在棧區(qū)中分配內(nèi)存空間的。因此,當(dāng)程序執(zhí)行到超出該變量的作用域時,就釋放它所占用的內(nèi)存空間,其值也隨之消失了。

在C++語言中,聲明一個自動存儲類型的變量是在變量類型前加上關(guān)鍵字auto,例如:

auto int  i;

若自動存儲類型的變量是在函數(shù)內(nèi)或語句塊中聲明的,則可省略關(guān)鍵字auto,例如:

void fun()
{
    int i;                         // 省略auto
    // …
}

2.寄存器類型(register)

使用關(guān)鍵字register聲明寄存器類型的變量的目的是將所聲明的變量放入寄存器內(nèi),從而加快程序的運行速度。例如:

register  int  i;                     // 聲明寄存器類型變量

但在使用register聲明時,若系統(tǒng)寄存器已經(jīng)被其他數(shù)據(jù)占據(jù),寄存器類型的變量就會自動當(dāng)做auto變量。

3.靜態(tài)類型

從變量的生存期來說,一個變量的存儲空間可以是永久的,即在程序運行期間該變量一直存在,如全局變量;也可以是臨時的,如局部變量,當(dāng)流程執(zhí)行到它的說明語句時,系統(tǒng)為其在棧區(qū)中動態(tài)分配一個臨時的內(nèi)存空間,并在它的作用域中有效,一旦流程超出該變量的作用域時,就釋放它所占用的內(nèi)存空間,其值也隨之消失。

但是,若在聲明局部變量類型前面加上關(guān)鍵字static,則將其定義成了一個靜態(tài)類型的變量。這樣的變量雖具有局部變量的作用域,但由于它是用靜態(tài)分配方式在靜態(tài)數(shù)據(jù)區(qū)中來分配內(nèi)存空間。因此,在這種方式下,只要程序還在繼續(xù)執(zhí)行,靜態(tài)類型變量的值就一直有效,不會隨它所在的函數(shù)或語句塊的結(jié)束而消失。簡單地說,靜態(tài)類型的局部變量雖具有局部變量的作用域,但卻有全局變量的生存期。

需要說明的是,靜態(tài)類型的局部變量只在第一次執(zhí)行時進行初始化,正因為如此,在聲明靜態(tài)類型變量時一定要指定其初值,若沒有指定,編譯器還會將其初值置為0。

例Ex_Static】 使用靜態(tài)類型的局部變量

#include <iostream.h>
void count()
{
    int  i=0;
    static int j=0;                      // 靜態(tài)類型
    i++;
    j++;
    cout<<"i = "<<i<<", j = "<<j<<"\n";
}
int  main()
{
    count();
    count();
    return 0;
}

程序中,當(dāng)?shù)?次調(diào)用函數(shù)count時,由于變量j是靜態(tài)類型,因此其初值設(shè)為0后不再進行初始化,執(zhí)行j++后,j值為1,并一直有效。第2次調(diào)用函數(shù)count時,由于j已分配內(nèi)存且進行過初始化,因此語句“static int j = 0;”被跳過,執(zhí)行j++后,j值為2。

程序運行結(jié)果如下:

i = 1, j = 1

i = 1, j = 2

事實上,在程序中聲明的全局變量總是靜態(tài)存儲類型。若在全局變量前加上static,使該變量只在這個源程序文件內(nèi)使用,則稱該變量為全局靜態(tài)變量或靜態(tài)全局變量。

4.外部類型

使用關(guān)鍵字extern聲明的變量稱為外部變量,一般是指定義在本程序外部的變量。當(dāng)某個變量被聲明成外部變量時可以直接在本程序中引用這個變量,不必再次為它分配內(nèi)存。在C++中,只有在兩種情況下需要使用外部變量。

第1種情況:在同一個源文件中,若定義的變量使用在前,聲明在后,這時在使用前要聲明為外部變量。

第2種情況:當(dāng)由多個文件組成一個完整的程序,且在一個源程序文件中定義的變量要被其他若干個源文件引用時,引用的文件中要用extern對該變量作外部聲明。

需要注意的是:

(1)可以對同一個變量進行多次extern的聲明。若在聲明時,給一個外部變量賦初值,則編譯器認為是一個具體的變量定義,而不是一個外部變量的聲明,此時要注意同名標識符的重復(fù)定義。例如:

extern int n=1;                     // 變量定義
…
int n;                              // 錯誤:變量n重復(fù)定義

(2)雖然外部變量對不同源文件中或函數(shù)之間的數(shù)據(jù)傳遞特別有用。但也應(yīng)該看到,這種能被許多函數(shù)共享的外部變量,其數(shù)值的任何一次改變,都將影響到所有引用此變量的函數(shù)的執(zhí)行結(jié)果,其危險性是顯然的。

1.5.9 編譯預(yù)處理

在進行C++編程時,可以在源程序中加入一些編譯命令,以告訴編譯器如何對源程序進行編譯。由于這些命令是在程序編譯時被執(zhí)行的,也就是說,在源程序編譯以前,要先處理這些編譯命令,所以,也把它們稱為編譯預(yù)處理。實際上,編譯預(yù)處理命令不能算是C++語言的一部分,但它擴展了C++程序設(shè)計的能力,合理地使用編譯預(yù)處理功能,可以使得編寫的程序便于閱讀、修改、移植和調(diào)試。

C++提供的預(yù)處理命令主要有三種:宏定義命令、文件包含命令、條件編譯命令。這些命令在程序中都以“#”來引導(dǎo),每一條預(yù)處理命令必須單獨占用一行;由于它不是C++的語句,因此在結(jié)尾沒有分號(;)。由于前面已介紹過文件包含命令的使用,故這里僅介紹宏定義命令和條件編譯命令。

宏定義就是用一個指定的標識符來代替一個字符串,C++中宏定義是通過宏定義命令#define來實現(xiàn)的,它有兩種形式:不帶參數(shù)的宏定義和帶參數(shù)的宏定義。

1.不帶參數(shù)的宏定義

在以前的程序中,曾用過#define定義一個標識符常量,一般都不帶參數(shù)。

不參數(shù)的宏定義命令的一般格式為:

#define   <宏名>  定義內(nèi)容

例如:

#define   PI  3.141593

其中,#define是宏定義命令,PI稱為宏名。在程序編譯時,編譯器首先將程序中的PI用3.141593來替換,然后再進行代碼編譯。需要注意的是:

(1)#define、PI和3.141593之間一定要有空格,且一般將宏名定義成大寫,以與普通標識符相區(qū)別。

(2)宏后面的內(nèi)容實際上是字符串,編譯器本身不對其進行任何語法檢查,僅僅用來在程序中作與宏名的簡單替換。例如,若有:

#define   PI  3.141ABC593

它是一個合法的宏定義。

(3)宏被定義后,使用下列命令可重新定義:

#undef    宏名

(4)一個定義過的宏名可以用來定義其他新的宏,但要注意其中的括號,例如:

#define   WIDTH   80
#define   LENGTH  (WIDTH+10)

宏LENGTH等價于:

#define   LENGTH  (80+10)

但其中的括號不能省略,因為當(dāng)

var = LENGTH * 20;

若宏LENGTH定義中有括號,則預(yù)處理后變成:

var = ( 80 + 10 ) * 20;

若宏LENGTH定義中沒有括號,則預(yù)處理后變成:

var = 80 + 10 * 20;

顯然,兩者的結(jié)果是不一樣的。

2.帶參數(shù)的宏定義

帶參數(shù)的宏定義命令的一般格式為:

#define   <宏名>(參數(shù)名表)  定義內(nèi)容

例如:

#define   MAX(a,b)  ((a)>(b)?(a):(b))

其中(a,b)是宏MAX的參數(shù)表,如果在程序中出現(xiàn)下列語句:

x = MAX(3, 9);

則預(yù)處理后變成:

x=((3)>(9)?(3):(9));                  // 結(jié)果為9

很顯然,帶參數(shù)的宏相當(dāng)于一個函數(shù)的功能,但卻比函數(shù)簡潔。但要注意:

(1)定義帶參數(shù)的宏時,宏名與左圓括號之間不能留有空格。否則,編譯器將空格以后的所有字符均作為替代字符串,而將該宏視為無參數(shù)的宏定義。

(2)帶參數(shù)的宏內(nèi)容字符串中,參數(shù)一定要加圓括號,否則不會有正確的結(jié)果。例如:

#define   AREA(r)  (3.14159*r*r)

如果在程序出現(xiàn)下列語句:

x = AREA(3+2);

則預(yù)處理后變成:

x=(3.14159*3+2*3+2);               // 結(jié)果顯然不等于3.14159*5*5

3.文件包含命令

所謂“文件包含”是指將另一個源文件的內(nèi)容合并到源程序中。C++語言提供了#include命令用來實現(xiàn)文件包含的操作,它有下列兩種格式:

#include  <文件名>
#include  "文件名"

文件名一般以.h為擴展名,因而稱它為“頭文件”,如前面程序中的iostream.h是頭文件的文件名。文件包含的兩種格式中,第1種格式是將文件名用尖括號“< >”括起來的,用來包含那些由系統(tǒng)提供的并放在指定子目錄中的頭文件,這稱為標準方式。第2種格式是將文件名用雙引號括起來的,這時,系統(tǒng)先在當(dāng)前工作目錄中查找要包含的文件,這稱為用戶方式,若找不到再按標準方式查找(即再按尖括號的方式查找)。所以,我們一般用尖括號的方式來包含系統(tǒng)庫函數(shù)所在的頭文件,以節(jié)省查找時間;而用雙引號來包含用戶自己編寫的文件。

“文件包含”命令是很有用的,它可以減少程序設(shè)計人員的重復(fù)勞動。例如,在編程中有時要使用一些符號常量(如PI=3.14159265, E=2.718),用戶可以將這些宏定義命令組成一個文件,然后其他人都可以用#include命令將這些符號常量包含到自己所寫的源文件中,避免了這些符號常量的再定義。

在使用#include命令時需要注意的是,一條#include命令只能包含一個文件,若想包含多個文件須用多條文件包含命令。例如:

#include <iostream.h>
#include <math.h>
//…

4.條件編譯命令

一般情況下,源程序中所有的語句都參加編譯,但有時也希望程序按一定的條件去編譯源文件的不同部分,這就是條件編譯。條件編譯使得同一源程序在不同的編譯條件下得到不同的目標代碼。C++提供的條件編譯命令有幾種常用的形式,現(xiàn)分別介紹如下:

(1)第1種形式:

#ifdef <標識符>
    <程序段1>
[#else
    <程序段2>]
#endif

其中,#ifdef、#else和#endif都是關(guān)鍵字,程序段是由若干條預(yù)處理命令或語句組成的。這種形式的含義是:如果標識符被#define命令定義過,則編譯程序段1,否則編譯程序段2。

例Ex_UseIfdef】 使用#ifdef條件編譯命令

#include <iostream.h>
#define  LI
int  main()
{
#ifdef  LI
    cout<<"Hello, LI!\n";
#else
    cout<<"Hello, everyone!\n";
#endif
    return 0;
}

程序運行結(jié)果如下:

Hello, LI!

(2)第2種形式:

#ifndef <標識符>
    <程序段1>
[#else
    <程序段2>]
#endif

這與前一種形式的區(qū)別僅在于,如果標識符沒有被#define命令定義過,則編譯程序段1,否則就編譯程序段2。

(3)第3種形式:

#if <表達式1>
    <程序段1>
[#elif <表達式2>
    <程序段2>
    …]
[#else
    <程序段n>]
#endif

其中,#if、#elif、#else和#endif是關(guān)鍵字。它的含義是,如果表達式1為true或不為0就編譯程序段1,如果表達式2為true或不為0就編譯程序段2……,如果各表達式都不為true就編譯程序段n。

例Ex_UseIf】 使用#if條件編譯命令

#include <iostream.h>
#define A  -1
int  main()
{
#if   A>0
    cout<<"a>0\n";
#elif  A<0
    cout<<"a<0\n";
#else
    cout<<"a==0\n";
#endif
    return 0;
}

程序運行結(jié)果如下:

a<0

若將“#define A -1”中的-1改為0,則程序的運行結(jié)果為:

a==0

以上是C++中最常用的預(yù)處理命令,它們都是在程序被正常編譯之前執(zhí)行的,而且它們可以根據(jù)需要放在程序的任何位置,但為了保證程序結(jié)構(gòu)的清晰性,提高程序的可讀性,應(yīng)將它們放在程序的開頭。

主站蜘蛛池模板: 濮阳市| 木兰县| 高雄市| 沂源县| 汶上县| 隆昌县| 勃利县| 阿坝| 龙江县| 江西省| 清苑县| 旺苍县| 凤城市| 景德镇市| 东平县| 高密市| 尤溪县| 平泉县| 洪洞县| 五峰| 北海市| 安化县| 潜山县| 新余市| 泾川县| 邵武市| 偃师市| 碌曲县| 西峡县| 盐源县| 开封县| 二连浩特市| 富宁县| 清原| 新乡县| 桑日县| 洮南市| 丹棱县| 大庆市| 尤溪县| 灌南县|