回顾:静态成员变量的访问
在前一课《静态成员变量》中,我们学到:静态成员变量属于类本身,而非类的对象。若其访问权限为 public
,可直接通过类名加作用域解析运算符 ::
访问:
#include <iostream>
class Something
{
public:
static inline int s_value{ 1 };
};
int main()
{
std::cout << Something::s_value; // s_value 为 public,可直接访问
}
若静态成员变量为 private
,则无法直接访问:
#include <iostream>
class Something
{
private: // 现为 private
static inline int s_value{ 1 };
};
int main()
{
std::cout << Something::s_value; // 错误:s_value 为 private
}
此时,我们无法在 main()
中直接访问 Something::s_value
。常规做法是通过公有的普通成员函数暴露接口,但这要求先实例化对象:
#include <iostream>
class Something
{
private:
static inline int s_value{ 1 };
public:
int getValue() { return s_value; } // 普通成员函数
};
int main()
{
Something s{};
std::cout << s.getValue(); // 可行,但必须实例化对象
}
显然,这种做法并不理想。
静态成员函数
成员函数亦可声明为 static
。将上例改为静态成员函数:
#include <iostream>
class Something
{
private:
static inline int s_value{ 1 };
public:
static int getValue() { return s_value; } // 静态成员函数
};
int main()
{
std::cout << Something::getValue() << '\n';
}
静态成员函数不与任何对象关联,可直接通过 类名::
调用;也可通过对象调用,但不推荐。
静态成员函数的两个特点
无
this
指针
静态成员函数不属于任何对象,自然没有this
指针。- 非静态成员必须依赖对象调用,因此需要
this
; - 静态成员函数无需对象即可执行,故无需
this
。
- 非静态成员必须依赖对象调用,因此需要
只能直接访问静态成员
静态成员函数可直接访问其他静态成员(变量或函数),但不能直接访问非静态成员,因为非静态成员必须依附于对象,而静态成员函数无对象可用。
在类外定义静态成员函数
静态成员函数亦可在类外实现,语法与普通成员函数一致:
#include <iostream>
class IDGenerator
{
private:
static inline int s_nextID{ 1 };
public:
static int getNextID(); // 声明
};
// 类外定义(注意无需再次使用 static)
int IDGenerator::getNextID() { return s_nextID++; }
int main()
{
for (int count{ 0 }; count < 5; ++count)
std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n';
}
输出:
The next ID is: 1
The next ID is: 2
The next ID is: 3
The next ID is: 4
The next ID is: 5
由于所有数据与函数均为静态,无需实例化即可使用其功能。
头文件中的静态成员函数
根据第 15.2 课《类与头文件》:
- 类内定义的静态成员函数隐式内联;
- 类外定义的静态成员函数不隐式内联,若置于头文件,应显式加
inline
,以避免违反一次定义规则(ODR)。
全静态成员类的注意事项
编写“全静态成员类”(亦称“单态类”)时需留意:
单例性
所有静态成员仅实例化一次,无法拥有多个独立副本。如需两个独立的IDGenerator
,则无法用纯静态类实现。全局状态风险
纯静态类等价于全局命名空间中的全局变量与函数,任何代码均可修改其状态,易导致难以察觉的错误。
替代方案:- 使用普通类并实例化一个全局对象;
- 需要时仍可创建局部对象,保持灵活性。
纯静态类 vs 命名空间
- 相同点:均可在作用域内定义静态变量和函数。
- 差异点:类具备访问控制(
private
、protected
、public
),命名空间则无。
若需隐藏数据成员或提供封装,优先使用类;否则使用命名空间。
C++ 无静态构造函数
C++ 不支持“静态构造函数”。若静态成员可直接初始化,可在定义处完成(即使为 private
):
#include <iostream>
struct Chars
{
char first{}, second{}, third{}, fourth{}, fifth{};
};
struct MyClass
{
static inline Chars s_mychars{ 'a', 'e', 'i', 'o', 'u' };
};
int main()
{
std::cout << MyClass::s_mychars.third; // 输出 i
}
若初始化需执行复杂逻辑(如循环),可借助函数返回对象:
#include <iostream>
struct Chars
{
char first{}, second{}, third{}, fourth{}, fifth{};
};
class MyClass
{
private:
static Chars generate()
{
Chars c{};
c.first = 'a';
c.second = 'e';
c.third = 'i';
c.fourth = 'o';
c.fifth = 'u';
return c;
}
public:
static inline Chars s_mychars{ generate() };
};
int main()
{
std::cout << MyClass::s_mychars.third; // 输出 i
}
相关技巧亦可用 lambda 实现,详见第 8.15 课《全局随机数》。
小测验
问题 1
将以下 Random
命名空间改写为“全静态成员类”:
#include <chrono>
#include <random>
#include <iostream>
namespace Random
{
inline std::mt19937 generate()
{
std::random_device rd{};
std::seed_seq ss{
static_cast<std::seed_seq::result_type>(
std::chrono::steady_clock::now().time_since_epoch().count()),
rd(), rd(), rd(), rd(), rd(), rd(), rd() };
return std::mt19937{ ss };
}
inline std::mt19937 mt{ generate() };
inline int get(int min, int max)
{
return std::uniform_int_distribution{min, max}(mt);
}
}
int main()
{
for (int count{ 1 }; count <= 10; ++count)
std::cout << Random::get(1, 6) << '\t';
std::cout << '\n';
return 0;
}
(答案略)