C++那些細節--static關鍵字__C++

來源:互聯網
上載者:User

static也是我們經常用到的關鍵字,關於static有很多用法,而且在面向過程和物件導向編程中,static有著不同的意義。之前總是記不住,於是,本人強迫症又發作了,一定要搞懂它。。。


一.面向過程編程中的static關鍵字 1.靜態全域變數 靜態全域變數:

// C++Test.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>#include <string>using namespace std;static int s_test;int nons_test;int _tmain(int argc, _TCHAR* argv[]){//靜態全域變數預設初始化為0cout<<"static :"<<s_test<<endl;//非靜態全域變數預設初始化也是0,但如果是局部非靜態變數,在VS中直接報錯,不能通過編譯cout<<"non-static: "<<nons_test<<endl;system("pause");return 0;}

結果: static :0
non-static: 0
請按任意鍵繼續. . .

我們在運行程式時,記憶體分為代碼區,全域資料區,堆區,棧區。正常的臨時變數auto等都在棧區,生命週期是函數結束,而new出來的對象一般都在堆區,聲明周期由我們控制,new時開始,delete時結束。而全域資料區則儲存全域變數以及靜態變數,他們的生命週期是整個程式的運行周期。
使用靜態全域變數和使用普通全域變數的差別: 1)如果全域變數在標頭檔中,或者使用和定義都在同一個檔案中,靜態全域變數和普通全域變數是相同的。
//StaticTest.h標頭檔中分別定義靜態全域變數和普通全域變數static int static_num = 20;int nonstatic_num = 30;

// C++Test.cpp : 定義控制台應用程式的進入點。//全域變數在標頭檔或者本檔案中兩者沒有什麼區別#include "stdafx.h"#include <iostream>#include <string>#include "StaticTest.h"#include "StaticTest1.h"using namespace std;int _tmain(int argc, _TCHAR* argv[]){//如果靜態變數在標頭檔中或者本檔案中,可以訪問靜態全域變數cout<<"static :"<<static_num<<endl;//如果靜態變數在標頭檔中或者本檔案中,可以訪問非靜態全域變數cout<<"non-static: "<<nonstatic_num<<endl;system("pause");return 0;}

結果: static :20
non-static: 30
請按任意鍵繼續. . .


2)如果全域變數在.cpp檔案中,普通全域變數是全域可見的,即其他檔案也可見,但是要使用時,就要在其他檔案中添加extern關鍵字。而且如果不添加的話,在這個檔案再聲明同名的變數,是會報錯的。但是使用靜態全域變數就可以解決這個問題,靜態全域變數在其他檔案中是不可見的,我們不需要關注其他檔案中的全域變數,也不會出現不能使用同名全域變數的問題啦。
//.h檔案#ifndef __STATICTEST1_H_#define __STATICTEST1_H_#pragma onceclass StaticTest1{public:StaticTest1(void);virtual ~StaticTest1(void);};#endif//.cpp檔案#include "stdafx.h"#include "StaticTest1.h"//在.cpp檔案中定義全域變數static int static_test = 10;int nonstatic_test = 20;StaticTest1::StaticTest1(void){}StaticTest1::~StaticTest1(void){}
main函數檔案:
// C++Test.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>#include "StaticTest1.h"using namespace std;extern int nonstatic_test;extern int static_test;int _tmain(int argc, _TCHAR* argv[]){//如果靜態變數在其他.cpp檔案中,不可以訪問靜態全域變數,即使在本檔案使用extern聲明也不行//cout<<"static :"<<static_test<<endl;//如果靜態變數在其他.cpp檔案中,可以訪問非靜態全域變數,但是要在本檔案使用extern聲明cout<<"non-static: "<<nonstatic_test<<endl;system("pause");return 0;}

不添加extern時,靜態全域變數為未定義,而即使添加extern聲明也會報錯: C++Test2.obj : error LNK2001: 無法解析的外部符號 "int static_test" (?static_test@@3HA)
1>: fatal error LNK1120: 1 個無法解析的外部命令


關於靜態全域變數,有下面三點要注意: 1)靜態全域變數如果未初始化,會被預設初始化為0,而非靜態全域變數,注意是全域變數,也是初值為0。(非靜態非全域變數如果未初始化在VS中會直接報錯的) 2)靜態全域變數在全域資料區非配記憶體,所以變數的生存期是整個程式的運行周期。(局部靜態變數的生存期也是整個程式運行周期) 3)靜態全域變數在變數聲明的檔案是可見的,但是在其他檔案中是不可見的。
關於全域變數: 如果我們將全域變數直接放在標頭檔中,變數會隨著標頭檔的引入,引入到各個檔案中。有時候這是我們不想看到的,所以另一種方法是將全域變數放在.cpp檔案中,這樣,全域變數就不會隨著.h檔案到處引入,不是全域可見。但是,這樣這個變數仍然是全域變數,要想在另外的檔案中使用這個變數,就要在這個檔案中使用extern關鍵字聲明一下。如果不聲明,就會出現變數未聲明的情況。如果直接在這個變數再定義一個同名的變數,就會出現沖定義的情況,這也是我們不想看到的,如果全域變數僅僅在本檔案中有用,那麼不如直接使用靜態全域變數。

2.靜態局部變數 局部變數,即儲存在棧空間的變數,我們在調用函數時,變數初始化,而函數調用結束時,局部變數的生存期就結束了,進而被銷毀。而有時候我們需要對兩次調用函數之間的變數進行儲存,最簡單的想法就是使用一個全域變數,但是這個變數就已經不屬於函數本身,而是全域可見,不符合局部性原理,給維護帶來了不便。而靜態局部變數剛好可以解決這個問題。靜態局部變數儲存在全域資料區,不會因函數結束而銷毀,並且在其他函數看來,靜態局部變數是不可見的,剛好滿足了我們的需求。
下面看一個例子:

// C++Test.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>#include <string>#include <vector>using namespace std;void TestFunc(){//定義一個靜態局部變數,但是這個變數僅僅在第一次的時候初始化,再次調用時不會再初始化static int static_num = 10;//但是普通局部變數,每次都會初始化int nonstatic_num = 10;static_num++;nonstatic_num++;cout<<"non_static: "<<nonstatic_num<<"  static :"<<static_num<<endl;}int _tmain(int argc, _TCHAR* argv[]){//調用10次該函數for(int i = 0; i < 10; i++)TestFunc();system("pause");return 0;}

結果:

non_static: 11  static :11
non_static: 11  static :12
non_static: 11  static :13
non_static: 11  static :14
non_static: 11  static :15
non_static: 11  static :16
non_static: 11  static :17
non_static: 11  static :18
non_static: 11  static :19
non_static: 11  static :20
請按任意鍵繼續. . .


從上面的結果我們看出,函數被調用了10次,非靜態變數每次都被初始化,因而結果沒變。而靜態變數卻只被初始化了一次,因而每次結果都比上一次大1。


關於局部靜態變數要注意的幾點:

1)局部靜態變數也在全域資料區分配記憶體,不會因為函數調用結束而銷毀。

2)局部靜態變數在首次調用到該變數的時候進行初始化,之後再次調用時不會再進行初始化。並且局部靜態變數一般就在聲明處初始化,如果未顯示初始化,則預設初始化為0

3)局部靜態變數的生命週期為聲明時到程式結束,但是它的範圍卻是局部的,僅僅在該函數內,不會破壞局部性原理。


3.靜態函數 函數預設是全域可見的,而如果我們希望某個函數只在本檔案中可見,那麼可以將它聲明為靜態函數。這一點與靜態全域變數差不多。 一個例子:

//StaticTest.h#include <iostream>using namespace std;void NormalFuncTest();static void StaticFuncTest();

//StaticTest.cpp#include "stdafx.h"#include "StaticTest.h"void NormalFuncTest(){cout<<"Normal Func!"<<endl;}static void StaticFuncTest(){cout<<"Static Func!"<<endl;}

// C++Test.cpp : 定義控制台應用程式的進入點。//main函數#include "stdafx.h"#include <iostream>#include "StaticTest.h"using namespace std;int _tmain(int argc, _TCHAR* argv[]){NormalFuncTest();StaticFuncTest();system("pause");return 0;}
這個例子中有兩個函數,都定義在另一個檔案中,一個為普通函數,另一個為static函數。編譯一下,會出現如下錯誤:
\vs2012\vs12\vc\include\xlocnum(155): error C2129: 靜態函數“void StaticFuncTest(void)”已聲明但未定義

就是說,編譯器只看到了.h檔案中的聲明,但是看不到.cpp檔案中的實現。這樣,static的目的就達到啦。但是,如果將函數的實現放到了.h中,那麼,不管是不是靜態,都是可以使用的。

二.物件導向編程中的Static關鍵字 1.待用資料成員 在類中,成員也可以聲明為待用資料成員,而在物件導向編程中,static關鍵字又有了新的功能。 先來一個例子:
// C++Test.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;class StaticTest{private://此處為聲明,聲明一個靜態成員變數static int count;int id;public:StaticTest(int i): id(i){count++;}void Show(){cout<<"ID: "<<id<<endl;}static void ShowCount(){cout<<"Count: "<<count<<endl;}};//在類外定義並初始化靜態成員變數,由於定義時需要分配空間,所以不能在類聲明中定義int StaticTest::count = 0;int _tmain(int argc, _TCHAR* argv[]){StaticTest test1(1);StaticTest test2(2);test1.Show();test2.Show();StaticTest::ShowCount();system("pause");return 0;}

結果: ID: 1
ID: 2
Count: 2
請按任意鍵繼續. . .

關於靜態成員變數有下面幾點: 1)靜態成員變數在一個類中只有一份,由該類所有的對象所共用,待用資料成員只分配一次記憶體。而非靜態成員變數則是每個對象都有一份自己的拷貝。 2)靜態成員變數遵循public,private,protected的訪問規則 3)待用資料成員在全域資料區分配記憶體,屬於本類所有對象共用,但是它不屬於任何一個對象,所以即使沒有產生對象時也可以使用靜態成員變數。 4) 靜態成員變數的初始化比較特殊,不能直接像普通成員變數那樣在類中給初值,更不能像非成員變數那樣直接初始化,靜態成員變數需要在類的定義中聲明,然後再類外面,用<類型><類名>::<變數名> = <值>的格式來初始化。如上面的例子中:int StaticTest::count = 0; 5)訪問靜態成員變數的方式也有兩種,一種是與普通成員變數的訪問方式一樣,<對象名>.<待用資料成員>。另一種是靜態成員變數特有的方式<類名>::<待用資料成員>



2.靜態成員函數

還是上面的那個例子,加了一部分功能:

// C++Test.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;class StaticTest{private://此處為聲明,聲明一個靜態成員變數static int count;int id;public:StaticTest(int i): id(i){count++;}void Show(){//但是非靜態成員函數可以訪問靜態成員函數和變數cout<<"ID: "<<id<<"  Count: "<<count<<endl;}static void ShowCount(){//靜態成員函數不能訪問非靜態成員,非靜態成員函數也不行。//cout<<"ID: "<<id<<endl;     error C2597: 對非靜態成員“StaticTest::id”的非法引用//Show();:                    error C2352: “StaticTest::Show”: 非靜態成員函數的非法調用cout<<"Count: "<<count<<endl;}};//在類外定義並初始化靜態成員變數,由於定義時需要分配空間,所以不能在類聲明中定義int StaticTest::count = 0;int _tmain(int argc, _TCHAR* argv[]){StaticTest test1(1);StaticTest test2(2);test1.Show();test2.Show();StaticTest::ShowCount();system("pause");return 0;}

結果:

ID: 1  Count: 2
ID: 2  Count: 2
Count: 2
請按任意鍵繼續. . .


關於靜態成員函數注意的地方:

1)靜態成員函數可以訪問靜態成員函數和靜態成員變數。但是靜態成員函數不能訪問普通成員變數和普通成員函數。(因為靜態成員函數沒有this指標,屬於共用)

2)非靜態成員函數可以訪問靜態成員變數和靜態成員函數。

3)定義在類外的靜態成員函數不能加static,聲明時加個static即可,定義時和普通成員函數相同。

4)靜態成員函數與靜態成員變數的調用規則一致,都不需要有對象就能調用。可以使用正常方法,也可以使用類名::調用。






聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.