一、作用域
變量的幾種作用域:
全局作用域,檔案作用域,命名空間作用域,類作用域,局部作用域,語句作用域。
二、C++記憶體的區
記憶體分成5個區,他們分别是堆、棧、自由存儲區、全局/靜态存儲區和常量存儲區。
二、比較
(不讨論類作用域靜态變量,隻說全局)
1.存儲區
全局變量、靜态局部變量、靜态全局變量都在 靜态存儲區配置設定空間。全局變量本身就是靜态存儲方式,靜态全局變量當然也是靜态存儲方式。
2. 作用域
(1)全局變量
全局變量具有全局作用域檔案作用域(檔案中的全局)。全局變量隻需在一個源檔案中定義,就可以作用于所有的源檔案。
其他不包括全局變量定義的源檔案需要用extern關鍵字再次聲明這個全局變量。
(2)靜态全局變量
靜态全局變量也具有全局作用域,他與全局變量的差別在于如果程式包含多個檔案的話,他作用于定義它的檔案裡,不能作用到其他檔案裡,即被static關鍵字修飾過的變量具有檔案作用域。
即使兩個不同的源檔案都定義了相同的靜态全局變量,他們也是不同的變量。
三、總體來說
非靜态全局變量的作用域是整個源程式,當一個源程式由多個源檔案組成時,非靜态的全局變量在各個源檔案中都是有效的。
靜态全局變量則限制了其作用域,即隻在定義該變量的源檔案内有效,在同一源程式的其他源檔案中不能使用它。由于靜态全局變量的作用域局限于一個源檔案内,隻能為該源檔案内的函數公用,是以可以避免在其他源檔案中引起錯誤。
就存儲區域來說是沒有差別的。
//-----------------------------------------------------------------------------------------------------------------
C++的static有兩種用法:面向過程程式設計中的static和面向對象程式設計中的static。前者應用于普通變量和函數,不涉及類;後者主要說明static在類中的作用。
一、面向過程設計中的static
1、靜态全局變量
在全局變量前,加上關鍵字static,該變量就被定義成為一個靜态全局變量。我們先舉一個靜态全局變量的例子,如下:
//Example 1
#include <iostream.h>
void fn();
static int n; //定義靜态全局變量
void main()
{
n=20;
cout<<n<<endl;
fn();
}
void fn()
{
n++;
cout<<n<<endl;
}
靜态全局變量有以下特點:
該變量在全局資料區配置設定記憶體;
未經初始化的靜态全局變量會被程式自動初始化為0(自動變量的值是随機的,除非它被顯式初始化);
靜态全局變量在聲明它的整個檔案都是可見的,而在檔案之外是不可見的;
靜态變量都在全局資料區配置設定記憶體,包括後面将要提到的靜态局部變量。對于一個完整的程式,在記憶體中的分布情況如下幾個區:
代碼區
全局資料區
堆區
棧區
一般程式的由new産生的動态資料存放在堆區,函數内部的自動變量存放在棧區。自動變量一般會随着函數的退出而釋放空間,靜态資料(即使是函數内部的靜态局部變量)也存放在全局資料區。全局資料區的資料并不會因為函數的退出而釋放空間。細心的讀者可能會發現,Example 1中的代碼中将
static int n; //定義靜态全局變量
改為 int n; //定義全局變量
程式照樣正常運作。
的确,定義全局變量就可以實作變量在檔案中的共享,但定義靜态全局變量還有以下好處:
靜态全局變量不能被其它檔案所用;
其它檔案中可以定義相同名字的變量,不會發生沖突;
您可以将上述示例代碼改為如下:
//Example 2
//File1
#include <iostream.h>
void fn();
static int n; //定義靜态全局變量
void main()
{
n=20;
cout<<n<<endl;
fn();
}
//File2
#include <iostream.h>
extern int n;
void fn()
{
n++;
cout<<n<<endl;
}
編譯并運作Example 2,您就會發現上述代碼可以分别通過編譯,但運作時出現錯誤。試着将 static int n; //定義靜态全局變量
改為 int n; //定義全局變量
再次編譯運作程式,細心體會全局變量和靜态全局變量的差別。
2、靜态局部變量
在局部變量前,加上關鍵字static,該變量就被定義成為一個靜态局部變量。
我們先舉一個靜态局部變量的例子,如下:
//Example 3
#include <iostream.h>
void fn();
void main()
{
fn();
fn();
fn();
}
void fn()
{
static n=10;
cout<<n<<endl;
n++;
}
通常,在函數體内定義了一個變量,每當程式運作到該語句時都會給該局部變量配置設定棧記憶體。但随着程式退出函數體,系統就會收回棧記憶體,局部變量也相應失效。
但有時候我們需要在兩次調用之間對變量的值進行儲存。通常的想法是定義一個全局變量來實作。但這樣一來,變量已經不再屬于函數本身了,不再僅受函數的控制,給程式的維護帶來不便。
靜态局部變量正好可以解決這個問題。靜态局部變量儲存在全局資料區,而不是儲存在棧中,每次的值保持到下一次調用,直到下次賦新值。
靜态局部變量有以下特點:
該變量在全局資料區配置設定記憶體;
靜态局部變量在程式執行到該對象的聲明處時被首次初始化,即以後的函數調用不再進行初始化;
靜态局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程式自動初始化為0;
它始終駐留在全局資料區,直到程式運作結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域随之結束;
//注:另一個對局部變量的了解
對靜态局部變量的說明:
(1) 靜态局部變量在靜态存儲區内配置設定存儲單元。在程式整個運作期間都不釋放。而自動變量(即動态局部變量)屬于動态存儲類别,存儲在動态存儲區空間(而不是靜态存儲區空間),函數調用結束後即釋放。
(2) 為靜态局部變量賦初值是在編譯時進行值的,即隻賦初值一次,在程式運作時它已有初值。以後每次調用函數時不再重新賦初值而隻是保留上次函數調用結束時的值。而為自動變量賦初值,不是在編譯時進行的,而是在函數調用時進行,每調用一次函數重新給一次初值,相當于執行一次指派語句。
(3) 如果在定義局部變量時不賦初值的話,對靜态局部變量來說,編譯時自動賦初值0(對數值型變量)或空字元(對字元型變量)。而對自動變量來說,如果不賦初值,則它的值是一個不确定的值。這是由于每次函數調用結束後存儲單元已釋放,下次調用時又重新另配置設定存儲單元,而所配置設定的單元中的值是不确定的。
(4) 雖然靜态局部變量在函數調用結束後仍然存在,但其他函數是不能引用它的,也就是說,在其他函數中它是“不可見”的。
eg:
#include <iostream>
using namespace std;
int f(int a) //定義f函數,a為形參
{auto int b=0; //定義b為自動變量
static int c=3; //定義c為靜态局部變量
b=b+1;
c=c+1;
return a+b+c;
}
int main( )
{int a=2,i;
for(i=0;i<3;i++)
cout<<f(a)<<″ ″;
cout<<endl;
return 0;
}
運作結果為
7 8 9
3、靜态函數
在函數的傳回類型前加上static關鍵字,函數即被定義為靜态函數。靜态函數與普通函數不同,它隻能在聲明它的檔案當中可見,不能被其它檔案使用。
靜态函數的例子:
//Example 4
#include <iostream.h>
static void fn();//聲明靜态函數
void main()
{
fn();
}
void fn()//定義靜态函數
{
int n=10;
cout<<n<<endl;
}
定義靜态函數的好處:
靜态函數不能被其它檔案所用;
其它檔案中可以定義相同名字的函數,不會發生沖突;
二、面向對象的static關鍵字(類中的static關鍵字)
1、靜态資料成員
在類内資料成員的聲明前加上關鍵字static,該資料成員就是類内的靜态資料成員。先舉一個靜态資料成員的例子。
//Example 5
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
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<<"Sum="<<Sum<<endl;
}
void 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成員,而全局變量不能;
2、靜态成員函數
與靜态資料成員一樣,我們也可以建立一個靜态成員函數,它為類的全部服務而不是為某一個類的具體對象服務。靜态成員函數與靜态資料成員一樣,都是類的内部實作,屬于類定義的一部分。普通的成員函數一般都隐含了一個this指針,this指針指向類的對象本身,因為普通成員函數總是具體的屬于某個類的具體對象的。通常情況下,this是預設的。如函數fn()實際上是this->fn()。但是與普通函數相比,靜态成員函數由于不是與任何的對象相聯系,是以它不具有this指針。從這個意義上講,它無法通路屬于類對象的非靜态資料成員,也無法通路非靜态成員函數,它隻能調用其餘的靜态成員函數。下面舉個靜态成員函數的例子。
//Example 6
#include <iostream.h>
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;
}
void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
Myclass::GetSum();
}
關于靜态成員函數,可以總結為以下幾點:
出現在類體外的函數定義不能指定關鍵字static;
靜态成員之間可以互相通路,包括靜态成員函數通路靜态資料成員和通路靜态成員函數;
非靜态成員函數可以任意地通路靜态成員函數和靜态資料成員;
靜态成員函數不能通路非靜态成員函數和非靜态資料成員;
由于沒有this指針的額外開銷,是以靜态成員函數與類的全局函數相比速度上會有少許的增長;
調用靜态成員函數,可以用成員通路操作符(.)和(->)為一個類的對象或指向類對象的指針調用靜态成員函數,也可以直接使用如下格式:
<類名>::<靜态成員函數名>(<參數表>)
調用類的靜态成員函數。
//-----------------------------------------------------------------------------------------------------------------
1.如果聲明一個全局變量為static,那麼表明這個全局變量隻能用在本子產品内,其他子產品不能通路。是以,與其相對的反義詞就是extern。
2.如果聲明一個非類成員函數為static,那麼表明這個函數不導出符号,是以隻能供本子產品使用,其他子產品不能通路,這其實在C語言層次中提供封裝的概念了
//-----------------------------------------------------------------------------------------------------------------
運作一下下面的程式你就知道static的一個用處了:
#include<stdio.h>
void f()
{
static int n = 0;
printf( "%d ", n );
n++;
}
void g()
{
int n = 0;
printf( "%d ", n );
n++;
}
void main(void)
{
int n;
for( n = 0; n < 10; n++ )
{
f();
}
printf( "\n" );
for( n = 0; n < 10; n++ )
{
g();
}
printf( "\n" );
}