C++ const 关键字详解(全网最全)

2023-11-06

1. const修饰符的作用

  • const类型定义: 指明变量或对象的值是不能被更新,引入目的是为了取代预编译指令
  • 可以保护被修饰的东西,防止意外的修改,增强程序的健壮性
  • 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高
  • 可以节省空间,避免不必要的内存分配

2. 初始化和const

const修改全局变量是存储在全局区(即静态存储区),修饰局部变量时存储在栈区,const修饰的对象一旦创建后其值便不能再改变 ,类的数据成员只是声明,如果const修饰类的数据成员,那么必须要有构造函数来初始化这个const对象,其他情况必须在声明的地方就初始化。

默认状态下,const对象仅在文件内有效,如果想在别的文件使用此对象,需要使用关键字extern

3. const和引用

我们把const修饰的引用称为”常用引用“,常量引用不能直接修改所引用的对象,看下面的代码:

const int ci = 1024;//ci是一个int型的常量
const int &r1 = ci;//正确,r1是一个常量引用,并且r1本身也是一个常量
r1 = 42;//错误,引用被const限制了,不能修改所引用对象的值了
int &r2 = ci;//错误,试图让一个非常量引用指向一个常量对象

一般来说引用的类型和其所引用的对象的类型一致,但有两个意外,这里是其中一种,

  • 初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可
int i = 42;
const int &r1 = i;     //允许讲const int&绑定到一个普通int对象上
const int &r2 = 42;    //正确,r2是一个常量引用
const int &r3 = r1 * 2;//正确,r3是一个常量引用
int &r4 = r1 * 2;      //错误,右边是常量而左边并不是常量
  • 常量引用所引用的对象可能是一个非常量
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0;//r1,r2都是i的引用
cout << "i " << i << endl;//0
cout << "r1 " << r1 << endl;//0
cout << "r2 " << r2 << endl;//0
r2 = 10 //错误,不能通过r2来修改i的值,因为有了const的限定

常量引用只是对引用可参与的操作做出了限定,对于引用的对象本身是不是常量未作限定,因此上面的代码中i不是常量,可以r1这个引用来修改它的值

4. const和指针

  • 指向常量的指针不能用于改变其所指对象的值,只能使用常量指针(即对指针加const修饰)
  • 常量指针都不可以去修改所指向的对象,常量引用也是不能修改所引用的对象,不管对象是不是常量都不能修改
const double pi = 3.1415;  //pi是一个常量,它的值不能改变
double *ptr = &pi; //错误:指向常量的指针必须是常量指针
const double *cptr = &pi; //正确
*cptr = 42; //错误:常量指针不能修改对应的值
  • const指针即指针存放的地址就不能再改变,我们称为指针常量,*在前

    int errNumb = 0;
    int *const curErr = &errNumb;// curErr将一直指向errNumb这块内存
    const double pi = 3.14;
    const double *const pip = &pi; //从右向左依次解修饰符,首先pip是一个常量,然后是*说明pip是一个常量指针,
    //再然后是double说明是指向double的指针,最后是const说明这个double不能变,即指向的对象是一个常量的double
    
  • 和引用一样,常量指针指向的对象并不一定是一个常量,虽然不能通过该指针修改,但是可以通过其他指针修改。

  • 顶层const指的是指针本身是不是常量,底层const指的是指针指向的对象是不是常量

    int i = 0;
    int *const p1 = &i;//p1是一个顶层const,可以修改*p1,但是不可以修改p1
    int ci = 42;//ci是一个const变量,本身值不允许发生变化都可以称为顶层const
    const int *p2 = &ci;//是一个底层const,不允许修改*p2,但可以修改p2
    const int *const p3 = p2;//既是底层const也是顶层const,*p3和p3都不可以修改
    const int &r = ci;//r是一个常量引用
    *p2 = 3; //错误,表达式左边必须是可修改的值
    p1 = &i;//错误,表达式左边必须是可修改的值
    

5. const形参和实参

  • 当用实参初始化形参时会忽略掉形参的顶层const,即实参值本身是const或者不是无所谓

    void func(const int i);//传入的i可以是const,也可以是非const,但是func不能修改i
    void func(int i);//错误,重复定义
    
  • 但如果形参不是顶层const,那么实参也不能是顶层const,也就是说如果形参是可变的,传进来的值必须可变才行

    int i = 0;
    const int ci = i;//一个顶层const
    //void reset(int* ptr);  void reset(int& ptr);
    reset(&i);//调用传入指针的函数
    reset(&ci);//错误,实参是一个常量,是指向const int的指针,那么&ci是const int*,不能传递给int*
    reset(i);//调用传入引用的reset函数
    reset(ci);//错误,形参变量,实参也必须是变量,ci是常量
    reset(42);//错误,字面值是一个常量,形参可变,不能传入常量
    
  • 我们在写函数的时候形参应尽量使用常量引用

    • 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标 对象(在主调函数中)的操作。
    • 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作; 而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的 副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
    • 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰

6. const返回值

返回值为const值,或者const指针,const引用时该如何考虑呢?

  • 如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值,那个临时存储单元确实是个const int。

    const int func1(int a);
    int func1(int a);
    //以上两个函数没有任何差异
    
  • 如果给以“指针传递”方式的函数返回值加const 修饰,那么返回值可以是指针常量,也可以是常量指针

    /*
    第一种返回指针常量,//const毫无意义
    */
    int* const func1(int a);//返回一个指针常量,虽然想返回一个顶层const的值,但是由于函数返回时复制到
    //外部的临时存储单元中,这个const没有意义,虽然临时存储单元的指向确实无法改变
    int* b = func1(2);//可以用int*接收返回值,b指向的值可以修改,b的指向也可以修改
    /*
    以上调用相当于
    int* const c = func1(2);
    int* b = c;
    */
    const int* b = func1(2);//也可以用const int*接收返回值,
    //此时的b是一个底层const,也就是说int* const转换成了const int*
    /*
    以上调用相当于
    int* const c = func1(2);
    const int* b = c;
    */
    
    
    /*
    第二种返回常量指针
    */
    const int*func1(int a);//返回一个常量指针,这时是有意义的,返回的也确实是const int*
    int* b = func1(2);//错误,因为const int*不能转成int*
    const int* b = func1(2);//正确,b指向的值无法改变
    
    /*
    第三种返回常量指针
    */
    int const*func1(int a);//和第二种完全一样
    
  • const修饰函数的引用返回值,函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数和拷贝构造函数中,赋值函数和一些操作符函数目的是为了实现链式表达,拷贝构造函数传引用是为了避免逻辑错误,因为函数返回时如果是返回值的话,会再次调用拷贝构造函数这样会无限循环,因此需要返回引用。返回的引用不能是函数内的临时变量的引用

    class B{	
    public:
    	int val = 0;
    	B(int val):val(val){};
    	B& operator=(const B &other) {
    		val = other.val;
    		return *this;
    	}
    };
    

    首先说明为什么这里要传引用而不是传值,即为什么不是B operate = (const B &other)?
    类的成员函数默认都有一个this指针代表对象本身作为函数的形参,只是隐藏起来了;

    a = b = c可以写成a = (b = c),若是传值,则返回的值是一个临时变量,如下:

    B tmp = (b = c);//左边的等号是拷贝的意思不是操作符=,tmp的值和c的值是相等的
    a = tmp;//此时a和b的值都被赋成了c
    //考虑下面的代码块
    B b1(3), b2(4),b3(5);
    cout << "b1: " << b1.val << endl;
    cout << "b2: " << b2.val << endl;
    cout << "b3: " << b3.val << endl;
    cout << "b1=b2=b3" << endl;
    b1 = b2 = b3;
    cout << "b1: " << b1.val << endl;
    cout << "b2: " << b2.val << endl;
    cout << "b3: " << b3.val << endl;
    /*
    输出:
    b1: 3
    b2: 4
    b3: 5
    b1=b2=b3
    b1: 5
    b2: 5
    b3: 5
    */
    

这里是正确的赋值了,但是如果考虑非正常的链式(a = b) = c,我们的本意是想先把b的值赋给a,再把c的值赋给a

 //以上链式相当于
     B tmp = (a = b);//左边的等号是拷贝的意思不是操作符=,tmp的值和a的值是相等的
     tmp = c;//此时tmp的值被赋成了c,并没有改变a,只是改变了这个临时变量而已
     //考虑下面的代码块
     B b1(3), b2(4),b3(5);
     cout << "b1: " << b1.val << endl;
     cout << "b2: " << b2.val << endl;
     cout << "b3: " << b3.val << endl;
     cout << "(b1=b2)=b3" << endl;
     (b1 = b2) = b3;
     cout << "b1: " << b1.val << endl;
     cout << "b2: " << b2.val << endl;
     cout << "b3: " << b3.val << endl;
     /*
     输出:
     b1: 3
     b2: 4
     b3: 5
     (b1=b2)=b3
     b1: 4
     b2: 4
     b3: 5
     *///不符合我们的预期结果

换成传引用之后,,以上结果都符合我们的预期,在(a = b) = c的链式中,可理解如下:

   B &tmp = (a = b);//返回的是a的引用,即tmp是a的引用
   tmp = c;//就通过a的引用tmp把a的值修改成了c

现在理解清楚返回值是引用的意义后,考虑用const限定引用返回值,即const B& operator=(const B &other)
我们首先考虑a = b = c链式

const B& tmp = (b = c);//因为b = c的返回值是b的常量引用,那么接收值也必须是常量引用,此时的B是一个底层cosnt
//但引用具有一旦被绑定后就不能再转移的特性,也就是说引用本身就具有了顶层const的特性
a = tmp;//a也被赋值为b的值,此时b的值和c的值是相等的
//总结a = b = c链式没有影响

我们接着考虑(a = b) = c链式

 const B& tmp = (a = b);
 tmp = c;//这里的语句是错误的,因为tmp是常量引用,底层const,不能通过tmp修改a的值
  • 考虑一般的返回值为引用的情况
   int& func1(int& a) {
       	return a;
       }
       const int& func2(int& a) {//这里可以是int const&, int& const都一样
       	return a;
       }
       int a = 1;
       int b = func1(a);
       int c = func2(a);
       c = 3;
       b = 2;
       //以上代码都是合法的,也就是说这时的const起不到任何作用

7. const函数

  • const去限定函数只允许发生在类的成员函数中,表明该函数不会修改类的成员变量,const放在成员函数的末尾,声明和定义都要加。

    只能限定类的成员函数
    const限定后,该成员函数不允许修改类的数据成员,也不允许调用非const函数,即使该函数没有修改类的数据成员,只要没有声明成const,就不能调用。

class Stack
{
public:
 void Push(int elem);
 int Pop(void);
 int GetCount(void) const; // const 成员函数
private:
 int m_num;
 int m_data[100];
};
int Stack::GetCount(void) const
{
 ++m_num; // 编译错误,企图修改数据成员m_num
 Pop(); // 编译错误,企图调用非const 函数
 return m_num;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ const 关键字详解(全网最全) 的相关文章

  • MEX 文件中的断言导致 Matlab 崩溃

    我正在使用mxAssert 宏定义为matrix h在我的 C 代码中 mex 可以完美编译 当我调用的 mex 代码中违反断言时 该断言不会导致我的程序崩溃 而是导致 Matlab 本身崩溃 我错过了什么吗 这是有意的行为吗 当我查看 M
  • Qt - 无法让 lambda 工作[重复]

    这个问题在这里已经有答案了 我有以下功能 我想在其中修剪我的std set
  • 获取两个字符串之间的公共部分c# [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我需要的是获取两个单词之间的共同部分并获取差异 例子 场景1 word1 感言 word2 Test 将返回 公共部分Test 不同之
  • 使用实体框架从集合中删除项目

    我正在使用DDD 我有一个 Product 类 它是一个聚合根 public class Product IAggregateRoot public virtual ICollection
  • 在 C++11 中省略返回类型

    我最近发现自己在 C 11 模式下的 gcc 4 5 中使用了以下宏 define RETURN x gt decltype x return x 并编写这样的函数 template
  • 有什么工具可以说明每种方法运行需要多长时间?

    我的程序的某些部分速度很慢 我想知道是否有我可以使用的工具 例如它可以告诉我可以运行 methodA 花了 100ms 等等 或者类似的有用信息 如果您使用的是 Visual Studio Team System 性能工具 中有一个内置分析
  • Linux TUN/TAP:无法从 TAP 设备读回数据

    问题是关于如何正确配置想要使用 Tun Tap 模块的 Linux 主机 My Goal 利用现有的路由软件 以下为APP1和APP2 但拦截并修改其发送和接收的所有消息 由Mediator完成 我的场景 Ubuntu 10 04 Mach
  • 是否有与 C++11 emplace/emplace_back 函数类似的 C# 函数?

    从 C 11 开始 可以写类似的东西 include
  • Xamarin Android:获取内存中的所有进程

    有没有办法读取所有进程 而不仅仅是正在运行的进程 如果我对 Android 的理解正确的话 一次只有一个进程在运行 其他所有进程都被冻结 后台进程被忽略 您可以使用以下代码片段获取当前正在运行的所有 Android 应用程序进程 Activ
  • 范围和临时初始化列表

    我试图将我认为是纯右值的内容传递到范围适配器闭包对象中 除非我将名称绑定到初始值设定项列表并使其成为左值 否则它不会编译 这里发生了什么 include
  • 两组点之间的最佳匹配

    I ve got two lists of points let s call them L1 P1 x1 y1 Pn xn yn and L2 P 1 x 1 y 1 P n x n y n 我的任务是找到它们点之间的最佳匹配 以最小化它
  • 组合框项目为空但数据源已满

    将列表绑定到组合框后 其 dataSource Count 为 5 但组合框项目计数为 0 怎么会这样 我习惯了 Web 编程 而且这是在 Windows 窗体中进行的 所以不行combo DataBind 方法存在 这里的问题是 我试图以
  • C# 创建数组的数组

    我正在尝试创建一个将使用重复数据的数组数组 如下所示 int list1 new int 4 1 2 3 4 int list2 new int 4 5 6 7 8 int list3 new int 4 1 3 2 1 int list4
  • 从匿名类型获取值

    我有一个方法如下 public void MyMethod object obj implement 我这样称呼它 MyMethod new myparam waoww 那么我该如何实施MyMethod 获取 myparam 值 Edit
  • 内核开发和 C++ [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 从我know https stackoverflow com questions 580292 what languages are windo
  • 为什么我使用google'smtp'无法发送电子邮件?

    我有以下程序使用 smtp gmail com 587 发送电子邮件 namespace TestMailServer class Program static void Main string args MailMessage mail
  • 同时从多个流中捕获、最佳方法以及如何减少 CPU 使用率

    我目前正在编写一个应用程序 该应用程序将捕获大量 RTSP 流 在我的例子中为 12 个 并将其显示在 QT 小部件上 当我超过大约 6 7 个流时 问题就会出现 CPU 使用率激增并且出现明显的卡顿 我认为它不是 QT 绘制函数的原因是因
  • 如何在 GCC 5 中处理双 ABI?

    我尝试了解如何克服 GCC 5 中引入的双重 ABI 的问题 但是 我没能做到 这是一个重现错误的非常简单的示例 我使用的GCC版本是5 2 如您所见 我的主要函数 在 main cpp 文件中 非常简单 main cpp include
  • 以编程方式使用自定义元素创建网格

    我正在尝试以编程方式创建一个网格 并将自定义控件作为子项附加到网格中 作为 2x2 矩阵中的第 0 行第 0 列 为了让事情变得更棘手 我使用了 MVVM 设计模式 下面是一些代码可以帮助大家理解这个想法 应用程序 xaml cs base
  • 在基类集合上调用派生方法

    我有一个名为 A 的抽象类 以及实现 A 的其他类 B C D E 我的派生类持有不同类型的值 我还有一个 A 对象的列表 abstract class A class B class A public int val get privat

随机推荐

  • js实现图片懒加载原理

    有时候一个网页会包含很多的图片 例如淘宝京东这些购物网站 商品图片多只之又多 页面图片多 加载的图片就多 服务器压力就会很大 不仅影响渲染速度还会浪费带宽 比如一个1M大小的图片 并发情况下 达到1000并发 即同时有1000个人访问 就会
  • 学习记录Linux搭建nginx

    安装编译工具及库文件 yum y install make zlib zlib devel gcc c libtool openssl openssl devel 首先要安装 PCRE 下载 PCRE 安装包 下载地址 http downl
  • Pycharm常用快捷键大全

    版权声明 本文为博主原创文章 遵循 CC 4 0 BY SA 版权协议 转载请附上原文出处链接和本声明 本文链接 https blog csdn net momoda118 article details 120155611 工欲善其事必先
  • 如何将任意大小的图片填充成一个方形

    这种方式我认为是最适合的图片预处理方式 随意resize会改变图片的特征 而进行填充的方式除了会改变原来标签的坐标以外 可以完全保留图片上所有的物体最原始的特征 def pad to square img pad value c h w i
  • 用Gerrit commit时报错missing Change-Id in message footer

    本人在第一次使用Gerrit时提交代码一直报change Id找不到 在git log的时候显示如下 查询其他文档发现是commit msg文件不存在导致的 现给出解决方案 提示错误 错误信息如下 remote Resolving delt
  • ctfshow终极考核wp

    文章目录 web640 web641 web642 web643 web644 web645 web646 web647 web648 web649 web650 web651 web652 web653 web654 web640 打开页
  • 【华为OD机试】支持优先级的队列【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 实现一个支持优先级的队列 高优先级先出队列 同优先级时先进先出 如果两个输入数据和优先级都相同 则后一个数据不入队列被丢弃 队列存储的数据内容是一个整数 输入描述 一
  • 修改 Linux VM 中单个用户最大进程数的限制

    在部署有并发任务执行的虚机上 会遇到 SSH 无法访问的问题 本文将帮助你找出其中一种比较特殊的原因 并提供解决方案 Note 以下案例分析基于 CentOS 7 对于其他版本的 Linux 操作系统 会略有不同 请注意 症状描述 虚机在正
  • BaoStock:一个免费、开源的python证券数据接口包

    如果需要获取历史行情数据 www baostock com是个很好的免费 开源的Python证券数据接口包 特点 使用方便 免费免费免费 返回的绝大部分的数据格式都是pandas DataFrame类型 入门代码如下 import baos
  • 分布式系统常用的模式

    分布式系统常用的模式 Ambassador 名称 大使 模式 介绍 作为应用程序和其他服务的 中间人 负责应用程序和其他服务之间的通信 包括日志 监控或重试处理等任务 举例 K8S使用Envoy作为一个 大使 来简化服务之间的通信 优点 降
  • MySQL多表查询

    目录 一 查询和新增结合 二 聚合查询 1 聚合函数 2 group by子句 三 联合查询 1 笛卡尔积 1 内连接 2 外连接 1 左外连接 2 右外连接 3 自连接 4 子查询 嵌套查询 5 合并查询 一 查询和新增结合 将表2中的数
  • 第十二届蓝桥杯省赛第二场C/C++大学B组编程题题目及解析

    目录 试题F 特殊年份 试题G 小平方 试题H 完全平方数 试题I 负载平衡 试题J 国际象棋 试题F 特殊年份 include
  • 【Linux】设计模式

    目录 1设计模式 1 1概念 1 2设计模式分类 1 3单例模式 1 4单例模式代码演示 1 4 1懒汉模式 1 4 2饿汉模式 2 读写锁 2 1概念 2 2加锁规则 2 3接口 2 3 1初始化接口 2 3 2销毁接口 2 3 3解锁接
  • 第1章第5节:如何使用模板创建风格统一的幻灯片 [PowerPoint精美幻灯片实战教程]

    使用模板可以创建美观 风格统一和专业的幻灯片 要使用模板创建幻灯片 首先点击此处的文件选项卡 进入文件功能页面 然后在左侧的命令列表中 点击新建命令 在页面的下方 是由微软提供的常见的演示文稿模板 也可以通过搜索框 搜索指定主题的模板 点击
  • SharedPreferences 操作

    public class SPActivity extends Activity 使用SharedPreferences 来储存与读取数据 SharedPreferences mShared null 程序中可以同时存在多个SharedPr
  • IndentationError:expected an indented block错误解决

    IndentationError expected an indented block错误解决 描述 有时一个简单的问题会困扰很久 当发现问题后才感觉自己是多蠢 下面记录一个在日常Python编程过程中碰见的典型问题 参考文章 http h
  • C++编写及注册windows服务程序

    1 注册服务 在 开始 gt 运行 gt cmd 中输入 sc create TEST binPath C TEST EXE 则在windows下注册了一项服务 sc create TestService binpath c Service
  • GitHub Action + Release:打造 Electron 持续交付系统文件配置

    main yml上的文件配置 This is a basic workflow to help you get started with Actions name build Electron App For Win Mac Control
  • 上班族适合的兼职副业,副业做什么比较靠谱,副业赚钱的路子有哪些

    要找能不影响上班的副业 那前提条件就必须不能让你投入太多的时间在上面 否则说不影响上班就是扯淡哈 对于找什么副业比较靠谱这样的问题 我们首先要清楚 哪里的用户多 我们就在那个地方寻找就准没错 这也是基本道理 无可厚非 短视频平台就是现在人流
  • C++ const 关键字详解(全网最全)

    1 const修饰符的作用 const类型定义 指明变量或对象的值是不能被更新 引入目的是为了取代预编译指令 可以保护被修饰的东西 防止意外的修改 增强程序的健壮性 编译器通常不为普通const常量分配存储空间 而是将它们保存在符号表中 这