C++ static、const和static const型別成員變數宣告及其初始化
C++ static、const和static const型別成員變數宣告及其初始化
const定義的常量在超出其作用域之後其空間會被釋放,而static定義的靜態常量在函式執行後不會釋放其儲存空間。
static表示的是靜態的。類的靜態成員函式、靜態成員變數是和類相關的,而不是和類的具體物件相關的。即使沒有具體物件,也能呼叫類的靜態成員函式和成員變數。一般類的靜態函式幾乎就是一個全域性函式,只不過它的作用域限於包含它的檔案中。
在C++中,static靜態成員變數不能在類的內部初始化。在類的內部只是宣告,定義必須在類定義體的外部,通常在類的實現檔案中初始化,如:double Account::Rate = 2.25;static
在C++中,const成員變數也不能在類定義處初始化,只能通過建構函式初始化列表進行,並且必須有建構函式。
const資料成員只在某個物件生存期內是常量,而對於整個類而言卻是可變的。因為類可以建立多個物件,不同的物件其const資料成員的值可以不同。所以不能在類的宣告中初始化const資料成員,因為類的物件沒被建立時,編譯器不知道const資料成員的值是什麼。
const資料成員的初始化只能在類的建構函式的初始化列表中進行。要想建立在整個類中都恆定的常量,應該用類中的列舉常量來實現,或者static cosnt。
class Test{
public:
Test():a(0){}
enum {size1=100,size2=200};
private:
const int a;//只能在建構函式初始化列表中初始化
static int b;//在類的實現檔案中定義並初始化
conststatic int c;//
};
int Test::b=0;//static成員變數不能在建構函式初始化列表中初始化,因為它不屬於某個物件。
cosnt intTest::c=0;//注意:給靜態成員變數賦值時,不需要加static修飾符,但要加cosnt。
cosnt成員函式主要目的是防止成員函式修改物件的內容。即const成員函式不能修改成員變數的值,但可以訪問成員變數。當方法成員函式時,該函式只能是const成員函式。
static成員函式主要目的是作為類作用域的全域性函式。不能訪問類的非靜態資料成員。類的靜態成員函式沒有this指標,這導致:1.不能直接存取類的非靜態成員變數,呼叫非靜態成員函式2.不能被宣告為virtual。
關於static、const、static cosnt、const static成員的初始化問題:
1. 類裡的const成員初始化:
在一個類裡建立一個const時,不能給它初值。
class foo{
public:
foo():i(100){}
private:
const int i=100;//error!!!
};
//或者通過這樣的方式來進行初始化
foo::foo():i(100){}
2. 類裡的static成員初始化:
類中的static變數是屬於類的,不屬於某個物件,它在整個程式的執行過程中只有一個副本,因此不能在定義物件時對變數進行初始化,就是不能用建構函式進行初始化,其正確的初始化方法是:
資料型別 類名::靜態資料成員名=值;
class foo{
public:
foo();
private:
staticint i;
};
intfoo::i=20;
這表明:
- 初始化在類體外進行,而前面不加static,以免與一般靜態變數或物件相混淆;
- 初始化時不加該成員的訪問許可權控制符private、public等;
- 初始化時使用作用域運算子來表明它所屬的類,因此,靜態資料成員是類的成員而不是物件的成員。
3. 類裡的static cosnt 和 const static成員初始化:
這兩種寫法的作用一樣,為了便於記憶,在此值說明一種通用的初始化方法:
class Test{
public:
static const int mask1;
conststatic int mask2;
};
constTest::mask1=0xffff;
constTest::mask2=0xffff;
它們的初始化沒有區別,雖然一個是靜態常量,一個是常量靜態。靜態都將儲存在全域性變數區域,其實最後結果都一樣。可能在不同編譯器內,不同處理,但最後結果都一樣。
這是一個完整的例子:
#ifdef A_H_
#define A_H_
#include <iostream>
usingnamespace std;
class A{
public:
A(int a);
static void print();//靜態成員函式
private:
static int aa;//靜態資料成員的宣告
static const int count;//常量靜態資料成員(可以在建構函式中初始化)
const int bb;//常量資料成員
};
int A::aa=0;//靜態成員的定義+初始化
const int A::count=25;//靜態常量成員定義+初始化
A::A(int a):bb(a){//常量成員的初始化
aa+=1;
}
void A::print(){
cout<<"count="<<count<<endl;
cout<<"aa="<<aa<<endl;
}
#endif
void main(){
A a(10);
A::print();//通過類訪問靜態成員函式
a.print();//通過物件訪問靜態成員函式
}
初始化位置
靜態成員不能在類的定義裡初始化(除int外),不能在標頭檔案裡初始化。
比如定義了myclass.h,一般放到myclass.cpp裡初始化它。
C++ 類的靜態成員(static)
靜態成員的提出是為了解決資料共享的問題。實現共享有許多方法,如:設定全域性性的變數或物件是一種方法。但是,全域性變數或物件是有侷限性的。這一章我們主要講述類的靜態成員來實現資料的共享。
靜態資料成員
在類中,靜態成員可以實現多個物件之間的資料共享,並且使用靜態資料成員還不會破壞隱藏的原則,即保證了安全性。因此,靜態成員是類的所有物件中共享的成員,而不是某個物件的成員。
使用靜態資料成員可以節省記憶體,因為它是所有物件所公有的,因此,對多個物件來說,靜態資料成員只儲存一處,供所有物件共用。靜態資料成員的值對每個物件都是一樣,但它的值是可以更新的。只要對靜態資料成員的值更新一次,保證所有物件存取更新後的相同的值,這樣可以提高時間效率。
靜態資料成員的使用方法和注意事項如下:
1. 靜態資料成員在定義或說明時前面加關鍵字static。
2. 靜態成員初始化與一般資料成員初始化不同。靜態資料成員初始化的格式如下:
<資料型別><類名>::<靜態資料成員名>=<值>
這表明:
(1) 初始化在類體外進行,而前面不加static,以免與一般靜態變數或物件相混淆。
(2) 初始化時不加該成員的訪問許可權控制符private,public等。
(3) 初始化時使用作用域運算子來標明它所屬類,因此,靜態資料成員是類的成員,而不是物件的成員。
3. 靜態資料成員是靜態儲存的,它是靜態生存期,必須對它進行初始化。
4. 引用靜態資料成員時,採用如下格式:
<類名>::<靜態成員名>
如果靜態資料成員的訪問許可權允許的話(即public的成員),可在程式中,按上述格式來引用靜態資料成員。
下面舉一例子,說明靜態資料成員的應用:
#include …
class Myclass{
public:
Myclass(int a, int b, int c);
void GetNumber();
void GetSum();
private:
int A, B, C;
static int Sum;
};
int Myclass::Sum = 0;
Myclass::Myclass(int a, int b, int c){
A = a;
B = b;
C = c;
Sum += A+B+C;
}
void Myclass::GetNumber(){
cout<<"Number="<<a<<","<<b<<","<<c<<endl;
}
void Myclass::GetSum(){
cout<<"Sum="<<sum<<endl;
}
void main(){
Myclass M(3, 7, 10),N(14, 9, 11);
M.GetNumber();
N.GetNumber();
M.GetSum();
N.GetSum();
}
從輸出結果可以看到Sum的值對M物件和對N物件都是相等的。這是因為在初始化M物件時,將M物件的三個int型資料成員的值求和後賦給了Sum,於是Sum儲存了該值。在初始化N物件時,對將N物件的三個int型資料成員的值求和後又加到Sum已有的值上,於是Sum將儲存另後的值。所以,不論是通過物件M還是通過物件N來引用的值都是一樣的,即為54。
靜態成員函式
靜態成員函式和靜態資料成員一樣,它們都屬於類的靜態成員,它們都不是物件成員。因此,對靜態成員的引用不需要用物件名。
在靜態成員函式的實現中不能直接引用類中說明的非靜態成員,可以引用類中說明的靜態成員。如果靜態成員函式中要引用非靜態成員時,可通過物件來引用。下面通過例子來說明這一點。
#include …
class M{
public:
M(int a){
A=a;
B+=a;
}
static void f1(M m);
private:
int A;
static int B;
};
void M::f1(M m)
{
cout<<"A="<<m.a<<endl;
cout<<"B="<<b<<endl;
}
int M::B=0;
void main()
{
M P(5),Q(10);
M::f1(P); //呼叫時不用物件名
M::f1(Q);
}
讀者可以自行分析其結果。從中可看出,呼叫靜態成員函式使用如下格式:
<類名>::<靜態成員函式名>(<引數表>);
C++中各種型別的成員變數的初始化方法
C++各種不同型別成員根據是否static、const型別的初始化方法不盡相同。以前面試時被人問到這個問題回答不出來,寫程式碼時也經常搞亂了,這裡翻了下書,總結一下。
-----------------Test.h----------------------------
#pragma once
class Test
{
private :
int var1;
// int var11= 4; 錯誤的初始化方法
const int var2 ;
// const int var22 =22222; 錯誤的初始化方法
static int var3;
// static int var3333=33333; 錯誤,只有靜態常量int成員才能直接賦值來初始化
static const int var44;
static const int var4=4444; //正確,靜態常量成員可以直接初始化
public:
Test(void);
~Test(void);
};
--------------------Test.cpp-----------------------------------
#include ".\test.h"
int Test::var3 = 3333333; //靜態,成員的正確的初始化方法
// int Test::var1 = 11111;; 錯誤,靜態成員才能初始化
// int Test::var2 = 22222; 錯誤
// int Test::var44 = 44444; // 錯誤,提示重定義
Test::Test(void):var1(11111),var2(22222)正確的初始化方法//var3(33333)不能在這裡初始化
{
var1 =11111; //正確, 普通變數也可以在這裡初始化
//var2 = 222222; 錯誤,因為常量不能賦值,只能在 “constructor initializer (建構函式的初始化列表)” 那裡初始化
var3 =44444; //這個賦值是正確的,不過因為所有物件一個靜態成員,所以會影響到其他的,這不能叫做初始化了吧
}
Test::~Test(void){}
有些成員變數的資料型別比較特別,它們的初始化方式也和普通資料型別的成員變數有所不同。這些特殊的型別的成員變數包括:
- 常量型成員變數
- 引用型成員變數
- 靜態成員變數
- 整型靜態常量成員變數
- 非整型靜態常量成員變數
對於常量型成員變數和引用型成員變數的初始化,必須通過建構函式初始化列表的方式進行。在建構函式體內給常量型成員變數和引用型成員變數賦值的方式是行不通的。
靜態成員變數的初始化也頗有點特別。
參考下面的程式碼以及其中註釋:
// Initialization of Special Data Member
#include <iostream>
using namespace std;
class BClass
{
public:
BClass() : i(1),ci(2), ri(i)
// 對於常量型成員變數和引用型成員變數,必須通過引數化列表的方式進行初始化。在建構函式體內進行賦值的方式,是行不通的。
{
}
voidprint_values()
{
cout<< "i =\t" << i << endl;
cout<< "ci =\t" << ci << endl;
cout<< "ri =\t" << ri << endl;
cout<< "si =\t" << si << endl;
cout<< "csi =\t" << csi << endl;
cout<< "csi2 =\t" << csi2 << endl;
cout<< "csd =\t" << csd << endl;
}
private:
int i; // 普通成員變數
const int ci; // 常量成員變數
int &ri; // 引用成員變數
static int si; // 靜態成員變數
//static int si2 = 100; // error: 只有靜態常量成員變數,才可以這樣初始化
static const int csi; // 靜態常量成員變數
static const int csi2 = 100; // 靜態常量成員變數的初始化(Integral type) (1)
static const double csd; // 靜態常量成員變數(non-Integral type)
//static const double csd2 = 99.9; // error: 只有靜態常量整型資料成員才可以在類中初始化
};
// 靜態成員變數的初始化(Integral type)
int BClass::si = 0;
// 靜態常量成員變數的初始化(Integral type)
const int BClass::csi = 1;
// 靜態常量成員變數的初始化(non-Integral type)
const double BClass::csd = 99.9;
// 在初始化(1)中的csi2時,根據Stanley B. Lippman的說法下面這行是必須的。
// 但在VC2003中如果有下面一行將會產生錯誤,而在VC2005中,下面這行則可有可無,這個和編譯器有關。
const int BClass::csi2;
int main(void)
{
BClassb_class;
b_class.print_values();
return0;
}
C++靜態成員小結
類中的靜態成員真是個讓人愛恨交加的特性。我曾經在面試時,被主考官抓住這個問題一陣窮追猛打,直把我問的面紅耳赤,敗下陣來。所以回來之後,我痛定思痛,決定好好總結一下靜態類成員的知識點,以便自己在以後面試中,在此類問題上不在被動。
靜態類成員包括靜態資料成員和靜態函式成員兩部分。
靜態資料成員
類體中的資料成員的宣告前加上static關鍵字,該資料成員就成為了該類的靜態資料成員。和其他資料成員一樣,靜態資料成員也遵守public/protected/private訪問規則。同時,靜態資料成員還具有以下特點:
1. 靜態資料成員的定義
靜態資料成員實際上是類域中的全域性變數。所以,靜態資料成員的定義(初始化)不應該被放在標頭檔案中。(宣告vs.定義(初始化)),其定義方式與全域性變數相同。舉例如下:
xxx.h檔案
class base{
private:
static const int _i;//宣告,標準c++支援有序型別在類體中初始化,但vc6不支援。
};
xxx.cpp檔案
const int base::_i=10;//定義(初始化)時不受private和protected訪問限制
注:不要試圖在標頭檔案中定義(初始化)靜態資料成員。在大多數的情況下,這樣做會引起重複定義這樣的錯誤。即使加上#ifndef #define #endif或者#pragma once也不行。
2. 靜態資料成員被類的所有物件所共享,包括該類派生類的物件。即派生類物件與基類物件共享基類的靜態資料成員。
舉例如下:
class base{
public :
static int _num;//宣告
};
int base::_num=0;//靜態資料成員的真正定義
class derived:public base{ };
main()
{
base a;
derived b;
a._num++;
cout<<"base class static data number_num is"<<a._num<<endl;
b._num++;
cout<<"derived class static datanumber _num is"<<b._num<<endl;
}
// 結果為1,2;可見派生類與基類共用一個靜態資料成員。
3. 靜態資料成員可以成為成員函式的可選引數,而普通資料成員則不可以。
舉例如下:
class base{
public :
static int _staticVar;
int _var;
void foo1(int i=_staticVar);//正確,_staticVar為靜態資料成員
void foo2(int i=_var);//錯誤,_var為普通資料成員
};
4.★靜態資料成員的型別可以是所屬類的型別,而普通資料成員則不可以。普通資料成員的只能宣告為所屬類型別的指標或引用。
舉例如下:
class base{
public :
static base_object1;//正確,靜態資料成員
base _object2;//錯誤
base *pObject;//正確,指標
base &mObject;//正確,引用
};
5.★靜態資料成員的值在const成員函式中可以被合法的改變。
這個特性,我不知道是屬於標準c++中的特性,還是vc6自己的特性。舉例如下:
class base{
public:
base(){
_i=0;
_val=0;
}
mutable int _i;
static int _staticVal;
int _val;
void test() const{//const 成員函式
_i++;//正確,mutable資料成員
_staticVal++;//正確,static資料成員
_val++;//錯誤
}
};
int base::_staticVal=0;
靜態成員函式
1. 靜態成員函式的地址可用普通函式指標儲存,而普通成員函式地址需要用類成員函式指標來儲存。
舉例如下:
class base{
static int func1();
int func2();
};
int (*pf1)()=&base::func1;//普通的函式指標
int (base::*pf2)()=&base::func2;//成員函式指標
2. 靜態成員函式不可以呼叫類的非靜態成員。因為靜態成員函式不含this指標。
3. 靜態成員函式不可以同時宣告為 virtual、const、volatile函式。
舉例如下:
class base{
virtual static void func1();//錯誤
static void func2() const;//錯誤
static void func3() volatile;//錯誤
};
最後要說的一點是,靜態成員是可以獨立訪問的,也就是說,無須建立任何物件例項就可以訪問。