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

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

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

岁月神偷发布于 2026/2/8更新于 2026/5/26350 浏览

一、静态成员

静态成员是在成员变量和成员函数前面加上关键字 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. 常对象
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 大模型系统学习路线:从入门基础到工程实战
  • OpenClaw AI 助手浏览器自动化功能配置实战
  • FPGA实现UART串口通信
  • 插入排序详解:直接插入排序与希尔排序及性能对比
  • Ubuntu 22.04 安装向日葵远程控制
  • GitHub 分支保护规则设置:禁止直接 Push 强制 Pull Request
  • 从记忆化搜索到动态规划:状态表示、转移方程与空间优化
  • Epoll 水平触发与边缘触发:面试核心考点解析
  • URDF 机器人模型描述标准 XML 格式详解
  • Llama-3.2V-11B-COT 模型视觉推理质量评估指南
  • SkyWalking 告警通知渠道集成:Webhook、Slack、钉钉、企业微信
  • 大模型 AI 产品经理学习路线:从基础到精通
  • 利用 GPT4 和 DALL·E 制作 AI 绘画短视频指南
  • C++ 面向控制标记编程(CMOP)核心思想与实现
  • Sora2 文生视频生成指南:漫剧创作与 Python 实现
  • AI 生成前端 UI 的三步优化技巧
  • PostgreSQL 模式(Schema)详解:数据库对象命名空间管理
  • Java 开发环境优化:Maven 镜像与 Spring 脚手架配置
  • C++ 入门指南:发展史、命名空间及输入输出
  • 调试段错误利器:开启和使用 Core Dump(进程信号·叁)

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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