C++中成员函数和变量的存储、this指针的使用和const关键词以及常对象

文章目录

      • 1.成员变量和成员函数分开存储
      • 2.this指针
        • 1.概念
        • 2.链式调用
      • 3.空指针调用成员函数
      • 4.const修饰成员函数
      • 5.常对象

1.成员变量和成员函数分开存储

  1. 成员变量

    • 成员变量是类的数据部分,它们存储了类的实例(对象)的状态信息。
    • 当创建一个类的实例时,会为该实例分配内存来存储其成员变量。这些变量通常存储在堆(对于动态分配的对象)或栈(对于局部或自动分配的对象)上。
    • 成员变量在内存中的存储位置是连续的(对于简单的数据类型),或者是指向实际数据的指针(对于复杂的数据类型,如字符串或动态数组)。
  2. 成员函数

    • 成员函数是类的行为部分,它们定义了对象可以执行的操作。
    • 与成员变量不同,成员函数本身并不存储在类的实例的内存中。相反,成员函数的代码(即函数体)存储在代码段(也称为文本段或文本区)中,这是程序的一部分,其中包含了程序的执行指令。
    • 当成员函数被调用时,它使用特定的机制(如this指针在C++中)来访问和修改对象的状态(即成员变量)。this指针是一个指向调用该成员函数的对象的指针,它允许成员函数知道它正在操作哪个对象。
#include <iostream>class MyClass {
public:// 成员变量int myInt;double myDouble;// 成员函数MyClass(int i, double d) : myInt(i), myDouble(d) {} // 构造函数void printValues() {std::cout << "myInt: " << myInt << ", myDouble: " << myDouble << std::endl;}
};int main() {// 创建MyClass的实例(对象)MyClass obj(10, 3.14);// 调用成员函数obj.printValues();return 0;
}

在这个示例中:

  • MyClass 是一个类,它有两个成员变量 myIntmyDouble,以及一个成员函数 printValues
  • main 函数中,我们创建了一个 MyClass 的实例 obj,并通过构造函数初始化了它的成员变量。
  • 当我们调用 obj.printValues() 时,成员函数 printValues 被执行。尽管我们调用的是 obj 的方法,但 printValues 函数的代码本身并不存储在 obj 所占用的内存中。相反,它的代码存储在程序的代码段中。

从内存的角度来看:

  • obj 的成员变量 myIntmyDouble 会被分配在栈上(如果 obj 是在 main 函数中局部声明的)或者在堆上(如果 obj 是通过 new 运算符动态分配的)。
  • 成员函数 printValues 的代码则存储在程序的代码段(或文本段)中,这是程序在加载到内存时就已经确定好的。

printValues 被调用时,它会通过 this 指针(尽管在上面的示例中没有显式使用)知道它正在操作哪个对象(即 obj),并可以访问和修改该对象的成员变量。

注意:空对象占用的内存空间大小为:1。非静态成员变量,属于类的对象上。静态成员变量,不属于类的对象上边。

2.this指针

1.概念

在C++中,this指针是一个隐式的指针,它指向调用成员函数的对象本身。当一个成员函数被调用时,编译器会自动将调用该函数的对象的地址赋值给this指针。this指针主要用于以下目的:

  1. 区分成员变量和局部变量:当成员变量和函数参数或局部变量重名时,this->前缀可以用来明确指出引用的是成员变量。

  2. 返回对象本身的引用:在成员函数中,this指针可以被返回以获取对调用对象的引用。

  3. 用于链式调用:一些成员函数返回*this的引用,允许链式调用多个成员函数。

  4. 在构造函数中初始化其他成员:在构造函数中,可以使用this指针来区分成员变量和构造函数参数。

下面是一个使用this指针的示例:

#include <iostream>
#include <string>class Person {
private:std::string name;int age;public:Person(std::string name, int age) : name(name), age(age) {}// 使用this指针来区分成员变量和参数void setName(std::string name) {this->name = name; // 这里的this->name指的是类的成员变量}void setAge(int age) {this->age = age; // 这里的this->age也是类的成员变量}// 返回一个指向调用对象的引用,用于链式调用Person& printAndModifyName(const std::string& newName) {std::cout << "Original name: " << this->name << std::endl;this->name = newName;std::cout << "Modified name: " << this->name << std::endl;return *this; // 返回当前对象的引用}void printInfo() const {std::cout << "Name: " << this->name << ", Age: " << this->age << std::endl;}
};int main() {Person p("Alice", 30);p.printInfo(); // 输出原始信息// 链式调用printAndModifyName和printInfop.printAndModifyName("Bob").printInfo(); // 输出修改后的信息return 0;
}

在这个例子中,setNamesetAge成员函数使用this指针来区分成员变量和参数。printAndModifyName成员函数返回*this的引用,允许链式调用。printInfo成员函数是一个常量成员函数,它使用this指针(尽管在这个特定的例子中并不需要显式使用this,因为编译器会自动处理)来访问成员变量。

尽管this指针在成员函数中经常被隐式使用,但在某些情况下,如需要显式区分成员变量和参数,或者需要返回对象的引用以进行链式调用时,你可以使用this指针。然而,在大多数情况下,你不需要(也不应该)在代码中显式地写出this指针。

2.链式调用

链式调用(Chaining)是一种在面向对象编程中常用的技术,它允许在单个语句中连续调用同一个对象的多个方法。为了支持链式调用,这些方法通常返回对调用对象的引用(通常是*this)。

#include <iostream>
#include <string>class Person {
private:std::string name;int age;public:Person(std::string name, int age) : name(name), age(age) {}// 设置名字,并返回对当前对象的引用以支持链式调用Person& setName(const std::string& newName) {name = newName;return *this; // 返回当前对象的引用}// 设置年龄,并返回对当前对象的引用以支持链式调用Person& setAge(int newAge) {age = newAge;return *this; // 返回当前对象的引用}// 打印信息void printInfo() const {std::cout << "Name: " << name << ", Age: " << age << std::endl;}
};int main() {Person p("Alice", 30);// 链式调用 setName 和 setAge 方法p.setName("Bob").setAge(40);// 打印信息p.printInfo(); // 输出:Name: Bob, Age: 40return 0;
}

在这个示例中,setNamesetAge 方法都返回 *this,即当前对象的引用。这样,我们就可以在单个语句中连续调用这两个方法,实现链式调用。最后,我们调用 printInfo 方法来打印修改后的信息。

链式调用可以使代码更加简洁和易读,特别是在需要连续设置多个属性或执行多个操作时。但是,过度使用链式调用也可能导致代码难以理解和维护,因此应该根据具体情况谨慎使用。

3.空指针调用成员函数

在C++中,尝试通过空指针(nullptrNULL)调用成员函数通常是未定义行为(Undefined Behavior, UB),但这是因为对成员函数本身的调用并不直接通过指针解引用。然而,如果成员函数内部试图访问或修改对象的成员变量(即使用this指针),并且该指针是空的,那么这会导致运行时错误或未定义行为。

成员函数并不存储在对象的内存中,而是存储在代码段中。当通过对象指针调用成员函数时,编译器实际上是在函数内部隐式地传递一个指向对象的指针(即this指针)。但是,如果传递的是一个空指针,那么在函数内部使用该指针来访问成员变量就会是危险的。

虽然很多编译器在通过空指针调用成员函数时可能不会立即报错(特别是当函数不实际使用this指针时),但这并不意味着这是安全的或可移植的。未定义行为意味着程序可能崩溃、产生不正确的结果或表现出其他不可预测的行为。

下面是一个示例,展示了通过空指针调用成员函数并尝试访问成员变量时可能发生的问题:

#include <iostream>class MyClass {
public:int value = 42;void printValue() {std::cout << "Value: " << this->value << std::endl; // 如果this是空指针,这里会崩溃}
};int main() {MyClass* ptr = nullptr;ptr->printValue(); // 这是未定义行为,可能会导致程序崩溃return 0;
}

在这个例子中,printValue成员函数试图通过this指针访问成员变量value。但是,因为ptr是一个空指针,所以this指针也是空的,这会导致尝试访问无效的内存地址,从而可能导致程序崩溃。

为了避免这种情况,你应该始终确保在调用成员函数之前,对象指针不是空的。可以通过检查指针是否为空来避免这种错误:

if (ptr != nullptr) {ptr->printValue();
} else {std::cout << "Pointer is null!" << std::endl;
}

4.const修饰成员函数

在C++中,使用const修饰成员函数表示该函数不会修改其所属对象的任何非静态成员变量(除非这些成员变量被声明为mutable)。这允许我们在常对象上调用这些函数,因为编译器知道这些函数不会改变对象的状态。

  1. 声明:在成员函数的声明后添加const关键字,以表示该函数是常量成员函数。

    class MyClass {
    public:int value;// 这是一个常量成员函数int getValue() const { return value; }// 这是一个非常量成员函数void setValue(int v) { value = v; }
    };
    
  2. 调用:常量成员函数可以在常对象或非常对象上调用,而非常量成员函数只能在非常对象上调用。

    int main() {MyClass obj;obj.setValue(10); // 正确:非常对象上调用非常量成员函数std::cout << obj.getValue() << std::endl; // 正确:非常对象上调用常量成员函数const MyClass constObj;// constObj.setValue(20); // 错误:不能在常对象上调用非常量成员函数std::cout << constObj.getValue() << std::endl; // 正确:常对象上调用常量成员函数return 0;
    }
    
  3. 函数体中的限制:在常量成员函数的函数体中,你不能修改任何非静态成员变量的值(除非它们被声明为mutable)。如果尝试这样做,编译器会报错。

    class MyClass {
    public:int value;// 错误:尝试在常量成员函数中修改非静态成员变量void someFunction() const {value = 10; // 错误:不能修改成员变量在常量成员函数中}
    };
    
  4. 重载与const成员函数:你可以根据成员函数是否带const来重载它。这意味着你可以有一个非const版本的成员函数和一个const版本的成员函数,它们具有相同的名称和参数列表,但一个带const而另一个不带。

    class MyClass {
    public:int value;int* getValuePtr() { return &value; } // 非常量版本const int* getValuePtr() const { return &value; } // 常量版本
    };
    
  5. this指针的const版本:在常量成员函数中,this指针的类型是指向常量对象的指针(即const MyClass* const this)。这确保了成员函数不能通过this指针来修改对象的状态。

  6. mutable关键字:尽管const成员函数通常不会修改其所属对象的状态,但某些情况下可能需要在const成员函数中修改某个成员变量。为此,可以使用mutable关键字来修饰该成员变量,这样即使在const成员函数中也可以修改它。

    class MyClass {
    public:mutable int mutableValue; // 可以在const成员函数中修改void someConstFunction() const {mutableValue = 10; // 正确:可以在const成员函数中修改mutable成员变量}
    };
    

5.常对象

常对象在C++中是一个特殊的对象,它是指用const修饰符声明的对象。常对象具有以下特性和限制:

  1. 定义

    • 常对象在声明时必须进行初始化,因为它不能被修改。
    • 定义格式通常如下:const 类名 对象名; 或者 类名 const 对象名;
  2. 特性

    • 不可修改:常对象的数据成员在对象创建后不能被更新。
    • 函数调用限制:常对象只能调用类的常成员函数(即成员函数声明中包含const关键字的函数)。这是因为非const成员函数可能会尝试修改对象的状态,而常对象是不允许修改的。
  3. 初始化

    • 常对象必须在定义时进行初始化,否则编译器会报错。
  4. 语法示例

    class MyClass {
    public:int value;void setValue(int v) { value = v; } // 非const成员函数void printValue() const { std::cout << "Value: " << value << std::endl; } // const成员函数
    };int main() {const MyClass obj1{42}; // 常对象,初始化时设置value为42// obj1.setValue(100); // 错误:不能调用非const成员函数来修改常对象obj1.printValue(); // 正确:可以调用const成员函数MyClass obj2{50}; // 非常对象obj2.setValue(100); // 正确:可以修改非常对象obj2.printValue(); // 正确:可以调用const成员函数return 0;
    }
    
  5. 注意事项

    • 当尝试在常对象上调用非const成员函数时,编译器会报错,因为这违反了常对象的不可修改性。
    • 类的常成员函数可以访问常对象和非常对象的成员(包括数据成员和成员函数),但是它们不能修改任何非静态成员变量(除非这些变量被声明为mutable)。
  6. 总结

    • 常对象是一种特殊的对象,它在整个生命周期中都是只读的,不能被修改。
    • 常对象只能调用类的常成员函数,这是为了确保对象的不可修改性。
    • 常对象在定义时必须进行初始化,并且之后不能被重新赋值。

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

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

相关文章

Qt图标字体文件中提取字体保存为图片

本文借用别人写的一个IconHelper来做说明。 1. 加载一个字体文件 QScopedPointer<IconHelper> iconHelper(new IconHelper(":/fa-regular-400.ttf", "Font Awesome 6 Pro Regular"));构造函数 IconHelper::IconHelper(const QString &fontFile…

【基础】LwM2M 通讯协议

【基础】LwM2M 通讯协议 LwM2M 协议基础LwM2M 简介LwM2M 基本架构LwM2M 资源定义 LwM2M 协议实现开源协议实现Java LwM2M Client LwM2M 协议基础 LwM2M 简介 LwM2M 的全称为 Lightweight Machine-To-Machine&#xff0c;是一种适用于物联网设备的轻量级的通讯协议&#xff0c…

Flutter基础 -- Flutter布局练习(小项目)

目录 1. Splash 布局&#xff08;第一页&#xff09; 1.1 目标 1.2 当前效果图 1.3 创建 Splash 界面 1.4 设置 MaterialApp 1.5 设置 Splash 背景色 1.6 布局 Splash 界面 1.7 总结 2. Splash 圆角图片 2.1 目标 2.2 当前效果图 2.3 蓝湖下载图片 2.4 图片导入项…

在编程Python的时候发生ModuleNotFoundError: No module named distutils报错怎么办

1.先查看Python版本 首先我们先去打开终端就是先widr再输入cmd 然后进去在输入Python -V要注意大小写 我的版本是3.9.7版本但是我使用的PyCharm 是 2021.1.1 x64版本没有办法主动去识别因为这个版太低了你的Python版本很高所以无法识别 2.解决方法 只需要把你的Python现版…

微信公众号【原子与分子模拟】: 熔化温度 + 超导电性 + 电子化合物 + 分子动力学模拟 + 第一性原理计算 + 数据处理程序

往期内容主要涵盖&#xff1a; 熔化温度 超导电性 电子化合物 分子动力学模拟 第一性原理计算 数据处理程序 【1】熔化温度 分子动力学 LAMMPS 相关内容 【文献分享】分子动力学模拟 LAMMPS 熔化温度 晶体缺陷 熔化方法 LAMMPS 文献&#xff1a;金属熔化行为的局域…

后端返回图片格式乱码

try {const response await request.get(checkCodeUrl.value,{responseType:"arraybuffer"});console.log("验证码请求成功:", response);checkCodeUrl.value data: image/jpeg;base64,${btoa(new Uint8Array(response).reduce((data, byte) > data …

『大模型笔记』大型语言模型(LLMs)微调(Fine-tuning)优化研究!

大型语言模型(LLMs)微调(Fine-tuning)优化研究! 文章目录 一. 摘要二. 大模型优化技术概述2.1 梯度检查点(Gradient Checkpointing)2.2 低秩适应2.3 DeepSpeed2.4 Flash Attention三. GPU内存需求的理论分析3.1. 模型状态内存(Model states memory) - 模型参数、梯度、优化器状…

vscode ctrl+鼠标左键无法跳转

打开设置&#xff0c;搜索intel…… 将这个智能感知改成default就可以了&#xff0c;我之前是在disable处。 分析了一下&#xff0c;其实跳转功能主要是根据上下文语法分析来实现的&#xff0c;并不是简单得全文匹配&#xff0c;因此需要相关得语法分析工具。 那么为什么默认式…

微软Edge浏览器深度解析:功能、同步、隐私与安全

微软Edge浏览器是微软公司开发的一款网页浏览器,它基于Chromium内核,提供了快速、安全和兼容性良好的网页浏览体验。以下是关于微软Edge浏览器的详细信息和使用指南: 微软Edge浏览器的主要特点: 1. 基于Chromium内核: 渲染引擎:Chromium内核是基于开源项目Blink的,它…

可视化数据科学平台在信贷领域应用系列五:零代码可视化建模

信贷风控模型是金融机构风险管理的核心工具&#xff0c;在信贷风险管理工作中扮演着至关重要的角色。随着信贷市场的环境不断变化&#xff0c;信贷业务的风险日趋复杂化和隐蔽化&#xff0c;开发和应用准确高效的信贷风控模型显得尤为重要。信贷风险控制面临着越来越大的挑战和…

问你为什么选择Kafka,你会怎么回答?

可靠的含义在百度百科的解释是&#xff1a;可以信赖、可以相信、可靠的朋友。那Kafka究竟是不是一个可靠的朋友呢&#xff1f;既然全世界绝大部分高可用系统都有Kafka的支持&#xff0c;Kafka必定有其过人之处&#xff0c;跟着我来分析分析。 另外多提一嘴Kafka在GitHub目前已…

六西格玛培训,带你解锁职场超能力工具!

当提及六西格玛培训的精髓时&#xff0c;我们不得不提到那些强大而实用的工具&#xff0c;它们如同探险者的指南针&#xff0c;引导我们走向卓越。今天&#xff0c;就让我们一起揭开这些神秘工具的面纱&#xff0c;探寻六西格玛背后的智慧。 首先&#xff0c;DMAIC流程是六西格…

python小游戏:猜数字、猜动物、单词接龙(带界面)

正在学习python的各位童鞋&#xff0c;可以多多找些程序来练练手&#xff0c;从而更快的掌握python编程。这里就为大家找了三个示例小程序&#xff1a;猜数字、猜动物、单词接龙。 一、猜数字 程序会随机生成一个1到100之间的数字&#xff0c;然后让用户尝试猜测这个数字。用户…

cesium 漫游

token记得换成您自己的&#xff01;&#xff01;&#xff01; 申请cesium的token 官网【Cesium: The Platform for 3D Geospatial】 <template><div id"cesiumContatiner"><!-- <div id"mapContainer1"></div> --></di…

如何理解敏捷开发和瀑布模型的区别

敏捷开发和瀑布模型是两种不同的软件开发方法&#xff0c;它们在多个方面存在显著的差异。以下是它们之间的主要区别&#xff1a; 开发流程&#xff1a; 瀑布模型&#xff1a;采用线性的开发流程&#xff0c;按照预先规划的顺序依次进行需求分析、设计、编码、测试和维护等环节…

【蒙特卡洛仿真的corner】

蒙特卡洛仿真的corner global variation指的是不同晶圆之间的process的差别 local variation指的是同一个晶圆内的不同管子之间的mismatch smic40ll工艺中&#xff0c;ttg&#xff0c;ssg&#xff0c;sfg&#xff0c;fsg corner只包含mismatch ss&#xff0c;tt&#xff0c;sf&…

员工离职删除自己做的文件违法么?如何杜绝这种现象?

员工离职时删除自己做的文件是否违法&#xff0c;需要视情况而定&#xff1a; 如果删除的是个人自己的文件&#xff1a; 在这种情况下&#xff0c;员工删除的是自己制作的、不涉及公司机密或经营数据的个人文件&#xff0c;通常不会被视为违法行为。 如果删除的是公司的文件…

探索 CSV 模块:Python 中 CSV 数据持久化的最佳实践

&#x1f340; 前言 博客地址&#xff1a; CSDN&#xff1a;https://blog.csdn.net/powerbiubiu &#x1f44b; 简介 本章节介绍使用 CSV 文件来存储数据&#xff0c;CSV 文件是一种常见的数据格式&#xff0c;可以用来存储和交换表格数据。CSV 文件由一系列的行组成&#x…

SQL Developer管理RESTful 服务

RESTful 服务依赖于ORDS&#xff08;Oracle REST Data Services&#xff09;&#xff0c;所以在进行本实验前&#xff0c;请先确认数据库服务器上的ORDS服务已启动&#xff1a; $ systemctl status ords ● ords.service - Oracle REST Data ServicesLoaded: loaded (/etc/sys…

小程序canvas的同层渲染 这个属性不加,就不会生效!

做微信小程序的时候&#xff0c;发现vant-weapp的图表相关的vant再实机上怎么用都有问题&#xff08;同层渲染失败&#xff09;。 看了官方文档结果说了半天一点用都没有&#xff0c;官方原话是&#xff1a;当前所有原生组件&#xff08;除 input 组件 focus 状态&#xff09;均…