静态成员变量

查看静态关键字使用情况
在关于文件范围和静态关键字的课程中,您了解到静态变量保留了它们的值,即使它们超出范围也不会被销毁。例如:

#include <iostream>
int generateID()
{
    static int s_id = 0;
    return ++s_id;
}

int main()
{
    std::cout << generateID() << endl;
    std::cout << generateID() << endl;
    std::cout << generateID() << endl;
    return 0;
}

该程序打印:
1
2
3
请注意,s_id在多个函数调用中保持其值。

当应用于全局变量时,static关键字具有另一种含义 - 它为它们提供了内部链接(限制它们在它们定义的文件之外被看见或使用)。由于通常避免使用全局变量,因此通常不会在此capacity中使用static关键字。

静态成员变量

当应用于类时,C ++为静态关键字引入了两个用途:静态成员变量和静态成员函数。幸运的是,这些用途相当简单。我们将在本课中讨论静态成员变量,并在下一章中讨论静态成员函数。

在我们进入应用于成员变量的static关键字之前,首先考虑以下类:

class Something
{
public:
    int m_value = 1;
};

int main()
{
    Something first;
    Something second;

    first.m_value = 2;

    std::cout << first.m_value << '\n';
    std::cout << second.m_value << '\n';

    return 0;
}

当我们实例化一个类对象时,每个对象都有自己的所有普通成员变量的副本。在这种情况下,因为我们已经声明了两个Something类对象,所以最终得到两个m_value副本:first.m_value和second.m_value。first.m_value与second.m_value不同。因此,上面的程序打印:
2
1
通过使用static关键字,可以使类的成员变量成为静态。与普通成员变量不同,静态成员变量由类的所有对象共享。考虑以下程序,类似于上面的程序:

class Something
{
public:
    static int s_value;
};

int Something::s_value = 1;

int main()
{
    Something first;
    Something second;

    first.s_value = 2;

    std::cout << first.s_value << '\n';
    std::cout << second.s_value << '\n';
    return 0;
}

该程序产生以下输出:
2
2
因为s_value是静态成员变量,所以s_value在类的所有对象之间共享。因此,first.s_value与second.s_value是同一个变量。上面的程序显示我们使用第一个设置的值可以使用秒来访问!

静态成员与类对象无关

虽然您可以通过类的对象访问静态成员(如上例中的first.s_value和second.s_value所示),但事实证明即使没有实例化类的对象,也存在静态成员!与全局变量非常相似,它们在程序启动时创建,在程序结束时销毁。

因此,最好将静态成员视为属于类本身,而不是类的对象。因为s_value独立于任何类对象而存在,所以可以使用类名和范围解析运算符(在本例中为Something :: s_value)直接访问它:

class Something
{
public:
    static int s_value; // 声明静态成员变量
};

int Something::s_value = 1; // defines the static member variable (we'll discuss this section below)

int main()
{
    // 注意: 我们没有实例化Something类型的任何对象

    Something::s_value = 2;
    std::cout << Something::s_value << '\n';
    return 0;
}

在上面的代码片段中,s_value由类名而不是对象引用。请注意,我们甚至没有实例化Something类型的对象,但我们仍然可以访问和使用Something :: s_value。这是访问静态成员的首选方法。

定义和初始化静态成员变量

当我们在一个类中声明一个静态成员变量时,我们告诉编译器存在静态成员变量,但实际上并没有定义它(很像前向声明)。因为静态成员变量不是单个类对象的一部分(它们与全局变量类似,并且在程序启动时初始化),所以必须在全局范围内显式定义类外部的静态成员。

在上面的例子中,我们通过这一行:

int Something::s_value = 1; // 定义静态成员变量

这一行有两个目的:它实例化静态成员变量(就像一个全局变量),并可选择初始化它。在这种情况下,我们提供初始化值1.如果没有提供初始化程序,C ++将值初始化为0。

注意,此静态成员定义不受访问控制的约束:您可以定义和初始化该值,即使它在类中声明为私有(或受保护)。

如果类是在.h文件中定义的,则静态成员定义通常放在类的相关代码文件中(例如Something.cpp)。如果类是在.cpp文件中定义的,则静态成员定义通常直接放在类的下面。不要将静态成员定义放在头文件中(很像全局变量,如果该头文件被多次包含,您将最终得到多个定义,这将导致编译错误)。

静态成员变量的内联初始化

上面有一些快捷方式。首先,当static成员是const整数类型(包括char和bool)或const enum时,静态成员可以在类定义中初始化:

class Whatever
{
public:
    static const int s_value = 4; // 可以直接声明和初始化静态const int
};

在上面的示例中,因为静态成员变量是const int,所以不需要显式定义行。

其次,从C ++ 11开始,支持constexpr初始化的任何类型的静态constexpr成员都可以在类定义中初始化:

#include <array>

class Whatever
{
public:
    static constexpr double s_value = 2.2; // ok
    static constexpr std::array<int, 3> s_array = { 1, 2, 3 }; // 这甚至适用于支持constexpr初始化的类
};

静态成员变量的示例

为什么在类中使用静态变量?一个很好的例子是为类的每个实例分配一个唯一的ID。这是一个例子:

class Something
{
private:
    static int s_idGenerator;
    int m_id;

public:
    Something() { m_id = s_idGenerator++; } // 从id生成器中获取下一个值

    int getID() const { return m_id; }
};

// 注意,我们正在定义和初始化s_idGenerator,即使它在上面被声明为private
// 这是可以的,因为定义不受访问控制的约束
int Something::s_idGenerator = 1; // 以值1启动我们的ID生成器

int main()
{
    Something first;
    Something second;
    Something third;

    std::cout << first.getID() << endl;
    std::cout << second.getID() <<endl;
    std::cout << third.getID() << endl;
    return 0;
}

该程序打印:
1
2
3

因为s_idGenerator由所有Something对象共享,所以当创建新的Something对象时,构造函数会从s_idGenerator中获取当前值,然后递增下一个对象的值。这保证了每个实例化的Something对象都会收到一个唯一的id(按创建顺序递增)。这在调试数组中的多个项时非常有用,因为它提供了一种方法来区分相同类类型的多个对象!

当类需要利用内部查找表(例如,用于存储一组预先计算的值的数组)时,静态成员变量也很有用。通过使查找表保持静态,所有对象只存在一个副本,而不是为每个实例化的对象创建副本。这可以节省大量内存。

10-07 15:28