(注:在看本文是如果感覺內容有點突兀,請先瀏覽《C++語法基礎(上)》這篇文章幫助更好理解)
一.缺省參數
缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數時,如果沒有指定實參則采用該形參的缺省值,否則使用指定的實參,缺省參數分為全缺省和半缺省參數。(有些地方把缺省參數也叫默認參數)
?
? 全缺省就是全部形參給缺省值,半缺省就是部分形參給缺省值。C++ 規定半缺省參數必須從右往左依次連續缺省,不能間隔跳躍給缺省值。
示例代碼:
?以上代碼,展示了全缺省參數和半缺省參數的函數定義與使用。 add 函數是全缺省參數示例, sub 函數為半缺省參數示例。通過在 main 函數中調用,呈現不傳參時使用缺省值,傳參時使用指定實參的效果。
? 帶缺省參數的函數調用,C++ 規定必須從左到右依次給實參,不能跳躍給實參。
?
? 函數聲明和定義分離時,缺省參數不能在函數聲明和定義中同時出現,規定必須函數聲明給缺省值。
?
二.引用
1.引用的概念與定義
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。比如:《水滸傳》中李逵,宋江叫“鐵牛”,江湖上人稱“黑旋風”;林沖,外號豹子頭。所以一個引用也可以有多個別名。
類型& 引用別名 = 引用對象;
以上C++ 代碼,在?main?函數中,先定義了變量?a?并賦值為2 。接著定義了?b?和?c?作為?a?的引用,由于引用與被引用變量共用內存空間,對?c?執行自增操作后,?a?、?b?、?c?的值都變為3 ,運行結果驗證C++ 中引用的這一特性。
2.引用的特性
①引用在定義時必須初始化
②一個變量可以有多個引用
③引用一旦引用一個實體,再不能引用其他實體
?3.引用的使用
在C++ 實踐中,引用主要發揮于引用傳參與引用返回值兩大場景:
- 引用傳參:與指針傳參功能近似,卻更具便捷性。通過引用傳遞參數,可規避對象拷貝,提升程序運行效率,且能直接操作實參,改變其值。
- 引用返回值:應用場景較為復雜,這里先作簡要介紹,后續在類和對象章節將深入探討。它同樣能減少拷貝開銷,有時還可用于實現對特定對象的持續引用操作 。
?引用與指針在實際運用中相互配合,雖功能有所重疊,但各自特性鮮明,無法相互取代。值得注意的是,C++ 的引用與Java 等語言的引用差異顯著。C++ 中引用一經定義,便無法更改指向;而Java 里的引用則無此限制,可靈活變更指向。
?部分以C 代碼為主的《數據結構》教材,會采用C++ 引用替換指針傳參,旨在簡化程序邏輯,規避指針操作的復雜性。
引用傳參和指針傳參
?以上C++ 代碼展示了兩種實現交換功能的函數:一個通過引用傳參( Swap(int& x, int& y) ?),另一個通過指針傳參( Swap(int* x, int* y) ?) 。在 test 函數中對這兩個函數進行調用測試,分別傳入變量和變量地址。運行結果表明,二者都能成功實現變量值的交換,直觀體現引用和指針在參數傳遞用于修改實參值時的作用。
4.const引用
在C++ 里,?const?引用即常量引用 ,它是對變量的只讀引用(但不能賦值)。
?
從定義來講,當用?const?修飾引用時,就創建了這種只讀關系,一旦綁定到某個對象,通過該引用不能修改所綁定對象的值 。比如?const int& ret?= a;??,此后不能執行?ret?= 新值;??這樣的操作 。
?
其主要作用體現在兩方面:
?①避免數據拷貝:在函數參數傳遞中,若傳遞大對象,按值傳遞會產生拷貝開銷,使用?const?引用傳遞,能避免拷貝,提升程序性能 。例如函數?void func(const 大對象類型& obj)??,調用時不會對實參進行拷貝 。
②保證數據不可變性:能確保在函數內部不會意外修改引用的數據,為數據安全性提供保障 。在多函數調用、多人協作開發等場景下,可防止數據被誤改,讓代碼邏輯更清晰、可靠 。
?
此外,?const?引用還有個特殊之處,它可以和聲明類型不匹配 ,比如?const int& r = 3.14;??(編譯器會創建臨時量來實現轉換和綁定 ),但普通引用不允許這樣做 。
以下示例:
?代碼展示了 const 引用的特性。在 main 函數中,定義變量 a 并賦值為2 ,接著創建 const int& ra ?作為 a 的常量引用。由于 ra 是 const 引用,不能對其執行自增操作(如 ra++ ?被注釋掉 ),但原變量 a 可以自增。運行結果顯示 a 自增后的值為3 , ra 的值也同步變為3 ,體現?const 引用只讀且與原變量關聯的特性。
?這段?代碼在 main 函數中,先定義了常量 a 和變量 b ?,然后分別創建了它們的 const 引用 ra 和 rb ?。代碼注釋指出, int& ra = a; ?這種寫法是錯誤的 。因為 a 是 const 類型,普通引用不能綁定到 const 對象上,而 const 引用能確保不會通過引用意外修改對象的值,此例直觀體現const 引用的使用規則和對象綁定限制。
?這段?代碼在 main 函數中,展示了 const 引用的特殊用法及普通引用的限制。定義變量 a 后, const int& ra = 30; ?直接綁定字面量,體現 const 引用可綁定臨時值。而 int& rb = a * 3; ?等普通引用綁定臨時值或類型不匹配值會編譯報錯。 const int& rb = a * 3; ?、 const int& rd = d; ?能成功,表明 const 引用可在類型轉換等情況下,通過創建臨時變量實現綁定,凸顯?const 引用與普通引用在對象綁定上的差異。
?
5.引用與指針對比
(1 )定義與初始化
?
- 指針:指針是一個變量,存儲的是另一個變量的內存地址。它可以先定義,之后再賦值。例如 ?int *ptr;??先定義了一個整型指針 ?ptr?,后續可以 ?ptr = #??讓它指向變量 ?num?。指針初始化不是必須的,但未初始化就使用會導致未定義行為。
?
- 引用:引用是已存在變量的別名,必須在定義時初始化。例如 ?int num; int &ref = num;??定義 ?ref??作為 ?num??的引用,此后 ?ref??一直關聯 ?num?,無法再引用其他變量。
?
(2)內存占用
?
- 指針:指針本身需要占用內存空間來存儲所指向變量的地址。在32位系統中,指針通常占用4字節;64位系統中,指針一般占用8字節。
?
- 引用:從概念上講,引用不占用額外內存空間,因為它只是原變量的別名。但在實際實現中,某些編譯器可能會為引用分配與指針相同大小的空間來實現引用的功能。
?
(3)使用方式
?
- 指針:使用指針訪問其所指向變量的值時,需使用解引用操作符 ?*?。例如 ?int num = 10; int *ptr = # int value = *ptr;?,這里通過 ?*ptr??獲得 ?ptr??所指向的 ?num??的值。指針還支持指針運算,如 ?ptr++??可移動指針指向同一類型數組的下一個元素。
?
- 引用:使用引用就像使用原變量本身,直接通過引用名訪問和操作關聯變量。例如 ?int num = 10; int &ref = num; int value = ref;?,直接使用 ?ref??即可,無需額外操作符。
?
(4)空值情況
?
- 指針:指針可以指向空值,即 ?nullptr?(C++11 引入,之前用 ?NULL?)。例如 ?int *ptr = nullptr;?,在使用指針前通常需要檢查其是否為空,以避免空指針解引用錯誤。
?
- 引用:引用不能指向空值,因為定義引用時必須初始化,且一旦初始化就不能再改變引用對象。
?
(5)可修改性
?
- 指針:指針本身的值(即所指向的地址)可以改變,使其指向不同的變量。例如 ?int num1 = 10, num2 = 20; int *ptr = &num1; ptr = &num2;?,指針 ?ptr??先指向 ?num1?,之后可重新指向 ?num2?。
?
- 引用:引用一旦初始化,就不能再引用其他變量,始終與初始化時的變量綁定。
?
(6)應用場景
?
- 指針:常用于動態內存分配與釋放,操作數組,實現數據結構(如鏈表、樹)等場景。指針的靈活性使其在復雜數據操作中發揮重要作用。
?
- 引用:主要用于函數參數傳遞和返回值,避免對象拷貝開銷,同時保證對原對象的直接操作。在運算符重載等場景也常使用引用,以提供自然的語法和高效的操作。
?
三.nullptr
在C++ 中, 引入nullptr? 是為了替代NULL ,雖然兩者都可以?用于表示指針為空的狀態,但兩者存在差異。
?從類型角度,?NULL? 本質是整數 ?0? 或預處理器宏定義為 ?0?,常被視為整型,雖可賦值給指針,但會造成類型不匹配隱患。而 ?nullptr? 是C++11引入的關鍵字,類型為 ?std::nullptr_t?,專門用于表示空指針,與任何指針類型都能隱式轉換,類型安全性更好。
?使用場景上,?NULL? 在C和早期C++代碼中廣泛使用,不過在C++ 中易導致函數重載歧義,因它可被當成整數參與重載決議。?nullptr? 則消除了這種歧義,在C++11及以后代碼中,推薦使用 ?nullptr? 表示空指針,使代碼更清晰、安全,符合現代C++ 類型系統規范。
?這段C++ 代碼定義了兩個重載函數 f(int x) ?和 f(int* ptr) ?。在 main 函數中進行調用測試, f(0) ?調用了 f(int x) ?。原本想通過 f(NULL) ?調用指針版本的函數,卻因 NULL ?被定義為0 ,實際調用了 f(int x) ?。 f((int*)NULL) ?能正確調用指針版本,而 f((void*)NULL) ?編譯報錯。使用 nullptr ?則可準確調用 f(int* ptr) ?,清晰展現函數重載下指針類型調用及 NULL ?與 nullptr ?的區別。
?