C/C++ static 的 5 種用法

本篇 ShengYu 介紹 C/C++ static 的用法與範例,C/C++ 使用 static 通常有兩種目的,一種是限制變數的作用域(scope),作用域的意思是變數在程式中可以被存取的範圍,另一種目的則是讓變數生命週期變得跟程式一樣長,C/C++ static 的概念與用法也容易出現在考試或面試的題目裡。

以下 C/C++ static 的用法介紹分別為這幾種,

  • C/C++ 中 static 放在區域變數之前
  • C/C++ 中 static 放在全域變數之前
  • C/C++ 中 static 放在函式之前
  • C++ 中 static 放在 class 的 member variable 之前
  • C++ 中 static 放在 class 的 member function 之前

那我們開始吧!

C/C++ 中 static 放在區域變數之前

C/C++ static 放在區域變數之前(該變數宣告在某個函式中)表示該變數離開該作用域(scope)後記憶體還保留著直到程式結束為止,在程式開始時就配置好記憶體,執行到這一行才進行實體化,

cpp-static1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ cpp-static1.cpp -o a.out
#include <iostream>
using namespace std;

void count() {
static int counter = 0;
counter++;
cout << "counter = " << counter << endl;
}

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

結果輸出如下,

1
2
3
counter = 1
counter = 2
counter = 3

執行到這一行才進行實體化是什麼意思呢?
請看以下範例,透過一個 Counter class 來輔助說明這件事,我們就來看看實際上 Counter 是什麼時候被建構的,

cpp-static2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// g++ cpp-static2.cpp -o a.out
#include <iostream>
using namespace std;

class Counter {
public:
Counter() {
cout << "Counter::Counter()" << endl;
}

~Counter() {
cout << "Counter::~Counter()" << endl;
}

void add(int n) {
c += n;
}

int getCounter() {
return c;
}
private:
int c = 0;
};

void count() {
cout << "count()" << endl;
static Counter counter;
counter.add(1);
cout << "counter = " << counter.getCounter() << endl;
}

int main() {
cout << "main()" << endl;
count();
count();
count();
return 0;
}

輸出結果如下,可以發現程式執行到 static Counter counter; 這一行才開始建構實例化,而且初始化只會有第一次的那一次,一直到程式結束時 Counter 才解構,所以實際上它是跟全域變數的生命週期有點不一樣,全域變數的生命週期是程式開始到程式結束,函式內的 static 變數的生命週期是執行到那一行才初始化然後一直到程式結束,程式輸出如下所示,

1
2
3
4
5
6
7
8
9
main()
count()
Counter::Counter()
counter = 1
count()
counter = 2
count()
counter = 3
Counter::~Counter()

C/C++ 中 static 放在全域變數之前

C/C++ static 放在全域變數之前(該變數不是宣告在某個函式中)是表示在 c/cpp 檔裡該變數無法被其他 c/cpp 檔用 extern 來使用。

因為在別支檔 extern 該變數後,就可以在別支檔裡修改這個變數,所以 static 放在全域變數是預防其它人把竄改你的變數,也有保護變數的味道,這就是 static 放在全域變數之前的主要功用。

以下範例分成 cpp-static3.cpp 與 counter.cpp 來說明,先用 g++ 對 cpp-static3.cpp 與 counter.cpp 各自編譯 g++ -c cpp-static3.cpp && g++ -c counter.cpp,最後再將兩個 .o 檔連結 (link) 最後輸出成 a.out 執行檔 g++ cpp-static3.o counter.o -o a.out

cpp-static3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// g++ -c cpp-static3.cpp && g++ -c counter.cpp
// g++ cpp-static3.o counter.o -o a.out
#include <iostream>
#include "counter.h"
using namespace std;

int counter = 0;

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

在 counter.h 裡有我們需要呼叫的 count() 函式原型,

counter.h
1
2
3
4
5
6
#ifndef __COUNTER_H__
#define __COUNTER_H__

void count();

#endif

在 counter.cpp 檔裡使用 extern int counter;,代表我想要在 counter.cpp 這支檔裡存取 cpp-static3.cpp 裡的 counter 這個變數,

counter.cpp
1
2
3
4
5
6
7
8
9
10
#include "counter.h"
#include <iostream>
using namespace std;

extern int counter;

void count() {
counter++;
cout << "counter = " << counter << endl;
}

剛剛上面都是介紹 extern 是怎麼使用的,現在要回歸主題了,如果我在 cpp-static3.cpp 裡的 int counter = 0; 前面加上 static 的話,如下範例所示,就表示我不希望 counter 這個變數被其它 .cpp 檔 extern 存取,在實務上有些情況下確實會有這樣需求,這樣能保護我的變數不被別人隨意存取,那麼當別人試圖在別隻檔使用 extern 時(就像前面的 counter.cpp 例子裡的 extern int counter; 那樣)就會出現編譯錯誤,

cpp-static3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// g++ -c cpp-static3.cpp && g++ -c counter.cpp
// g++ cpp-static3.o counter.o -o a.out
#include <iostream>
#include "counter.h"
using namespace std;

static int counter = 0;

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

延伸閱讀:C/C++ extern 用法與範例

C/C++ 中 static 放在函式之前

C/C++ static 放在函式之前是表示在 c/cpp 檔裡該函式無法被其他 c/cpp 檔用 extern 來引用呼叫,跟前述 static 全域變數是差不多的意思。

C/C++ static 放在函式之前的用意是希望該函式只能在這支原始檔裡使用,不想給別人呼叫,有點像 class 裡的 private function 的味道,反之如果函式之前沒有加 static 的話,任何人都可以透過 extern 的方式來呼叫這支函式。

這邊的範例跟前述例子差不多,只是這個範例沒有 counter.h,取而代之的是在 cpp-static4.cpp 用 extern 的方式引用外部 count 函式,

cpp-static4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// g++ -c cpp-static4.cpp && g++ -c counter.cpp
// g++ cpp-static4.o counter.o -o a.out
#include <iostream>
using namespace std;

extern void count();

int counter = 0;

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

而 counter.cpp 新增了一個 print_counter 函式專門用來印 counter 的,

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

extern int counter;

static void print_counter(int c) {
cout << "counter = " << c << endl;
}

void count() {
counter++;
print_counter(counter);
}

但是如果在 cpp-static4.cpp 嘗試用 extern 的方式引用外部 print_counter 函式來呼叫 print_counter 的話,在編譯時會連結錯誤,

cpp-static4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// g++ -c cpp-static4.cpp && g++ -c counter.cpp
// g++ cpp-static4.o counter.o -o a.out
#include <iostream>
using namespace std;

extern void print_counter(int c); // 連結錯誤, 因為 print_counter 是 static
extern void count();

int counter = 0;

int main() {
count();
count();
count();
print_counter(counter); // 連結錯誤, 因為 print_counter 是 static
return 0;
}

以上就是 C/C++ 中 static 放在函式之前的用法。
延伸閱讀:C/C++ extern 用法與範例

C++ 中 static 放在 class 的 member variable 之前

C++ static 放在 class 的 member variable 之前,稱為靜態成員變數 (static member variable),
靜態成員變數是不屬於任何一個實體 (instance),即所有的實體共享這個變數,
以下就利用靜態成員變數簡單示範如何計算這個 Object class 被生成了幾個實體 (instance),需要注意的是 Object class 的 static int counter 要初始化必須寫在 class 外面,否則編譯器也不讓你通過,
靜態成員變數這個技巧概念也被應用在 std::shared_ptr 上,用來追蹤有幾個指標共享這一塊記憶體,

cpp-static5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ cpp-static5.cpp -o a.out
#include <iostream>
using namespace std;

class Object {
public:
Object() {
++counter;
cout << "counter = " << counter << endl;
}
private:
static int counter;
};

int Object::counter = 0; // initializing the static int

int main() {
Object obj1;
Object obj2;
Object obj3;
return 0;
}

輸出結果如下,

1
2
3
counter = 1
counter = 2
counter = 3

C++ 中 static 放在 class 的 member function 之前

C++ static 放在 class 的 member function 之前,稱為靜態成員函式 (static member function),
靜態成員函式是不屬於任何一個實體 (instance),即不需要任何實體就可以呼叫該類別的成員函式,
承上例,新增一個 getCounter() 成員函式來取得 counter,我們使用 Object::getCounter() 直接呼叫 Object 類別的 getCounter() 成員函式,即使是還沒有產生任何的實體(例如下例中的 obj1~obj3),需要注意的是靜態成員函式裡存取的所有變數都要是 static,

cpp-static6.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// g++ cpp-static6.cpp -o a.out
#include <iostream>
using namespace std;

class Object {
public:
Object() {
++counter;
cout << "counter = " << counter << endl;
}

static int getCounter() { return counter; }
private:
static int counter;
};

int Object::counter = 0; // initializing the static int

int main() {
cout << Object::getCounter() << endl;
Object obj1;
Object obj2;
Object obj3;
cout << Object::getCounter() << endl;
return 0;
}

輸出結果如下,

1
2
3
4
5
0
counter = 1
counter = 2
counter = 3
3

另外對於 C/C++ static member variable 初始化方式有興趣的可以看這篇

以上就是 C/C++ static 的 5 種用法的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ static member variable 用法與初始化方式
C/C++ 新手入門教學懶人包
std::vector 用法與範例
std::deque 介紹與用法
std::queue 用法與範例
std::map 用法與範例
std::unordered_map 用法與範例
std::sort 用法與範例
std::shared_ptr 用法與範例