C++面向对象:const的使用

目录

常变量

常量指针与指针常量

常量与引用

常量返回值

常数据成员


常变量

 在C++中,推荐使用const对象或 enum class 定义常量,而不使用#define预处理器。

(1)类型安全

     #define宏定义只是一个简单的文本替换,不携带任何类型信息。例如,#define PI 3.14,在编译时PI将被替换为3.14,但它不具备类型,如果误用可能导致类型不匹配的问题。相反,使用const对象如`const double PI = 3.14。编译器会检查PI的类型,确保在使用时类型一致。

(2)作用域控制和可见性规则
      宏定义在整个预处理范围内都是可见的,容易造成命名冲突。而const对象或枚举成员在作用域内定义,只在该作用域内可见,有助于减少命名冲突。 
(3)健壮性
      const对象在编译期间就能检测出错误的使用方式,例如尝试修改const对象的值。而宏定义在编译阶段无法进行这种类型和作用域的检查,错误可能直到运行时才显现出来。

     在C语言中,全局作用域下的const变量默认具有外部链接属性,可以在不同文件中共享。而在C++中,全局const变量默认为内部链接,若要在不同文件间共享,需要显式声明为extern。

  在C++中,局部作用域常内的const变量会被优化为编译时量,编译器不会为其分配独立的存储空间,而是直接将值嵌入到指令流中。而对于全局作用域下的const对象,默认情况下具备内部链接属性,这意味着在同一编译单元内它是可见的,而在不同编译单元中若未明确声明为extern,则会分别创建各自的实例,导致重复定义错误。若需在不同文件间共享全局const对象,应在声明时添加extern关键字,使其具有外部链接属性。

      C++中对于const变量是否分配存储空间,确实取决于上下文。当const变量具有外部链接属性(通过extern声明),或者对其取地址时,编译器会为其分配存储空间。否则,如果编译器能够确定其值在编译期即可知且不需要在运行时改变,则可能不分配存储空间。

常量指针与指针常量

常量指针(const int *m1  或 int const *m1 )表示指针所指向的数据内容是不可修改的,但指针自身可以改变指向其它内存区域。
指针常量(int* const m2)表示指针自身是不可修改的,即始终指向同一块内存区域,但可以通过该指针修改其指向内存区域的内容。

记忆:左定值,右定向。

    在函数参数中,使用指针常量可以限制实参在函数内部改变指向;而使用常量指针则限制在函数内部通过该指针修改实参的值。

func函数展示了即使传入的参数是常量指针,依然可以通过类型转换创建一个新的指针并修改原始数据。虽然这种做法在语法上正确,但实际上破坏了常量指针传递时希望保护数据不变的意图。因此,在编程实践中应遵循“const”的语义约定,尽量避免意外修改不应变动的数据。 

常量与引用

   将函数传入参数声明为const,以指明使用这种参数不仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。
    通常修饰指针参数和引用参数:
void Fun( const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数

      如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。所以,对于值传递来说,加const没有太多意义。

     对于非内置类型(例如自定义的类或结构体),它们通常包含多个数据成员,构造、拷贝和析构过程可能会涉及复杂的操作,尤其是在类中包含指针、动态分配的内存或其他资源时,拷贝的成本会相对较高。当我们作为函数参数传递这样的非内置类型时,如果不采用引用传递,而是采取值传递的方式,编译器会自动创建原对象的一个副本,这个过程可能会消耗更多的时间和内存资源。

    因此,为了提高效率并减少不必要的开销,建议在函数参数设计时,对于非内置类型使用const引用传递。这样做可以避免创建对象副本,而是直接通过引用操作原有对象,既保留了原对象的不变性(通过const保证),又能有效减少拷贝带来的性能损失。

    对于内置类型(如int、double等),因其拷贝成本较低,通常不需要使用const引用传递。直接使用值传递方式即可,如void Func(int x),改写为void Func(const int& x)并不能带来性能提升,反而可能会降低代码的可读性和直观性,增加维护难度。

常量成员函数

   在C++中,常量函数是一种用于增强类封装性的机制,通过在成员函数声明末尾添加const关键字,可以将其定义为常量成员函数。使用常量函数有助于提高代码安全性,限制无意义或潜在危险的对对象状态的修改。

  常量成员函数的关键特征是编译器禁止其修改类的任何非mutable数据成员,确保了通过常量对象调用这类函数时不会改变对象的状态。

  • 常量对象只能调用常量成员函数,非常量对象既可以调用常量成员函数,也能调用非常量成员函数。这是因为常量对象的this指针实际上是对象的常量指针,故不能通过常量成员函数修改对象的非mutable成员。

  • C++引入了mutable关键字,用于标记某些数据成员即使在常量成员函数中也可以被修改。这对于那些不影响对象外部状态,但在内部需要变化的成员变量特别有用,如缓存或其他内部计算结果等。

  • 当存在同名同参数及返回类型的常量成员函数和非常量成员函数时,调用哪个版本的函数取决于调用上下文:如果对象是常量,则调用常量版本;如果是非常量对象,则调用非常量版本。

#include <iostream>
using namespace std;
class Point{public :Point(int _x):x(_x){}void testConstFunction(int _x) const{///错误,在const成员函数中,不能修改任何类成员变量x=_x;///错误,const成员函数不能调用非onst成员函数,因为非const成员函数可以会修改成员变量modify_x(_x);}void modify_x(int _x){x=_x;}int x;
};

常成员函数和非常成员函数之间的重载

常成员函数声明:<类型标志符>函数名(参数表)const;

说明:

(1)const是函数类型的一部分,在实现部分也要带该关键字。

(2)const关键字可以用于对重载函数的区分。

(3)常成员函数不能更新类的成员变量,也不能调用该类中没有用const修饰的成员函数,只能调用常成员函数。

(4)非常量对象也可以调用常成员函数,但是如果有重载的非常成员函数则会调用非常成员函数。 在C++中,只有被声明为const的成员函数才能被一个const类对象调用。

#include<iostream>  
using namespace std;  class Test  
{  
protected:  int x;  
public:  Test (int i):x(i) { }  void fun() const  {  cout << "fun() const called " << endl;  }  void fun()  {  cout << "fun() called " << endl;  }  
};  int main()  
{  Test t1 (10);  const Test t2 (20);  t1.fun();  t2.fun();  return 0;  
}

常量返回值

     很多时候,函数中会返回一个地址或者引用。调用这得到这个返回的地址或者引用后就可以修改所指向或者代表的对象。这个时候如果我们不希望这个函数的调用这修改这个返回的内容,就应该返回一个常量。可以阻止用户修改返回值,返回值也要相应的付给一个常量或常指针。

     返回值为const的主要目的有两个:一是确保返回的对象不被意外修改,维持数据一致性;二是传达设计意图,告知使用者函数返回的是一个只读结果,不应对其进行修改。在运算符重载等场景下,通过返回const对象,可以有效防止产生临时对象后又被随意修改的问题。

   当返回值类型为const A,如const A fun2(),表示函数返回的是一个常量对象。调用该函数后,得到的结果对象是不可修改的,任何尝试修改该对象成员变量的操作都将导致编译错误。用户不能执行类似result.fun2().mutate();这样的操作,如果mutate()是一个会修改对象状态的成员函数。

   当返回值类型为const A*,如const A* fun3(),这意味着返回的是一个指向A对象的常量指针。调用该函数后,虽然可以通过指针访问对象,但不能通过该指针去修改对象的任何属性。例如,(fun3())->setValue(newValue);这样的代码会导致编译错误。const 修饰返回的指针或者引用,是否返回一个指向const的指针,取决于我们想让用户干什么。

    在运算符重载函数中,比如重载乘法操作符const Rational operator*(const Rational& lhs, const Rational& rhs),返回值被声明为const Rational,是因为乘法运算的结果通常是新的Rational对象,我们期望这个新产生的对象是不可变的。这样可以防止后续的赋值操作,例如(a * b) = c;,因为 (a * b) 返回的是一个const对象,不能作为左值进行赋值。const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。

 
[思考]这样定义赋值操作符重载函数可以吗? 
const A& operator=(const A& a);

       在C++中,通常赋值操作符=(operator=)的重载形式不带有返回值为const引用。正常的赋值操作符重载函数定义应如下:

A& A::operator=(const A& a) {// 实现赋值操作的具体逻辑,比如逐个成员赋值,或者深拷贝等// ...return *this;  // 返回*this是为了支持连续赋值,如 a = b = c;
}

    这里的返回值类型是A的非const引用,而非const引用。返回*this主要是为了支持连续赋值操作,同时也表明该操作符修改了左侧对象的状态。

常数据成员

   在C++中,当const关键字用来修饰类的数据成员时,它表示这些成员在对象生存期内是不可修改的。对于同一个类的不同对象,const数据成员的值可以不同,因为每个对象都有自己独立的const数据成员副本。

class MyClass {
public:MyClass(int initValue) : myConstMember(initValue) {} // 在构造函数初始化列表中初始化const数据成员
private:const int myConstMember; // 类的数据成员被声明为const
};

      const数据成员不能在类的声明部分直接初始化,因为在创建类的对象之前,编译器无法得知其确切的初始化值。因此,我们必须在类的构造函数初始化列表中对其进行初始化。

      关于在整个类中保持不变的常量,可以使用类内枚举常量来实现。枚举常量在编译时就被赋予了固定的值,它们不占用对象的存储空间,适用于定义一些固定数值(如数组大小等),但要注意枚举常量的隐式类型为整型,且所能表示的最大值有限制,无法直接表示浮点数。

class A {
public:A() {}
private:enum { size1 = 100, size2 = 200 }; // 枚举常量在整个类中都是恒定的int array1[size1]; // 使用枚举常量定义数组大小int array2[size2];
};

      这样定义的枚举常量size1和size2在整个程序中被视为固定值,不会随类的对象不同而变化,且不占据对象的存储空间。

    如果有个成员函数想修改对象中的某一个成员怎么办?这时我们可以使用mutable关键字修饰这个成员,mutable的意思也是易变的,容易改变的意思,被mutable关键字修饰的成员可以处于不断变化中。

const数据成员的初始化

    在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。const数据成成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据员的值是什么。

要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static const。

const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不释放其存储空间。static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。

  

  


 

 

 

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

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

相关文章

53、简述GCN、NIR、FMIR技术在脑机BCI的发展调查[什么?你咋也叫王富贵?]

最近在搞GCN处理EEG&#xff0c;调查了十几篇文献&#xff0c;总结了一些东西&#xff0c;和学生分享一下&#xff0c;此处只分享一些较为浅显的知识。如下&#xff1a; GCN在其他领域的应用&#xff1a; 1、计算机视觉&#xff1a; 图卷积神经网络在计算机视觉中的应用包括图…

好用的客服快捷回复软件推荐

在当今快节奏的商业环境中&#xff0c;客户服务的效率和质量已经成为企业成功的关键因素之一。对于客服工作人员来说&#xff0c;面对海量的客户咨询和问题解答&#xff0c;如何快速而准确地回复&#xff0c;成为了他们日常工作中的一大挑战。选择一款好用的快捷回复工具是非常…

刷题DAY30 | LeetCode 332-重新安排行程 51-N皇后 37-解数独

332 重新安排行程&#xff08;hard&#xff09; 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&…

SSC9211_USB-CAM解决方案

一、方案描述 SSC9211是一种用于USB-CAM应用程序跟场景的高度集成的SOC产品。平台本身基于ARM层-A7双核&#xff0c;内置16位&#xff0c;64M的DDR2&#xff0c;集成了图像传感器接口、高级ISP、高性能JPEG编码器和其他丰富的外设接口。支持单&#xff0c;双 MIPI sensor方案&…

目标检测——植物病害数据集

植物病害是植物正常状态的偏离&#xff0c;会破坏或改变其生命功能。植物病害会导致严重的产量损失&#xff0c;全球潜在损失估计高达16%。因此&#xff0c;研究植物病害以及开发诊断和治疗它们的方法是植物病理学领域的重要研究内容。 有效识别植物病害对于采取有效的控制措施…

Go语言学习Day1:什么是Go?

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、走近Go①Go语言的Logo②Go语言的创始人③Go语…

八大排序算法之希尔排序

希尔排序是插入排序的进阶版本&#xff0c;他多次调用插入排序&#xff0c;在插入排序上进行了改造&#xff0c;使其处理无序的数据时候更快 核心思想&#xff1a;1.分组 2.直接插入排序&#xff1a;越有序越快 算法思想&#xff1a; 间隔式分组&#xff0c;利用直接插入排序…

【Python】Python中装饰器和魔法方法的区别

在Python中&#xff0c;装饰器&#xff08;Decorators&#xff09;和魔法方法&#xff08;Magic Methods&#xff09;是两种不同的高级特性&#xff0c;分别服务于不同的目的。 装饰器 (Decorators) 装饰器是一种强大的工具&#xff0c;它可以修改或增强函数、方法或类的行为…

IoT物联网可以带来什么?——青创智通

工业物联网解决方案-工业IOT-青创智通 随着科技的飞速发展&#xff0c;IoT物联网已逐渐渗透到我们生活的方方面面&#xff0c;它以其独特的方式&#xff0c;将各种设备、系统和人连接起来&#xff0c;为我们带来了前所未有的便利和惊喜。那么&#xff0c;IoT物联网究竟可以为我…

linux下docker容器的使用

1、根据已有镜像images创建容器 1.1、查看镜像 如果是接手的别人的项目&#xff0c;需要从以往的images镜像中创建新容器&#xff0c;使用命令查看当前机器上的docker镜像&#xff1a; docker images1.2、创建容器 使用docker run 根据images镜像名创建容器&#xff0c;命令…

江南布衣的新商业主义

全球正经历一次商业伦理迭代&#xff0c;从以效率、创新、竞争、公平交易、优胜劣汰等为关键词的旧商业主义&#xff0c;转向商业主义和社会主义兼顾的新商业主义。 联合国全球契约组织于2004年提出的ESG正是这一商业伦理转向的产物&#xff0c;与传统以利润为企业考核核心指标…

Android 静默安装二(无障碍服务版)

近期开发上线一个常驻app&#xff0c;项目已上线&#xff0c;今天随笔记录一下静默安装相关内容。我分三篇静默安装&#xff08;root版&#xff09;、静默安装&#xff08;无障碍版&#xff09;、监听系统更新、卸载、安装。 先说说我的项目需求&#xff1a;要求app一直运行&am…

数字科技优化金融供给,内外协同激活新质生产力

来源 | 镭射财经&#xff08;leishecaijing&#xff09; 新一轮产业变革悄然发生&#xff0c;决定产业高度和竞争格局的底层生产力&#xff0c;也正在经历一场从量变到质变的跃迁。新质生产力则是这场跃迁后的最新呈现。 站在新质生产力爆发的时代拐点&#xff0c;金融业达成…

【鸿蒙HarmonyOS开发笔记】组件编程技巧之样式复用

样式复用 概述 当多个组件具有相同的样式时&#xff0c;若每个组件都单独设置&#xff0c;将会有大量的重复代码。为避免重复代码&#xff0c;开发者可使用Styles或者Extend装饰器将多条样式设置提炼成一个方法&#xff0c;然后直接在各组件声明的位置进行调用&#xff0c;这…

中国贸易金融跨行交易区块链平台CTFU、区块链福费廷交易平台BCFT、中国人民银行贸易金融区块链平台CTFP、银行函证区块链服务平台BPBC

中国人民银行贸易金融区块链平台CTFP介绍 贸易金融的发展概况及存在的问题 1.1 贸易金融的概况 贸易金融是指商业银行在贸易双方债权债务关系的基础上&#xff0c;为国内或跨国的商品和服务贸易提供的贯穿贸易活动整个价值链、全程全面性的综合金融服务。伴随全球化的进程&a…

Docker安装配置

1. 安装docker-ce sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum -y install docker-ce sudo systemctl enable docker 2. 设置代理 参照&#xff1a;https://docs.docker.com/config/daemon/systemd/#httpht…

基于yolov5的单目测距实现与总结+相机模型+标定

写这篇文章的目的是为了总结我之前看的标定&#xff0c;相机模型以及单目测距的内容&#xff0c;如果有错误&#xff0c;还请不吝赐教。 参考链接&#xff1a; 相机模型、相机标定及基于yolov5的单目测距实现 深度学习目标检测目标追踪单目测距 单目测距代码部署&#xff08;目…

深度学习入门:pytorch基础学习、各模块解析、调优技巧和问题结局

整理了一下之前写的深度学习基础知识文章&#xff0c;方便浏览&#xff01; 1. pytorch基础学习系列文章&#xff0c;里面代码和示例 《PyTorch深度学习实践》05 用PyTorch实现线性回归 《PyTorch深度学习实践》06 用PyTorch实现Logistic回归 《PyTorch深度学习实践》07加载数…

【Flask开发实战】防火墙配置文件解析(二)之shell读取内容

一、前言 上一篇文章中&#xff0c;介绍了防火墙配置文件包含的基本元素和格式样式&#xff0c;并模拟了几组有代表性的规则内容&#xff0c;作为基础测试数据。在拿到基础测试数据后&#xff0c;关于我们最终想解析成的数据是什么样式的&#xff0c;其实不难看出&#xff0c;…

Dynamo设置明细表字段格式——保留小数位数

Hello大家好&#xff01;我是九哥~ 今天简单分享一个API的用法&#xff0c;就是设置明细表的中字段的字段格式。 本次呢&#xff0c;主要介绍下如何通过Dynamo设置长度、面积等几种字段的格式&#xff0c;设置小数位数的显示&#xff0c;如下图&#xff1a; 当然了&#xf…