C++之静态函数用法及说明
类中的静态函数,就是属于“类本身”的函数,不属于某一个具体对象。
最典型的写法是:
class ZlogBase
{
public:
static void Initialize(const std::string& config_path);
static void Shutdown();
};
这两个函数前面有 static,它们就是静态成员函数。
一、怎么使用
静态函数最常见的调用方式是:
ZlogBase::Initialize("log.conf");
ZlogBase::Shutdown();
也就是:
类名::函数名()
因为它属于类本身,所以不需要先创建对象。
对比一下普通成员函数
普通成员函数:
class ZlogBase
{
public:
void logInfo(const char* msg) const;
};
这种函数必须通过对象调用:
ZlogBase logger("tcpserver");
logger.logInfo("server start");
不能直接:
ZlogBase::logInfo("server start"); // 错
因为 logInfo() 需要依赖某个对象内部的数据,比如:
m_pZlogCategorythis
二、为什么静态函数可以直接用类名调用
因为静态函数没有 this 指针。
普通成员函数在内部其实默认都带着一个隐藏参数:
this
表示“当前对象是谁”。
比如:
logger.logInfo("hello");
本质上像是:
logger.logInfo(&logger, "hello");
只是这个 this 是编译器自动传进去的。
但是静态函数没有 this,所以它不能访问对象成员变量,也不依赖某个对象。
既然它不依赖对象,那就可以直接:
ZlogBase::Initialize(...)
三、静态函数里能做什么,不能做什么
能做的
可以做和“整个类”有关、但和“某个具体对象”无关的事情。
例如:
- 全局初始化
- 全局释放
- 计数器
- 工具函数
- 工厂函数
- 单例访问入口
不能直接做的
不能直接访问普通成员变量,比如:
class ZlogBase
{
protected:
zlog_category_t* m_pZlogCategory = nullptr;
public:
static void Initialize(const std::string& config_path);
};
在 Initialize() 里不能直接写:
m_pZlogCategory = ...; // 错
因为 m_pZlogCategory 属于某个对象,
而静态函数不知道你想操作哪个对象。
四、为什么Initialize()适合做成静态函数
你现在这个例子最典型:
ZlogBase::Initialize(LOG_FILE_PATH);
因为“初始化日志系统”这件事,本质上是整个程序级别的事情,不是某一个 TcpServer 或某一个 System 对象自己的事情。
它通常做的是:
zlog_init(LOG_FILE_PATH);
这是全局初始化。
所以它很适合写成:
static void Initialize(...);
这样你在 main() 里程序启动时就能先调用:
ZlogBase::Initialize(LOG_FILE_PATH);
后面再去创建:
SystemTcpServerDevInfo
这些对象。
五、为什么要“先 Initialize,再创建对象”
因为很多对象在构造时就要用日志。
比如:
class TcpServer : public ZlogBase
{
public:
TcpServer() : ZlogBase("tcpserver") {}
};
而 ZlogBase("tcpserver") 里面可能会调用:
zlog_get_category("tcpserver");
那前提就是:
zlog 系统已经先初始化好了。
所以顺序必须是:
ZlogBase::Initialize(...); // 先初始化整个日志系统 TcpServer server; // 再创建具体对象
这就是为什么 Initialize() 设计成静态函数,并且在 main() 里直接调用最合理。
六、一个非常好理解的类比
比如学校系统:
class Student
{
public:
static void SetSchoolName(const std::string& name);
void SetStudentName(const std::string& name);
};
学校名称
这是所有学生共享的,属于“整个类”的事情,所以适合静态函数:
Student::SetSchoolName("No.1 Middle School");
学生姓名
这是某个具体学生自己的事情,所以必须通过对象:
Student s;
s.SetStudentName("Tom");
ZlogBase::Initialize()就像“设置学校名字”,是全局动作。logInfo()就像“设置学生名字”,是对象动作。
七、静态函数常见使用场景
在实际项目里,静态函数常见于这几类:
1. 全局初始化 / 释放
像你这个:
ZlogBase::Initialize(...); ZlogBase::Shutdown();
2. 计数器
class Test
{
public:
static int Count();
};
统计一共创建了多少对象之类。
3. 工具函数
class Math
{
public:
static int Add(int a, int b);
};
直接:
Math::Add(1, 2);
不需要先创建 Math 对象。
4. 单例入口
class Config
{
public:
static Config& instance();
};
八、静态函数和静态成员变量经常配合使用
例如:
class ZlogBase
{
public:
static void Initialize(const std::string& path);
private:
static bool s_inited;
};
这里:
Initialize()是静态函数s_inited是静态变量
这两个都属于类本身,而不是某个对象。
所以非常适合做“全局只初始化一次”的逻辑。
九、你现在这个例子应该怎么理解
你截图里的:
QCoreApplication a(argc, argv); ZlogBase::Initialize(LOG_FILE_PATH); System* pApp = new System(); pApp->Init();
应该理解成:
第一步
程序启动,先初始化日志系统:
ZlogBase::Initialize(LOG_FILE_PATH);
第二步
再创建系统对象:
System* pApp = new System();
如果 System 继承了 ZlogBase,那它在构造时就能安全使用日志功能。
十、一句话总结
静态函数用于处理“属于整个类、而不属于某个具体对象”的事情。
所以像:
- 初始化日志系统
- 关闭日志系统
这种全局动作,最适合写成:
ZlogBase::Initialize(...); ZlogBase::Shutdown();
而像:
logInfo()logError()
这种依赖对象内部状态的函数,就必须通过对象调用。
如果你愿意,我下一条可以继续专门讲一下:
静态成员变量和静态成员函数的关系,以及它们在 .h / .cpp 里应该怎么写。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。


最新评论