小白學習大型C++源碼項目系列之函數

函數的定義

函數就是一段封裝好的,可以重複使用的代碼,它使得我們的程序更加模塊化,不需要編寫大量重複的代碼。

函數可以提前保存起來,並給它起一個獨一無二的名字,只要知道它的名字就能使用這段代碼。
函數還可以接收數據,並根據數據的不同做出不同的操作,最後再把處理結果反饋給我們。

對於函數的定義來說,使用return語句可以向外提供該函數執行的一下結果;
對於函數的調用者來說,是否可以使用函數中執行的一些操作結果,就在於函數是否使用return語句返回了對應的執行結果。

函數的的使用方法

從表面上看,函數在使用時必須帶上括號,有必要的話還要傳遞參數,函數的執行結果也可以賦值給其它變量。
例如,strcmp() 是一個用來比較字符串大小的函數,它的用法如下:

#include <stdio.h>
#include <string.h>
int main(){
char str1[] = “http://c.biancheng.net”;
char str2[] = “http://www.baidu.com”;
//比較兩個字符串大小
int result = strcmp(str1, str2);
printf(“str1 - str2 = %d\n”, result);
return 0;
}

str1 和 str2 是傳遞給 strcmp() 的參數,strcmp() 的處理結果賦值給了變量 result。

函數的返回值

既然函數可以處理數據,那就有必要將處理結果告訴我們,所以很多函數都有返回值(Return Value)。所謂返回值,就是函數的執行結果。例如:

char str1[] = “C Language”;
int len = strlen(str1);

strlen() 的處理結果是字符串 str1 的長度,是一個整數,我們通過 len 變量來接收。

函數返回值有固定的數據類型(int、char、float等),用來接收返回值的變量類型要一致。

引用是 C++ 的新增內容,在實際開發中會經常使用;
C++ 用的引用就如同C語言的指針一樣重要,但它比指針更加方便和易用,有時候甚至是不可或缺的。

同指針一樣,引用能夠減少數據的拷貝,提高數據的傳遞效率。

我們知道,參數的傳遞本質上是一次賦值的過程,賦值就是對內存進行拷貝。
所謂內存拷貝,是指將一塊內存上的數據複製到另一塊內存上。

對於像 char、bool、int、float 等基本類型的數據,它們佔用的內存往往只有幾個字節,對它們進行內存拷貝非常快速。
而數組、結構體、對象是一系列數據的集合,數據的數量沒有限制,可能很少,也可能成千上萬,對它們進行頻繁的內存拷貝可能會消耗很多時間,拖慢程序的執行效率。

C/C++ 禁止在函數調用時直接傳遞數組的內容,而是強制傳遞數組指針,而對於結構體和對象沒有這種限制,調用函數時既可以傳遞指針,也可以直接傳遞內容;爲了提高效率,我曾建議傳遞指針,這樣做在大部分情況下並沒有什麼不妥。

但是在 C++ 中,我們有了一種比指針更加便捷的傳遞聚合類型數據的方式,那就是引用(Reference)。

在 C/C++ 中,我們將 char、int、float 等由語言本身支持的類型稱爲基本類型,將數組、結構體、類(對象)等由基本類型組合而成的類型稱爲聚合類型。

引用(Reference)是 C++ 相對於C語言的又一個擴充。引用可以看做是數據的一個別名,通過這個別名和原來的名字都能夠找到這份數據。引用類似於 Windows 中的快捷方式,一個可執行程序可以有多個快捷方式,通過這些快捷方式和可執行程序本身都能夠運行程序;引用還類似於人的綽號(筆名),使用綽號(筆名)和本名都能表示一個人。

引用的定義方式類似於指針,只是用&取代了*,語法格式爲:type &name = data;
type 是被引用的數據的類型,name 是引用的名稱,data 是被引用的數據。
引用必須在定義的同時初始化,並且以後也要從一而終,不能再引用其它數據,這有點類似於常量(const 變量)。

凡是有引用類型的成員變量的類,不能有缺省構造函數。默認構造函數沒有對引用成員提供默認的初始化機制,也因此造成引用未初始化的編譯錯誤。

構造函數的形參必須爲引用類型,暫時還不知道該怎麼解釋,牽涉到引用的機制。

C++函數的三種傳遞方式爲:值傳遞、指針傳遞和引用傳遞

值傳遞

void fun(int x){
    x += 5;
    //修改的只是y在棧中copy x,
    // x只是y的一個副本,在內存中重新開闢的一塊臨時空間把y的值送給了x;
    // 這樣也增加了程序運行的時間,降低了程序的效率。
}

void main(void)
{
    int y = 0;
    fun(y);
    cout<<"y = \"<<y<<endl; //y = 0;
}

指針傳遞

void fun(int *x){
    *x += 5; //修改的是指針x指向的內存單元值
}
void main(void){
    int y = 0;
    fun(&y);
    cout<<<<\"y = \"<<y<<endl; //y = 5;
}

引用傳遞

void fun(int &x){
    x += 5; //修改的是x引用的對象值 &x = y;
}
void main(void){
    int y = 0;
    fun(y);
    cout<<<<\"y = \"<<y<<endl; //y = 5;
}

1.值傳遞:有一個形參向函數所屬的棧拷貝數據的過程,如果值傳遞的對象是類對象或是大的結構體對象,將耗費一定的時間和空間。

2.指針傳遞:同樣有一個形參向函數所屬的棧拷貝數據的過程,但拷貝的數據是一個固定爲4字節的地址。

3.引用傳遞:同樣有上述的數據拷貝過程,但其是針對地址的,相當於爲該數據所在的地址起了一個別名。
注意,引用在定義時需要添加&,在使用時不能添加&,使用時添加&表示取地址。

效率上講,指針傳遞和引用傳遞比值傳遞效率高。一般主張使用引用傳遞,代碼邏輯上更加緊湊、清晰。

引用傳遞做函數參數”是C++的特性,C語言不支持。

例如:數據結構帶&與不帶&

帶&的是引用型參數,它是地址傳遞,其實參會隨着形參的改變而改變;
不帶&的參數是一般參數,是值傳遞,其實參不會隨着形參的改變而改變。
所以,結構改變,並且需要傳回這種改變的要用引用型參數,否則用一般參數。
GetElem(L,i)只是找到第i個元素的值,線性表的結構並未發生任何改變,所以參數L前面不用加&。
ListInsert(&L,i,e)是在線性表L的第i個元素處插入一個數值爲e的元素,線性表L的結構發生了改變,長度增加了,所以在L前必須加上&。如果不加,顯示L時,新增元素就顯示不出來,顯示L的長度,也仍然是增加以前的值,比實際長度少1.

C++引用作爲函數返回值

引用除了可以作爲函數形參,還可以作爲函數返回值,請看下面的例子:

    #include <iostream>
    
    using namespace std;
    
    int &plus10(int &r) {
        r += 10;
        return r;
    }
    
    int main() {
        int num1 = 10;
        int num2 = plus10(num1);
        cout << num1 << " " << num2 << endl;
        return 0;
    }

運行結果:
20 20

在將引用作爲函數返回值時應該注意一個小問題,就是不能返回局部數據(例如局部變量、局部對象、局部數組等)的引用,
因爲當函數調用完成後局部數據就會被銷燬,有可能在下次使用時數據就不存在了,C++ 編譯器檢測到該行爲時也會給出警告。

更改上面的例子,讓 plus10() 返回一個局部數據的引用:

 #include <iostream>
    using namespace std;
    int &plus10(int &r) {
        int m = r + 10;
        return m;  //返回局部數據的引用
    }
    int main() {
        int num1 = 10;
        int num2 = plus10(num1);
        cout << num2 << endl;
        int &num3 = plus10(num1);
        int &num4 = plus10(num3);
        cout << num3 << " " << num4 << endl;
        return 0;
    }

在 GCC 下的運行結果:

20
30 30

plus10() 返回一個對局部變量 m 的引用,這是導致運行結果非常怪異的根源,因爲函數是在棧上運行的,並且運行結束後會放棄對所有局部數據的管理權,後面的函數調用會覆蓋前面函數的局部數據。

函數重載(Function Overloading)

所謂重載,就是賦予新的含義。函數重載(Function Overloading)可以讓一個函數名有多種功能,在不同情況下進行不同的操作。運算符重載(Operator Overloading)也是一個道理,同一個運算符可以有不同的功能。

實際上,我們已經在不知不覺中使用了運算符重載。例如,+號可以對不同類型(int、float 等)的數據進行加法操作;<<既是位移運算符,又可以配合 cout 向控制檯輸出數據。C++ 本身已經對這些運算符進行了重載。

運算符重載(Operator Overloading)

運算符重載其實就是定義一個函數,在函數體內實現想要的功能,當用到該運算符時,編譯器會自動調用這個函數。也就是說,運算符重載是通過函數實現的,它本質上是函數重載。

運算符重載的格式爲:
返回值類型 operator 運算符名稱 (形參表列){
//TODO:
}
operator是關鍵字,專門用於定義重載運算符的函數。我們可以將operator 運算符名稱這一部分看做函數名。

運算符重載函數除了函數名有特定的格式,其它地方和普通函數並沒有區別。

以成員函數的形式重載

Complex & operator+=(const Complex &c);
Complex & operator-=(const Complex &c);
Complex & operator*=(const Complex &c);
Complex & operator/=(const Complex &c);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章