C++不同的数据成员类型
C++为数据成员提供了多种选择,除了在类中简单地声明数据成员外,还可以创建static数据成员(类的所有对象共享)、const数据成员、引用数据成员、const引用数据成员和其他成员。本文将解释这些不同类型的数据成员。
静态数据成员
有时让类的所有对象都包含某个变量的副本是没有必要的,数据成员可能只对类有意义,而每个对象都拥有其副本是不合适的。C++用static数据成员解决了这个问题。static数据成员属于类但不是对象的数据成员,可将static数据成员当作类的全局变量。
class Spreadsheet
{
private:
static size_t ms_counter; // 不能在此初始化值,也不能在构造函数内初始化,只能在类外初始化
};
不仅要在类定义中列出static类成员,还需要在源文件中为其分配内存,通常是定义类方法的那个源文件。还可以初始化static成员,但注意与普通的变量和数据成员不同,默认情况下它们会初始化为0。static指针会初始化为nullptr。示例:为ms_counter分配空间并初始化为0的代码
size_t Spreadsheet::ms_counter;
静态数据成员默认情况下初始化为0,但是如果有需要,可以将它们显示的初始化为0。
size_t Spreadsheet::ms_counter{0};
内联变量
从c++17开始,可以将静态数据成员声明为inline。这样做的好处就是不必在源文件中为它们分配空间。
class Spreadsheet
{
private:
static inline size_t ms_counter{0}; // 之前类外初始化的代码可以删除了
};
在类方法内访问静态数据成员
在类方法内部,可以像使用普通数据成员一样使用静态数据成员。例如,为Spreadsheet类创建一个m_id成员,并在Spreadsheet构造函数中用ms_counter成员初始化它。
class Spreadsheet
{
public:
Spreadsheet() : m_id{ms_counter++} {};
private:
size_t m_id{0};
static size_t inline ms_counter{0};
};
本例中,假使一旦给某个对象指定id,就不应该改变。所以在拷贝赋值运算符中不应该复制id,因此可以把m_id设置为const数据成员。
class Spreadsheet
{
private:
const size_t m_id{0};
};
由于const数据成员一经创建就无法更改,例如,无法在构造函数体内初始化它们。此类数据成员必须直接在类定义内部或构造函数初始化器中初始化,这也意味着不能在赋值运算符中为此类数据成员赋值。
在方法外访问静态数据成员
访问控制限定符适用于static数据成员,ms_counter是private的,因此不能在类方法外访问。如果ms_counter是public的,就可以在类方法外访问,具体方法是用 :: 作用域解析运算符指出这个变量是Spreadsheet类的一部分。
int c { Spreadsheet::ms_counter };
然而,不建议使用public数据成员。应该提供public的get/set方法来授权访问权限。如果要访问static数据成员,可以实现static的get/set方法。
const static数据成员
类中的数据成员可声明为const,意味着在创建并初始化后,值不能再改变。如果某个常量只适用于类,应该使用static const(或者const static)数据成员,也称为类常量,而不是全局常量。可以在类定义中和初始化整型和枚举类型的static const数据成员,而不需要将其指定为内联变量。
例如,创建类指定了电子表格的最大高度和宽度,如果用户想要创建的电子表格的高度或者宽度大于最大值,那么就改用指定的最大值。可将最大高度和宽度设置为Spreadsheet类的static const成员。
class Spreadsheet
{
public:
static const size_t MaxHeight{100};
static const size_t MaxWidth{100};
Spreadsheet(size_t width, size_t height)
: m_id{ms_counter++},
m_width{min(width, MaxWidth)},
m_height{min(height, MaxHeight)} {}
private:
size_t m_width;
size_t m_height;
const size_t m_id{0};
static size_t inline ms_counter{0};
};
这些常量也可以作为构造函数的参数的默认值。需要注意的是,只能为一组连续的参数(从右向左)指定默认值。示例:
Spreadsheet(size_t width = MaxWidth, size_t height = MaxHeight);
引用数据成员
假设Spreadsheet类需要用到一个控制电子表格的类SpreadsheetApplication,示例:
class SpreadsheetApplication {};
class Spreadsheet
{
public:
static const size_t MaxHeight{100};
static const size_t MaxWidth{100};
Spreadsheet(size_t width, size_t height, SpreadsheetApplication &app)
: m_id{ms_counter++},
m_width{min(width, MaxWidth)},
m_height{min(height, MaxHeight)},
m_app{app} {}
private:
size_t m_width;
size_t m_height;
const size_t m_id{0};
static size_t inline ms_counter{0};
SpreadsheetApplication &m_app;
};
将一个SpreadsheetApplication引用作为数据成员添加进来,在此情况下建议使用引用而不是指针,因为Spreadsheet总会指向一个SpreadsheetApplication,而指针则无法保证这一点。
注意:在构造函数中,每个Spreadsheet都得到了一个应用程序引用。如果不指向某些事物,引用将无法存在,因此在构造函数初始化器中必须给m_app指定一个值!
在拷贝构造函数中也必须初始化这个引用成员。再次提醒,在初始化一个引用之后,不能改变它指向的对象。因此无法在赋值运算符中对引用赋值。这就意味着根据使用的情形,可能无法为具有引用数据成员的类提供赋值运算符。这种情况下,通常将赋值运算符标记为删除。