在 C++ 里,棧空間主要用來存放局部變量、函數調用信息等。下面為你介紹棧空間在 C++ 里的運用方式。
1. 局部變量的使用
在函數內部定義的變量會被存于棧空間,當函數執行結束,這些變量會自動被銷毀。
#include <iostream>void exampleFunction() {// 定義一個局部變量,存于棧空間int localVar = 10;std::cout << "Local variable value: " << localVar << std::endl;
}int main() {exampleFunction();return 0;
}
2. 函數調用棧
每次調用函數時,系統會在棧上為該函數創建一個棧幀,用來保存函數的局部變量、參數、返回地址等信息。函數返回時,對應的棧幀會被銷毀。
#include <iostream>void func2(int value) {std::cout << "Value in func2: " << value << std::endl;
}void func1() {int localVar = 20;func2(localVar);
}int main() {func1();return 0;
}
3. 遞歸調用
遞歸函數是在函數內部調用自身,每次遞歸調用都會在棧上創建新的棧幀。要注意遞歸深度,防止棧溢出。
#include <iostream>// 遞歸計算階乘
int factorial(int n) {if (n == 0 || n == 1) {return 1;}return n * factorial(n - 1);
}int main() {int num = 5;std::cout << "Factorial of " << num << " is: " << factorial(num) << std::endl;return 0;
}
棧空間使用的注意事項
- 棧溢出:若棧空間使用過多,例如遞歸過深或者局部變量占用空間過大,就會引發棧溢出錯誤。
- 生命周期:棧上的變量生命周期局限于定義它的代碼塊,出了代碼塊就會被銷毀。
在 C++ 中,堆空間用于動態分配內存,可在程序運行時根據需要分配和釋放內存。下面詳細介紹堆空間的使用方法。
1. 使用?new
?和?delete
?操作符進行內存分配和釋放
- 分配單個對象:使用?
new
?操作符為單個對象分配內存,使用?delete
?操作符釋放內存。
#include <iostream>int main() {// 在堆上分配一個 int 類型的對象int* ptr = new int;*ptr = 42;std::cout << "Value: " << *ptr << std::endl;// 釋放堆上的內存delete ptr;return 0;
}
- 分配數組:使用?
new[]
?操作符為數組分配內存,使用?delete[]
?操作符釋放內存。
#include <iostream>int main() {// 在堆上分配一個包含 5 個 int 元素的數組int* arr = new int[5];for (int i = 0; i < 5; ++i) {arr[i] = i;}// 輸出數組元素for (int i = 0; i < 5; ++i) {std::cout << arr[i] << " ";}std::cout << std::endl;// 釋放堆上的數組內存delete[] arr;return 0;
}
2. 使用智能指針管理堆內存
為了避免手動管理內存帶來的內存泄漏問題,C++ 提供了智能指針。常用的智能指針有?std::unique_ptr
、std::shared_ptr
?和?std::weak_ptr
。
std::unique_ptr
:獨占所指向的對象,同一時間只能有一個?std::unique_ptr
?指向該對象。
#include <iostream>
#include <memory>int main() {// 使用 std::unique_ptr 管理堆上的 int 對象std::unique_ptr<int> ptr = std::make_unique<int>(42);std::cout << "Value: " << *ptr << std::endl;// 不需要手動釋放內存,std::unique_ptr 會在離開作用域時自動釋放return 0;
}
std::shared_ptr
:多個?std::shared_ptr
?可以共享同一個對象,使用引用計數來管理對象的生命周期。
#include <iostream>
#include <memory>int main() {// 使用 std::shared_ptr 管理堆上的 int 對象std::shared_ptr<int> ptr1 = std::make_shared<int>(42);std::shared_ptr<int> ptr2 = ptr1;std::cout << "Value: " << *ptr2 << std::endl;// 當所有指向該對象的 std::shared_ptr 都被銷毀時,對象會自動釋放return 0;
}
3. 自定義類對象的堆內存管理
在自定義類中,需要注意析構函數的實現,確保在對象銷毀時正確釋放堆上的內存。
#include <iostream>class MyClass {
private:int* data;
public:MyClass(int value) {// 在構造函數中分配堆內存data = new int(value);}~MyClass() {// 在析構函數中釋放堆內存delete data;}int getValue() const {return *data;}
};int main() {MyClass obj(42);std::cout << "Value: " << obj.getValue() << std::endl;return 0;
}
堆空間使用的注意事項
- 內存泄漏:如果使用?
new
?分配了內存,但沒有使用?delete
?或?delete[]
?釋放,或者智能指針管理不當,會導致內存泄漏。 - 懸空指針:釋放內存后,指針仍然指向原來的內存地址,使用這樣的指針會導致未定義行為。
4.C/C++ 中static
的作用
- 靜態局部變量:在函數內部用
static
修飾的局部變量,存儲在全局數據區而非棧區。它的生命周期貫穿整個程序運行期間,在程序執行到其聲明處時首次初始化,之后的函數調用不再初始化;若未顯式初始化,會自動初始化為 0 。其作用域仍在定義它的函數內部。常用于記錄函數調用次數或狀態 - 靜態全局變量:在全局變量前加
static
,該變量存儲在全局數據區,作用域為聲明它的文件,其他文件即使使用extern
聲明也無法訪問。可提高程序的封裝性,防止全局變量被意外修改,還能避免多文件項目中不同文件同名全局變量的命名沖突。比如在一個多人協作的大型項目中,每個源文件里的靜態全局變量只在本文件內有效,不同文件可使用相同變量名。
- 靜態函數:被
static
修飾的函數只能在聲明它的文件中可見和調用,不能被其他文件使用。有助于提高程序的封裝性,減少函數被其他文件錯誤調用的風險 - 類的靜態數據成員:在類內數據成員聲明前加
static
,該數據成員為類的所有對象共享,在程序中只有一份拷貝,存儲在全局數據區。 - 類的靜態成員函數:用
static
修飾的類成員函數,屬于類本身而非類的對象,沒有this
指針,不能直接訪問非靜態成員變量和非靜態成員函數。可在創建對象前調用,常作為工具函數或用于訪問靜態數據成員。
--------------------------------------------------------------------------------------------------------------------------------------關于一個函數的地址這里可以提到的是。函數名就是一個函數的地址!但是不能查地址的時候忽略作用域
關于函數指針問題:typedef void (*PluginFunction)();首先PluginFunction它是這個指針的別名,最后的一個括號說明這個指針可以用于沒有參數的函數!
#include <iostream>// 定義一個插件函數
void pluginFunction() {std::cout << "This is a plugin function." << std::endl;
}// 使用函數指針類型表示插件函數類型
typedef void (*PluginFunction)();// 主程序加載插件并調用插件函數
void loadAndCallPlugin(PluginFunction func) {func();
}int main() {loadAndCallPlugin(pluginFunction);return 0;
}
從這個圖也可以看出虛表也是存在于常量區代碼段的位置!
5.多繼承的虛表
//多繼承的虛函數表
class Base1 {
public:virtual void func1() { std::cout << "Base1::func1" << std::endl; }virtual void func2() { std::cout << "Base1::func2" << std::endl; }
private:int b1;
};class Base2 {
public:virtual void func1() { std::cout << "Base2::func1" << std::endl; }virtual void func2() { std::cout << "Base2::func2" << std::endl; }
private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { std::cout << "Derive::func1" << std::endl; }virtual void func3() { std::cout << "Derive::func3" << std::endl; }
private:int d1;
};
derive內有兩個虛表的原因:分別是Base1內的一個虛表,Base2一個續表,而func3會通過編譯器自動放在一個已有的虛表中。
C++ 編譯器為每個包含虛函數的類生成虛函數表,目的是為了實現運行時多態。當一個類繼承自多個基類,且基類都有虛函數時,該派生類會繼承基類的虛表結構。編譯器通常會復用已有的虛表,將新的虛函數指針添加到合適的虛表中,而不是為每個新虛函數單獨創建一個虛表。這樣可以節省內存空間,并保持虛函數調用機制的一致性。
?
?