C++ 静态数据成员与静态成员函数
1. 概述
在 C++ 中,使用static关键字修饰的类数据成员或成员函数,称为静态数据成员和静态成员函数。它们与类本身关联,而非与类的某个具体对象关联。静态成员在整个程序运行期间只有一份副本,被所有类对象共享。
2. 静态数据成员
2.1 定义与初始化
静态数据成员必须在类内声明,在类外(文件作用域)定义并初始化(除静态常量整型成员外)。定义时不需要重复static关键字,但需要加上类名和作用域运算符::。
// CountedObject.h#ifndefCOUNTED_OBJECT_H#defineCOUNTED_OBJECT_HclassCountedObject{public:CountedObject();~CountedObject();staticintGetCount();// 静态成员函数声明private:staticintcount_;// 静态数据成员声明(引用性声明)};#endif// CountedObject.cpp#include"CountedObject.h"// 静态数据成员定义性声明,在文件作用域初始化intCountedObject::count_=100;CountedObject::CountedObject(){++count_;}CountedObject::~CountedObject(){--count_;}intCountedObject::GetCount(){returncount_;}2.2 访问方式
- 通过类名加作用域运算符:
CountedObject::GetCount() - 通过对象或对象指针:
co1.GetCount()或co2->GetCount()(不推荐,易混淆)
2.3 特点与注意事项
- 静态数据成员独立于任何对象存在,即使没有创建类对象,它也已分配内存。
- 所有对象共享同一个静态数据成员。
- 静态数据成员可以是
private,通过公有静态成员函数访问,实现封装。 - 静态数据成员的名字位于类作用域中,避免与全局变量冲突。
3. 静态成员函数
3.1 特点
- 静态成员函数没有
this指针,因此不能直接访问非静态数据成员或调用非静态成员函数。 - 非静态成员函数可以任意访问静态数据成员和静态成员函数。
- 静态成员函数只能访问静态数据成员和其他静态成员函数。
3.2 示例:错误与正确访问
#include<iostream>usingnamespacestd;classTest{public:Test(inty):y_(y){}voidNonStaticFun(){cout<<"非静态函数中访问静态成员 x_ = "<<x_<<endl;StaticFun();// 非静态成员函数可以调用静态成员函数}staticvoidStaticFun(){// NonStaticFun(); // 错误!静态成员函数不能调用非静态成员函数// cout << y_; // 错误!不能访问非静态成员cout<<"静态函数中访问静态成员 x_ = "<<x_<<endl;}private:staticintx_;// 静态数据成员声明inty_;};// 静态数据成员定义及初始化intTest::x_=100;intmain(){Testt(10);t.NonStaticFun();// 输出:非静态函数中访问静态成员 x_ = 100Test::StaticFun();// 输出:静态函数中访问静态成员 x_ = 100// 通过对象访问静态成员(允许但不推荐)cout<<t.x_<<endl;// 100cout<<Test::x_<<endl;// 推荐方式return0;}4. 静态常量数据成员
static const修饰的静态常量成员,其值不可修改。对于整型(int、char、bool等)静态常量成员,可以在类内直接初始化(C++11 以后也允许非整型,但推荐统一在类外定义)。
示例
#include<iostream>usingnamespacestd;classTestConst{public:staticconstintkValue=100;// 静态常量整型,可在类内初始化staticconstdoublekPi;// 非整型,只能在类外定义};// 静态常量成员类外定义(不带 static 关键字)constdoubleTestConst::kPi=3.14159;intmain(){cout<<TestConst::kValue<<endl;// 100cout<<TestConst::kPi<<endl;// 3.14159// TestConst::kValue = 200; // 错误:常量不可修改return0;}5. 综合示例:统计对象个数
以下是一个完整的、可运行的统计某类对象创建与销毁个数的程序。
// CountedObject.h#ifndefCOUNTED_OBJECT_H#defineCOUNTED_OBJECT_HclassCountedObject{public:CountedObject();~CountedObject();staticintGetCount();private:staticintcount_;};#endif// CountedObject.cpp#include"CountedObject.h"intCountedObject::count_=0;CountedObject::CountedObject(){++count_;}CountedObject::~CountedObject(){--count_;}intCountedObject::GetCount(){returncount_;}// main.cpp#include"CountedObject.h"#include<iostream>usingnamespacestd;intmain(){cout<<"初始对象数: "<<CountedObject::GetCount()<<endl;// 0CountedObject obj1;CountedObject obj2;cout<<"创建两个对象后: "<<CountedObject::GetCount()<<endl;// 2CountedObject*pObj=newCountedObject;cout<<"动态创建一个对象后: "<<CountedObject::GetCount()<<endl;// 3deletepObj;cout<<"删除动态对象后: "<<CountedObject::GetCount()<<endl;// 2return0;}6. 重点总结
| 特性 | 静态数据成员 | 非静态数据成员 |
|---|---|---|
| 存储位置 | 全局数据区,独立于对象 | 每个对象各自的内存空间 |
| 初始化 | 类外定义时初始化,仅一次 | 构造函数初始化列表或体内 |
| 访问方式 | 类名::成员或 对象.成员 | 只能通过对象或指针访问 |
| 能否被静态成员函数访问 | 可以 | 不可以 |
| 能否被非静态成员函数访问 | 可以 | 可以 |
| 特性 | 静态成员函数 | 非静态成员函数 |
|---|---|---|
是否含有this指针 | 否 | 是 |
| 能否访问静态成员 | 可以 | 可以 |
| 能否访问非静态成员 | 不可以 | 可以 |
| 调用方式 | 类名::函数名或 对象.函数名 | 必须通过对象或指针调用 |
7. 常见误区与注意事项
- 静态成员函数不能调用非静态成员函数,因为非静态成员函数隐含
this指针,而静态函数没有。 - 静态数据成员不能在类内初始化(除
static const整型外),必须在类外单独定义。 - 通过对象访问静态成员容易产生误解,建议使用
类名::静态成员以明确其属于类。 - 静态成员变量即使不创建任何类对象,也已存在并可通过类名访问(如果为
public)。