【C++ 筆記】儲存類別(Storage classes) - part 12

很感謝你點進來這篇文章。

你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!

簡介(Introduction)

C++ Storage Classes are used to describe the characteristics of a variable/function. It determines the lifetime, visibility, default value, and storage location which helps us to trace the existence of a particular variable during the runtime of a program.

C++ 儲存類別用於描述變數 / 函數的特徵。它決定了生命週期、可見性、預設值和儲存位置,這有助於我們在程式執行時,追蹤特定變數的存在。

語法(Syntax)

1
storage_class var_data_type var_name;

storage_class:儲存類別。

var_data_type:變數型態。

var_name:變數名稱。

C++ 共支援六種的儲存類別,如下(看看就好,之後慢慢看下去會懂這些是什麼作用跟什麼意思):

  • auto:預設的儲存類別修飾詞,通常可以省略不寫。auto 指定的變數具有自動儲存期,即它們的生命週期僅限於定義它們的區塊範圍(block)。auto 變數通常在堆疊上分配。
  • register:用於建議編譯器將變數儲存在 CPU 暫存器(Register)中以提高存取速度。在 C++11 及以後的版本中,register 已經是一個廢棄的特性,不再具有實際作用。
  • extern:用於宣告具有外部連結的變數或函數,它們可以在多個文件之間共用。預設情況下,全域變數和函數具有 extern 儲存類別。在一個檔案中使用 extern 宣告另一個檔案中定義的全域變數或函數,可以實作跨檔案共用。
  • static:用於定義具有靜態儲存期的變數或函數,它們的生命週期貫穿整個程式的執行期。在函數內部,static 變數的值在函數呼叫之間保持不變。在文件內部或全域作用域,static 變數具有內部鏈接,只能在定義它們的文件中存取。
  • mutable (C++11):用於修飾類別中的成員變數,允許在 const 成員函數中修改這些變數的值。通常用於快取或計數器等需要在 const 上下文中修改的資料。
  • thread_local (C++11):用於定義具有線程局域存儲期的變數,每個線程都有自己的獨立副本。線程局域變數的生命週期與執行緒的生命週期相同。

從 C++ 17 開始,auto 關鍵字不再是 C++ 儲存類別修飾詞,而 register 關鍵字被棄用。

從 C++11 開始,register 已經失去了原有的作用,而 mutable 和 thread_local 則是新引入的特性,用於解決特定的程式設計問題。

來源:https://www.runoob.com/cplusplus/cpp-storage-classes.html

mutable 需要有物件導向程式設計的先備知識,故這次先不談及。

thread_local 的部分,由於這個筆記是基礎教學,所以也不談及。

auto

auto 儲存類別是一個程式碼區塊內宣告的所有變數的預設類別。

區塊內所有的局域變數均屬 auto。

auto 共有下列特點:

  • 作用域(Scope):局部作用域,僅在其所在的區塊內可見。
  • 生命週期(Lifetime):變數的生命週期持續到其作用域結束。
  • 預設值(Default Value):未初始化的變數會有垃圾值(garbage value)。
  • 記憶體位置(Memory Location):儲存在 RAM 中。

另外 auto 由於「auto」,所以宣告變數時編譯器會根據運算式來自動推斷此變數的資料型態是啥。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

void demonstrateAuto() {
// 宣告自動變數 -> 編譯器自動偵測變數資料型態
auto a = 10; // 整數
auto b = 3.14; // 浮點數
auto c = 'A'; // 字元

// 輸出變數的值
cout << "整數 a: " << a << endl;
cout << "浮點數 b: " << b << endl;
cout << "字元 c: " << c << endl;
}

int main() {
demonstrateAuto();
return 0;
}

以下是錯誤的示範:

1
auto x1 = 5, x2 = 5.0, x3='r'; //錯誤,必須是初始化為相同型態

register

register 是一種儲存類別(storage class),用於宣告變數,並提示編譯器將這些變數儲存在CPU 的暫存器中,以便快速存取。

使用 register 關鍵字可以提高程式的執行速度,因為它減少了對記憶體的存取次數。

然而,需要注意的是,register 儲存類別只是一種提示,編譯器可以忽略它(暫存器的數量有限)
,因為現代的編譯器通常會自動最佳化程式碼,選擇合適的儲存位置。

register 共有下列特點:

  • 作用域(Scope):局部作用域,只能在其所在的區塊內可見。
  • 生命週期(Lifetime):變數的生命週期持續到其作用域結束。
  • 預設值(Default Value):未初始化的變數會有不確定值。
  • 無法使用指標:不能對 register 變數使用取址運算子(&),因為暫存器沒有位址。
  • 不適用於全域變數:register 變數必須在函數內部或作為函數參數宣告。

語法格式如下:

1
register data_type variable_name;

register:關鍵字
data_type:資料型態
variable_name:變數名稱

以下是個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

void demonstrateRegister() {
// 宣告 register 變數
register int count = 0;

for (int i = 0; i < 5; ++i) {
count++;
cout << "Count: " << count << endl;
}
}

int main() {
demonstrateRegister();
return 0;
}

在 C++ 中,可以使用引用或指標來提高存取速度,尤其是在處理大型資料結構時。

C++ 11 中,這東西已經廢了,現在會自動優化,所以不建議使用。

static

在 C++ 中,static 儲存類別用來定義變數的生命週期和作用域。當一個變數被宣告為 static 時,它的生命週期會延續至整個程式執行期間,而不限於其所在的程式碼區塊或函數。

所以 static 變數在第一次初始化後,他的值會不變,一直到程式結束。

static 共有下列特點:

  • 生命週期(Lifetime):static 變數的生命週期從第一次初始化開始,直到程式結束。
  • 作用域(Scope):
    • 如果在函數內部宣告 static,則該變數的作用域僅限於該函數,但其值會在多次呼叫中保持不變。
    • 如果在全域範圍內宣告為 static,則該變數的作用域限於該檔案,不能被其他檔案存取。
  • 預設值(Default Value):static 變數如果未初始化,會自動設定為零。

static 英翻中就是「靜止的」,也可以想成是一種靜態的變數,跟水豚一樣超穩定XD

以下是個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void demonstrateStatic() {
static int count = 0; // 宣告 static 變數
count++; // 每次呼叫函數時遞增
cout << "Count: " << count << endl;
}

int main() {
demonstrateStatic(); // 第一次呼叫
demonstrateStatic(); // 第二次呼叫
demonstrateStatic(); // 第三次呼叫
return 0;
}

輸出結果:

1
2
3
Count: 1
Count: 2
Count: 3

extern

在 C++ 中,extern 儲存類別用來宣告變數或函數的外部連結性,表示該變數或函式的定義存在於其他檔案中。

使用 extern 關鍵字可以讓不同的檔案共享相同的變數或函數,在大型專案中非常有用。

extern 共有下列特點:

  • 外部連結性(External Linkage):extern 變數可以在多個檔案中共享,使得不同的編譯單位能夠存取同一個變數或函數。
  • 無法分配儲存空間:extern 宣告不會分配儲存空間,僅告訴編譯器該變數或函數在其他地方已經定義。
  • 常用於全域變數:通常用於宣告全域變數,以便在多個檔案中使用。

以下是個範例:

檔案1:A.cpp

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int sharedVariable = 42; // 定義全域變數

void displayValue() {
cout << "Value of sharedVariable: " << sharedVariable << endl;
}

檔案2:B.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

extern int sharedVariable; // 宣告外部變數

void modifyValue() {
sharedVariable = 100; // 修改外部變數的值
}

int main() {
cout << "Initial value: " << sharedVariable << endl; // 輸出初始值
modifyValue(); // 修改值
cout << "Modified value: " << sharedVariable << endl; // 輸出修改後的值
return 0;
}

輸出結果:

1
2
Initial value: 42
Modified value: 100

透過 extern,開發者可以避免重複定義相同的全域變數,從而提高程式碼的可維護性和可讀性。

參考資料

Storage Classes in C++ with Examples - GeeksforGeeks

儲存類別 (C++) | Microsoft Learn

C 語言的宣告、定義、儲存類型 (storage class) 與連結性 (linkage) - DEV Community

C++ 存储类 | 菜鸟教程