c++ ——靜態成員變量和靜態成員函數

前言

c++ 靜態成員變量用static 關鍵字來聲明,是整個類的數據成員,其存儲不佔用某個具體對象的空間,其存儲在靜態存儲區
c++ 靜態成員函數用static 關鍵字來聲明,用於處理靜態成員變量,可以用類名來調用,也可以用對象名來調用。

使用靜態成員變量的目的:靜態成員變量是整個類的數據成員,使用靜態成員變量可以實現多個對象共享數據

測試1:

#include <iostream>
using namespace std;

class Point{
public:
    Point(int xx,int yy):x(xx),y(yy){count++;}
    Point():Point(0,0){}
    Point(const Point &p);
    ~Point(){count--;}
    void show(){cout<<x<<" "<<y<<endl;}
    static void showcount();  //用static來聲明靜態成員函數
private:
    int x,y;
    static int count; //用static來聲明靜態成員變量
};

int Point::count = 0;   //靜態成員變量的定義,定義時前面不可以再有static, 且它必須要在類外面來初始化,不能在類內部初始化。靜態成員變量在初始化時分配內存。

void Point::showcount() {    //靜態成員函數的定義,前面不可以再有static,可以在類內部定義,也可以在類外定義。此例爲在類外面進行定義。
    cout<<"count="<<count<<endl;
}

Point::Point(const Point &p){  //複製構造函數
    x = p.x;
    y = p.y;
    count++;
}

Point fun(Point m){ 
    return m;
}

int main(){
    Point::showcount(); //用     類名::函數名      來調用靜態成員函數
    Point a(1,2);
    a.show();
    a.showcount();//也可用    對象名.函數名   來調用靜態成員函數
    Point b(a);
    b.show();
    b.showcount();
    Point c;
    c.show();
    c.showcount();
    Point d = fun(c);
    d.show();
    d.showcount();
    return 0;
}

運行結果:
$ ./a.out
count=0
1 2
count=1
1 2
count=2
0 0
count=3
0 0
count=4

在這裏插入圖片描述
如圖,對象a佔用8個字節(成員x,y的空間,count不在裏面),對象a是局部對象,存放在棧上0x7fffffffdde0的位置處。
在這裏插入圖片描述
count存放在靜態存儲區的 0x555555756134,不存儲在對象中。count屬於整個類,不屬於某一個具體的對象。
靜態成員函數也是一樣,屬於整個類,不屬於某一個對象,所以靜態成員函數可以用類名來調用,比如:Point::showcount() 。

static 成員變量必須在類聲明的外部初始化,具體形式爲:

 type class::name = value;

如本例:int Point::count = 0;
靜態成員變量在初始化時不能再加 static,但必須要有數據類型。

static 成員變量的內存既不是在聲明類時分配,也不是在創建對象時分配,而是在(類外)初始化時分配。沒有在類外初始化的 static 成員變量不能使用,因爲沒分配空間。

測試2:

#include <iostream>
#include <cmath>
using namespace std;

class Point{
public:
    Point(int xx,int yy):x(xx),y(yy){count++;}
    Point():Point(0,0){}
    Point(const Point &p);
    ~Point(){count--;}
    void show(){cout<<x<<" "<<y<<endl;}
    static void showcount();
public:
    static int count;   //此例爲public屬性
private:
    int x,y;
};

int Point::count = 0;
 
void Point::showcount() {
    cout<<"count="<<count<<endl;
}

Point::Point(const Point &p){
    x = p.x;
    y = p.y;
    count++;
}
int main(){
    Point::showcount();
    Point a(1,2);
    cout<<a.count<<endl;  //通過對象a來訪問靜態成員變量。若count不是public屬性,不能用對象來訪問,編譯時就會報錯
    Point::showcount();
    Point b(3,4);
    Point::showcount();

    return 0;
}

用GDB來查看靜態成員變量的存儲:
在這裏插入圖片描述
可以看到,當b中count變爲2,a中count也變爲2,即a和b中的count是共享的。查看a.count和b.count的地址,可以發現是同一個地址,即是同一個存儲空間。且這個存儲空間是全局存儲區(靜態存儲區),不在對象a和對象b的棧(stack)存儲區。
此例中count是public,修改爲protected和private,編譯報錯。

protected:
    static int count;

在這裏插入圖片描述

private:
    static int count;

在這裏插入圖片描述
如上圖,也就是說,想通過對象來訪問靜態成員變量,靜態成員變量只能是public屬性。這一點和對象去訪問普通的成員變量一樣。類外面,對象只能訪問public成員變量,不能訪問protected和private的成員變量。

測試3:

void Point::showcount() {
    cout<<"count="<<count<<endl;
    cout<<x<<endl; //新增,嘗試在靜態成員函數中去訪問普通的成員變量,編譯報錯。
}

在這裏插入圖片描述
也即是說,在靜態成員函數中,不能去訪問普通的成員變量。原因是沒有this指針,不知道普通成員變量x放在哪裏。
原因分析:
編譯器在編譯一個普通成員函數時,會隱式地增加一個形參 this,並把當前對象的地址賦值給 this,所以普通成員函數只能在創建對象之後通過對象來調用,因爲它需要當前對象的地址。也即是有了對象,纔有對象的this指針,沒有對象,就無法調用普通成員函數。由於類沒有this指針,所以不能用類名去調用普通成員函數,只能用實際的對象去調用普通成員函數

而靜態成員函數可以通過類來直接調用,編譯器不會爲它增加形參 this,它不需要當前對象的地址,所以不管有沒有創建對象,都可以調用靜態成員函數。因爲靜態成員函數要訪問的是靜態成員變量(存儲在全局存儲區,和this指針沒毛關係)。
由於靜態成員函數沒有 this 指針,不知道指向哪個對象,無法訪問對象的成員變量,所以靜態成員函數不能訪問普通成員變量,只能訪問靜態成員變量

總結:

  1. 一個類中可以有一個或多個靜態成員變量,所有的對象都共享這些靜態成員變量,都可以引用它。注意,是共享,共享,共享,也即是隻會佔同一份靜態存儲空間
  2. static 成員變量和普通 static 變量一樣,都在內存分區中的全局數據區分配內存,到程序結束時才釋放。這就意味着,static 成員變量不隨對象的創建而分配內存,也不隨對象的銷燬而釋放內存。而普通成員變量在對象創建時分配內存,在對象銷燬時釋放內存。根本原因是靜態成員變量和對象的存儲空間不同,是在不同的時期去分配的
  3. 靜態成員變量必須初始化,而且只能在類體外進行。例如:
    int Point::count = 0;
    初始化時可以賦初值,也可以不賦值。如果不賦值,那麼會被默認初始化爲 0
    全局數據區的變量都有默認的初始值 0,而**動態數據區(堆區、棧區)**變量的默認值是不確定的,一般認爲是垃圾值。
  4. 靜態成員變量既可以通過對象名訪問,也可以通過類名訪問,但要遵循 private、protected 和 public 關鍵字的訪問權限限制。
    當在類外面通過對象名訪問時,靜態成員變量屬性必須是public,對於不同的對象時,訪問的是同一份內存(本質是共享)。
  5. 靜態成員函數在聲明時要加 static,在定義時不能加 static 。靜態成員函數只能訪問靜態成員變量,去訪問普通成員變量,編譯就報錯。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章