-
static
(計算機高級語言關鍵字)
鎖定
staticC++中
static面向過程
//示例1 #include <iostream> using namespace std; void fn(); //聲明函數 static int n; //聲明靜態全局變量 int main() { n = 20; //為n賦初值 cout<<n<<endl;//輸出n的值 fn(); //調用fn函數 } void fn() { n++; //n的值自加一(n=n+1) cout<<n<<endl; //輸出n的值 }
靜態全局變量有以下特點:
該變量在全局數據區分配內存;
未經初始化的靜態全局變量會被程序自動初始化為0(在函數體內聲明的自動變量的值是隨機的,除非它被顯式初始化,而在函數體外被聲明的自動變量也會被初始化為0);
靜態全局變量在聲明它的整個文件都是可見的,而在文件之外是不可見的;
代碼區 //low address全局數據區堆區棧區 //high address
一般程序把新產生的動態數據存放在堆區,函數內部的自動變量存放在棧區。自動變量一般會隨着函數的退出而釋放空間,靜態數據(即使是函數內部的靜態局部變量)也存放在全局數據區。全局數據區的數據並不會因為函數的退出而釋放空間。細心的讀者可能會發現,Example 1中的代碼中將
static int n; //定義靜態全局變量
改為
int n; //定義全局變量
程序照樣正常運行。
的確,定義全局變量就可以實現變量在文件中的共享,但定義靜態全局變量還有以下好處:
靜態全局變量不能被其它文件所用;
其它文件中可以定義相同名字的變量,不會發生衝突;
您可以將上述示例代碼改為如下:
//示例2 //File1.c第一個代碼文件的代碼 #include <iostream> using namespace std; void fn(); //聲明fn函數 static int n; //定義靜態全局變量 int main() { n = 20; cout<<n<<endl; fn(); } //File2.c第二個代碼文件的代碼 #include <iostream> using namespace std; extern int n; void fn() { n++; cout<<n<<endl; }
編譯並運行Example 2,您就會發現上述代碼可以分別通過編譯,但運行時出現錯誤。試着將
static int n; //定義靜態全局變量
改為
int n; //定義全局變量
再次編譯運行程序,細心體會全局變量和靜態全局變量的區別。
注意:全局變量和全局靜態變量的區別
2)全局靜態變量是顯式用static修飾的全局變量,作用域是聲明此變量所在的文件,其他的文件即使用extern聲明也不能使用。
靜態局部變量
在局部變量前,加上關鍵字static,該變量就被定義成為一個靜態局部變量。
我們先舉一個靜態局部變量的例子,如下
//示例3 #include <iostream> using namespace std; void fn(); int main() { fn(); fn(); fn(); return 0; } void fn() { static int n = 10; cout<<n<<endl; n++; }
但有時候我們需要在兩次調用之間對變量的值進行保存。通常的想法是定義一個全局變量來實現。但這樣一來,變量已經不再屬於函數本身了,不再僅受函數的控制,給程序的維護帶來不便。
靜態局部變量正好可以解決這個問題。靜態局部變量保存在全局數據區,而不是保存在棧中,每次的值保持到下一次調用,直到下次賦新值。
靜態局部變量有以下特點:
該變量在全局數據區分配內存;
靜態局部變量在程序執行到該對象的聲明處時被首次初始化,即以後的函數調用不再進行初始化;
靜態局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程序自動初始化為0;
靜態函數
在函數的返回類型前加上static關鍵字,函數即被定義為靜態函數。靜態函數與普通函數不同,它只能在聲明它的文件當中可見,不能被其它文件使用。
靜態函數的例子:
//示例4 #include <iostream> using namespace std; static void fn();//聲明靜態函數 int main() { fn(); } static void fn()//定義靜態函數 { int n = 10; cout<<n<<endl; }
定義靜態函數的好處:
靜態函數不能被其它文件所用;
其它文件中可以定義相同名字的函數,不會發生衝突;
static面向對象
(類中的static關鍵字)
靜態數據成員
在類內數據成員的聲明前加上關鍵字static,該數據成員就是類內的靜態數據成員。先舉一個靜態數據成員的例子。
//示例5 #include <iostream> using namespace std; class Myclass { public: Myclass(int a, int b, int c); void GetSum(); private: int a; int b; int c; static int Sum;//聲明靜態數據成員 }; int Myclass::Sum = 0;//定義並初始化靜態數據成員 Myclass::Myclass(int a,int b,int c) { this->a = a; this->b = b; this->c = c; Sum += a + b + c; } void Myclass::GetSum() { cout<<"Sum="<<Sum<<endl; } int main() { Myclass M(1, 2, 3); M.GetSum(); Myclass N(4, 5, 6); N.GetSum(); M.GetSum(); }
可以看出,靜態數據成員有以下特點:
對於非靜態數據成員,每個類對象都有自己的拷貝。而靜態數據成員被當作是類的成員。無論這個類的對象被定義了多少個,靜態數據成員在程序中也只有一份拷貝,由該類型的所有對象共享訪問。也就是説,靜態數據成員是該類的所有對象所共有的。對該類的多個對象來説,靜態數據成員只分配一次內存,供所有對象共用。所以,靜態數據成員的值對每個對象都是一樣的,它的值可以更新;
靜態數據成員存儲在全局數據區。靜態數據成員定義時要分配空間,所以不能在類聲明中定義。在Example 5中,語句int Myclass::Sum=0;是定義靜態數據成員;
靜態數據成員和普通數據成員一樣遵從public,protected,private訪問規則;
因為靜態數據成員在全局數據區分配內存,屬於本類的所有對象共享,所以,它不屬於特定的類對象,在沒有產生類對象時其作用域就可見,即在沒有產生類的實例時,我們就可以操作它;
靜態數據成員初始化與一般數據成員初始化不同。靜態數據成員初始化的格式為:
<數據類型><類名>::<靜態數據成員名>=<值>
類的靜態數據成員有兩種訪問形式:
<類對象名>.<靜態數據成員名> 或 <類類型名>::<靜態數據成員名>
如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式來引用靜態數據成員 ;
靜態數據成員主要用在各個對象都有相同的某項屬性的時候。比如對於一個存款類,每個實例的利息都是相同的。所以,應該把利息設為存款類的靜態數據成員。這 有兩個好處,第一,不管定義多少個存款類對象,利息數據成員都共享分配在全局數據區的內存,所以節省存儲空間。第二,一旦利息需要改變時,只要改變一次, 則所有存款類對象的利息全改變過來了;
同全局變量相比,使用靜態數據成員有兩個優勢:
靜態數據成員沒有進入程序的全局名字空間,因此不存在與程序中其它全局名字衝突的可能性;
可以實現信息隱藏。靜態數據成員可以是private成員,而全局變量不能;
靜態成員函數
與靜態數據成員一樣,我們也可以創建一個靜態成員函數,它為類的全部服務而不是為某一個類的具體對象服務。靜態成員函數與靜態數據成員一樣,都是類的內部 實現,屬於類定義的一部分。普通的成員函數一般都隱含了一個this指針,this指針指向類的對象本身,因為普通成員函數總是具體的屬於某個類的具體對象的。通常情況下,this 是缺省的。如函數fn()實際上是this->fn()。但是與普通函數相比,靜態成員函數由於不是與任何的對象相聯繫,因此它不具有this指 針。從這個意義上講,它無法訪問屬於類對象的非靜態數據成員,也無法訪問非靜態成員函數,它只能調用其餘的靜態成員函數。下面舉個靜態成員函數的例子。
//示例6 #include <iostream> using namespace std; class Myclass { public : Myclass(int a, int b, int c); static void GetSum(); // 聲明靜態成員函數 private : int a, b, c; static int Sum; //聲明靜態數據成員 }; int Myclass::Sum=0; //定義並初始化靜態數據成員 Myclass::Myclass(int a, int b, int c) { this->a = a; this->b = b; this->c = c; Sum += a + b + c; //非靜態成員函數可以訪問靜態數據成員 } void Myclass::GetSum() //靜態成員函數的實現 { // cout<<a<<endl; //錯誤代碼,a是非靜態數據成員 cout<<"Sum="<<Sum<<endl; } int main() { Myclass M(1, 2, 3); M.GetSum(); Myclass N(4, 5, 6); N.GetSum(); Myclass::GetSum(); }
關於靜態成員函數,可以總結為以下幾點:
出現在類體外的函數定義不能指定關鍵字static;
靜態成員之間可以相互訪問,包括靜態成員函數訪問靜態數據成員和訪問靜態成員函數;
非靜態成員函數可以任意地訪問靜態成員函數和靜態數據成員;
靜態成員函數不能訪問非靜態成員函數和非靜態數據成員;
由於沒有this指針的額外開銷,因此靜態成員函數與類的全局函數相比速度上會有少許的增長;
調用靜態成員函數,可以用成員訪問操作符(.)和(->)為一個類的對象或指向類對象的指針調用靜態成員函數,也可以直接使用如下格式:
<;類名>::<;靜態成員函數名>;(<;參數表>;)
調用類的靜態成員函數。
作用
為什麼要引入static
函數內部定義的變量,在程序執行到它的定義處時,編譯器為它在棧上分配空間,大家知道,函數在棧上分配的空間在此函數執行結束時會釋放掉,這樣就產生了一個問題: 如果想將函數中此變量的值保存至下一次調用時,如何實現? 最容易想到的方法是定義一個全局的變量,但定義為一個全局變量有許多缺點,最明顯的缺點是破壞了此變量的訪問範圍(使得在此函數中定義的變量,不僅僅受此函數控制)。
什麼時候用static
需要一個數據對象為整個類而非某個對象服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內部,對外不可見。
內部機制
靜態數據成員要在程序一開始運行時就必須存在。因為函數在程序運行中被調用,所以靜態數據成員不能在任何函數內分配空間和初始化。
靜態數據成員要實際地分配空間,故不能在類的聲明中定義(只能聲明數據成員)。類聲明只聲明一個類的“尺寸和規格”,並不進行實際的內存分配,所以在類聲明中寫成定義是錯誤的。它也不能在頭文件中類聲明的外部定義,因為那會造成在多個使用該類的源文件中,對其重複定義。
static被引入以告知編譯器,將變量存儲在程序的靜態存儲區而非棧上空間,靜態
優勢
可以節省內存,因為它是所有對象所公有的,因此,對多個對象來説,靜態數據成員只存儲一處,供所有對象共用。靜態數據成員的值對每個對象都是一樣,但它的值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新後的相同的值,這樣可以提高時間效率。
應用格式
引用靜態數據成員時,採用如下格式:
<;類名>::<;靜態成員名>
如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式來引用靜態數據成員。
注意事項
⑴類的靜態成員函數是屬於整個類而非類的對象,所以它沒有this指針,這就導致了它僅能訪問類的靜態數據和靜態成員函數。
⑵不能將靜態成員函數定義為虛函數。
⑶由於靜態成員聲明於類中,操作於其外,所以對其取地址操作,就多少有些特殊,變量地址是指向其數據類型的指針 ,函數地址類型是一個“nonmember函數指針”。
⑷由於靜態成員函數沒有this指針,所以就差不多等同於nonmember函數,結果就產生了一個意想不到的好處:成為一個callback函數,使得我們得以將C++和C-based X Window系統結合,同時也成功的應用於線程函數身上。
⑹靜態數據成員在<;定義或説明>;時前面加關鍵字static。
⑺靜態數據成員是靜態存儲的,所以必須對它進行初始化。
⑻靜態成員初始化與一般數據成員初始化不同:
初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆;
初始化時不加該成員的訪問權限控制符private,public等;
所以我們得出靜態數據成員初始化的格式:
<;數據類型><;類名>::<;靜態數據成員名>=<;值>
⑼為了防止父類的影響,可以在子類定義一個與父類相同的靜態變量,以屏蔽父類的影響。這裏有一點需要注意:我們説靜態成員為父類和子類共享,但我們有重複定義了靜態成員,這會不會引起錯誤呢?不會,我們的編譯器採用了一種絕妙的手法:name-mangling 用以生成唯一的標誌。在各通信公司的筆試面試中經常出現的考題就是static的作用及功能。
staticC語言中
函數分為內部函數和外部函數
內部函數(又稱靜態函數)
如果在一個源文件中定義的函數,只能被本文件中的函數調用,而不能被同一程序其它文件中的函數調用,這種函數稱為內部函數。
定義一個內部函數,只需在函數類型前再加一個“static”關鍵字即可,如下所示:
static 函數類型 函數名(函數參數表){……}
使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名,因為同名也沒有關係。
外部函數
外部函數的定義:在定義函數時,如果沒有加關鍵字“static”,或冠以關鍵字“extern”,表示此函數是外部函數:
[extern] 函數類型 函數名(函數參數表){……}
調用外部函數時,需要對其進行説明:
[extern] 函數類型 函數名(參數類型表)[,函數名2(參數類型表2)……];
[案例]外部函數應用。
⑴文件mainf.c
main()
{
extern void input(…),process(…),output(…);
input(…);
process(…);
output(…);
}
⑵文件subf1.c
……extern void input(……) /*定義外部函數*/{……}
⑶文件subf2.c
……extern void process(……) /*定義外部 函數*/{……}
⑷文件subf3.c
……extern void output(……) /*定義外部函數*/{……}靜態局部變量static的存儲 有時希望函數中的局部變量的值在函數調用結束後不消失而繼續保留原值,即其佔用的存儲單元不釋放,在下一次再調用該函數時,該變量已有值(就是上一次函數調用結束時的值)。這時就應該指定該局部變量為“靜態局部變量”,用關鍵字static進行聲明。用靜態存儲要多佔內存(長期佔用不釋放,而不能像動態存儲那樣一個存儲單元可以先後為多個變量使用,節約內存),而且降低了程序的可讀性,因此若非必要,不要多用靜態局部變量。vb中語句
在過程級別中使用,用於聲明變量並分配存儲空間。在整個代碼運行期間都能保留使用 Static 語句聲明的變量的值。
static語句聲明的變量,與dim語句聲明的變量的主要區別是:前者只能在sub或function過程中使用,在退出sub或function過程後變量的值保留;後者使用在sub或function過程中時,退出sub或function過程後變量的值不保留。
語法Staticvarname[([subscripts])] [As [New]type] [,varname[([subscripts])] [As [New]type]] . . .
Static 語句的語法包含下面部分:
描述
[lower To] upper [,[lower To] upper] . . .
如果不顯式指定 lower,則數組的下界由 Option Base 語句控制。如果沒有 Option Base 語句則下界為 0。
New 可選的。用它可以隱式地創建對象的關鍵字。如果使用 New 聲明對象變量,則在第一次引用該變量時將新建該對象的實例,因此不必使用 Set 語句來對該對象引用賦值。New 關鍵字不能用來聲明任何內部數據類型的變量,也不能用來聲明從屬對象的實例。
type 可選的。變量的數據類型;可以是
Byte、Boolean、Integer、Long、Currency、Single、Double、Decimal(尚不支持)、Date、String(對變長的字符串)、String * length(對定長的字符串)、Object、Variant、用户定義類型或對象類型。
所聲明的每個變量都要有一個單獨的 As type 子句。
説明
模塊的代碼開始運行後,使用 Static 語句聲明的變量會一直保持其值,直至該模塊復位或重新啓動。可以在非靜態的過程中使用 Static 語句顯式聲明只在該過程內可見,但具有與包含該過程定義的模塊相同生命期的變量。
可以在過程中使用 Static 語句來聲明在過程調用之間仍能保持其值的變量的數據類型。例如,下面的語句聲明瞭一個定長的整型數組:
Static EmployeeNumber(200) As Integer
下面的語句為 worksheet 的新實例聲明瞭一個變量:
Static X As New Worksheet
如果在定義對象變量時沒有使用 New 關鍵字,則在使用該變量之前,必須使用 Set 語句將一個已有的對象賦給這個引用對象的變量。在被賦值之前,所聲明的這個對象變量有一個特定值 Nothing,這個值表示該變量沒有指向任何對象的實例。若在聲明中使用了 New 關鍵字,則在第一次引用對象時將新建一個該對象的實例。
如果不指定數據類型或對象類型,且在模塊中沒有使用 Deftype 語句,則按缺省情況,定義該變量為 Variant 類型。
注意
Static 語句與 Static 關鍵字很相似,但是針對不同的效果來使用的。如果使用 Static 關鍵字(如 Static Sub CountSales ())來聲明一個過程,則該過程中的所有局部變量的存儲空間都只分配一次,且這些變量的值在整個程序運行期間都存在。對非靜態過程而言,該過程每次被調用時都要為其變量分配存儲空間,當該過程結束時都要釋放其變量的存儲空間。Static 語句則用來在非靜態的過程中聲明特定的變量,以使其在程序運行期間能保持其值。
在初始化變量時,數值變量被初始化為 0,變長的字符串被初始化為一個零長度的字符串 (""),而定長的字符串則用 0 填充。Variant 變量被初始化為 Empty。用户自定義類型的變量的每個元素作為各自獨立的變量進行初始化。
注意 如果在過程中使用 Static 語句,應和其它的聲明語句(如 Dim)一樣將其放在過程的開始。
作用
static的作用
在C語言中,static的字面意思很容易把我們導入歧途,其實它的作用有三條。
(1)先來介紹它的第一條也是最重要的一條:隱藏。
下面是a.c的內容
#include <stdio.h> char a = 'A'; // global variable void msg(void) { printf("Hello\n"); }
下面是main.c的內容
#include <stdio.h> int main(void) { extern char a; // 變量使用前必須聲明 extern void msg(void); printf("%c ", a); (void)msg(); return 0; }
程序的運行結果是:
A Hello
你可能會問:為什麼在a.c中定義的全局變量a和函數msg能在main.c中使用?前面説過,所有未加static前綴的全局變量和函數都具有全局可見性,其它的源文件也能訪問。此例中,a是全局變量,msg是函數,並且都沒有加static前綴,因此對於另外的源文件main.c是可見的。
如果加了static,就會對其它源文件隱藏。例如在a和msg的定義前加上static,main.c就看不到它們了。利用這一特性可以在不同的文件中定義同名函數和同名變量,而不必擔心命名衝突。static可以用作函數和變量的前綴,對於函數來講,static的作用僅限於隱藏,而對於變量,static還有下面兩個作用。
(2)static的第二個作用是保持變量內容的持久。存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量,只不過和全局變量比起來,static可以控制變量的可見範圍,説到底static還是用來隱藏的。雖然這種用法不常見,但我還是舉一個例子。
#include <stdio.h> int fun(void) { static int count = 10; // 此語句只在函數第一次調用時執行,後續函數調用此變量的初始值為上次調用後的值,每次調用後存儲空間不釋放 return count--; } int count = 1; int main(void) { printf("global\t\tlocal static\n"); for(; count <= 10; ++count) printf("%d\t\t%d\n", count, fun()); return 0; }
程序的運行結果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
(3)static的第三個作用是默認初始化為0。其實全局變量也具備這一屬性,因為全局變量也存儲在靜態數據區。在靜態數據區,內存中所有的字節默認值都是0x00,某些時候這一特點可以減少程序員的工作量。比如初始化一個稀疏矩陣,我們可以一個一個地把所有元素都置0,然後把不是0的幾個元素賦值。如果定義成靜態的,就省去了一開始置0的操作。再比如要把一個字符數組當字符串來用,但又覺得每次在字符數組末尾加’\0’太麻煩。如果把字符串定義成靜態的,就省去了這個麻煩,因為那裏本來就是’\0’。不妨做個小實驗驗證一下。
#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; }
程序的運行結果如下
最後對static的三條作用做一句話總結。首先static的最主要功能是隱藏,其次因為static變量存放在靜態存儲區,所以它具備持久性和默認值0。
staticJAVA語言中
有時你希望定義一個類成員,使它的使用完全獨立於該類的任何對象。通常情況下,類成員必須通過它的類的對象訪問,但是可以創建這樣一個成員,它能夠被它自己使用,而不必引用特定的實例。在成員的聲明前面加上關鍵字static(靜態的)就能創建這樣的成員。如果一個成員被聲明為static,它就能夠在它的類的任何對象創建之前被訪問,而不必引用任何對象。你可以將方法和變量都聲明為static。static 成員的最常見的例子是main()。因為在程序開始執行時必須調用main() ,所以它被聲明為static。
聲明為static的變量稱為靜態變量或類變量。可以直接通過類名引用靜態變量,也可以通過實例名來引用靜態變量,但最好採用前者,因為後者容易混淆靜態變量和一般變量。靜態變量是跟類相關聯的,類的所有實例共同擁有一個靜態變量。
舉例
如果你需要通過計算來初始化你的static變量,你可以聲明一個static塊,Static 塊僅在該類被加載時執行一次。下面的例子顯示的類有一個static方法,一些static變量,以及一個static 初始化塊:
// Demonstrate static variables,methods,and blocks. class UseStatic{ static int a = 3; static int b; static void meth(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b); } static { System.out.println("Static block initialized."); b = a * 4; } public static void main(String args[]) { meth(42); } }
一旦UseStatic 類被裝載,所有的static語句被運行。首先,類屬性變量開始賦值,a被設置為3,b默認初始化為 0 ,接着運行static 塊,執行(打印一條消息),最後,b被賦值為a*4 或12。然後調用main(),main() 調用meth() ,把值42傳遞給x。3個println () 語句引用兩個static變量a和b,以及局部變量x。
注意:在一個static 方法中引用任何實例變量都是非法的。
下面是該程序的輸出:
Static block initialized.
x = 42
a = 3
b = 12
使用 static修飾符聲明屬於類型本身而不是屬於特定對象的靜態成員。static修飾符可用於字段、方法、屬性、運算符、事件和構造函數,但不能用於索引器、析構函數或類以外的類型。例如,下面的類聲明為 static,並且只包含 static方法。
例如:
static class CompanyEmployee{ public static string GetCompanyName(string name) { ... } public static string GetCompanyAddress(string address) { ... } }
一般來説,類中標註了static的函數與變量能在類外直接引用,比如説:
String M_string1 =CompanyEmployee.GetCompanyName(M_string2)
而沒有標註static的函數則必須聲明一個類的實體,有實體來引用。比如説:
static class CompanyEmployee{//靜態類 public string GetCompanyName(string name) { ... } //沒有Static public static string GetCompanyAddress(string address) { ... } } CompanyEmployee M_CompE = new CompanyEmployee(); String M_string1 =M_CompE.GetCompanyName(M_string2);//直接引用
staticC#語言中
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyNamespace { class Program { public static void Main(string[] args) { Console.WriteLine("返回數組的和"); int[] values = { 1,2,3,4,5}; int sum = Sum(values); Console.WriteLine(sum); } //計算數組的和 static int Sum(int[] valuesValue) { int sum = 0; for (int i = 0; i < valuesValue.Length; i++) { sum = sum + valuesValue[i]; } return sum; } } }