C++靜态成員變量和靜态成員函數
[轉]原文
注意:
類中靜态變量在類外初始化格式:
<資料類型><類名>::<靜态資料成員名>=<值>
加上聲明是為了在構造函數之前運作。
private:
int a,b,c;
static const int Sum[];//聲明靜态資料成員!!!!!!!!!!!!!!!
};
const int Myclass::Sum={,,,,,};//定義并初始化靜态資料成員!!!!!!!!
資料成員可以分靜态變量、非靜态變量兩種.
靜态成員: 靜态類中的成員加入static修飾符,即是靜态成員.可以直接使用類名::靜态成員名通路此靜态成員,因為靜态成員存在于記憶體,非靜态成員需要執行個體化才會配置設定記憶體,是以靜态成員不能通路非靜态的成員.因為靜态成員存在于記憶體,是以非靜态成員可以直接通路類中靜态的成員.
非成靜态員: 所有沒有加Static的成員都是非靜态成員,當類被執行個體化之後,可以通過執行個體化的類名進行通路.非靜态成員的生存期決定于該類的對象的生存期..而靜态成員則不存在生存期的概念,因為靜态成員始終駐留在内容中..
一個類中也可以包含靜态成員和非靜态成員,類中也包括靜态構造函數和非靜态構造函數..
分兩個方面來總結,第一方面主要是相對于面向過程而言,即在這方面不涉及到類,第二方面相對于面向對象而言,主要說明static在類中的作用。
一、在面向過程設計中的static關鍵字
1、靜态全局變量
定義:在全局變量前,加上關鍵字 static 該變量就被定義成為了一個靜态全局變量。
特點:
- A、該變量在全局資料區配置設定記憶體。
- B、初始化:如果不顯式初始化,那麼将被隐式初始化為0(自動變量是随機的,除非顯式地初始化)。
- C、訪變量隻在本源檔案可見,嚴格的講應該為定義之處開始到本檔案結束。
- D、檔案作用域下聲明的const的常量預設為static存儲類型。
例(摘于C++程式設計教程—錢能主編P103): “`
//file1.cpp
//Example 1
#include <iostream.h>
void fn();
static int n; //定義靜态全局變量
void main()
{
n=;
cout < <n < <endl;
fn();
}
void fn()
{
n++;
cout < <n < <endl;
}
靜态變量都在全局資料區配置設定記憶體,包括後面将要提到的靜态局部變量。對于一個完整的程式,在記憶體中的分布情況如下:
代碼區
全局資料區
堆區
棧區
一般程式的由new産生的動态資料存放在堆區,函數内部的自動變量存放在棧區。自動變量一般會随着函數的退出而釋放空間,靜态資料(即使是函數内部的靜态局部變量)也存放在全局資料區。全局資料區的資料并不會因為函數的退出而釋放空間。細心的讀者可能會發現,Example 1中的代碼中将
static int n; //定義靜态全局變量
改為:
int n; //定義全局變量程式照樣正常運作。的确,定義全局變量就可以實作變量在檔案中的共享,但定義靜态全局變量還有以下好處:
靜态全局變量不能被其它檔案所用;(好像是差別extern的)
其它檔案中可以定義相同名字的變量,不會發生沖突;
您可以将上述示例代碼改為如下:
//Example 2
//File1
#include <iostream.h>
void fn();
static int n; //定義靜态全局變量(隻能在本檔案中使用)
void main()
{
n=;
cout < <n < <endl;
fn();
}
//File2
#include <iostream.h>
extern int n;(可在别的檔案中引用這個變量)
void fn()
{
n++;
cout < <n < <endl;
}
編譯并運作Example2,您就會發現上述代碼可以分别通過編譯,但link時出現錯誤。
試着将 static int n; //定義靜态全局變量
改為
int n; //定義全局變量
再次編譯運作程式,細心體會全局變量和靜态全局變量的差別。
2、靜态局部變量
定義:在局部變量前加上static關鍵字時,就定義了靜态局部變量。我們先舉一個靜态局部變量的例子,如下://Example 3
#include <iostream.h>
void fn();
void main()
{
fn();
fn();
fn();
}
void fn()
{
static n=;
cout < <n < <endl;
n++;
}
通常,在函數體内定義了一個變量,每當程式運作到該語句時都會給該局部變量配置設定棧記憶體。但随着程式退出函數體,系統就會收回棧記憶體,局部變量也相應失效。
但有時候我們需要在兩次調用之間對變量的值進行儲存。通常的想法是定義一個全局變量來實作。但這樣一來,變量已經不再屬于函數本身了,不再僅受函數的控制,給程式的維護帶來不便。
靜态局部變量正好可以解決這個問題。靜态局部變量儲存在全局資料區,而不是儲存在棧中,每次的值保持到下一次調用,直到下次賦新值。
特點:
- A、該變量在全局資料區配置設定記憶體。
- B、初始化:如果不顯式初始化,那麼将被隐式初始化為0,以後的函數調用不再進行初始化。
- C、它始終駐留在全局資料區,直到程式運作結束。但其作用域為局部作用域,當定義它的函數或 語句塊結束時,其作用域随之結束。
3、靜态函數(注意與類的靜态成員函數差別)
定義:在函數的傳回類型前加上static關鍵字,函數即被定義成靜态函數。
特點:
- A、靜态函數與普通函數不同,它隻能在聲明它的檔案當中可見,不能被其它檔案使用。
靜态函數的例子:
//Example 4
#include <iostream.h>
static void fn();//聲明靜态函數
void main()
{
fn();
}
void fn()//定義靜态函數
{
int n=;
cout < <n < <endl;
}
定義靜态函數的好處:靜态函數不能被其它檔案所用;
其它檔案中可以定義相同名字的函數,不會發生沖突;
二、面向對象的static關鍵字(類中的static關鍵字)
1、靜态資料成員
在類内資料成員的聲明前加上關鍵字static,該資料成員就是類内的靜态資料成員。
先舉一個靜态資料成員的例子。
//Example
#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=;//定義并初始化靜态資料成員!!!!!!!!
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(,,);
M.GetSum();
Myclass N(,,);
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=;//定義并初始化靜态資料成員
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是非靜态資料成員 靜态成員函數由于不是與任何的對象相聯系,是以它不具有this指針。從這個意義上講,它無法通路屬于類對象的非靜态資料成員,也無法通路非靜态成員函數,它隻能調用其餘的靜态成員函數。
cout < <"Sum=" < <Sum < <endl;
}
void main()
{
Myclass M(,,);
M.GetSum();
Myclass N(,,);
N.GetSum();
Myclass::GetSum();
}
關于靜态成員函數,可以總結為以下幾點:
出現在類體外的函數定義不能指定關鍵字static;
靜态成員之間可以互相通路,包括靜态成員函數通路靜态資料成員和通路靜态成員函數;
非靜态成員函數可以任意地通路靜态成員函數和靜态資料成員;
靜态成員函數不能通路非靜态成員函數和非靜态資料成員;
由于沒有this指針的額外開銷,是以靜态成員函數與類的全局函數相比速度上會有少許的增長;
調用靜态成員函數,可以用成員通路操作符(.)和(->)為一個類的對象或指向類對象的指針調用靜态成員函數.