<基于 C++ Primer P268>
我们需要类的一些成员与类本身直接相关,而不是与类的各个对象保持关联。
(1)声明静态成员
我们通过在成员的声明之前加上关键字 static 使得其与类关联在一起。
静态成员既可以是 public 也可以是 private。
静态数据成员的类型可以是常量、引用、指针、类类型等。
class Account
{
public:
void calculate() {amount += amount * interestRate;}
static double rate() {return interestRate;}
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
}
类的静态成员存在与任何对象之外,对象中不包含任何与静态数据成员有关的数据。因此,每个 Account 对象只包含两个数据成员:owner 和 amount。只存在一个 interestRate 对象而且它被所有 Account 对象共享。
类似的,静态成员函数也不与任何对象绑定在一起,它们不包含 this 指针。因此,静态成员函数不能被声明成 const 的,而且也不能在函数体内使用 this 指针。
(2)使用类的静态成员
我们使用作用运算符直接访问静态成员。
double r
r = Account::rate();
虽然静态成员不属于类的某个对象,但是我们仍然可以使用类的对象、引用或指针访问静态成员。
Account ac1;
Account *ac2 = &ac1;
r = ac1.rate();
r = ac2->rate();
成员函数不用通过作用域运算符就可以直接使用静态成员。
class Account
{
public:
void calculate() {amount += amount * interestRate;}
private:
static double interestRate;
};
(3)定义静态成员
我们既可以在类的内部也可以在类的外部定义静态成员函数。但 static 关键字只能出现在类内部的声明语句中。
void Account::rate(double newRate)
{
interestRate = newRate;
}
因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的,而且一般来说,我们不能在类的内部初始化静态成员。
我们必须在类的外部定义和初始化每个静态成员,一个静态成员只能被定义一次,因此我们最好将静态数据成员的定义与其他非内联函数的定义放在同一个文件中。
类似于全局变量,静态数据成员定义在任何函数之外。因此一旦被定义,就将一直存在于程序的整个生命周期中。
定义静态数据成员的方式和在类的外部定义成员函数差不多。指定对象的类型名,然后是类名、作用域运算符以及成员自己的名字。
double Account::interestRate = initRate();
(4)静态成员的类内初始化
通常情况下,类的静态成员不应该在类的内部初始化。
但是,我们也可以为静态成员提供 const 整数类型的类内初始值,要求静态成员必须是字面值常量类型的constexpr,初始值必须是常量表达式。
class Account
{
public:
static double rate() {return interestRate;}
static void rate(double);
private:
static constexpr int period = 30; // period 是常量表达式
double daily_tbl[period];
}
即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义以下该成员。此时不能再指定一个初始值了。
(5)静态成员能用于某些场景,而普通成员不能
①静态数据成员可以是不完全类型。
特别的,静态数据成员的类型可以就是它所属的类类型。
而非静态成员则收到限制,只能声明成它所属类的指针或引用。
class Bar
{
public:
// ...
private:
static Bar mem1; // 正确:静态成员可以是不完全类型
Bar *mem2; // 正确:非静态成员可以声明指针
Bar mem3; // 错误
};
②我们可以使用静态成员作为默认实参。
class Screen
{
public:
// bkground 表示一个在类中稍后定义的静态成员
Screen& clear(char = bkground);
private:
static const char bkground;
};
非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。