volatile与const综合分析

在C/C++ 编程中,volatile与const关键字一向容易让人困惑,当然,新手可能从来不用,但是 在高质量和稳健的程序中,这两个关键字 是相当重要的。

相比const,volatile关键字的发展(变化)较少,从C到C++的演变中,一直保持着 它的语义,因此,我们先从volatile来了解下,这两个关键字

一、volatile

1.volatile 的基础 认知:

volatile 的英文 释义是 容易 挥发的,

作为 关键字,可以 记忆为 它修饰的 变量 是 不稳定的,可能被其他地方的某些方式改变,因此为了 获取正确的值,编译器 不该对其做优化,比如为了 获取较快的 读取速度,将它 放入寄存器中等,而是每次都要从它所在的内存中 读取。

BS在 书中 对 volatile 的定义是:

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so thataggressive optimizationsmust be avoided.

volatile修饰符 意在暗示 编译器,该被修饰的对象 通过该语言未指定的方式改变他的值,因此,积极的优化都应该被消除。

未指定的方式 ,比如 操作系统,硬件或者其他线程等。

遇到volatile修饰的变量,编译器对访问该变量的代码 不再进行优化,从而可以提供对特殊地质的稳定访问。

稳定的访问的方式 是 ,系统总是重新从该 变量的 内存中读取数据,即使 它 前面的 指令 刚刚 从该处 读取过数据。

2.volatile 修饰指针

我们可以使用 volatile修饰 指针,比如

volatile char * myVolatileStr;
char *volatile strVolatilePtr;

volatile 修饰 charchar 是 有较大区别的,和const修饰一样,volatile可以将其修饰的 内存区域 声明为 易挥发的,也可以将 指针变量本身声明为 易挥发的。通常,有以下注意点:

注意:(1) 可以把一个非volatile int 赋给 volatile int,但是不能把非volatile对象赋给一个volatile对象。

(2) 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。

(3) C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

3.volatile在 多线程中

在 多线程 中,有些变量是要用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,

下面,来对比学习 下 const,

二、const

1.const基础

在C++中,老手们建议 我们 尽可能的 多使用 const,但是 为啥呢?如果 面试官 问起,你就说,为了程序的稳健性,但是 这和问 锻炼身体为啥呢,保卫祖国 ,没什么 区别。

const 理论上 是 constant的简写,constant的英文释义是 不变的;恒定的;经常的。但是 很多大神将它理解成了 只读的,readonly,甚至觉得 要把这个关键词 替换成readonly。这在gun编译器中也是这么 提错的,很有意思。

和volatile一样,const也是对编译器的约束(废话),它明确的告诉 编译器,const修饰的变量 是 不变的,如果出现了 其他地方的对其修饰值的改变,应该在编译期间就报错。这样能大大提高程序的健壮性,当然,对于程序员,在编译期间就发现错误本就是极好的。

2.const修饰局部变量

这是 最基本的用法,如

const int i = 5;
int const i = 4;

二者 并无本质差异,都是表示 变量i是 不变的,

3.const 与指针

const char* str;
char * const str;
char const* str;
const char* const str;
const char const* str;

以上,声明了 5种const 与指针的 位置 修饰 关系。我们一一说明下:声明 1 ,与 const int i,没有区别 ,修饰 其所指向的 内存区域 是 只读的,不允许 修改声明 2 ,const 直接修饰 str,即指针变量本身,说明 该指针变量 本身是只读的,但是,其所指向的内存区域还是可以改变的。声明 3 ,与声明1 本质一致,见第2声明 4, 声明1 和声明 2 的 合并,其意义也是 2者的 合并声明 5 ,错误的声明。通过以上 分析可以 看出,const与其修饰的哪个 类型近,就限定了哪个为只读,这是规定,但是要防止像声明5 那样的 错误声明,因为有两个const都在修饰 char,这明显是错误的。

4.const与引用

在C++中 ,可以使用 const 修饰引用,

5. const修饰函数参数

const修饰函数参数是它最广泛的一种用途,它表示函数体中不能修改参数的值(包括参数本身的值或者参数其中包含的值)。
1. <span class="kwd">void</span><span class="pln">function</span><span class="pun">(</span><span class="kwd">const</span><span class="typ">int</span><span class="typ">Var</span><span class="pun">);</span><span class="com">//传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参)</span>
2. <span class="kwd">void</span><span class="pln">function</span><span class="pun">(</span><span class="kwd">const</span><span class="kwd">char</span><span class="pun">*</span><span class="typ">Var</span><span class="pun">);</span><span class="com">//参数指针所指内容为常量不可变</span>
3. <span class="kwd">void</span><span class="pln">function</span><span class="pun">(</span><span class="kwd">char</span><span class="pun">*</span><span class="kwd">const</span><span class="typ">Var</span><span class="pun">);</span><span class="com">//参数指针本身为常量不可变(也无意义, 因为char* Var也是形参)</span>
将参数修饰为常量引用,增加了效率同时防止修改。修饰引用参数时:
1. <span class="kwd">void</span><span class="pln">function</span><span class="pun">(</span><span class="kwd">const</span><span class="typ">Class</span><span class="pun">&</span><span class="typ">Var</span><span class="pun">);</span><span class="com">//引用参数在函数内不可以改变</span>
2. <span class="kwd">void</span><span class="pln">function</span><span class="pun">(</span><span class="kwd">const</span><span class="pln">TYPE</span><span class="pun">&</span><span class="typ">Var</span><span class="pun">);</span><span class="com">//引用参数在函数内为常量不可变</span>

6. const 修饰函数返回值

const修饰函数返回值其实用的并不是很多,它的含义和const修饰普通变量以及指针的含义基本相同。(1) const int fun1() 这个其实无意义,因为参数返回本身就是赋值。(2) const int * fun2()调用时 const int pValue = fun2();我们可以把fun2()看作成一个变量,那么就是我们上面所说的1.(1)的写法,即指针内容不可变。(3) int const fun3()调用时 int * const pValue = fun2();我们可以把fun2()看作成一个变量,那么就是我们上面所说的1.(2)的写法,即指针本身不可变。

7. const修饰类对象/对象指针/对象引用

const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。例如:

class AAA
{
   void func1();
void func2()const;
}
const AAA aObj;
aObj.func1();//×
aObj.func2();//正确

const AAA* aObj =new AAA();
aObj->func1();// ×
aObj->func2();//正确

8. const修饰成员变量

const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

class A
{
   …
   constint nValue;       //成员常量不能被修改
   …
   A(int x): nValue(x){};//只能在初始化列表中赋值
}

9. const修饰成员函数

const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。一般写在函数的最后来修饰。

class A
{
   …
void function()const;//常成员函数, 它不改变对象的成员变量. 也不能调用类中任何非const成员函数。
}

对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用。

10. const常量与define宏定义的区别

(1) 编译器处理方式不同define宏是在预处理阶段展开。const常量是编译运行阶段使用。(2) 类型和安全检查不同define宏没有类型,不做任何类型检查,仅仅是展开。const常量有具体的类型,在编译阶段会执行类型检查。(3) 存储方式不同define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。const常量会在内存中分配(可以是堆中也可以是栈中)。



三、总结 分析那么问题来了,理解了 volatile 和 const关键词的使用 场景 ,那么 二者可以同时使用吗?比如 const volatile int i;这样的声明 是否 有问题呢?从语义上讲,似乎不可能,但是 ,这样的声明 其实 是合法的。因为const和volatile这两个类型限定符并不矛盾。const表示(运行时)常量语义:被const修饰的对象在所在的作用域无法进行修改操作,编译器对于试图直接修改const对象的表达式会产生编译错误。volatile表示“易变的”,即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况):被volatile修饰的对象,编译器不会对这个对象的操作进行优化。一个对象可以同时被const和volatile修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。













原文链接: https://www.cnblogs.com/Stultz-Lee/p/6718079.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/252617

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月14日 上午6:13
下一篇 2023年2月14日 上午6:14

相关推荐