静态成员变量
查看静态关键字使用情况
在关于文件范围和静态关键字的课程中,您了解到静态变量保留了它们的值,即使它们超出范围也不会被销毁。例如:
#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(按创建顺序递增)。这在调试数组中的多个项时非常有用,因为它提供了一种方法来区分相同类类型的多个对象!
当类需要利用内部查找表(例如,用于存储一组预先计算的值的数组)时,静态成员变量也很有用。通过使查找表保持静态,所有对象只存在一个副本,而不是为每个实例化的对象创建副本。这可以节省大量内存。