C++类的静态成员总结

 
Category: C_C++

引子: 类为什么需要静态成员

有时候类需要与它的一些成员与类本身直接相关, 而不是与类的各个对象都保持关联, 这就减少了成员与每一个类的实例对象的联系, 从而降低资源占用. 另一方面, 如果每次都需要重新更新该成员, 使得对象使用新的值, 这时候只需要修改一份该成员.

本文内容参考: cppprimer

声明静态成员

要点

  1. 在成员声明之前加上关键字static使得其与类关联在一起

  2. 静态成员可以是public或private的, 类型可以是常量, 引用, 指针, 类等

  3. 类的静态成员存在于任何对象之外, 对象中不包含任何与静态数据成员有关的数据(这也是为什么需要类内声明, 类外初始化的原因, 静态数据成员均存在于全局区)

  4. 类的静态成员函数也不与任何实例对象绑定在一起, 不包含this指针.

  5. 类的静态成员函数不能声明为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限定没有任何意义。

  6. 在类外可以直接通过作用域运算符直接访问静态成员, 即使直接通过类名访问, 也不会出错.

  7. 成员函数可以不通过作用域运算符直接使用静态变量;

  8. 在类的外部定义静态成员时, 不能重复使用static关键字, 该关键字只能出现在类内部的声明语句中.


  1. 因为静态数据成员不属于类的任何一个对象, 所以它们并不是在创建类的对象时被定义的. 这意外着它们不是由类的构造函数初始化的.

  2. 一般来说, 不能在类的内部初始化静态成员, 而必须在类的外部定义和初始化每一个静态成员.

    经过测试, 静态常量数据成员可以在类内初始化, 静态成员函数可以在类内定义.

  3. 一个静态数据成员只能定义一次. (ODR, One Definition Rule)

  4. 类似于全局变量, 静态数据成员定义在任何函数之外, 所以静态数据成员一旦被定义, 就将一直存在于程序的整个生命周期中.

  5. 可以为静态成员提供const整数类型的类内初始值, 不过要求静态成员必须是字面值常量类型的constexpr. 初始值必须是常量表达式, 因为这些成员本身就是常量表达式, 所以能用在所有适合于常量表达式的地方.

  6. 如果某个静态成员的应用场景仅限于编译器可替换其值的情况, 则一个初始化的const或者constexpr static不需要分别定义; 反之, 如果将其用于值不能替换的情况, 则该静态成员必须有一条定义语句.

    例子: 如果某一成员的作用就是定义类内另一成员的size, 就不需要专门在类外定义该静态常量了.

      // 在类内
      static constexpr int size = 10;
      double array[size];
    
  7. 如果在类内提供了一个初始值, 则成员的定义不能再指定一个初始值了.

    例子:

      // 当需要把下面的值传给一个接受`const int&`的函数时, 必须定义`size`, 否则会link错误, 找不到定义
      // (声明为 constexpr 则不会出现这个情况)
      static const int P::size = 10;
      void test(const int &a) { cout << a << endl; }
      void t2() { test(P::size); }
    
  8. 即使一个常量静态成员在类内被初始化了, 通常情况下也应该在类外部定义(我认为应该翻译为声明)一下该成员.

    // 类内: 
    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;
}

与非静态成员的区别

要点

  1. 静态成员可以是不完全类型(可以不是指针或引用, 但是非静态成员不行, 只能声明为指针或引用)

    不完全类型:

      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;
    };
    
  2. 静态成员可以作为默认实参. 非静态数据成员不能作为默认实参, 因为其值属于对象的一部分, 如果作为默认实参, 将无法真正提供一个具体对象以便从中获取成员的值, 导致错误.

    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) { }
    };