跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 静态成员与非静态成员详解

C++ 静态成员与非静态成员的区别在于内存分配与共享方式。静态成员变量属于类而非对象,所有对象共享同一份数据,在编译期分配内存,需类外初始化。静态成员函数无 this 指针,只能访问静态成员。非静态成员属于对象实例。常函数和常对象限制了成员属性的修改权限,mutable 关键字可突破此限制。空指针调用成员函数若未使用 this 指针则安全,否则会导致崩溃。通过 sizeof 可验证只有非静态成员变量占用对象内存。

岁月神偷发布于 2026/2/8更新于 2026/9/4394 浏览

一、静态成员

静态成员是在成员变量和成员函数前面加上关键字 static。

二、静态成员变量

1. 静态成员变量的性质

作为静态成员变量,符合以下特点:

  • 所有对象共享同一份数据;
  • 在编译阶段分配内存,在主函数之前进行构造;
  • 类内声明,类外初始化;
  • 存放在静态区,不占用对象内存;
  • 在发生继承时,静态成员变量不会被继承,父类子类共享同一个静态成员。
#include <iostream>
using namespace std;

class Person {
public:
    static int a;
    Person() {
        this->a++;
    }
};

// 类外初始化
int Person::a = 1;

int main() {
    // 所有对象共享一份数据
    Person a1, a2, a3, a4;
    cout << a1.a << " " << a2.a << " " << a3.a << " " << a4.a << endl; // 输出 5 5 5 5
    
    // 存放在静态区,不占用对象内存
    cout << sizeof(a1); // 输出 1
    return 0;
}

代码中将静态变量 a 初始化为 1,在无参构造函数中,通过自增变量进行初始化。按理来说创建四个类的对象 a1、a2、a3、a4,每次都会调用无参构造方法,这样分别输出四个对象的变量 a,得到的值应该是 2、3、4、5 才对,那为什么实际输出的结果不是这样呢?

这是因为'针对静态变量而言,所有对象都会共享同一份数据',所以静态变量 a 的最终态(a=5)是所有对象时刻共享的。

2. 静态成员变量的访问

两种方式:1. 通过对象访问;2. 通过类名访问。

class Person {
public:
    static int a;
};

int main() {
    Person a1;
    // 通过对象访问
    a1.a = 100;
    // 通过类名访问
    Person::a = 100;
}

三、静态成员函数

作为静态成员函数,存在以下性质:

  • 所有对象共享同一个函数;
  • 静态成员函数只能访问静态成员变量;
  • 没有 this 指针,所以静态成员函数内部不能访问成员变量和成员函数。

注: 这是因为静态成员函数中没有 this 指针,也就不能区分哪个变量属于哪个对象,进而不能访问非静态成员变量。

据上述几条性质,分别对下面代码中的语句 1、2、3 进行分析如下:

#include <iostream>
using namespace std;

class Person {
    static int a;
    int b;
    void work() { }
    static void fun() {
        a = 100; // 1
        b = 100; // 2
        work();  // 3
        cout << "fun" << endl;
    }
};
  • 语句 1:正常执行,因为静态成员函数能够访问静态成员变量。
  • 语句 2:报错,因为静态成员函数 Person 中没有 this 指针,不能访问非静态成员变量。
  • 语句 3:报错,静态成员函数不能调用非静态成员函数,因为无法给 work() 中的隐藏参数 this 传参。

四、静态成员编程练习

1. 【例】统计对象个数

设计一个类 Counter,要求:

  • (1)使用静态成员变量来记录对象的数量;
  • (2)提供静态成员函数来获取当前对象的数量。
#include <iostream>
using namespace std;

class Counter {
public:
    static int num;
    Counter() {
        num++;
        cout << "创建对象,当前总数:" << num << endl;
    }
    ~Counter() {
        num--;
        cout << "销毁对象,剩余总数:" << num << endl;
    }
    static int getNum() {
        return num;
    }
};

int Counter::num = 0;

int main() {
    Counter c1, c2, c3, c4, c5;
    cout << "块内对象数:" << Counter::getNum() << endl;
    return 0;
}

程序如上,这里需要注意的点如下:

  1. 调用析构函数的时候,对象会自动销毁,所以需要添加对象减少的逻辑;
  2. 静态成员变量的初始化应该在类外。

2. 【例】银行账户

设计一个 BankAccount 类,模拟银行账户的基本操作:

  • (1)使用静态成员变量记录所有账户的总余额,每个账户对象有自己的余额属性;
  • (2)每个账户能够存取;
  • (3)提供静态成员函数来查询余额;
  • (4)在构造函数和析构函数中更新总余额。
#include <iostream>
using namespace std;

class BankAccount {
public:
    static double sum;
    BankAccount() {}
    BankAccount(double sum) {
        this->sum = sum;
    }
    void addA(double sum) {
        if (sum > 0) {
            cout << "成功存入" << sum << "元" << endl;
            this->sum += sum;
        } else {
            cout << "存储失败" << endl;
            return;
        }
    }
    void disaddA(double sum) {
        if (this->sum >= sum) {
            cout << "成功取出" << sum << "元" << endl;
            this->sum -= sum;
        } else {
            cout << "取出失败,余额不足" << endl;
            return;
        }
    }
    static void getAInf() {
        cout << "余额为:" << sum << "元" << endl;
    }
    ~BankAccount() {
        cout << "余额为:" << sum << endl;
    }
};

double BankAccount::sum = 0;

int main() {
    BankAccount b1;
    b1.addA(100);
    b1.disaddA(78.5);
    b1.getAInf();
    return 0;
}

五、成员与对象大小的关系

在 C++ 中,类里的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上。也就是说,非静态成员函数、静态成员函数、静态成员变量都不属于类的对象中。可以通过直接输出 sizeof(对象) 来验证该结论,这里不做赘述。

六、空指针访问成员函数

在 C++ 中,空指针也是能调用成员函数的,但是要注意有没有用到 this 指针,如果用到 this 指针,就需要加以判断来保证代码的健壮性。

通过下面代码能够看到空指针访问成员函数的两种不同情况的区别:

#include <iostream>
class MyClass {
public:
    void safeMethod() { }
    void unsafeMethod() {
        std::cout << "Value: " << value << std::endl; // 危险:访问成员变量
    }
private:
    int value = 42;
};

int main() {
    MyClass* ptr = nullptr;
    // 安全调用:空对象不访问成员变量
    ptr->safeMethod(); // 可以正常运行
    // 危险调用:访问成员变量
    ptr->unsafeMethod(); // 运行时崩溃
    return 0;
}

在成员函数 unsafeMethod() 中使用了成员变量 value(使用了 this 指针),程序崩溃。

为了避免这种情况的发生,需要事先判断是否为空指针,再继续下面的程序,如下:

#include <iostream>
using namespace std;

class MyClass {
public:
    void safeMethod() { }
    void unsafeMethod() {
        if (this == nullptr) {
            cout << "空指针不能访问成员变量" << endl;
            return;
        }
        std::cout << "Value: " << value << std::endl;
    }
private:
    int value = 42;
};

int main() {
    MyClass* ptr = nullptr;
    // 安全调用:空指针不访问成员变量
    ptr->safeMethod(); // 可以正常运行
    // 危险调用:访问成员变量
    ptr->unsafeMethod(); // 运行时崩溃
    return 0;
}

七、常函数和常对象

1. 常函数

在成员函数后加 const 就变成了常量成员函数,即常函数。常函数的性质如下:

  • 常函数内不可以修改成员属性,也不可以调用非常函数;

注: 这样能够使常函数无法通过修改成员属性来修改自己内部的值,不会因此失去常量性。

  • 成员属性声明时加关键字 mutable 后,在常函数中可以修改;
  • 如果两个函数名字相同、参数相同,但是一个是常函数,一个是非常函数,那么这两个函数属于函数重载(函数重载的特殊情况),非常对象优先调用非常函数,常对象只能调用常函数。

(1)下面的代码报错,是因为常函数内不能修改成员属性。

#include <iostream>
using namespace std;

class People {
public:
    int num;
    People(int num) { this->num = num; }
    void S() const {
        num = 1;
    }
};

int main() {
    return 0;
}

解决方法是在成员属性处声明 mutable:

#include <iostream>
using namespace std;

class People {
public:
    mutable int num;
    People(int num) { this->num = num; }
    void S() const {
        num = 1;
    }
};

int main() {
    return 0;
}

(2)下面的代码报错,是因为常函数中只能调用常函数。

#include <iostream>
using namespace std;

class People {
public:
    mutable int num;
    People(int num) { this->num = num; }
    void Q() { }
    void S() const {
        this->Q();
    }
};

int main() {
    return 0;
}

2. 常对象

声明对象前加 const 称该对象为常量成员对象,即常对象。常对象的性质如下:

  • 常对象只能调用常函数。

下面的代码报错,是因为常对象只能调用常函数,而非常对象既能调用常函数也能调用非常函数,非常对象优先调用非常函数。

#include <iostream>
using namespace std;

class People {
public:
    mutable int num;
    People() {}
    People(int num) { this->num = num; }
    void S() { }
};

int main() {
    const People p;
    p.S();
    return 0;
}

解决方法是将 People 类中的 S() 函数设置为常函数,或者将对象 p 设置为非常对象。

目录

  1. 一、静态成员
  2. 二、静态成员变量
  3. 1. 静态成员变量的性质
  4. 2. 静态成员变量的访问
  5. 三、静态成员函数
  6. 四、静态成员编程练习
  7. 1. 【例】统计对象个数
  8. 2. 【例】银行账户
  9. 五、成员与对象大小的关系
  10. 六、空指针访问成员函数
  11. 七、常函数和常对象
  12. 1. 常函数
  13. 2. 常对象

更多推荐文章

查看全部
  • AI 产品架构设计:从 0 到 1 搭建信息架构与核心流程
  • KingbaseES SQL 防火墙技术解析与实战
  • 单链表常见算法题解析:反转、合并与环检测
  • 华为 OD 机试:停车场收入统计
  • AI 产品经理实战进阶指南:核心技能与职业转型路径
  • Git 团队开发全流程实战指南
  • LazyLLM 低代码框架实战:代码专家智能体开发
  • 使用 AI 工具快速生成专业架构图的几种方法
  • AIGC 去 AI 味 Prompt 设计:从原理到实践解析
  • Kimi-K2.5 视觉驱动编程:原生多模态架构解析与实践
  • 把 GitHub Copilot 在 VS Code 里用顺手:中文环境下的调校与实战配置
  • Linux 下 Java JAR 包后台运行与端口检查实战
  • 基于 FastAPI 自动构建 SSE MCP 服务器
  • Jenkins 通过 JNLP 启动 Slave 节点配置详解
  • 归并排序非递归实现详解
  • OpenClaw ACP 协议深度解析:IDE 直接驱动 AI Agent
  • 前端三剑客基础与 Web 安全入门
  • MySQL InnoDB MVCC 多版本并发控制实现原理
  • 2023 年 8 月编程语言排行榜:Python 第一,C++ 升至第三
  • Ubuntu 24.04 安装 OpenClaw 时 systemctl is-enabled 报错解决

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online