沒有學不會的C++:為什麼不要使用全域性變數
在寫程式時,我們都知道一條規範:不要使用全域性變數。至於為什麼,有可能是因為它會汙染名稱空間,也有可能是因為它會造成程式的不確定性,本文主要使用一個例子,來說明全域性變數是如何讓程式變得不確定的。
我們先定義兩個類,一個 Cat
,一個 Dog
,如下是 cat.h
和 cat.cc
檔案
// cat.h
#include <iostream>
using namespace std;
class Cat;
extern Cat c;
class Cat {
public:
Cat(char* name);
void meow();
private:
char* _name;
};
// cat.cc
#include "cat.h"
#include "dog.h"
Cat c("mimi");
Cat::Cat(char* name) {
cout << "construct cat" << endl;
_name = name;
}
void Cat::meow() {
cout << "cat " << _name << " meow" << endl;
}
Cat
類很簡單,只有一個成員 _name
,它是一個指標變數,且通過 meow
方法把它列印到螢幕上,同時,我們還定義了一個全域性變數 Cat c("mimi");
,同樣的,Dog
類的定義也很簡單,如下:
// dog.h
#include <iostream>
using namespace std;
class Dog;
extern Dog d;
class Dog {
public:
Dog(char* name);
void bark();
private:
char* _name;
};
// dog.cc
#include "dog.h"
#include "cat.h"
Dog d("kobe");
Dog::Dog(char* name) {
cout << "construct dog" << endl;
_name = name;
}
void Dog::bark() {
cout << "dog " << _name << " bark" << endl;
}
我們給 Dog
也定義了一個全域性變數 d("kobe");
,此時,我們修改一下 Cat
的建構函式,在裡面引用全域性變數 d
,看看會發生什麼
Cat::Cat(char* name) {
cout << "construct cat" << endl;
_name = name;
d.bark();
}
編譯執行前,別忘了我們的入口檔案 main.cc
,如下
int main() {
return 0;
}
現在,我們將其進行編譯執行
...
g++ -o main main.o cat.o dog.o -std=c++11 // 編譯時的輸出,說明了連結順序
$ ./main
construct cat
// [1] 50759 segmentation fault ./main
可以看到程式崩潰了,崩潰原因是我們剛才在 Cat
建構函式中增加的一行呼叫全域性變數的程式碼,因為在呼叫這行程式碼時,全域性變數 d
(實際上是 d._name
)還沒初始化。
而全域性變數的初始化順序是由編譯器決定的,所以如果我們的全域性變數間又有互相依賴的話,就很容易造成程式崩潰。
避免使用全域性變數的方法也有很多,其中最廣泛的應數 Singleton 模式了,針對上面的程式碼,我們可以定義一個 Singleton
類,其中包含 Cat
和 Dog
的靜態指標,如下
// singleton.h
class Cat;
class Dog;
class Singleton {
static Dog* pd;
static Cat* pc;
public:
~Singleton();
static Dog* getDog();
static Cat* getCat();
};
與此同時,我們還宣告瞭兩個靜態方法,用來獲取 Dog
或 Cat
的指標,並且,我們希望 Dog
和 Cat
是以 lazy 的方式進行初始化的,即下面的 singleton.cc 檔案的實現
// singleton.cc
#include "singleton.h"
#include "dog.h"
#include "cat.h"
Dog* Singleton::pd = 0;
Cat* Singleton::pc = 0;
Singleton::~Singleton() {
delete pd;
delete pc;
pd = 0;
pc = 0;
}
Dog* Singleton::getDog() {
if (pd == 0) {
pd = new Dog("kobe");
}
return pd;
}
Cat* Singleton::getCat() {
if (pc == 0) {
pc = new Cat("mimi");
}
return pc;
}
可以看到初始化 Cat
和 Dog
的時機是在第一次呼叫 getCat
和 getDog
時。現在你就可以刪掉程式中的全域性變數了,當你需要使用 Cat
物件或 Dog
物件時,直接呼叫 Singleton::getCat()
或 Singleton::getDog()
即可。
參考:
相關文章
- angular中定義全域性變數及全域性變數的使用Angular變數
- javascript全域性變數的使用注意JavaScript變數
- 靜態全域性變數和全域性變數變數
- android使用全域性變數Android變數
- 全域性變數變數
- 為什麼C++ 並沒有"王者歸來"?C++
- Python的區域性變數和全域性變數使用解惑Python變數
- QT 全域性變數使用方法QT變數
- node 全域性物件和全域性變數物件變數
- 為什麼Facebook不會有“dislike”按鈕?
- 全域性變數與區域性變數變數
- 全域性 DOM 變數變數
- lua全域性變數變數
- [python]為什麼父類的值沒有改變Python
- 沒有學不會的C++:使用者自定義的隱式型別轉換C++型別
- C語言區域性變數、全域性變數、靜態區域性變數、靜態全域性變數C語言變數
- Android中全域性變數與區域性變數的使用總結Android變數
- 沒有學不會的C++:顯示型別轉換(Casting)C++型別AST
- Python學習筆記(2)慎重使用全域性變數Python筆記變數
- 全域性DOM變數的坑變數
- 少用全域性變數的原因變數
- golang變數作用域問題-避免使用全域性變數Golang變數
- Java區域性變數與全域性變數Java變數
- java 全域性變數和區域性變數Java變數
- JavaScript —— 區域性變數和全域性變數JavaScript變數
- jmeter全域性變數和區域性變數JMeter變數
- 【c】全域性變數與區域性變數變數
- 這就是為什麼你學不會DDD
- Android全域性變數的定義與使用Android變數
- c++ 全域性變數初始化的一點總結C++變數
- vue定義全域性變數和全域性方法Vue變數
- js宣告全域性變數JS變數
- python全域性變數Python變數
- SQL Server 全域性變數SQLServer變數
- Python中的全域性變數和區域性變數Python變數
- js-js的全域性變數和區域性變數JS變數
- 為什麼Hook沒有ErrorBoundary?HookErrorORB
- 為什麼Google沒有客服?Go