c++之靜態成員詳細介紹

       靜態成員的提出是爲了解決數據共享的問題。實現共享有許多方法,如:設置全局性的變量或對象是一種方法。但是,全局變量或對象是有侷限性的。

1.靜態數據成員

(1)作用1:節省內存

使用靜態數據成員可以節省內存,因爲它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用(比如雙向鏈表的頭節點就可以用static)。靜態數據成員的值對每個對象都是一樣,但它的值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新後的相同的值,這樣可以提高時間效率。

(2)隱藏性和安全性

       在類中,靜態成員可以實現多個對象之間的數據共享,並且使用靜態數據成員還不會破壞隱藏的原則,即保證了安全性。因此,靜態成員是類的所有對象中共享的成員,而不是某個對象的成員。

 

 

 

 

 靜態數據成員的使用方法和注意事項如下:

1、靜態數據成員在定義或說明時前面加關鍵字static。

2、靜態成員初始化與一般數據成員初始化不同。靜態數據成員初始化的格式如下:

    <數據類型><類名>::<靜態數據成員名>=<值>  //靜態變量的初始化      類外定義——保證了定義只有一次

3、初始化在類體外進行,而前面不加static,(這點需要注意)以免與一般靜態變量或對象相混淆。初始化的地方就是定義的地方,一定要在全局作用域中定義,切記。

4、初始化時不加該成員的訪問權限控制符private,public等。

5、初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員。

6、靜態數據成員是靜態存儲的,它是靜態生存期,必須對它進行初始化。

7、如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式來引用靜態數據成員。

8、類的對象可以使用靜態成員函數和非靜態成員函數。(類的對象可以調用靜態或者非靜態)

#include <iostream>
#include <string>
using namespace std;
class Point{
public:
  void init(){}
  static double output(){return 1.2345;}
  
private:

};
int main()
{
  Point::output();
  Point pt;
  pt.init();
  cout << pt.output() << endl;
  cout << Point::output() << endl;
  return 0;
}

9、靜態成員函數中不能使用非靜態成員.如果使用如下,那麼報錯,因爲靜態數據成員沒有this指針,它不屬於任何對象。

#include <string>
#include <iostream>
#include <stdio.h>
using namespace std;
class Point{
public:
  void init() {}
  static void output(){printf("%d\n",m_x);}
  
private:
  int m_x = 1;

};

int main()
{
  Point pt;
    pt.output();
  return 0;
}

g++報錯如下:
error: invalid use of member ‘Point::m_x’ in static member function
   static void output(){printf("%d\n",m_x);}

10.類的靜態成員變量必須先初始化再使用,爲了保證定義(或初始化)的唯一,和函數的實現放在一起。

#include <string>
#include <iostream>
using namespace std;
class Point{
public:
  Point(){m_nPointCount ++;}
  ~Point(){m_nPointCount --;}
  static void output(){
    printf("%d\n",m_nPointCount);
  }
private:
  static int m_nPointCount;

};
//int Point::m_nPointCount = 0;     //如果在這裏用註釋符號//屏蔽這句話,那麼會報錯

int main()
{
  Point pt;
  pt.output();

  return 0;
}


報錯:
undefined reference to `Point::m_nPointCount'

11.在類的非靜態成員函數中使用類的靜態成員,反之則不能。

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

class Point{
public:
  void init(){output();}
  static void output(){cout << k << endl;}
  static int k ;
};
int Point::k = 123;
int main()
{
  Point pt;
  pt.init();
  pt.output();

  return 0;
}

12.不能使用類名訪問非靜態成員。(不能用作用域運算符訪問非靜態成員)

#include <string>
#include <iostream>
using namespace std;
class Point{
public:
  void init(){}
  static void output(){}
private:
};

int main()
{
  Point::init();
  Point::output();
  return 0;
}


error: cannot call member function ‘void Point::init()’ without object
   Point::init();

13.公共的靜態數據成員可以在類外直接引用,也可以通過對象名引用,但私有的靜態數據成員只能公用的成員函數引用。

14.構造函數:初始化對象的非static數據成員,以及一些其他的工作;
析構函數:釋放對象使用的資源,並銷燬對象的非static數據成員;

15.由於其沒有隱含的this指針,所以不能夠直接存取其class object中的非靜態成員數據。

16.靜態數據成員甚至在類沒有任何對象的時候都可以訪問,靜態成員可以獨立訪問,無需依賴任何對象的建立。

17.靜態數據成員可以作爲成員函數的默認形參,而普通數據成員則不可以


#include <iostream>
#include <string>
using namespace std;
class Test{
  static int a;
  int b;
  void fun_1(int i = a);       //right
  void fun_2(int i = b);       //這裏報錯,默認形參必須先先於類的對象而建立。所以要靜態數據成 
                               //員

};

int main()
{
  return 0;
}
以上程序會報錯。
error: invalid use of non-static data member ‘Test::b’

18.靜態數據成員在const函數中可以修改,而普通的數據成員是不能修改的!

#include <iostream>
#include <string>
using namespace std;
class Test{
public:
  Test():b(0){}
  //static member data
  static int a; //right
  int b;
  void test()const{
    a++; //right
    b++;//(wrong)const pointed you can't change data of the object who called it
  }
};
int Test::a = 1;
int main()
{
  Test t;
   return 0;
}

const修飾的時當前this指針所指向的對象是const,但是靜態數據成員不屬於任何類的對象,它被類的所有對象修改,所以this指針不修飾靜態的數據成員,所以可以更改。

19.關於靜態成員函數

(1)靜態成員函數不能調用非靜態成員函數,但是反過來是可以的

(2)靜態成員函數沒有this指針,也就是說靜態成員函數不能使用修飾符(也就是函數後面的const關鍵字)

注意:靜態成員函數中不能出現隱式的或者顯式的this,即不能調用普通非static成員

(3)靜態成員函數的地址可用普通函數指針儲存,而普通成員函數地址需要用 類成員函數指針來儲存。

#include<string>
#include <iostream>
using namespace std;
class Student{
public:
  //define the constructor
  Student(int n,int a,float s):num(n),age(a),score(s){}
  void total();
  //declare static member function
  static float average();
private:
  int num;
  int age;
  float score;
  //static data member,add the numbers of students
  static float sum;
  //static data member,add the counts of students
  static int count;
};

//initialize the static data member,if you don't give it a value,then vill use the default value
float Student::sum;
int   Student::count;
//define the non-static member function
void Student::total(){
  //add the total score
  sum += score;
  //add the counts of students
  count ++;
}
//define the member function
float Student::average(){
  return (sum/count);
}
int main()
{

  Student stud[3]={
    //define object array and initialize
    Student(1001,18,70),
    Student(1002,19,78),
    Student(1003,20,98)
  };
  int n;
  std::cout << "please input the number of students:";
  std::cin >> n;
  //call 3 times of the function "total"
  for(int i = 0;i < n;i++)
     {
       stud[i].total();
     }

  //call the static member function
  std::cout<< "the average score of" << n << " students is " << Student::average() << std::endl;
  
  return 0;
}

20.類內初始化的靜態const 成員只能是int型的.

c++ primer 250頁:
我們可以爲靜態成員提供const整數類型的類內初始值。
#include <iostream>
#include <string>
using namespace std;
class Account{
 public:
  void calculate();          //calculate amount of one person
  static double rate();      //return interestRate
  static void rate(double);  //init interestRate
  static const int chineseRate =2 ;                //這個是對的
  static const double chineseRate3 = 1;            //這個是錯的
  static constexpr double chineseRate2 = 0.2;
 private:
  string owner;
  double amount;
  static double interestRate;
  static double initRate();
  static const int int_type_variable = 1;      
};

結論:類內初始化的static const 初始化類型只能是int,不能是別的類型。

21.類內的static const int(這裏static const 也只能修飾int)類型如果程序中需要取地址,那麼必須有類外定義,否則會出現錯誤如下(如果不在程序中使用地址,那麼不需要在類外定義):

class Account{
 public:
  void calculate();          //calculate amount of one person
  static double rate();      //return interestRate
  static void rate(double);  //init interestRate
  static const int chineseRate =2 ;
  static constexpr double chineseRate2 = 0.2;
 private:
  string owner;
  double amount;
  static double interestRate;
  static double initRate();
  static const int int_type_variable = 1;
};

那麼會在這裏報錯:

undefined reference to `Account::chineseRate'

 結論:即使一個常量靜態對象在類內初始化了,通常情況下在類外定義也是一個一下該成員。

22.如果要定義類內指定非int靜態常量的初始值(初始值必須是constexpr),那麼常量修飾符必須使用constexpr(非const,且初值必須是常量),如下:

class Account{
 public:
  void calculate();          //calculate amount of one person
  static double rate();      //return interestRate
  static void rate(double);  //init interestRate
  static const int chineseRate =2 ;
  static const  double chineseRate2 = 0.2;   //這裏報錯,非int常量必須constexpr
 private:
  string owner;
  double amount;
  static double interestRate;
  static double initRate();
  static const int int_type_variable = 1;
};

error: ‘constexpr’ needed for in-class initialization of static data member ‘const double Account::chineseRate2’ of non-integral type [-fpermissive]

注意:(1)g++編譯器內static constexpr 和 constexpr static 功能一樣 

(2)若定義了常量表達式,那麼可以用在任何需要常量表達式的地方,比如數組維度。但是如果使用static constexpr(或者static const int)的地址或者執行 const int &a =靜態數據常量,必須在類外進行定義(類內指定初值),否則會報錯,因爲取地址必須先定義。

結論:即使一個常量靜態數據成員在類內部被初始化了,通常情況下也應該在類的外部定義一下該成員。

23.雖然程序不能直接訪問和修改私有static成員,但是可以定義成員函數更改和打印私有成員的值。

如下:

class Account{
 public:
  void calculate();          //calculate amount of one person
  static double rate();      //打印私有成員interestRate的值
  static void rate(double);  //更改私有成員interestRate的值
  static const int chineseRate =2 ;
  static constexpr  double chineseRate2 = 0.2;
 private:
  string owner;
  double amount;
  static double interestRate;
  static double initRate();
  static const int int_type_variable = 1;
};

double Account::Rate(){return interestRate;}
void   Account::Rate(double newRate)
  {
   interestRate  = newRate;
  }

24.靜態成員的使用:可以通過類的對象、類的對象的引用、指針來訪問非靜態成員。

  Account t;
  Account *pt = &t;
  Account &rt = t; 
  cout << "rate 3" << endl;
  cout << t.rate() << endl;          //成員調用
  cout << pt -> rate() << endl;        //指針調用
  cout << rt.rate() << endl;          //引用調用

25.成員函數(靜態或者非靜態的)不用通過作用域運算符就可以訪問靜態成員(interestRate就是靜態成員).

void Account::calculate()
{
  amount += amount * (Account::interestRate);
}

結論:類的作用域內使用任何成員都不用加作用域運算符。(類作用域內可以用作用域運算符使用類的一切數據成員包括靜態成員,所以成員函數往往有一些小的函數)

26.類內可以定義所有類型的 static const + 類型(類型可以是內置類型,string類型,等,還可以是不完全類型Account)

class Account{
 public:
 
  void calculate();          //calculate amount of one person
  static double rate();      //
  static void rate(double);  //init interestRate
  static const int chineseRate =2 ;
  static constexpr  double chineseRate2 = 0.2;
 private:
  string owner;
  double amount;
  static double interestRate;
  static double initRate();
  static const int int_type_variable = 1;
  static const char bkground;
  static const int  bk2;
  static const double bk3;
  static const string bk4;
  static const Account acc;
  
};

但是如果使用未定義的static const 就會報錯,如下:

r@r-Sys:~/now/7.6$ g++ main.cc function.cc -o 123
/tmp/ccLrFZYt.o: In function `Account::print_static()':
main.cc:(.text._ZN7Account12print_staticEv[_ZN7Account12print_staticEv]+0xe): undefined reference to `Account::bk2'
main.cc:(.text._ZN7Account12print_staticEv[_ZN7Account12print_staticEv]+0x38): undefined reference to `Account::bk3'
main.cc:(.text._ZN7Account12print_staticEv[_ZN7Account12print_staticEv]+0x69): undefined reference to `Account::bk4[abi:cxx11]'
collect2: error: ld returned 1 exit status

結論:可以定義static const + 類型(一切類型),但是隻有int型可以給定初值,其它都無法給定初值,使用前沒有初值必然報錯,所以只能用constexpr代替(非int的時候)。

27.static 類型(可以是static,static const或者static constexpr)可以作爲函數的參數,非static類型不可以(提示:成員函數默認實參在申明的時候指定,申明時候不指定,都指定會報錯,其它的不知道)

void Account::calculate(double d = interestRate)
{
  cout << "print the interestRate :: " << d <<  endl;
  amount += amount * (Account::interestRate);
}

28.靜態類型數據成員必須在類外定義,而靜態類型函數可以在類外或者類內定義,定義方式類似於普通成員函數。但是在類外一律不允許出現static語句,static語句只能出現在申明的時候。

29.類內使用類外的static成員的時候,必須指明類的名字。

30.定義類的static成員的時候,從類名開始,作用域就在類內了,這意味着,接下來你可以使用類的一切成員。(注意:例中initRate()是類的私有成員函數)

double Account::interestRate = initRate();//從Account開始,作用域就在類內了,所以可以調用類的私有成員函數initRate()

結論:類作用域可以使用類的一切成員。(前提是類定義完畢時候)

31.類似於全局變量,靜態成員定義在類外,一旦被定義,就存在於程序的整個生命週期中。

32.爲了確保靜態成員只定義一次,把類的靜態數據成員的定義和其它的非內聯函數的定義放在同一個文件中。

33.靜態數據成員可以用於某些場景,而非靜態成員不能的幾個情況舉例。

(1)靜態數據成員可以是不完全類型,非靜態數據成員不能.

class Bar{
public:

//...
private:
static Bar men1; //正確,靜態成員可以是不完全類型
Bar * mem2;      //正確,指針可以是不完全類型
Bar mem3;        //錯誤,數據成員必須是完全類型     

};

(2)靜態成員可以作爲默認實參,普通成員不能。

class Screen{
public:
//bkground 表示一個在類中稍後定義的靜態成員
 Screen & clear(char = bkground);
private:
 static const char bkground;
};

這裏有一個疑問:const char bkground在這裏沒有初值,外面也不能給值,有個鳥用啊?????????????????????????????????????????
c++ primer 271頁源代碼

結論:非靜態數據成員不能作爲默認形參,因爲它的值本身屬於對象的一部分,這麼做的結果是無法真正提供一個對象以便從中獲取成員的的值,最終將會引發錯誤。

34.靜態類型成員的相關定義.

(1)定義:靜態成員是指申明語句中有關鍵字static的成員,靜態成員不是任意單獨對象的組成部分,而是由該類的全體對象所共享。

(2)優點:作用域位於類的範圍內,避免與其它的成員或者全局作用域的名字衝突;可以是私有成員,而全局對象不可以;通過閱讀程序可以非常容易地看出靜態成員與特定類的關聯,使得程序非常容易懂。

(3)靜態成員和普通成員的區別:

主要體現在普通成員與類的對象關聯,是某個具體對象的組成部分;而靜態成員不屬於任意具體對象,它由該類的所有對象共享;還有一個細微的區別,靜態成員可以作爲默認實參,而普通成員不可以。

35.

//example.h
class Example{
static double rate;
static const int vecSize = 20;
static vector<double> vec;
};

//function.cc
double Example::rate = 1.2;
vector<double> Example::vec{3.14};

//main.cc
#include "example.h"
double Example::rate;
vector<double> Example::vec;

注意:
這裏如果使用rate和vec的值,rate的使用是合法的,結果0;
但是vec是不合法的,g++會提示溢出如下:
Segmentation fault (core dumped)

說明:c++ primer,說的272頁習題7.58解釋最後2行程序都是錯的,但是g++編譯器顯示倒數第二個是可以使用的,值是0,但是倒數第一個確實是不能使用(如果不適用就可以通過編譯,使用值則不能通過編譯)。注意編譯的時候main.cc 單獨編譯,如果和function.cc一起編譯確實結果就錯誤了。

13.練習(有待於進一步完成)

再給一個利用類的靜態成員變量和函數的例子以加深理解,這個例子建立一個學生類,每個學生類的對象將組成一個雙向鏈表,用一個靜態成員變量 記錄這個雙向鏈表的表頭,一個靜態成員函數輸出這個雙向鏈表。

[cpp] view plaincopy

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. const int MAX_NAME_SIZE = 30;    
  4.   
  5. class Student    
  6. {    
  7. public:    
  8.     Student(char *pszName);  
  9.     ~Student();  
  10. public:  
  11.     static void PrintfAllStudents();  
  12. private:    
  13.     char    m_name[MAX_NAME_SIZE];    
  14.     Student *next;  
  15.     Student *prev;  
  16.     static Student *m_head;  
  17. };    
  18.   
  19. Student::Student(char *pszName)  
  20. {    
  21.     strcpy(this->m_name, pszName);  
  22.   
  23.     //建立雙向鏈表,新數據從鏈表頭部插入。  
  24.     this->next = m_head;  
  25.     this->prev = NULL;  
  26.     if (m_head != NULL)  
  27.         m_head->prev = this;  
  28.     m_head = this;    
  29. }    
  30.   
  31. Student::~Student ()//析構過程就是節點的脫離過程    
  32. {    
  33.     if (this == m_head) //該節點就是頭節點。  
  34.     {  
  35.         m_head = this->next;  
  36.     }  
  37.     else  
  38.     {  
  39.         this->prev->next = this->next;  
  40.         this->next->prev = this->prev;  
  41.     }  
  42. }    
  43.   
  44. void Student::PrintfAllStudents()  
  45. {  
  46.     for (Student *p = m_head; p != NULL; p = p->next)  
  47.         printf("%s\n", p->m_name);  
  48. }  
  49.   
  50. Student* Student::m_head = NULL;    
  51.   
  52. void main()    
  53. {     
  54.     Student studentA("AAA");  
  55.     Student studentB("BBB");  
  56.     Student studentC("CCC");  
  57.     Student studentD("DDD");  
  58.     Student student("MoreWindows");  
  59.     Student::PrintfAllStudents();  
  60. }  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章