类7(静态成员)
声明静态成员:
通过再成员之前加上关键字 static 使得其与类关联在一起。和其他成员一样,静态成员可以是 public 的或 private 的。静态数据成员的类型可以是常量,引用,指针,类类型等。
我们定义一个类,用来表示银行的账户记录:
1 class Account{ 2 private: 3 std::string owner; 4 double amount; 5 static double interest_rate; 6 static double init_rate(); 7 8 public: 9 void calculate(){ 10 amount += amount + interest_rate; 11 } 12 static double rate(){ 13 return interest_rate; 14 } 15 static void rate(double); 16 // Account(); 17 // ~Account(); 18 };
类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。因此,每个 Account 对象将包含两个数据成员:owner 和 amount。只存在一个 interest_rate 对象且它被所有 Account 对象共享。
类似的,静态成员函数也不与任何对象绑定在一起,它们不包含 this 指针。作为结果,静态成员函数不能声明成 const 的,而且我们也不能在 static 函数体内使用 this 指针。这一限制既适用于 this 的显示使用,也对调用非静态成员的隐式使用有效:
1 class Account{ 2 private: 3 std::string owner; 4 double amount; 5 static double interest_rate; 6 7 public: 8 // static double interest_rate; 9 static double rate(){ 10 return interest_rate;//在static成员函数中可以直接使用static成员 11 } 12 13 // static double rate() const{//错误,const是用来修饰this指针的,但static成员函数不包含this指针 14 // return interest_rate; 15 // } 16 17 // static string get(){ 18 // return this->owner;//在static成员函数中不包含this指针,无论隐式还是显示的使用this指针都是不正确的 19 // return owner;//错误,隐式的使用了this指针 20 // } 21 22 void calculate(){ 23 amount += amount * interest_rate;//成员函数不用通过域作用符就能直接使用静态成员 24 } 25 };
使用静态成员:
1 #include <iostream> 2 using namespace std; 3 4 class Account{ 5 private: 6 std::string owner; 7 double amount; 8 // static double interest_rate; 9 10 public: 11 static double interest_rate; 12 static double rate(){ 13 return interest_rate; 14 } 15 void calculate(){ 16 amount += amount * interest_rate;//成员函数不用通过域作用符就能直接使用静态成员 17 } 18 static void rate(double); 19 }; 20 21 void Account::rate(double new_rate){//在类外部定义静态成员时不能加static关键字,否则会报错,static只需在类内声明时添加即可 22 interest_rate = new_rate; 23 } 24 25 double Account::interest_rate = 0;//通常需要类外部重新定义静态成员变量,无论改静态成员是public的还是private的都可以直接用域运算符访问 26 27 // Account gel; 28 // gel.interest_rate = 1;//不能通过类来初始化类静态成员变量,无论该静态成员是public的还是private的 29 30 int main(void){ 31 // double Account::interest_rate = 0;//错误,不能在函数内部初始化类静态成员 32 Account gel; 33 double r; 34 r = Account::rate();//使用作用域运算访问静态成员 35 gel.rate();//使用类的实例来调用静态成员函数,此时需要遵循public,private等访问权限 36 //注意:rate函数中用到了interest_rate成员,所以在使用rate静态成员函数前需要先定义interest_rate静态成员 37 r = gel.interest_rate;//静态成员在定义之后也可以被实例对象访问,此时需要遵循public,private等访问权限 38 39 Account ac1; 40 Account *ac2 = &ac1; 41 r = ac1.rate(); 42 r = ac2->rate(); 43 return 0; 44 }
通过上面的例子可以发现:
1.类静态成员变量使用前需要在类外部且函数外部定义。
2.虽然静态成员不属于类的某个对象,但是我们任然可以使用类的对象,引用或者指针来访问静态成员。
3.可以通过域运算符使用类静态成员变量和静态成员函数。
4.成员函数可以不用通过域运算符就能直接使用静态成员。
5.在类外部定义静态成员时不能加static关键字,否则会报错,static只需在类内声明时添加即可。
因为静态数据成员不属于任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的。而且一般来说,我们不能在类的内部初始化静态成员。相反的,必须在类的外部定义和初始化每个静态成员。和其他对象一样,一个静态数据成员只能定义一次。
类似于全局变量,静态数据成员定义在任何函数之外。因此一旦它被定义,就将一直存在于程序的整个声明周期中。为了确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一个文件中。
静态成员的类内初始化:
通常情况下,类的静态成员不应该在类的内部初始化。然而我们可以为静态成员提供 const 整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的 constexpr(const也一样)。初始值必须是常量表达式,因为这些成员本身就是常量表达式,所以它们能用在所有适合于常量表达式的地方。如,我们可以用一个初始化了的静态数据成员指定数组的维度:
1 #include <iostream> 2 using namespace std; 3 4 class Account{ 5 private: 6 static constexpr int maxn = 30;//maxn是常量表达式 7 // static const int max = 30;//在此例中也是可以的 8 // static int maxn = 30;//错误,静态成员必须是字面值常量类型的constexpr才能在类内初始化 9 10 public: 11 static double daily_tbl[maxn]; 12 static constexpr int get(){ 13 return maxn; 14 } 15 16 }; 17 18 constexpr int Account::maxn;//maxn的初始值在类内提供,定义maxn的类型必须与类内一致 19 // constexpr int Account::maxn = 1;//错误,不能改变初始值 20 21 int main(void){ 22 int cnt = Account::get();//get成员函数里使用了maxn静态成员,因此必须先定义maxn才能使用get函数 23 return 0; 24 }
如果某个静态成员的应用场景仅限于编译器可以替换它的值的情况,则一个初始化的 const 或 constexpr 不需要分别定义。相反,如果我们将它用于值不能替换的场景中,则改成员必须有一条定义语句。即使一个带常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。
注意:如果在类内提供了一个初始值,则成员的定义不能再指定一个初始值了。
静态成员能用于某些场景,而普通成员不能:
如我们所见,静态成员独立于任何对象。因此,在某些非静态数据成员可能非法的场合,静态成员却可以正常地使用。如,静态数据成员可以是不完全类型(声明但还没定义的类型)。特别的,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用:
1 class Bar{ 2 private: 3 static Bar mem1;//正确,静态成员可以是不完全类型 4 Bar *mem2;//正确,指针成员可以是不完全类型 5 // Bar mem3;//错误,数据成员必须是完全类型 6 };
静态成员和普通成员的另外一个区别就是我们可以使用静态成员作为默认实参:
1 class Screen{ 2 private: 3 static const char bkground; 4 5 public: 6 Screen& clear(char = bkground); 7 };
非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。