C++面向对象编程之四:成员变量和成员函数分开存储、this指针、const修饰成员和对象

news2024/9/28 15:31:54

在C++中,成员变量和成员函数是分开存储的,只有非静态成员变量才存储在类中或类的对象上。通过该类创建的所有对象都共享同一个函数

#include <iostream>
using namespace std;

class Monster
{
    public:
    //成员函数不占对象空间,所有对象共享同一个函数
    Monster():m_monsterId(0)
    {

    }
    //成员函数不占对象空间,所有对象共享同一个函数
    Monster(const int monsterId):m_monsterId(monsterId)
    {

    }
    //成员函数不占对象空间,所有对象共享同一个函数
    Monster(const Monster &m)
    {
        m_monsterId = m.m_monsterId;
    }
    //成员函数不占对象空间,所有对象共享同一个函数
    ~Monster()
    {

    }

    //静态成员函数存储在全局静态区,不占对象空间
    static void setMonsterCounter(const int counter)
    {
        ms_counter = counter;
    }

    //静态成员函数存储在全局静态区,不占对象空间
    static int getMonsterCounter()
    {
        return ms_counter;
    }

    private:
    //静态成员变量存储在全局静态区,不占对象空间
    static int ms_counter; //计数

    //非静态成员变量,占对象空间
    int m_monsterId; //怪物id
};

int Monster::ms_counter = 0;

int main(int argc, char *argv[])
{
    cout << "sizeof(Monster) = " << sizeof(Monster) << endl;
    Monster m;
    cout << "sizeof(m) = " << sizeof(m) << endl;

    return 0;
}

在C++中,空类占用内存大小为一个字节,因为即使是一个空对象,也要分配存储空间来识别这个空对象,所以C++分配了一个字节大小的空间给每一个空对象。

#include <iostream>
using namespace std;

class Monster
{

};

int main(int argc, char *argv[])
{
    Monster m;

    cout << "空类占用内存大小为:" << sizeof(Monster) << endl;
    cout << "空对象占用内存大小为:" << sizeof(m) << endl;

    return 0;
}

C++中成员变量和成员函数是分开存储的,只有非静态成员才存储在类中或类的对象上。不管通过该类创建了多少对象,该类的每一个非静态成员函数只有一份,所有对象都同享非静态成员函数,那么这些非静态成员函数是如何区分是哪个对象调用自己的呢?C++通过this指针,解决这个问题。

this指针的特点:

  1. this指针隐含在每一个非静态成员函数内,每一个非静态成员函数都隐含有一个this指针。当对象调用非静态成员函数时,this指针指向该对象。

  1. this指针是是在非静态成员函数的开始前构造,并在非静态成员函数的结束后清除的,不需要声明,直接使用即可。

  1. this指针的本质是指针常量,存储了调用该非静态成员函数的对象的地址,所以this指针的指向是不可以修改的。

注意:这里我们一直强调:每一个非静态成员函数都隐含有一个this指针,而静态成员函数的函数体内只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数,所以静态成员函数是没有this指针的

this指针的作用:

  1. 在类的非静态成员函数中,形参和成员变量同名时,可以通过this指针来指明是对象的成员,还是形参名。

  1. 在类的非静态成员函数中需要返回该对象时,通过使用:return *this; 返回该对象自身。

#include <iostream>
using namespace std;

class Monster
{
    public:
    Monster():monsterId(0)
    {

    }
    
    //这里通过对象初始化列表的方式,是可以区分:monsterId是成员变量,(monsterId)是形参的
    //而且对象初始化列表中不能用:this->monsterId(monsterId)
    Monster(const int monsterId):monsterId(monsterId)
    {

    }
    Monster(const Monster &m):monsterId(m.monsterId)
    {

    }

    ~Monster()
    {

    }

    Monster& getSelf()
    {
        return *this; //返回对象本身
    }

    // static Monster& getSelfByStatic()
    // {
    //     return *this; //错误:非静态成员函数没有this指针
    // }

    void setMonsterId(const int monsterId)
    {
        this->monsterId = monsterId; //形参和成员变量同名,所以需要用this指针区分,不然编译器不知道monsterId是成员变量还是形参
    }

    int getMonsterId()
    {
        return monsterId;
    }

    private:
    int monsterId;
};

int main(int argc, char *argv[])
{
    Monster m(10001);
    cout << "怪物id = " << m.getMonsterId() << endl;
    m.setMonsterId(10002);
    cout << "怪物id = " << m.getSelf().getMonsterId() << endl;

    return 0;
}

注意:通过对象初始化列表的方式,是可以区分:monsterId是成员变量,(monsterId)是形参的,而且对象初始化列表中不能用:this->monsterId(monsterId),编译器会报错的

//这里通过对象初始化列表的方式,是可以区分:monsterId是成员变量,(monsterId)是形参的
//而且对象初始化列表中不能用:this->monsterId(monsterId),编译器会报错的
Monster(const int monsterId):monsterId(monsterId)
{

}

C++中是允许空指针调用成员函数的,这时this = NULL;所以在非静态成员函数中,如果用到this指针,应该在用到this指针前,加以判断this指针是否为NULL。但是在程序设计的过程中,我们应该尽量禁用用空指针调用成员函数,稍微不注意,可能会导致我们的程序出现崩掉的情况。

const修饰的类成员

  1. 常量成员变量:在成员变量前,用const修饰该的成员变量(eg:const int m_monsterId;)。常量成员变量只能在构造函数中用初始化列表进行初始化,并且初始化后不能再修改。

  1. 常量成员函数:在成员函数后加上const修饰的成员函数(eg:void getMonsterId() const),也叫常函数。常量成员函数不能修改普通的成员属性,但常量成员函数可以对有mutable修饰的成员属性进行修改。

常对象

const修饰的对象叫常对象,常对象只能调用常函数

#include <iostream>
using namespace std;

class Monster
{
    public:
    Monster():m_monsterId(0), m_name("怪物"), m_blood(500)
    {

    }
    Monster(const int monsterId, const string name, const int blood):m_monsterId(monsterId), 
                                                                     m_name(name), m_blood(blood)
    {

    }
    Monster(const Monster &m):m_monsterId(m.m_monsterId), m_name(m.m_name), m_blood(m.m_blood)
    {
        //m_name = m.m_name; //错误:常量成员属性只能用初始化列表进行初始化,这句调用编译器会报错
    }
    ~Monster()
    {

    }

    void setMonsterId(const int monsterId)
    {
        m_monsterId = monsterId;
    }
    int getMonsterId() const
    {
        //m_monsterId = 0; //错误:常函数不能修改普通成员变量的值
        return m_monsterId;
    }

    void setName(const string name)
    {
        //m_name = name; //错误:m_name是常量成员属性,不能修改
    }
    string getName() const
    {
        return m_name;
    }

    void setBlood(const int blood) const
    {
        m_blood = blood; //正确:常函数可以修改用mutable修饰的成员属性
    }
    int getBlood()
    {
        return m_blood;
    }

    int getCounter()
    {
        return ms_counter;
    }

    private:
    int m_monsterId; //怪物id
    const string m_name; //怪物名字  常量成员属性
    mutable int m_blood; //血量

    static int ms_counter;
};
int Monster::ms_counter = 0;

int main(int argc, char *argv[])
{
    Monster m1(10001, "雪女", 10000);
    cout << "怪物id = " << m1.getMonsterId() << ",怪物名字 = " << m1.getName() << ",怪物血量 = " << m1.getBlood() << endl;

    const Monster m2(20001, "紫衣仙子", 20000);
    cout << "怪物id = " << m2.getMonsterId() << endl; //正确:常对象可以调用常函数
    //m2.setMonsterId(20002); //错误:常对象不可以调用普通成员函数
    //m2.getCounter(); //错误:常对象不可以调用静态成员函数
    return 0;
}

类的静态(static)成员和常量(const)成员是两个不同的概念,我们不应该混淆

类的静态成员

  1. static成员变量

  1. 所有对象共享一份数据

  1. 在编译阶段分配内存

  1. 类内声明,类外初始化

  1. 有(public,protected,private)访问权限

  1. static成员函数(在成员函数前加 static)

  1. 所有对象共享一个函数

  1. 静态成员函数的函数体内只能访问静态成员变量或静态成员函数,不能访问非静态成员变量和非静态成员函数

  1. 有(public,protected,private)权限

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库基本功之复杂查询的子查询

子查询返回的值可以被外部查询使用,这样的复合查询等效与执行两个连续的查询. 1. 单行单列子查询 (>,<,,<>,>,<)内部SELECT子句只返回一行结果 2.多行单列子查询 (all, any, in,not in) all (>大于最大的,<小于最小的) SQL> select ename, sal from…

Linux创建并挂载NAS

1 目标 在Linux服务器1上创建nas服务器&#xff0c;并指定可读写目录在Linux服务器2上挂载上述nas磁盘在Linux服务器2上设置开机自动挂载nas磁盘 2 搭建环境 两台Linux系统服务器&#xff0c;如下&#xff1a; 服务器1 IP为192.168.31.101 服务器2 IP为192.168.31.102 3 在服…

自动化测试——selenium多浏览器处理

这里写目录标题一、背景二、pytes hook函数1、conftest.py2、测试用例3、执行测试用例一、背景 用户使用的浏览器(frefox,chrome,IE 等) web应用应该能在任何浏览器上正常的工作&#xff0c;这样能吸引更多的用户来使用。 是跨不同浏览器组合验证网站或web应用程序功能的过程 …

Python高频面试题——生成器(最通俗的讲解)

生成器定义在 Python 中&#xff0c;使用了 yield 的函数被称为生成器&#xff08;generator&#xff09;。跟普通函数不同的是&#xff0c;生成器是一个返回迭代器的函数&#xff0c;只能用于迭代操作&#xff0c;更简单点理解生成器就是一个迭代器。 在调用生成器运行的过程中…

Ubuntu系统开机自动挂载NTFS硬盘【超实用】

由于跑深度学习实验(图像分割)f非常消耗内存&#xff0c;系统盘sda1内存小&#xff0c;配置了一个大容量得出NTFS机械盘&#xff0c;网上招了一些资料如何挂在&#xff0c;但是每次开机得手动挂载一遍才能使用硬盘&#xff0c;非常不方便&#xff0c;还容易造成数据丢失。 Step…

Elasticsearch使用系列-ES增删查改基本操作+ik分词

一、安装可视化工具KibanaES是一个NoSql数据库应用。和其他数据库一样&#xff0c;我们为了方便操作查看它&#xff0c;需要安装一个可视化工具 Kibana。官网&#xff1a;https://www.elastic.co/cn/downloads/kibana和前面安装ES一样&#xff0c;选中对应的环境下载&#xff0…

如何通过C++ 将数据写入 Excel 工作表

直观的界面、出色的计算功能和图表工具&#xff0c;使Excel成为了最流行的个人计算机数据处理软件。在独立的数据包含的信息量太少&#xff0c;而过多的数据又难以理清头绪时&#xff0c;制作成表格是数据管理的最有效手段之一。这样不仅可以方便整理数据&#xff0c;还可以方便…

【Python】tqdm 模块

import mathfrom tqdm import tqdm, trange# 计算阶乘 results_1 []for i in range(6666):results_1.append(math.factorial(i))这是一个循环计算阶乘的程序&#xff0c;我们不知道程序运行的具体情况&#xff0c;如果能加上一个程序运行过程的进度条&#xff0c;那可就太有趣…

REG.EXE修改注册表-解决win10微软输入法默认中文,将其全局修改为英文

REG.EXE修改注册表-解决win10微软输入法默认中文&#xff0c;将其全局修改为英文 使用REG.EXE 可以直接强制修改注册表字段 修改注册表&#xff1a; REG.EXE ADD 注册表路径 /v 注册表项字段 /t 注册表字段类型 /d 注册表值 /f 例如&#xff1a; REG. EX ADD HKLM\System\C…

Activiti7

文章目录Activiti官网一、BPM二、BPM软件三、BPMN四、Activiti使用步骤1、部署activiti2、流程定义3、流程定义部署4、启动一个流程实例5、用户查询待办任务(Task)6、用户办理任务7、流程结束五、Activiti环境准备1、下载扩展程序camunda-modeler2、配置idea扩展程序&#xff1…

[2.2.2]进程调度的时机、方式、切换与过程

文章目录第二章 进程管理进程调度的时机、方式、切换与过程&#xff08;一&#xff09;进程调度的时机&#xff08;二&#xff09;进程调度的方式&#xff08;三&#xff09;进程的切换与过程小结第二章 进程管理 进程调度的时机、方式、切换与过程 时机 什么时候需要进程调度…

在 KubeSphere 中开启新一代云原生数仓 Databend

作者&#xff1a;尚卓燃&#xff08;https://github.com/PsiACE&#xff09;&#xff0c;Databend 研发工程师&#xff0c;Apache OpenDAL (Incubating) PPMC。 前言 Databend 是一款完全面向云对象存储的新一代云原生数据仓库&#xff0c;专为弹性和高效设计&#xff0c;为您…

导入你的 ST 项目到 Visual Studio

去年我们官宣了 Visual Studio Code 可以直接导入 ST 项目&#xff0c;今天再次宣布&#xff1a;它的好兄弟 Visual Studio 2022 17.6 也支持此功能&#xff0c;详细请看下文。 在 ARM 微控制器领域&#xff0c;有许多芯片供应商&#xff0c;其中最大的是意法半导体(ST)。ST 拥…

【冲刺蓝桥杯的最后30天】day6

大家好&#x1f603;&#xff0c;我是想要慢慢变得优秀的向阳&#x1f31e;同学&#x1f468;‍&#x1f4bb;&#xff0c;断更了整整一年&#xff0c;又开始恢复CSDN更新&#xff0c;从今天开始更新备战蓝桥30天系列&#xff0c;一共30天&#xff0c;如果对你有帮助或者正在备…

【魅力开源】第9集:管理者的大局观:企业数字化转型的能力逆向规划设计模型

文章目录前言一、背景&#xff1a;数字化转型机遇与挑战1.1 国家层面&#xff1a;数字化转型的背后代表的是“国家意志”1.2 企业层面&#xff1a;积极拥抱数字化转型&#xff0c;抢占的是“红利先机”1.3 个人层面&#xff1a;全民数字化时代到来&#xff0c;最为炙手可热的当…

CPDA|如何证明你的数据分析能力?

数据分析能力是一个很重要的能力&#xff0c;那么如何去证明这个能力呢&#xff1f; 一般来说&#xff0c;证明你的数据分析能力需要以实际的数据分析项目和成果为基础&#xff0c;可以从以下几个方面来证明&#xff1a; 项目经历&#xff1a;列举你参与的数据分析项目&#x…

Easy Deep Learning——PyTorch中的自动微分

目录 什么是深度学习&#xff1f;它的实现原理是怎么样的呢&#xff1f; 什么是梯度下降&#xff1f;梯度下降是怎么计算出最优解的&#xff1f; 什么是导数&#xff1f;求导对于深度学习来说有何意义&#xff1f; PyTorch 自动微分&#xff08;自动求导&#xff09; 为什么…

分享一个 hive on spark 模式下使用 HikariCP 数据库连接池造成的资源泄露问题

最近在针对某系统进行性能优化时&#xff0c;发现了一个hive on spark 模式下使用 HikariCP 数据库连接池造成的资源泄露问题&#xff0c;该问题具有普适性&#xff0c;故特地拿出来跟大家分享下。 1 问题描述 在微服务中&#xff0c;我们普遍会使用各种数据库连接池技术以加快…

二叉树,二叉搜索树相关模板

目录1.先序遍历2.中序遍历3.后序遍历4.层序遍历(可用于需按层进行计算的题目)5.判定二叉树的对称性6.二叉树最大深度&#xff08;结点深度&#xff1a;根节点到该结点。结点高度&#xff1a;该结点到叶子结点&#xff09;7.二叉树最小深度8.二叉树的平衡性9.求左叶子的和10.通过…

ArcGIS制图技巧:制图入门与点、线、面状符号制作

目的&#xff1a; 1、了解地图制作目的&#xff1b; 2、了解在ArcMap平台中制作地图大致过程。 3、掌握地形图生成的操作&#xff1b; 4、掌握地形图的正确输出方法。 5、理解点状符号、线状符号、面状符号的基本概念&#xff1b; 6、理解地形点状符号、线状符号、面状符…