首页 > 编程语言 >01 | C++关键字const/volatile

01 | C++关键字const/volatile

时间:2024-01-26 11:14:03浏览次数:16  
标签:01 const 常量 编译器 volatile 变量 指针

const与volatile

const从字面意思上是表示“常量”。最基础的用法就是定义程序用到的数字、字符串常量,代替宏定义。

const int MAX_LEN      = 1024;
const std::string NAME = "metroid";

不过从C++程序的生命周期角度来看的话,const定义的常量和宏定义还是有本质的区别:const定义的常量在预处理阶段并不存在,而是知道运行阶段才会出现,而宏定义在预处理阶段就已经存在了

所以const定义的常量实际上是运行时的“变量”,只不过不允许修改,叫“只读变量”更合适。

我们可以通过程序验证const常量是否为“变量”。

// 需要加上volatile修饰,运行时才能看到效果
const volatile int MAX_LEN  = 1024;

auto ptr = (int*)(&MAX_LEN);
*ptr = 2048;
cout << MAX_LEN << endl;      // 输出2048

可以看到,这段代码最开始定义的常数是 1024,但是输出的却是 2048.

在这里我们多加了volatile修饰MAX_LEN,这是例程的关键。若没有volatile修饰,那么即使得到了const常量的地址,并进行修改,输出仍然会是常熟1024.

这里是因为const常量在编译器里被优化了。
const常量看上去虽然不是“真正的常量”,但通常情况下,它都被编译器认定为常量,在运行期间不会改变,当编译器看到const定义,就会进行优化,把所有const常量出现的地方全部替换成原始值。

所以,在没用volatile修饰const常量时,即使你用只能改变了常量的值,但这个是在运行阶段没有用到,因为它在变异阶段就被优化掉了。

我们再来看一下volatile的作用。

volatile便是“不稳定的”、“易变的”,在C/C++里表示变量的值可能会以“难以察觉”的方式被修改,所以禁止编译器做任何形式的优化,每次使用volatile修饰的变量是,都必须去取值。

现在,再回头去看例程,我们就应该明白了。MAX_LEN 虽然是个“只读变量”,但加上了 volatile 修饰,就表示它不稳定,可能会悄悄地改变。编译器在生成二进制机器码的时候,不会再去做那些可能有副作用的优化,而是用最“保守”的方式去使用 MAX_LEN。

也就是说,编译器不会再把 MAX_LEN 替换为 1024,而是去内存里取值(而它已经通过指针被强制修改了)。所以,这段代码最后输出的是 2048,而不是最初的 1024。

到了这里,我们总结一下:

对于const修饰的变量,我们可以把其理解为“只读” const 理解成 read only(虽然是“只读”,但在运行阶段没有什么是不可以改变的,也可以强制写入),把变量标记成 const 可以让编译器做更好的优化。

而volatile会禁止编译器做优化,但是一般程序很少用到volatile(在嵌入式领域比较多),我们尽量也少用。

const的用法

作为一个类型修饰符,const的用途非常多,除了修饰变量外,下面我们在带你看看它的常量引用、常量指针等其他用法。

C++除了最基本的值类型,还有引用类型和指针类型,它们加上const就成了常量引用常量指针

int x = 100;

const int& rx = x;
const int* px = &x;

const&被称为万能引用,也就是说,它可以引用任何类型,即不管是值、指针、左引用还是右引用,它都能“照单全收”.

而且,它还会给变量附加上 const 特性,这样“变量”就成了“常量”,只能读、禁止写。编译器会帮你检查出所有对它的写操作,发出警告,在编译阶段防止有意或者无意的修改。这样一来,const 常量用起来就非常安全了。

因此,在设计函数的时候,我建议你尽可能地使用它作为入口参数,一来保证效率,二来保证安全

const 用于指针的情况会略微复杂一点。常见的用法是,const 放在声明的最左边,表示指向常量的指针。这个其实很好理解,指针指向的是一个“只读变量”,不允许修改:

string name = "uncharted";
const string* ps1 = &name; //指向常量
*ps1 = "spiderman";  //不允许被修改

另外一种比较“恶心”的用法是,const 在“*”的右边,表示指针不能被修改,而指向的变量可以被修改:

string name1 = "uncharted";
string name2 = "spiderman";
string* const ps2 = &name1;  // 指向变量,但指针本身不能被修改
*ps2 = "spiderman";          // 正确,允许修改
 ps2 = &name2;               // 错误,指针本身不允许被修改

再进一步,就是“*”两边都有 const,你看看是什么意思呢:

const string* const ps3 = &name; 

其实,就是上述两种情况的结合。

与类相关的const用法

上述的用法都是面向过程的,在面向对象里,const也很有用。

定义const成员变量很简单,但还有const成员函数。

class DemoClass final
{
private:
    const long  MAX_SIZE = 256;    // const成员变量
    int         m_value;           // 成员变量
public:
    int get_value() const        // const成员函数
    {
        return m_value;
    }
};

这里的const用法有点特别,它被放在了函数的后面,表示这个函数是一个“常量”。(如果放在最前面,就代表返回值是const int)

“const成员函数”并不是表示函数不可以修改。实际上,在C++里,函数并不是变量(lambda表达式除外),所以,“只读”对于函数来说没有任何意义。它的真正含义为:函数的执行过程是const的,并不会修改对象的状态(即成员变量),也就是说,成员函数是一个“只读操作”。

对于const修饰成员函数,看起来也许有点平淡无奇,但结合“常量引用”或“常量指针”,就“大有学问”了。

“常量引用”、“常量指针”关系的对象是只读、不可修改的,那么也就是意味着,对它的任何操作也应该是只读、不可修改,否则就无法保证它的安全性。所以,编译器会检查const对象相关的代码,如果成员函数不是const,就不允许调用。

这其实也是对“常量”语义的一个自然延伸,既然对象是const,那么它所有的相关操作也必然是const。同样,保证了安全之后,编译器确认对象不会变,也就可以去做更好的优化。

看到这里,我们可以总结一下常量引用、常量指针、常量函数。

const 非const
对象(实例) (const T)对象只读,只能调用const成员函数 可以修改对象,调用任意成员函数
引用 (const T&)引用的对象只读,只能调用const成员函数
指针 (const T*)指针指向的对象只读,只能调用const成员函数
成员函数 (func()const)不允许修改成员变量 可以修改成员变量
这方面你还可以借鉴一下标准库,比如 vector,它的 empty()、size()、capacity() 等查看基本属性的操作都是 const 的,而 reserve()、clear()、erase() 则是非 const 的。

标签:01,const,常量,编译器,volatile,变量,指针
From: https://www.cnblogs.com/ydqblogs/p/17972758

相关文章

  • CF1917F Construct Tree
    Link:http://codeforces.com/problemset/problem/1917/F知识点:背包,构造简述\(T\)组数据,每组数据给定参数\(d\)与一长度为\(n\)的数列\(l\),仅需判断是否可以构造出一棵树,满足:树的所有边长与数列元素一一对应。树的直径为\(d\)。\(1\leT\le250\),\(2\len\le2000\)......
  • day01
    /**这是一个入门程序可以在控制台输出HelloWorld*/publicclassHelloWorld{ /* 这是一个main方法,又称主方法,是程序的入口 */ publicstaticvoidmain(String[]args){ //这是一条输入语句,会输入小括号里的内容 System.out.println("HelloWorld"); System.out......
  • 【pwn】axb_2019_fmt32 --格式化字符串漏洞进一步利用
    照例检查程序保护情况堆栈不可执行,再导入ida看一下代码逻辑如上图此处代码有格式化字符串漏洞先找出偏移可以发现偏移是8那么我们可以利用printf泄露出libc地址,如何修改printf_got表为system的地址,然后再传入/bin/sh就可以getshellexp:frompwnimport*fromLibcSearc......
  • 240125 杂题选谈
    PKUSC2022Mahjonghttp://222.180.160.110:1024/contest/4813/problem/1https://www.bilibili.com/video/BV1JB4y1R7AP/这里是PKUSC当时的讲解视频。听说可以证明本题一定有\(\le5\)的解。好神奇。就比如说我们爆搜,\(9^4\times13^4\)这个显然干不动对吧,所以我们考虑......
  • P8659 [蓝桥杯 2017 国 A] 数组操作 题解
    题目链接:洛谷或者蓝桥杯或者C语言中文网几个OJ的AC记录:忘了哪个OJ的:洛谷:C语言中文网:蓝桥杯:emmmmmmm,好像每个OJ给的时限和空间还不一样,蓝桥杯官方还给了$3s$和$2G$,C语言中文网机子比较老可能,挺卡常的,开了个究极快读和指令集就过去了,也可以自己调下重构常数,偷懒......
  • [转]vs2019升级后,启动调试,谷歌浏览器无法正常使用 - 温故纳新 - 博客园
    解决方法:vs2019按如下步骤设置:工具  =》 选项  =》调试 =》 常规 =》勾选“启用APS.NET 的 Javascript 调试(Chrome和IE)”---------------------作者:温故纳新来源:CNBLOGS原文:https://www.cnblogs.com/tomorrow0/p/14383870.html版权声明:本文为作者原创文章,转载请......
  • svelte路由01
    1、about.svelte页面a、使用 use:link 方式<script>import{link}from'svelte-spa-router';</script><div>这里是about关于我们</div><div><ahref="/home"use:link>进入首页</a></div>......
  • 洛谷 P6681 [CCO2019] Bad Codes
    洛谷传送门QOJ传送门被QOJ1193AmbiguousEncoding撞了。考虑直接dp,设\(f_{i,j}\)为较长的串未被较短的串覆盖的部分是第\(i\)个字符串的长为\(j\)的后缀。转移考虑枚举接在较短的串后面是第\(k\)个串,然后讨论一下\(j\)和第\(k\)个字符串的大小关系就可以确定......
  • 20240125打卡——《构建之法》读书笔记第1~4章
    第一章概论在这一章中,作者为我们介绍了一些关于软件工程的基本知识。①软件=程序+软件工程:正是因为对软件开发活动(构建管理、源代码管理、软件设计、软件测试、项目管理)相关的内容的完成,才能完成把整个程序转化成为一个可用的软件的过程。扩展的推论:软件企业=软件+商业模式......
  • 如何手工制作绿色免安装单文件同花顺免费版Windows客户端 2024-01-25
    如何手工制作绿色免安装单文件同花顺免费版Windows客户端  2024-01-25第1步、下载同花顺免费版http://download.10jqka.com.cn/第2步、安装同花顺免费版第3步、移动同花顺免费版软件到文件夹 D:\Prog\同花顺第4步、新建批处理脚本文件 D:\Prog\同花顺\一键打包\一键打......