一、函數的基礎
1.什么是函數?(獨立的功能單位)
函數是C++中封裝代碼邏輯的基本單元,用于執行特定任務。
作用:代碼復用、模塊化、提高可讀性。
2、函數的基本結構
返回類型 函數名(參數列表) {// 函數體return 返回值; // 若返回類型非void
}
示例1:最簡單的函數
#include<iostream>
using namespace std;
int func(int n)
{
/*求解階乘
輸入:一個正整數n
輸出:n的階乘*/int result = 1;while(n > 1){result *= n--;}return result;
}int main() {int r=func(5);//調用cout<<r<<endl;return 0;
}
調用過程 如下:
函數的調用完成兩項工作:一是用實參初始化函數對應的形參,二是將控制權轉移給被調用函數。此時,主調函數(calling function)的執行被暫時中斷,被調函數(called function)開始執行。



當遇到一條 return 語句時函數結束執行過程。和函數調用一樣,return 語句也完成兩項工作:一是返回 return 語句中的值(如果有的話),二是將控制權從被調函數轉移回主調函數。函數的返回值用于初始化調用表達式的結果,之后繼續完成調用所在的表達式的剩余部分。?

func等效如下:
int n = 5; // 用字面值 5 初始化 n
int result = 1; //func 函數體內的代碼
while (n> 1)
result *= n--;
int r = result; // 用 result的副本初始化 j
?3.局部對象
?什么是局部對象?
局部對象是指在函數內部或代碼塊(如{}包圍的語句塊)內定義的對象。
特點:
- ?作用域(Scope)?:僅在定義它的函數或代碼塊內可見。
- ?生命周期(Lifetime)?:從定義處開始,到離開作用域時自動銷毀。
局部對象的生命周期
1. 普通局部對象(自動對象)
- ?創建:執行到定義語句時創建。
- ?銷毀:離開作用域時,按定義順序的逆序自動銷毀。
- ?存儲位置:通常存儲在棧(Stack)內存中,由編譯器自動管理。
#include<iostream>
using namespace std;
void func(int n)
{int a = n; // 局部對象a,作用域在func內{ // 進入代碼塊double b = 3.14; // 局部對象b,作用域僅在此代碼塊內cout << a << " " << b << endl; // 輸出a和b}// 離開代碼塊,b被銷毀// a仍有效 cout << a << endl;
}// 離開函數,a被銷毀int main() {func(10);return 0;
}
?生命周期可視化如下:



2. 局部靜態對象
- 用
static關鍵字修飾的局部對象。 - ?生命周期:從首次執行定義語句開始,到程序結束。
- ?初始化:僅第一次執行時初始化一次。
如果有必要令局部變量的生命周期貫穿函數調用及之后的時間,可以將局部變量定義成 static 類型從而獲得這樣的對象。局部靜態對象(local static object)在程序的執行路徑第一次經過對象定義語句時初始化,并且直到程序終止才被銷毀,在此期間即使對象所在的函數結束執行也不會對它有影響。
void counter() {static int count = 0; // 靜態局部對象,生命周期為整個程序count++;cout << count << endl;
}int main() {counter(); // 輸出1counter(); // 輸出2return 0;
}


?
3、局部對象的作用域限制
局部對象僅在定義的作用域內可見,外部無法訪問。
void func1() {int x = 5; // 局部對象x
}void func2() {// cout << x; // 錯誤!x在func1中,此處不可見
}
4、局部對象與全局對象的對比
| 特性 | 局部對象 | 全局對象 |
|---|---|---|
| 作用域 | 定義所在的函數或代碼塊 | 整個程序 |
| 生命周期 | 作用域內有效 | 程序運行期間始終有效 |
| 存儲位置 | 棧內存(自動管理) | 全局數據區(靜態存儲) |
| 訪問權限 | 僅在作用域內可訪問 | 所有函數可訪問 |
| 初始化 | 每次進入作用域時初始化 | 程序啟動時初始化 |
5、注意事項
1. 不要返回局部對象的指針或引用
局部對象在函數返回后會被銷毀,返回其指針或引用會導致懸垂指針/引用?(Dangling Pointer/Reference)。
// 錯誤示例:返回局部對象的引用
int& badFunction() {int x = 10; // x是局部對象,函數返回后x被銷毀return x; // 返回懸垂引用,未定義行為!
}// 正確做法:返回值而非引用
int goodFunction() {int x = 10;return x; // 返回x的副本
}
2. 對象銷毀順序
局部對象按定義的逆序銷毀,依賴其他對象的資源時需注意順序。
class Logger {
public:Logger() { cout << "Logger created" << endl; }~Logger() { cout << "Logger destroyed" << endl; }
};void test() {Logger log1; // 先創建Logger log2; // 后創建
} // 先銷毀log2,再銷毀log1(逆序銷毀)
3. 避免在局部作用域中創建過大對象
棧內存有限(通常幾MB),過大的對象可能導致棧溢出。
解決方案:使用動態內存(堆內存)分配。
void safeFunction() {// 棧內存可能溢出(錯誤)// int hugeArray[1000000]; // 改用堆內存(正確)int* hugeArray = new int[1000000];delete[] hugeArray; // 需手動釋放
}
6、實際應用示例
示例1:局部對象在循環中的使用
#include <iostream>
using namespace std;int main() {for (int i = 0; i < 5; i++) { // i是局部對象,僅在循環內有效string message = "Iteration: " + to_string(i); // message每次循環重新創建cout << message << endl;} // 每次循環結束,message和i的當前副本被銷毀return 0;
}
示例2:利用RAII管理局部資源
#include <fstream>
using namespace std;void readFile() {ifstream file("data.txt"); // 局部對象file,打開文件if (file.is_open()) {// 讀取文件內容string line;while (getline(file, line)) {cout << line << endl;}} // 離開作用域時,file的析構函數自動關閉文件
}
readFile?函數展示了 C++ 中文件操作的基本用法,以及局部對象的生命周期管理。1.?局部對象?
file?的創建
ifstream file("data.txt"); // 局部對象file,打開文件
使用?
ifstream?類型創建了一個名為?file?的局部對象,用于讀取文件?"data.txt"。文件在構造函數中被打開。如果文件不存在或無法打開,
file.is_open()?將返回?false。2.?檢查文件是否成功打開
if (file.is_open()) { // 讀取文件內容 }
調用?
file.is_open()?檢查文件是否成功打開。如果文件未成功打開,程序將跳過文件讀取部分。
3.?讀取文件內容
string line; while (getline(file, line)) { cout << line << endl; }
使用?
getline?函數逐行讀取文件內容。每次讀取一行并存儲到字符串變量?
line?中。如果文件結束或發生錯誤,
getline?返回?false,循環結束。4.?局部對象的作用域和析構
} // 離開作用域時,file的析構函數自動關閉文件
當程序執行到?
}?時,局部對象?file?的作用域結束。在 C++ 中,當局部對象超出作用域時,其析構函數會被自動調用。
對于?
ifstream?對象,析構函數會自動關閉文件,因此無需手動調用?file.close()。
總結
這段代碼展示了 C++ 中文件讀取的基本流程,以及局部對象的生命周期管理:
使用?
ifstream?打開文件。檢查文件是否成功打開。
使用?
getline?逐行讀取文件內容。離開作用域時,局部對象的析構函數自動釋放資源(如關閉文件)。
這種設計利用了 C++ 的 RAII(Resource Acquisition Is Initialization)機制,確保資源(如文件句柄)在不再需要時能夠被正確釋放,避免資源泄漏。
小貼士:
C++ 的?RAII(Resource Acquisition Is Initialization,資源獲取即初始化)?是一種管理資源的編程機制,核心思想是:通過對象的生命周期來控制資源的獲取與釋放。
實現方式
構造函數獲取資源:對象創建時,在構造函數中申請資源(如內存、文件句柄、網絡連接、互斥鎖等)。
析構函數釋放資源:對象生命周期結束(如離開作用域、被銷毀)時,自動調用析構函數釋放資源。
核心優勢
避免資源泄漏:無論程序正常退出還是因異常終止,對象析構函數都會執行,確保資源釋放。
異常安全:若構造函數成功獲取資源,后續操作即使拋出異常,析構函數仍會釋放資源,保證程序狀態正確。
7、小結
- ?局部對象的作用域和生命周期是C++高效內存管理的基礎。
- ?避免返回局部對象的指針或引用,防止未定義行為。
- ?合理使用靜態局部對象實現跨函數調用的狀態保持。
- ?棧內存限制要求對大型對象使用動態內存分配。
4、函數組成要素詳解
1. 返回類型
void表示無返回值。- 必須與
return語句的類型匹配。
void printHello() {std::cout << "Hello!";// 無return語句(允許)
}
2. 參數列表
- 可以是零個或多個參數,用逗號分隔。
- 參數傳遞方式:值傳遞、引用傳遞、指針傳遞(后文詳解)。
3. 函數體
- 包含具體執行的代碼。
- 局部變量在函數結束時銷毀。
?5、函數聲明與定義
1、函數聲明的作用
函數聲明(也稱函數原型,Function Prototype)的主要目的是向編譯器告知函數的存在,允許在函數定義之前調用它。
核心作用:
-
?類型檢查:確保調用時參數類型和返回值類型正確。
-
?分離編譯:支持多文件編程,聲明通常放在頭文件(
.h)中。 -
?解決依賴:允許函數A調用函數B,而無需考慮定義的先后順序。
2、函數聲明的語法
返回類型 函數名(參數類型列表); // 結尾必須有分號!
-
?參數類型列表:只需指定參數類型,形參名可省略(但建議保留以增強可讀性)。
-
?示例:
// 聲明一個加法函數 int add(int, int); // 省略形參名 int multiply(int a, int b); // 保留形參名(推薦)
3、函數聲明 vs. 函數定義
| 特性 | 函數聲明 | 函數定義 |
|---|---|---|
| ?分號 | 必須有分號結尾 | 無分號 |
| ?函數體 | 無函數體(僅聲明接口) | 必須包含函數體(具體實現) |
| ?出現次數 | 可多次聲明(需一致) | 只能定義一次(單定義規則) |
示例1:聲明與定義分離
// 聲明(頭文件 math_utils.h)
int add(int a, int b);// 定義(源文件 math_utils.cpp)
int add(int a, int b) {return a + b;
}
4、函數聲明的位置
1. 在頭文件中聲明(推薦)
-
頭文件(
.h)用于集中放置函數聲明,供多個源文件共享。 -
?示例:
// math_utils.h #ifndef MATH_UTILS_H // 頭文件保護,防止重復包含 #define MATH_UTILS_Hint add(int a, int b); double add(double a, double b); // 函數重載聲明#endif
2. 在調用前聲明
-
在調用函數的代碼前直接聲明(適用于簡單程序)。
-
?示例:
#include <iostream> using namespace std;// 聲明printMessage函數 void printMessage(const string& message);int main() {printMessage("Hello, World!");return 0; }// 定義printMessage void printMessage(const string& message) {cout << message << endl; }
5、函數聲明的注意事項
1. 默認參數的聲明規則
-
默認參數只能在函數聲明中指定,不能在定義中重復。
-
若聲明和定義分離,默認參數應放在聲明中(通常位于頭文件)。
// 聲明(math_utils.h) void log(const string& message, int level = 1);// 定義(math_utils.cpp) void log(const string& message, int level) { // 此處不可寫=1// 實現... }
2. 函數重載的聲明
-
重載函數的聲明必須通過參數列表區分(返回類型不同不算重載)。
// 合法重載 int max(int a, int b); double max(double a, double b);// 錯誤:僅返回類型不同,無法重載 double getValue(); int getValue(); // 編譯錯誤
3. 函數聲明的作用域
-
聲明的作用域從聲明位置開始,到所在作用域結束。
-
?示例:
void outer() {// 錯誤:inner()未聲明// inner(); void inner(); // 聲明inner函數inner(); // 合法調用 }void inner() {cout << "Inner function" << endl; }
6、未聲明函數的后果
若調用函數前未聲明,可能導致:
-
?編譯錯誤:編譯器無法識別函數。
-
?隱式聲明風險:C語言允許隱式聲明,但C++已廢棄此行為,現代編譯器會報錯。
int main() {int result = add(3, 5); // 若未聲明add,編譯失敗return 0; }
7、實際應用示例
示例1:多文件編程
// ---------- math_utils.h ----------
#ifndef MATH_UTILS_H
#define MATH_UTILS_Hint factorial(int n); // 聲明階乘函數#endif// ---------- math_utils.cpp ----------
#include "math_utils.h"int factorial(int n) { // 定義if (n <= 1) return 1;return n * factorial(n - 1);
}// ---------- main.cpp ----------
#include <iostream>
#include "math_utils.h"int main() {std::cout << factorial(5); // 輸出120return 0;
}
示例2:前置聲明解決循環依賴
// 前置聲明B類
class B;class A {
public:void callB(B& b); // 聲明函數,參數為B的引用
};class B {
public:void callA(A& a) { a.callB(*this); } // 定義函數
};// A::callB的定義需在B類定義之后
void A::callB(B& b) { /* ... */ }
8、小結
-
?聲明先于調用:函數必須在使用前聲明。
-
?頭文件集中管理:聲明應放在頭文件中,并通過頭文件保護避免重復包含。
-
?默認參數在聲明中指定:確保調用者可見。
-
?聲明與定義一致:參數列表和返回類型必須嚴格匹配。
二、參數傳遞
1、參數傳遞的基本概念
參數傳遞是函數調用時,將數據從調用方傳遞給函數的方式。
核心作用:
- 控制函數內對原始數據的訪問權限(是否允許修改原數據)。
- 優化性能(避免不必要的拷貝)。
2、值傳遞(Pass by Value)
1. 機制
-
將實參的副本傳遞給函數,函數內修改形參不會影響原始數據。
-
?適用場景:基本數據類型(
int,?double等)或小型結構體。
2. 示例
void increment(int x) {x++; // 修改的是副本
}int main() {int a = 5;increment(a);cout << a; // 輸出5(原值未變)
}

3. 特點
-
?優點:安全,函數內操作不影響原數據。
-
?缺點:對大型對象(如類、數組)會產生拷貝開銷。
3、引用傳遞(Pass by Reference)
1. 機制
- 傳遞實參的別名?(引用),函數內修改形參直接影響原始數據。
- ?適用場景:需要修改原數據,或傳遞大型對象避免拷貝。
2. 示例
void increment(int &x) {x++; // 修改原數據
}int main() {int a = 5;increment(a);cout << a; // 輸出6(原值被修改)
}

3. 特點
- ?優點:避免拷貝開銷,允許修改原數據。
- ?缺點:需注意函數可能意外修改數據(可用
const解決)。
4、指針傳遞(Pass by Pointer)
1. 機制
- 傳遞實參的內存地址,函數內通過指針操作原始數據。
- ?適用場景:需要顯式傳遞地址,或允許
nullptr(空指針)的情況。
2. 示例
void increment(int *x) {if (x != nullptr) { // 必須檢查空指針!(*x)++; // 解引用后修改原數據}
}int main() {int a = 5;increment(&a); // 傳遞地址cout << a; // 輸出6
}

3. 特點
- ?優點:明確表示可能修改原數據,支持動態內存操作。
- ?缺點:語法復雜,需手動檢查空指針。
5、const修飾符的應用
1.?const引用
- 禁止通過引用修改原數據,同時避免拷貝。
void printLargeObject(const BigClass &obj) {// 只能讀取obj,無法修改// 避免拷貝BigClass的副本
}
2.?const指針
- 禁止通過指針修改數據,或禁止修改指針本身。
void readData(const int *ptr) { // 不能通過ptr修改數據cout << *ptr;
}void readDate(int* const ptr){//不能修改ptr本身cout<<*ptr;
}
6、值傳遞 vs. 引用傳遞 vs. 指針傳遞對比
| 特性 | 值傳遞 | 引用傳遞 | 指針傳遞 |
|---|---|---|---|
| ?修改原數據 | 不允許 | 允許 | 允許 |
| ?拷貝開銷 | 有(副本) | 無(別名) | 無(傳遞地址) |
| ?空值支持 | 不適用 | 不適用 | 支持(nullptr) |
| ?語法簡潔性 | 簡單 | 簡單 | 較復雜 |
| ?典型用途 | 基本數據類型 | 大型對象/需修改數據 | 動態內存/可選參數 |
7、高級傳遞方式(C++11+)
1. 移動語義(Move Semantics)
- 通過
std::move將資源所有權轉移,避免拷貝(適用于臨時對象)。
void processBigData(BigData &&data) { // 右值引用// 移動資源,避免拷貝
}BigData data;
processBigData(std::move(data)); // 轉移所有權
2. 萬能引用(Universal Reference)與完美轉發
- 使用
auto&&和std::forward保留參數類型(模板編程中常見)。
template<typename T>
void relay(T &&arg) {process(std::forward<T>(arg)); // 完美轉發
}
8、參數傳遞的選擇指南
- ?需要修改原數據?→ 使用引用傳遞?(優先)或指針傳遞。
- ?僅讀取數據且對象較大?→ 使用
const引用傳遞。 - ?基本數據類型(如
int)??→ 值傳遞或const引用均可。 - ?動態內存或可選參數?→ 指針傳遞(需檢查
nullptr)。 - ?避免拷貝臨時對象?→ 使用移動語義(C++11+)。
9、常見錯誤與解決方案
1. 返回局部對象的引用或指針
int& badFunction() {int x = 10;return x; // 錯誤:x在函數結束時被銷毀
}
解決:返回值(副本)或傳遞動態分配的對象。
2. 懸垂引用(Dangling Reference)
int& ref;
{int a = 5;ref = a; // a的引用在代碼塊外失效
}
cout << ref; // 未定義行為
解決:確保引用的對象生命周期足夠長(可以嘗試static)。
3. 未初始化指針
int *ptr;
*ptr = 5; // ptr未初始化,指向無效地址
解決:始終初始化指針(如int *ptr = &valid_var或ptr = new int)。
10、一個小栗子
#include <iostream>
using namespace std;// 值傳遞:安全但低效(適合小對象)
void printValue(int val) {cout << "Value: " << val << endl;
}// 引用傳遞:高效且可修改原數據
void square(int &num) {num *= num;
}// const引用:高效且防止修改(適合大對象)
void printBigObject(const BigData &data) {data.display();
}// 指針傳遞:允許空值,顯式傳遞地址
void allocateMemory(int ?**ptr) {*ptr = new int(100); // 修改指針指向的內容
}int main() {int a = 5;square(a); // 引用傳遞,a變為25printValue(a); // 值傳遞,輸出25int *p = nullptr;allocateMemory(&p); // 指針傳遞,p指向新內存cout << *p; // 輸出100delete p;return 0;
}
11、小結
- ?值傳遞:簡單安全,適合小型數據。
- ?引用傳遞:高效靈活,優先用于大型對象或需修改原數據。
- ?指針傳遞:靈活但需謹慎,適合動態內存或可選參數。
- ?
const修飾符:保護數據不被意外修改,提升代碼健壯性。 - ?現代C++特性:移動語義和完美轉發可優化資源管理。
三、返回類型和return語句
1、返回類型的作用
返回類型定義了函數返回數據的類型,決定了調用者如何接收和處理結果。
關鍵點:
- 必須與
return語句返回的值類型匹配(可隱式轉換)。 - 若返回類型為
void,函數不能返回任何值。
2、基本返回類型與return用法
1. 返回基本數據類型(int,?double等)
int add(int a, int b) {return a + b; // 返回int類型值
}double divide(double a, double b) {return a / b; // 返回double類型值
}
2. 返回void(無返回值)
void printHello() {cout << "Hello";return; // 可省略,函數執行到末尾自動返回
}void earlyExit(bool flag) {if (flag) {return; // 提前退出函數}cout << "Continue...";
}
3、返回引用與指針
1. 返回引用(避免拷貝,但需注意生命周期)
// 返回靜態局部變量的引用(安全)
int& getStaticValue() {static int value = 10; // 靜態變量,生命周期到程序結束return value;
}// 錯誤示例:返回局部變量的引用(懸垂引用)
int& badFunction() {int x = 5; // x是局部變量,函數返回后被銷毀return x; // 未定義行為!
}
2. 返回指針(需確保內存有效)
// 返回動態內存的指針(調用者需負責釋放)
int* createArray(int size) {int* arr = new int[size];return arr;
}// 錯誤示例:返回局部變量的指針
int* badPointer() {int x = 10;return &x; // x被銷毀后指針失效!
}
4、返回對象
1. 返回值對象(觸發拷貝構造或移動構造)
class MyClass {
public:MyClass() { cout << "Constructor" << endl; }MyClass(const MyClass&) { cout << "Copy Constructor" << endl; }MyClass(MyClass&&) { cout << "Move Constructor" << endl; }
};MyClass createObject() {MyClass obj;return obj; // 可能觸發返回值優化(RVO)
}int main() {MyClass obj = createObject(); // 輸出可能僅為"Constructor"(RVO優化)
}
2. 使用移動語義提升效率(C++11+)
MyClass createObject() {MyClass obj;return std::move(obj); // 強制移動語義(可能阻止RVO)
}
5、返回類型與return的匹配規則
1. 隱式類型轉換
return的值類型可隱式轉換為返回類型。
double func() {return 3; // int隱式轉換為double
}
2. 列表初始化(C++11+)
std::vector<int> getNumbers() {return {1, 2, 3}; // 返回初始化列表
}
3. 返回auto(C++14+)
auto add(double a, double b) -> decltype(a + b) {return a + b; // 返回類型由a+b推導
}// 更簡化的寫法(C++14)
auto multiply(double a, double b) {return a * b; // 自動推導返回類型
}
6、常見錯誤與解決方案
1. 返回局部對象的引用/指針
int& badRef() {int x = 10;return x; // x被銷毀,返回懸垂引用
}
解決:返回靜態變量、動態內存或參數中的引用。
2. 返回類型與return不匹配
int func() {return 3.14; // double轉int導致精度丟失(警告)
}
解決:顯式轉換或修改返回類型。
3. 遺漏return語句
int badFunc(bool flag) {if (flag) {return 1;}// 未處理flag=false的情況,導致未定義行為
}
解決:確保所有路徑都有返回值。
7、高級特性
1. 尾置返回類型(C++11+)
auto getData() -> int (*)[5] { // 返回指向int[5]的指針static int arr[5] = {1,2,3,4,5};return &arr;
}
2. 多返回值(通過結構體或std::tuple)
#include <tuple>std::tuple<int, double> getValues() {return std::make_tuple(42, 3.14);
}int main() {auto [a, b] = getValues(); // C++17結構化綁定
}
8、小結
- ?返回類型需與
return值匹配,注意隱式轉換規則。 - ?返回引用/指針時,必須確保對象生命周期足夠長。
- ?返回對象時,優先依賴編譯器優化(RVO),而非顯式
std::move。 - ?現代C++特性(如
auto、移動語義、結構化綁定)可簡化代碼并提升效率。 - ?避免常見錯誤:懸垂引用、遺漏
return、類型不匹配。
四、函數重載(Overloading)
1、什么是函數重載?
函數重載允許在同一作用域內定義多個同名函數,但這些函數的參數列表必須不同?(參數類型、數量或順序不同)。
核心目的:提供語義相同的操作,但支持不同類型或數量的參數,提升代碼可讀性和靈活性。
// 重載示例
int sum(int a, int b) { return a + b; }
double sum(double a, double b) { return a + b; }
int sum(int a, int b, int c) { return a + b + c; }
2、函數重載的規則
- ?參數列表必須不同:以下任一條件滿足即可:
- 參數類型不同(如
int?vs?double)。 - 參數數量不同。
- 參數順序不同(但需類型不同)。
- 參數類型不同(如
- ?返回類型不同不構成重載:僅返回類型不同會導致編譯錯誤。
- ?作用域相同:重載函數必須在同一作用域(如全局作用域或同一類中)。
3、函數重載的示例
示例1:參數類型不同
void print(int x) {cout << "Integer: " << x << endl;
}void print(double x) {cout << "Double: " << x << endl;
}int main() {print(5); // 調用print(int)print(3.14); // 調用print(double)
}
示例2:參數數量不同
int sum(int a, int b) {return a + b;
}int sum(int a, int b, int c) {return a + b + c;
}int main() {cout << sum(1, 2); // 輸出3cout << sum(1, 2, 3); // 輸出6
}
示例3:參數順序不同
void show(int a, double b) {cout << "int, double" << endl;
}void show(double a, int b) {cout << "double, int" << endl;
}int main() {show(3, 4.5); // 調用第一個函數show(4.5, 3); // 調用第二個函數
}
4、函數重載的解析機制
編譯器根據調用時的實參類型和數量選擇最匹配的函數,優先級如下:
- ?精確匹配?(類型完全一致)。
- ?隱式轉換匹配?(如
int→double)。 - ?用戶定義的轉換?(如類類型轉換運算符)。
示例:匹配優先級
void func(int x) { cout << "int" << endl; }
void func(double x) { cout << "double" << endl; }int main() {func(5); // 精確匹配func(int)func(5.0); // 精確匹配func(double)func('a'); // 隱式轉換char→int,調用func(int)
}
5、函數重載與const修飾符
1. 頂層const不構成重載
void func(int x) { /* ... */ }
void func(const int x) { /* ... */ } // 錯誤:重復定義
2. 底層const指針或引用構成重載
void func(int *ptr) { /* ... */ }
void func(const int *ptr) { /* ... */ } // 合法重載void func(int &x) { /* ... */ }
void func(const int &x) { /* ... */ } // 合法重載
示例:const引用重載
void process(string &str) {str += " (modified)";
}void process(const string &str) {cout << str << " (read-only)" << endl;
}int main() {string s = "Hello";const string cs = "Hi";process(s); // 調用非常量版本process(cs); // 調用常量版本
}
6、函數重載與默認參數
默認參數可能導致二義性調用,需謹慎使用。
void draw(int x, int y = 0) { /* ... */ }
void draw(int x) { /* ... */ }int main() {draw(5); // 錯誤:兩個函數都匹配
}
7、函數重載與模板
1. 函數模板支持隱式重載
template<typename T>
void print(T value) {cout << "Template: " << value << endl;
}void print(int x) {cout << "Non-template: " << x << endl;
}int main() {print(5); // 調用非模板函數(更匹配)print(3.14); // 調用模板函數(T=double)
}
2. 特化模板時需注意重載規則
template<>
void print(int x) { // 顯式特化cout << "Specialized: " << x << endl;
}
8、類成員函數的重載
類成員函數可以重載,包括構造函數。
class Rectangle {
public:// 構造函數重載Rectangle() { width = height = 0; }Rectangle(int w, int h) : width(w), height(h) {}// 成員函數重載void scale(double factor) { width *= factor; height *= factor; }void scale(int factor) { width *= factor; height *= factor; }
private:int width, height;
};
9、常見錯誤與解決方案
1. 二義性調用
void func(float x) {}
void func(double x) {}int main() {func(3.14); // 匹配func(double)func(3.14f); // 匹配func(float)func(5); // 錯誤:5可轉float或double,編譯器無法決定
}
解決:顯式指定類型,如func(static_cast<float>(5))。
2. 隱藏基類重載函數
class Base {
public:void func(int x) {}
};class Derived : public Base {
public:void func(double x) {} // 隱藏Base::func(int)
};int main() {Derived d;d.func(5); // 調用Derived::func(double),Base::func(int)被隱藏
}
解決:使用using Base::func;引入基類重載。
10、小結
- ?函數重載的核心是參數列表不同,返回類型不影響重載。
- 優先通過參數類型、數量或順序實現重載,避免二義性。
const修飾符在指針或引用參數中可構成重載。- 注意與模板、默認參數和繼承的交互關系。
- 合理使用重載提升代碼可讀性,但避免過度設計。
溫馨小貼士:重載和作用域的關系
1、函數重載的基本前提
函數重載要求同名函數必須在同一作用域內,并且參數列表不同。
核心規則:
只有在同一作用域內,編譯器才會將同名函數視為重載候選。
不同作用域中的同名函數會觸發名稱隱藏?(Name Hiding),而非重載。
2、局部作用域 vs 全局作用域
?局部作用域中聲明同名函數會隱藏外部作用域的同名函數
#include <iostream> using namespace std;void func(int x) { cout << "Global func(int)" << endl; }int main() {func(5); // 調用全局func(int){ // 進入局部作用域void func(double x); // 聲明局部作用域的funcfunc(3.14); // 調用局部func(double)// func(5); 錯誤!全局func(int)被隱藏}func(5); // 再次調用全局func(int)return 0; }void func(double x) { cout << "Global func(double)" << endl; }結果:
Global func(int) Global func(double) Global func(int)解釋:
局部作用域中的
func(double)聲明隱藏了全局作用域的func(int)。全局
func(double)實際在局部作用域外定義,但局部作用域內的聲明優先。
3、類作用域中的函數重載
類的成員函數重載
class MyClass { public:void print(int x) { cout << "int: " << x << endl; }void print(double x) { cout << "double: " << x << endl; } };int main() {MyClass obj;obj.print(5); // 調用print(int)obj.print(3.14); // 調用print(double) }合法重載:兩個
4、繼承中的函數重載與作用域
派生類中同名函數隱藏基類重載
class Base { public:void func(int x) { cout << "Base::func(int)" << endl; }void func(double x) { cout << "Base::func(double)" << endl; } };class Derived : public Base { public:void func(const char* s) { cout << "Derived::func(const char*)" << endl; } };int main() {Derived d;d.func("Hello"); // 調用Derived::func(const char*)// d.func(5); 錯誤!Base::func(int)被隱藏d.Base::func(5); // 顯式調用基類函數 }解釋:
派生類中的
func(const char*)隱藏了基類的所有func重載版本。必須通過
Base::作用域運算符顯式調用基類函數。使用
using聲明恢復基類重載class Derived : public Base { public:using Base::func; // 引入基類func的所有重載void func(const char* s) { cout << "Derived::func(const char*)" << endl; } };int main() {Derived d;d.func(5); // 調用Base::func(int)d.func(3.14); // 調用Base::func(double)d.func("Hello"); // 調用Derived::func(const char*) }
5、命名空間作用域與重載
同一命名空間內的函數重載
namespace NS {void log(int x) { cout << "NS::log(int)" << endl; }void log(double x) { cout << "NS::log(double)" << endl; } }int main() {NS::log(5); // 調用log(int)NS::log(3.14); // 調用log(double) }不同命名空間的同名函數不構成重載
namespace NS1 { void log(int x) { /* ... */ } } namespace NS2 { void log(double x) { /* ... */ } }int main() {NS1::log(5); // 調用NS1::log(int)NS2::log(3.14); // 調用NS2::log(double)// log(5); 錯誤!未指定命名空間 }
6、函數模板與作用域
模板與非模板函數的重載
void process(int x) { cout << "Non-template" << endl; }template<typename T> void process(T x) { cout << "Template" << endl; }int main() {process(5); // 調用非模板函數(更匹配)process(3.14); // 調用模板函數(T=double) }規則:非模板函數優先于模板函數匹配。
7、作用域對重載的影響
?同一作用域:函數名相同且參數不同 → 合法重載。
?不同作用域:內層作用域的同名函數會隱藏外層作用域的所有重載版本。
?繼承中的重載:派生類函數隱藏基類同名函數,需用
using聲明恢復。?解決方案:
使用
using聲明引入外層作用域的重載。通過作用域運算符(如
Base::func)顯式調用隱藏函數。避免在不同作用域中定義同名函數,除非有意隱藏。
關鍵結論
?函數重載僅在相同作用域內有效。
?作用域隔離會導致名稱隱藏,而非重載。
合理使用
using聲明和作用域運算符管理不同作用域中的函數可見性。
五、內聯函數(inline)
1、內聯函數的作用
目的:通過將函數代碼直接插入調用位置,消除函數調用的開銷(壓棧、跳轉、返回),提高程序運行效率。
適用場景:短小且頻繁調用的函數(如簡單計算、訪問類成員)。
2、內聯函數的定義
1. 使用inline關鍵字
inline int add(int a, int b) {return a + b;
}
2. 類內定義的成員函數隱式內聯
class Calculator {
public:int multiply(int a, int b) { // 隱式內聯return a * b;}
};
小對比:
auto lambda = [](int a, int b) { return a + b; }; cout << lambda(2, 3); // 輸出5
3、內聯函數的底層邏輯
1. 編譯器行為
- ?建議而非強制:
inline是給編譯器的優化建議,最終是否內聯由編譯器決定。 - ?代碼膨脹風險:若函數體較大或頻繁調用,內聯會導致可執行文件體積增大。
2. 匯編代碼對比(示例)
- ?普通函數調用:生成
call指令跳轉到函數地址。 - ?內聯函數:函數體代碼直接插入調用位置,無
call指令。
4、內聯函數的優缺點
| ?優點 | ?缺點 |
|---|---|
| 減少函數調用開銷 | 增加代碼體積(多次展開) |
| 避免跳轉指令提升執行效率 | 不適合復雜或遞歸函數 |
| 可替代宏(類型安全、易調試) | 編譯器可能忽略inline建議 |
5、內聯函數的限制
- ?編譯器決策權:以下情況編譯器通常拒絕內聯:
- 函數體包含循環或遞歸。
- 函數體過長(如超過10行)。
- 虛函數(需動態綁定)。
- ?頭文件要求:內聯函數定義必須放在頭文件中,確保所有調用位置可見(否則鏈接錯誤)。
6、內聯函數 vs 宏
| ?特性 | 內聯函數 | 宏(#define) |
|---|---|---|
| ?處理階段 | 編譯期(語法檢查) | 預處理期(文本替換) |
| ?類型安全 | 支持(類型檢查) | 不支持(易引發錯誤) |
| ?調試支持 | 支持(可設置斷點) | 不支持 |
| ?作用域 | 遵循C++作用域規則 | 全局替換 |
示例:內聯函數替代宏
// 宏的隱患
#define SQUARE(x) ((x) * (x))
int a = 5;
int b = SQUARE(a++); // a被遞增兩次(未定義行為)// 內聯函數(安全)
inline int square(int x) { return x * x; }
int a = 5;
int b = square(a++); // a只遞增一次
解釋一下:
1.?宏定義?
SQUARE(x)
#define SQUARE(x) ((x) * (x))
宏定義是一種簡單的文本替換機制。在編譯之前,預處理器會將所有出現的?
SQUARE(x)?替換為?((x) * (x))。示例:
int b = SQUARE(a++); // 被替換為:int b = ((a++) * (a++));問題:
在表達式?
((a++) * (a++))?中,a++?被計算了兩次。C++ 標準中明確規定,對于同一個變量,在一個序列點(sequence point)之前對其修改多次是未定義行為。
因此,
a?的值可能會被遞增兩次,導致結果不可預測。
2.?內聯函數?
square(int x)
inline int square(int x) { return x * x; }
內聯函數是一種編譯器優化手段。它告訴編譯器盡量將函數調用替換為函數體本身,類似于宏展開,但具有類型檢查和作用域的優點。
示例:
int b = square(a++); // 調用時,a++ 只會被計算一次優點:
參數?
a++?只會被計算一次,并將其結果傳遞給函數?square。避免了宏定義中可能出現的重復計算問題。
具有類型檢查和作用域的安全性。
7、例子
示例1:短小工具函數
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_Hinline int clamp(int value, int min, int max) {return (value < min) ? min : (value > max) ? max : value;
}#endif// main.cpp
#include "math_utils.h"
int main() {int x = clamp(150, 0, 100); // 展開為 (150 < 0) ? 0 : (150 > 100) ? 100 : 150;return 0;
}
示例2:類成員內聯函數
class Vector2D {
public:Vector2D(float x, float y) : x(x), y(y) {}inline float magnitude() const {return sqrt(x*x + y*y);}private:float x, y;
};
8、溫馨小貼士
- ?避免濫用內聯:優先考慮函數調用開銷與代碼體積的平衡。
- ?復雜函數非內聯:包含循環、遞歸或大量代碼的函數不宜內聯。
- ?跨模塊可見性:內聯函數需在頭文件中定義,否則導致鏈接錯誤。
- ?現代編譯器優化:編譯器自動內聯簡單函數(即使未標記
inline)。
9、小結
- ?內聯函數是性能優化工具:通過消除調用開銷提升效率。
- ?短小函數適用:適用于簡單、高頻調用的場景。
- ?編譯器最終決策:
inline僅為建議,實際內聯由編譯器決定。 - ?替代宏的更安全方案:提供類型安全和調試支持。
六、默認參數
1、默認參數的作用
默認參數允許在函數聲明時為參數指定默認值,調用時可省略該參數。
核心優勢:
- 簡化函數調用,增強代碼靈活性。
- 減少需要重載的函數數量。
2、基本語法與使用
1. 聲明默認參數
在函數聲明中指定默認值(通常在頭文件中):
// 聲明函數(默認參數在聲明中指定)
void printMessage(const std::string& msg = "Hello, World!");
2. 定義函數
定義時不再重復默認值:
void printMessage(const std::string& msg) {std::cout << msg << std::endl;
}
3. 調用示例
printMessage(); // 輸出"Hello, World!"
printMessage("Hi"); // 輸出"Hi"
3、默認參數的規則
1. 參數從右向左依次設置默認值
- ?右側參數必須先設置默認值,左側參數可選。
// 正確:從右向左設置默認值
void draw(int x, int y = 0, int color = 255);// 錯誤:左側參數有默認值,右側無
void errorFunc(int a = 5, int b); // 編譯失敗
2. 默認值必須是常量或全局可訪問的表達式
- 允許使用常量、字面量、全局變量或靜態變量。
- ?不允許使用局部變量或函數參數。
const int DEFAULT_SIZE = 10;
int globalValue = 100;void init(int size = DEFAULT_SIZE, int value = globalValue); // 合法
3. 默認參數只能在函數聲明中指定一次
- 若函數聲明和定義分離,?默認參數必須在聲明中指定,定義中不可重復。
// 頭文件(math_utils.h)
int multiply(int a, int b = 2); // 聲明時指定默認值// 源文件(math_utils.cpp)
int multiply(int a, int b) { // 定義時不寫默認值return a * b;
}
4、默認參數與函數重載
默認參數可簡化重載,但需避免歧義。
示例:默認參數替代重載
// 用默認參數替代以下兩個重載函數
void log(const std::string& msg, bool addTimestamp);
void log(const std::string& msg);// 合并為一個函數
void log(const std::string& msg, bool addTimestamp = false);
錯誤示例:二義性調用
void connect(int timeout = 10);
void connect();// 調用connect()時,編譯器無法確定調用哪個函數
5、默認參數的實際應用
1. 構造函數初始化對象
class Circle {
public:// 構造函數:radius和color都有默認值Circle(double r = 1.0, int c = 0xFF0000) : radius(r), color(c) {}
private:double radius;int color;
};int main() {Circle c1; // 使用默認半徑和顏色Circle c2(5.0); // 半徑5.0,顏色默認Circle c3(3.0, 0x00FF00);
}
2. 靈活配置函數行為
// 繪制矩形:默認顏色為紅色,邊框可選
void drawRect(int width, int height, const std::string& color = "red", bool hasBorder = true);// 調用
drawRect(100, 200); // 使用所有默認值
drawRect(100, 200, "blue"); // 自定義顏色,邊框默認
drawRect(100, 200, "green", false); // 自定義所有參數
6、注意事項
- ?避免與重載函數沖突:確保默認參數不會導致調用歧義。
- ?優先在頭文件中聲明默認值:確保所有調用代碼可見。
- ?慎用復雜默認值:默認值應是簡單、明確的默認行為。
- ?默認參數與C兼容性:C語言不支持默認參數。
7、總結
- ?默認參數簡化調用:減少冗余代碼,提升可讀性。
- ?從右向左設置默認值:確保左側參數在調用時可選。
- ?聲明與定義分離時:默認參數僅在聲明中指定。
- ?合理替代重載:避免過度設計重載函數。
七、函數指針?
1、函數指針的作用
函數指針是指向函數內存地址的變量,允許通過指針動態調用函數。
核心應用:
- 實現回調(Callback)機制。
- 動態選擇函數邏輯(如策略模式)。
- 與C語言庫交互(如qsort中的比較函數)。
2、函數指針的聲明與初始化
1. 聲明語法
返回類型 (*指針變量名)(參數類型列表);
示例:
int (*funcPtr)(int, int); // 指向返回int,參數為兩個int的函數
2. 初始化與賦值
// 假設存在函數
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }// 初始化方式1:直接賦值函數名(隱式取地址)
funcPtr = add;// 初始化方式2:顯式取地址
funcPtr = ⊂
3、通過函數指針調用函數
1. 直接調用(推薦)
int result = funcPtr(3, 5); // 等價于調用add(3,5)或sub(3,5)
2. 解引用調用
int result = (*funcPtr)(3, 5);
3.用一個完整的例子來看看:?
#include <iostream>
using namespace std;
// 假設存在函數
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }int main(){int add(int a, int b);int sub(int a, int b);int (*ptr1)(int ,int);int (*ptr2)(int ,int);// 初始化方式1:直接賦值函數名(隱式取地址)ptr1 = add;// 初始化方式2:顯式取地址ptr2 = ⊂// 使用函數指針調用函數cout << ptr1(5, 3) << endl; // 輸出8cout << ptr2(5, 3) << endl; // 輸出2return 0;
}
?內存圖如下:(有點小問題,但需要表達的意思已經達到了)


4、函數指針的典型應用
1. 回調函數示例
#include <iostream>
using namespace std;// 回調函數類型定義
typedef void (*Callback)(const string&);// 執行任務并回調
void doTask(Callback callback) {cout << "Processing task..." << endl;callback("Task completed!");
}// 具體回調函數
void onComplete(const string& message) {cout << "Callback: " << message << endl;
}int main() {doTask(onComplete);return 0;
}
2. 函數指針數組
int (*operations[])(int, int) = {add, sub};int main() {int a = 10, b = 5;cout << operations[0](a, b) << endl; // 15 (add)cout << operations[1](a, b) << endl; // 5 (sub)
}
5、使用typedef或using簡化聲明
1.?typedef簡化
typedef int (*MathFunc)(int, int);
MathFunc funcPtr = add; // 更易讀
2. C++11的using別名
using MathFunc = int (*)(int, int);
MathFunc funcPtr = sub;
6、成員函數指針(高級)
1. 聲明與調用類成員函數指針
class Calculator {
public:int multiply(int a, int b) { return a * b; }
};int main() {Calculator calc;// 聲明成員函數指針int (Calculator::*memFuncPtr)(int, int) = &Calculator::multiply;// 調用int result = (calc.*memFuncPtr)(3, 5); // 輸出15return 0;
}
2. 結合對象指針調用
Calculator* pCalc = new Calculator();
int result = (pCalc->*memFuncPtr)(4, 6); // 24
delete pCalc;
7、函數指針與std::function的對比
| ?特性 | 函數指針 | std::function(C++11+) |
|---|---|---|
| ?類型安全 | 弱(無類型擦除) | 強(支持類型檢查) |
| ?攜帶狀態 | 不能 | 能(可綁定lambda、函數對象等) |
| ?性能 | 高(直接調用) | 略低(可能有間接開銷) |
| ?靈活性 | 僅支持普通函數和靜態成員 | 支持函數、lambda、成員函數等 |
8、注意事項
- ?類型嚴格匹配:函數指針類型必須與實際函數簽名完全一致。
- ?空指針檢查:調用前需確保指針非空。
- ?C++11+替代方案:優先使用
std::function和lambda表達式,除非需要與C兼容。
9、實際應用示例:排序算法回調
#include <algorithm>
#include <vector>
using namespace std;// 比較函數類型
typedef bool (*CompareFunc)(int, int);// 升序比較
bool ascending(int a, int b) { return a < b; }// 降序比較
bool descending(int a, int b) { return a > b; }// 使用函數指針的排序函數
void customSort(vector<int>& vec, CompareFunc comp) {sort(vec.begin(), vec.end(), comp);
}int main() {vector<int> nums = {3, 1, 4, 1, 5};customSort(nums, ascending); // 升序排序customSort(nums, descending); // 降序排序return 0;
}
?解釋一下
1.?函數指針類型定義
typedef bool (*CompareFunc)(int, int);
定義了一個名為?
CompareFunc?的類型,表示指向返回值為?bool、參數為兩個?int?類型的函數的指針。這種類型定義簡化了后續代碼中函數指針的聲明和使用。
2.?比較函數
bool ascending(int a, int b) { return a < b; }
bool descending(int a, int b) { return a > b; }
定義了兩個比較函數:
ascending: 判斷?a?是否小于?b,用于升序排序。
descending: 判斷?a?是否大于?b,用于降序排序。這些函數符合?
CompareFunc?的定義,可以作為函數指針傳遞。
3.?自定義排序函數
void customSort(vector<int>& vec, CompareFunc comp)
{ sort(vec.begin(), vec.end(), comp); }
定義了一個通用的排序函數?
customSort,接受以下參數:
vec: 需要排序的整數向量。
comp: 比較函數的函數指針。使用 C++ 標準庫中的?
std::sort?函數進行排序,并將用戶提供的比較函數?comp?作為第三個參數傳遞給?std::sort。通過這種方式,
customSort?可以根據不同的比較函數實現升序或降序排序。
4.?主函數調用
int main()
{ vector<int> nums = {3, 1, 4, 1, 5};
customSort(nums, ascending); // 升序排序
customSort(nums, descending); // 降序排序
return 0; }
創建了一個包含整數的向量?
nums。調用?
customSort?函數兩次:
第一次傳遞?
ascending?比較函數,實現升序排序。第二次傳遞?
descending?比較函數,實現降序排序。
輸出結果
假設在每次排序后輸出向量內容,程序的輸出可能如下:
升序排序后: 1 1 3 4 5
降序排序后: 5 4 3 1 1
10、小結
- ?函數指針提供動態函數調用能力,是C/C++靈活性的重要體現。
- ?語法復雜但功能強大,需注意類型匹配和空指針問題。
- ?現代C++中可結合
std::function提升靈活性和安全性。 - ?成員函數指針需結合對象實例使用,適用于面向對象設計模式。