【c++面向对象编程】第7篇:static成员:属于类而不是对象的变量和函数

目录

一、一个场景:想知道创建了多少个对象?

二、静态成员变量:类级别的共享数据

基本写法

三、静态成员变量的初始化(最容易出错的地方)

const静态成员可以类内初始化

四、静态成员函数:不依附于对象的函数

五、单例模式:静态成员的经典应用

六、完整例子:银行账户系统

七、三个常见错误

[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。下篇讲清楚这些边界情况。

相关推荐
影sir1 小时前
STL容器——list类
c++·链表·stl·list
ooseabiscuit1 小时前
PHP与C++:Web与系统编程的终极对决
前端·c++·php
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第47题】【JVM篇】第7题:Young GC 和 Full GC 分别采用什么算法?
java·jvm·后端·算法·面试
lyp90h1 小时前
Claude Code CLI System Prompt 完整分析
java·前端·prompt
xu_ws1 小时前
redis的io多路复用和Java NIO的区别
java·redis·nio
艾莉丝努力练剑1 小时前
【Linux网络】Linux 网络编程:应用层自定义协议与序列化(3):网络计算器实现和守护进程
linux·运维·服务器·网络·c++·计算机网络·安全
Ulyanov1 小时前
《从质点到位姿:基于Python与PyVista的导弹制导控制全栈仿真》: 同台竞技——3-DOF与6-DOF模型的终极对决与误差分析
开发语言·python·算法·系统仿真·雷达电子对抗仿真
Hesionberger1 小时前
LeetCode98:验证二叉搜索树(多解)
java·开发语言·python·算法·leetcode·职场和发展
千寻girling1 小时前
周日那天参加的力扣周赛... —— 10号
java·javascript·c++·python·算法·leetcode·职场和发展