C/C++的關鍵字之static你瞭解嗎
C語言
隱藏
場景演示
當我們同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性。會導致符號表認為存在同名全局變量和函數發生碰撞。
場景:全局的變量/函數在.h
中會在多個.cc
文件中擁有且全局可見有鏈接問題。
a.h
#pragma once #include<stdio.h> void Test() { printf("I am test..\n"); }
b.c
#include"a.h" void call() { Test(); }
c.c
#include"a.h" int main() { Test(); }
makefile
all:c c:c.o b.o gcc -o $@ $^ c.o:c.c gcc -c $^ b.o:b.c gcc -c $^ .PHONY:clean clean: rm -rf *.o c
運行結果
此時查看b.o
和c.o
符號表。(readelf -s xxx.o)
可以看到雙方的.o
符號表中都認為有一個GLOBAL
全局函數的Test
,兩者匯編階段形成的符號表此時在匯總的階段就會產生同名全局函數沖突。
此時在兩者的二進制文件裡都認為各自擁有Test()
函數,且都在全局。而全局函數隻能有一個名字(註:重載是底層重新命名瞭)。雖然我們知道兩個Test()是同一個,但是link的時候認為有兩個同名函數實現,因此報link錯。
解決方法
聲明和定義分離
養成聲明和定義分離的習慣,在.h中隻聲明不定義。在.c文件中定義。
a.h
#include<stdio.h> void Test();
a.c
#include"a.h" void Test() { cout<<"I am test..."<<endl; }
makefile
all:c c:c.o b.o a.o gcc -o $@ $^ c.o:c.c gcc -c $^ b.o:b.c gcc -c $^ a.o:a.c gcc -c $^ .PHONY:clean clean: rm -rf *.o c
為什麼此時就可以正常運行瞭?
依然查看符號表,可以發現b.o和c.o中此時隻是給Test
聲明留瞭一個全局的NOTYPE位置。
而在a.o中定義Test()
,因此a.o中是func類型。
最後三個.o文件鏈接的時候確定Test()
實際在最後生成的.out文件中的虛擬內存地址。運行時加載到內存中,之後的詳細過程就是linux創建進程中的事情。
使用static關鍵字及缺陷
那如果我就是想要直接在.h中存放一個公共的全局的對象來供其他所有文件使用呢?使用static關鍵字。
a.h
#pragma once #include<stdio.h> static void Test() { printf("I am test..\n"); }
代碼結構
:
此時為什麼又成立呢?兩者.o文件中為什麼對同名的全局函數包容瞭呢?可以看到此時兩者的符號表中仍然是func,按照場景演示中的例子,應該報錯的。
此時反匯編查看Test()函數地址。我們發現此時生成瞭兩個test函數,不過函數地址不同。
結論:static函數作用域僅在自己所在文件,其實是編譯後不同文件下的同名static函數會有不同的內部命名
不同.c文件include瞭static變量之後該變量隻在各自包含該變量的.c中可見。
既然生成瞭兩份,我們就可以發現,如果是一個靜態的全局變量,我們分別進行修改實際上對兩個不同的變量進行修改的。如果要解決全局變量統一性訪問,保證全局變量不可變即可。另外一種方式就是使用單例模式。
a.h
#pragma once #include<stdio.h> static int a =0;
b.h
#include"a.h" void call();
b.c
#include"b.h" void call() { a = 1; printf("a=%d\n",a); }
c.c
#include"b.h" int main() { a=2; printf("a=%d\n",a); call(); printf("a=%d\n",a); }
保持變量內容的持久
- 全局靜態變量
在全局變量前加上關鍵字static,全局變量就定義成一個全局靜態變量。
內存中位置:靜態存儲區,在整個程序運行期間移植存在。
初始化:未經初始化的全局靜態變量會被自動初始化為0(自動對象的值是任意的,除非他是被顯示初始化)。
作用域:全局靜態變量是從定義指出開始,到文件結尾,在聲明他的文件之外是不可見的。
- 局部靜態變量
內存位置:靜態存儲區
初始化:未經初始化的局部靜態變量會被自動初始化為0(自動對象的值是任意的,除非他是被顯示初始化)。
作用域:為局部作用域,當定義他的函數或者語句塊結束時,作用域結束。但是當局部靜態變量離開作用域後,並沒有被銷毀,依然駐留在內存中,隻不過我們不能再對它進行訪問,直到該函數再次被調用,並且值不變。
如下的count
變量作用域在test
函數中,而生命周期是整個程序。在第一次進入test()的時候會初始化,之後進入test()就不再執行第5行代碼瞭。
#include<stdio.h> void test() { static int count =0; count++; } int main() { for(int i =0 ; i < 10 ; i++ ) test(); }
默認初始化為0
默認初始化為0:在靜態存儲區,內存中所有的字節默認值都是0x00。
#include <stdio.h> int a; int main(void) { int i; static char str[10]; printf("integer: %d; string: (begin)%s(end)", a, str); return 0; }
Cpp
static類成員變量
聲明為static的類成員稱為類的靜態成員,用static修飾的成員變量,稱之為靜態成員變量;靜態的成員變量一定要在類外進行初始化。
- 靜態變量屬於整個類,所有對象,生命周期在整個程序間運行
- 在類成員函數中,可以隨便訪問
static類成員方法
用static修飾的成員函數,稱之為靜態成員函數。(因為該成員變量沒有this指針)
static成員函數,沒有this指針,不使用對象就可以調用–>fun::。
靜態成員函數可以調用非靜態成員函數(/成員)嗎?不行。沒有this指針
非靜態成員函數可以調用類的靜態成員函數嗎?可以
class Date { public: Date(int year=0,int month=1,int day=1) { } void f1() { } static void f2() { f1();//沒有this指針 } private: }
class Date{ public: void f3() { f4();//突破類域+訪問限定符就可以訪問 Date::f4();/對象.f4() //類裡面是一個整體都在類域中,類裡面不受訪問限定符限制 } static void f4() { } private: };
單例模式
- 單例模式
一個類隻能創建一個對象,即單例模式,該模式可以保證系統中該類隻有一個實例,並提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然後服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化瞭在復雜環境下的配置管理。
1.如何保證全局(一個進程中)隻有一個唯一的實例對象
參考隻能在堆上創建對象和在棧上創建對象,禁止構造和拷貝構造及賦值。
提供一個GetInstance獲取單例對象。
2.如何提供隻有一個實例呢?
餓漢模式和懶漢模式。
3.使用場景
由於全局的變量在.h
中會在多個.cc
文件中擁有且可見容易有鏈接問題。而static
又隻能在當前文件可見。因此真要處理成全局的就使用單例模式。
具體的單例模式在特殊類設計中提及。
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!