类的静态成员是类中很重要的角色。在平常使用中,类的静态成员基本用法已经足够应用大部分场景了,但是一些细节很重要,而且常常被我们忽略了。下文将详细介绍静态成员,细节(我曾经忽视的地方)的内容会专门标注出来。
类的静态成员——基础部分
声明静态成员
- 在类中的成员的声明之前加上关键字static使得其与类关联在一起。
- 静态成员可以是public或private的。
- 静态数据成员的类型可以是常量、引用、指针、类类型等。
静态数据成员是与类直接相联系的,却不与类声明的对象相联系。
// Account.h
class Account{
public:
// 对变量的声明
static double rate;
int money;
// 对函数的声明
double func1();
void func2() const; // 错误
};
// Account.cpp
include "Account.h"
double Account::func1()
{
this.money = 1; // 错误
// 不能显示的使用this
money = 1 ; // 错误
// 不可以调用非静态成员的隐式调用
rate = 1.1; // 正确
...
}
上面代码中,money是与对象相关联的,即类Account声明的每一个对象都会有一个存储空间存储一个整形(int类型)money。而rate是与类Account直接关联的,与由类声明出的每一个对象无关,即rate是存储在一个静态存储单元类,无论声明多少个类Account的对象,都只会存储一个rate成员。
细节
同理,静态成员函数也不与任何对象绑定。this指针是与类声明的对象相关联的,因此静态函数它们不包含this指针。作为结果,静态成员函数不能声明成const的,而且我们也不能在static函数体内使用this指针。这一限制既适用于this的显示使用,也对调用非静态成员的隐式使用有效。
使用类的静态成员
使用作用域运算符直接访问静态成员:
double r;
r = Account::rate; // 使用作用域运算符访问静态成员
我们可以使用类的对象、引用或者指针来访问静态成员:
Account ac1;
Account8 *pac1 = &ac1;
double r = ac1.rate; // 对象或引用调用静态成员
r = pac1->rate; // 指针调用静态成员
定义静态成员
我们既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句。
细节
静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的。而且一般来说,我们不能在类的内部初始化静态成员。所以,必须在类的外部定义和初始化每个静态成员。
一个静态成员只能被定义一次。
静态数据成员定义在任何函数之外,因此一旦它被定义,就被一直存在于程序的整个生命周期中。
定义静态数据成员需要指定对象的类型名、类名、作用域运算符及成员自己的名字:
double Account::Rate = Account::func1();
double Account::Rate = func1(); // 不需要作用域运算符
细节
Rate是类Account的静态成员,从类名开始,这条定义语句的剩余部分就位于类的作用域之内了。因此,我们可以直接使用func1()函数。如果func1()是私有的,也能用它初始化Rate。即对成员数据的定义可以访问类的私有成员。
小技巧
要想确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一个文件中。
静态成员的类内初始化
通常情况下,类的静态成员不应该在类的内部初始化。
细节
我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型constexpr。
如果某个静态成员的应用仅限于将变量名替换为它所代表的值时,则一个初始化的const 或 constexpr static不需要分别定义。相反,如果我们将它用于值不能替换的场景中,则该成员必须有一条定义语句。
如果在类的内部提供了一个初始值,则成员的定义不能再指定一个初始值了:
// 一个不带初始值的静态成员的定义 constexpr int Account::period; // 初始值已经在类的定义内提供了
小技巧
即使一个常量静态数据成员在类内部初始化了,通常情况下也应该在类的外部定义一下该成员。
静态成员能用于某些场景,而普通成员不能(全是细节)
静态成员独立于任何对象,因此静态成员可以是不完全类型。特别的,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用:
class Bar{
public:
// ...
private:
static Bar bar1; // 正确:静态成员可以是不完全类型
Bar* pbar; // 正确:指针成员可以是不完全类型
Bar bar2; // 错误:数据成员必须是完全类型
}
静态成员可以作为默认实参:
class Screen{
public:
Screen& clear(char = bkground);
private:
static const char bkground;
}
非静态数据成员不能作为默认实参,因为它的值需要对象被定义出来后才能初始化,在未定义之前,编译器不知道非静态数据成员的值,因此不能作为默认实参。