引子: 类为什么需要静态成员
有时候类需要与它的一些成员与类本身直接相关, 而不是与类的各个对象都保持关联, 这就减少了成员与每一个类的实例对象的联系, 从而降低资源占用. 另一方面, 如果每次都需要重新更新该成员, 使得对象使用新的值, 这时候只需要修改一份该成员.
本文内容参考: cppprimer
声明静态成员
要点
-
在成员声明之前加上关键字
static
使得其与类关联在一起 -
静态成员可以是public或private的, 类型可以是常量, 引用, 指针, 类等
-
类的静态成员存在于任何对象之外, 对象中不包含任何与静态数据成员有关的数据(这也是为什么需要类内声明, 类外初始化的原因, 静态数据成员均存在于全局区)
-
类的静态成员函数也不与任何实例对象绑定在一起, 不包含this指针.
-
类的静态成员函数不能声明为const的, 也不能在static函数体内使用this指针.
正因为没有this指针, 所以不能加cv-修饰符
error: static member function 'static void P::f()' cannot have cv-qualifier
当将const限定符应用于非静态成员函数时,它会影响this指针。对于类C的const限定成员函数,this指针的类型为
C const*
,而对于非const限定的成员函数,this指针的类型为C*
。静态成员函数没有this指针(此类函数不会在类的特定实例上调用),因此静态成员函数的const限定没有任何意义。 -
在类外可以直接通过作用域运算符直接访问静态成员, 即使直接通过类名访问, 也不会出错.
-
成员函数可以不通过作用域运算符直接使用静态变量;
-
在类的外部定义静态成员时, 不能重复使用static关键字, 该关键字只能出现在类内部的声明语句中.
-
因为静态数据成员不属于类的任何一个对象, 所以它们并不是在创建类的对象时被定义的. 这意外着它们不是由类的构造函数初始化的.
-
一般来说, 不能在类的内部初始化静态成员, 而必须在类的外部定义和初始化每一个静态成员.
经过测试, 静态常量数据成员可以在类内初始化, 静态成员函数可以在类内定义.
-
一个静态数据成员只能定义一次. (ODR, One Definition Rule)
-
类似于全局变量, 静态数据成员定义在任何函数之外, 所以静态数据成员一旦被定义, 就将一直存在于程序的整个生命周期中.
-
可以为静态成员提供const整数类型的类内初始值, 不过要求静态成员必须是字面值常量类型的constexpr. 初始值必须是常量表达式, 因为这些成员本身就是常量表达式, 所以能用在所有适合于常量表达式的地方.
-
如果某个静态成员的应用场景仅限于编译器可替换其值的情况, 则一个初始化的const或者constexpr static不需要分别定义; 反之, 如果将其用于值不能替换的情况, 则该静态成员必须有一条定义语句.
例子: 如果某一成员的作用就是定义类内另一成员的size, 就不需要专门在类外定义该静态常量了.
// 在类内 static constexpr int size = 10; double array[size];
-
如果在类内提供了一个初始值, 则成员的定义不能再指定一个初始值了.
例子:
// 当需要把下面的值传给一个接受`const int&`的函数时, 必须定义`size`, 否则会link错误, 找不到定义 // (声明为 constexpr 则不会出现这个情况) static const int P::size = 10; void test(const int &a) { cout << a << endl; } void t2() { test(P::size); }
-
即使一个常量静态成员在类内被初始化了, 通常情况下也应该在类外部定义(我认为应该翻译为声明)一下该成员.
// 类内: static constexpr int size = 10; // 类外: constexpr int P::size;
参考:
例子: 访问静态成员
注释掉的就是错误的用法.
#include <iostream>
using namespace std;
class P {
public:
P() = default;
~P() = default;
/* static void f()const{} */
static void g() {
/* this->MAX_age = 1; */
cout << "g()\n";
}
static void h();
static const int MAX_age = 1;
static constexpr int MAX_length = 1;
/* static int MAX_account = 1; */
static int MAX_account;
};
int P::MAX_account = 10;
/* static void P::h() { cout << "h()\n"; } */
void P::h() { cout << "h()\n"; }
void t1() {
int b = P::MAX_age;
cout << b << endl;
P::MAX_account = 12;
cout << P::MAX_account << endl; // 12
}
int main(int argc, char *argv[]) {
t1();
return 0;
}
与非静态成员的区别
要点
-
静态成员可以是不完全类型(可以不是指针或引用, 但是非静态成员不行, 只能声明为指针或引用)
不完全类型:
class P; // 前向声明
在声明之后, 定义之前,
P
是不完全类型. 即: 只知道P是一个类类型, 但是不清楚到底包含哪些成员.因为静态成员并不通过构造函数初始化, 而是存在于程序的整个生命周期
class Q; // 不完整类型 class R { // Q q1; // error: field has incomplete type 'Q' Q* q2; Q& q3 = *q2; static Q q4; // ------------------------------------------------ // R r1; // error: field has incomplete type 'R' static R r2; R* r3; R& r4 = *r3; };
-
静态成员可以作为默认实参. 非静态数据成员不能作为默认实参, 因为其值属于对象的一部分, 如果作为默认实参, 将无法真正提供一个具体对象以便从中获取成员的值, 导致错误.
class P { public: int x; static const int y; void f(int = x) { // error: invalid use of non-static data member 'x' std::cout << y; } void g(int = y) { } };