目录
[1. 忘记在类外定义静态成员变量](#1. 忘记在类外定义静态成员变量)
[2. 静态成员函数里访问非静态成员](#2. 静态成员函数里访问非静态成员)
[3. 用对象来改变静态成员的值(语义别扭)](#3. 用对象来改变静态成员的值(语义别扭))
一、一个场景:想知道创建了多少个对象?
写一个Student类,每创建一个学生,就自动计数。最后能知道一共创建了多少个学生。
不用static的话,你会怎么设计?
cpp
class Student {
private:
string name;
int count = 0; // 试图用普通成员变量计数
public:
Student(string n) : name(n) {
count++; // 每个对象都有自己的count,各加各的
}
int getCount() { return count; }
};
运行一下:
cpp
Student s1("张三");
Student s2("李四");
cout << s1.getCount(); // 输出1 ❌ 想要2
cout << s2.getCount(); // 输出1 ❌
问题:每个对象的count是独立的,s1.count++只影响s1自己的count。
解决方案:让count变成一个所有对象共享 的变量------这就是static成员变量。
二、静态成员变量:类级别的共享数据
基本写法
cpp
class Student {
private:
string name;
static int count; // 声明静态成员变量(注意是声明,不是定义)
public:
Student(string n) : name(n) {
count++;
}
static int getCount() { return count; } // 静态成员函数
};
// ⚠️ 关键:在类外定义并初始化静态成员变量
int Student::count = 0; // 定义,分配内存
现在运行:
cpp
Student s1("张三");
Student s2("李四");
cout << Student::getCount(); // 输出2 ✅
核心区别:
| 普通成员变量 | 静态成员变量 |
|---|---|
| 每个对象有一份 | 所有对象共享一份 |
| 随对象创建而存在 | 在程序开始时已存在 |
| 在构造函数中初始化 | 必须在类外单独定义 |
| 通过对象访问 | 可通过类名访问 |
三、静态成员变量的初始化(最容易出错的地方)
这是新手最容易犯错的地方:静态成员变量必须在类外单独定义一次。
cpp
class Demo {
public:
static int x; // 声明
static int y; // 声明
};
// 类外定义(通常放在.cpp文件中)
int Demo::x = 0; // 定义并初始化
int Demo::y; // 定义,默认初始化为0
int main() {
cout << Demo::x; // 0
}
为什么这么麻烦?
-
类的声明通常在头文件中,而头文件可能被多个
.cpp包含 -
如果静态成员变量在类内定义,每个包含头文件的
.cpp都会产生一份定义 → 链接冲突 -
类外定义保证了只有一份,放在一个
.cpp里编译一次
C++17的简化(inline static):
cpp
class Demo {
public:
inline static int x = 0; // C++17后可以在类内直接定义
};
如果你用C++17及以上,可以这样写,省去类外定义的麻烦。
const静态成员可以类内初始化
cpp
class Math {
public:
static const double PI = 3.14159; // const静态整型/枚举可以类内初始化
// 或者用 constexpr
static constexpr double E = 2.71828; // C++11
};
四、静态成员函数:不依附于对象的函数
静态成员函数也有明显的特征:
cpp
class Student {
private:
static int total;
string name;
public:
static int getTotal() {
return total; // ✅ 只能访问静态成员
}
static void showInfo() {
// cout << name; // ❌ 错误!不能访问非静态成员
cout << total; // ✅ 可以访问静态成员
}
void normalFunc() {
name = "test"; // ✅ 普通函数可以访问所有
total = 10; // ✅ 包括静态成员
}
};
关键限制 :静态成员函数没有this指针,所以:
-
不能访问非静态成员变量(不知道是哪个对象的)
-
不能调用非静态成员函数(也需要
this) -
不能被
const修饰(const修饰的是this,但没有this)
访问方式:
cpp
Student::getTotal(); // 推荐:通过类名访问
Student s;
s.getTotal(); // 也可以:通过对象访问(但语义上不推荐)
五、单例模式:静态成员的经典应用
单例模式确保一个类只有一个实例,全局都能访问它。比如配置管理器、日志器、线程池。
cpp
class Singleton {
private:
static Singleton* instance; // 唯一的实例指针
int data;
// 私有化构造函数和拷贝控制,防止外部创建
Singleton() : data(0) {
cout << "Singleton created" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton(); // 懒加载
}
return instance;
}
void setData(int val) { data = val; }
int getData() { return data; }
};
// 类外初始化
Singleton* Singleton::instance = nullptr;
int main() {
// 只能通过 getInstance() 获取实例
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->setData(100);
cout << s2->getData(); // 100,证明是同一个对象
cout << (s1 == s2); // 1(true)
}
注意:上面的实现不是线程安全的。C++11之后,有更简单的线程安全写法:
cpp
class Singleton {
private:
Singleton() {}
public:
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量,C++11保证线程安全
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
这个叫Meyers Singleton,推荐使用。
六、完整例子:银行账户系统
用静态成员来记录银行系统里所有账户的总余额:
cpp
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
static double totalBalance; // 所有账户的总余额
static int accountCount; // 账户总数
public:
BankAccount(string name, double initial)
: owner(name), balance(initial) {
accountCount++;
totalBalance += initial;
cout << "开户:" << owner << ",存入" << initial << endl;
}
~BankAccount() {
accountCount--;
totalBalance -= balance;
cout << "销户:" << owner << ",剩余余额" << balance << endl;
}
void deposit(double money) {
if (money > 0) {
balance += money;
totalBalance += money;
}
}
bool withdraw(double money) {
if (money > 0 && money <= balance) {
balance -= money;
totalBalance -= money;
return true;
}
return false;
}
// 静态成员函数:获取全局信息
static int getAccountCount() { return accountCount; }
static double getTotalBalance() { return totalBalance; }
void print() const {
cout << owner << " 余额:" << balance << endl;
}
};
// 类外定义静态成员
double BankAccount::totalBalance = 0;
int BankAccount::accountCount = 0;
int main() {
cout << "当前账户数:" << BankAccount::getAccountCount() << endl;
BankAccount a("张三", 1000);
BankAccount b("李四", 2000);
cout << "账户数:" << BankAccount::getAccountCount() << endl;
cout << "总余额:" << BankAccount::getTotalBalance() << endl;
a.deposit(500);
b.withdraw(300);
cout << "操作后总余额:" << BankAccount::getTotalBalance() << endl;
return 0;
}
输出:
text
当前账户数:0
开户:张三,存入1000
开户:李四,存入2000
账户数:2
总余额:3000
操作后总余额:3200
销户:李四,剩余余额1700
销户:张三,剩余余额1500
七、三个常见错误
1. 忘记在类外定义静态成员变量
cpp
class Demo {
static int x; // 只是声明,不是定义
};
int main() {
Demo::x = 10; // ❌ 链接错误:undefined reference
}
修复:在某个.cpp里加一行int Demo::x = 0;
2. 静态成员函数里访问非静态成员
cpp
class Demo {
int a;
static void func() {
a = 10; // ❌ 错误!没有this,不知道是哪个对象的a
}
};
3. 用对象来改变静态成员的值(语义别扭)
cpp
Student s;
s.setCount(100); // 虽然能编译,但很奇怪:改的是所有学生的数量,为什么通过一个对象?
更好的做法是Student::setCount(100),让人一眼看出是类级别操作。
八、这一篇的收获
你现在应该理解:
-
静态成员变量:所有对象共享,类外定义一次
-
静态成员函数 :没有
this,只能访问静态成员 -
静态成员可以通过类名
::访问,也可以通过对象访问(不推荐) -
单例模式利用了构造函数私有化 + 静态成员函数返回唯一实例
-
C++11之后可以用局部静态变量实现线程安全的单例
💡 小作业:写一个
Car类,用静态成员记录生产的汽车总数。构造函数里自动增加计数,析构函数里减少计数。再加一个静态函数getTotalCount()。创建几个对象,验证计数是否正确。
下一篇预告 :第8篇《const成员与mutable:常对象与常函数》------const修饰对象时,只能调用const成员函数。但如果某个成员变量即使在const函数里也想被修改(比如缓存计数),可以用mutable。下篇讲清楚这些边界情况。