C++ 修饰符类型,C++ 中的类型限定符volatile,explicit

c++ c++ 1804 人阅读 | 0 人回复

发表于 2022-11-29 17:42:56 | 显示全部楼层 |阅读模式

C++ 修饰符类型
C++ 允许在 char、int 和 double 数据类型前放置修饰符。修饰符用于改变基本类型的含义,所以它更能满足各种情境的需求。
下面列出了数据类型修饰符:
  • signed
  • unsigned
  • long
  • short
修饰符 signed、unsigned、long 和 short 可应用于整型,signedunsigned 可应用于字符型,long 可应用于双精度型。
修饰符 signedunsigned 也可以作为 longshort 修饰符的前缀。例如:unsigned long int
C++ 允许使用速记符号来声明无符号短整数无符号长整数。您可以不写 int,只写单词 unsigned、shortlongint 是隐含的。例如,下面的两个语句都声明了无符号整型变量。
  1. unsigned x;
  2. unsigned int y;
复制代码
为了理解 C++ 解释有符号整数和无符号整数修饰符之间的差别,我们来运行一下下面这个短程序:
  1. #include <iostream>
  2. using namespace std;

  3. /*
  4. * 这个程序演示了有符号整数和无符号整数之间的差别
  5. */
  6. int main()
  7. {
  8.    short int i;           // 有符号短整数
  9.    short unsigned int j;  // 无符号短整数

  10.    j = 50000;

  11.    i = j;
  12.    cout << i << " " << j;

  13.    return 0;
  14. }
复制代码
当上面的程序运行时,会输出下列结果:
-15536 50000
上述结果中,无符号短整数 50,000 的位模式被解释为有符号短整数 -15,536。
C++ 中的类型限定符
类型限定符提供了变量的额外信息。
限定符
含义
constconst 类型的对象在程序执行期间不能被修改改变。
volatile修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。
restrictrestrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。

C/C++ 中 volatile 关键字详解1、为什么用volatile?
C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 "The C++ Programming Language" 对 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 that aggressive optimizations must be avoided.
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:
  1. volatile int i=10;
  2. int a = i;
  3. ...
  4. // 其他代码,并未明确告诉编译器,对 i 进行过操作
  5. int b = i;
复制代码
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无 volatile 关键字,对程序最终代码的影响,输入下面的代码:
  1. #include <stdio.h>

  2. void main()
  3. {
  4.     int i = 10;
  5.     int a = i;

  6.     printf("i = %d", a);

  7.     // 下面汇编语句的作用就是改变内存中 i 的值
  8.     // 但是又不让编译器知道
  9.     __asm {
  10.         mov dword ptr [ebp-4], 20h
  11.     }

  12.     int b = i;
  13.     printf("i = %d", b);
  14. }
复制代码
然后,在 Debug 版本模式运行程序,输出结果如下:
i=10
i=32
然后,在 Release 版本模式运行程序,输出结果如下:
i=10
i=10
输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:
  1. #include <stdio.h>

  2. void main()
  3. {
  4.     volatile int i = 10;
  5.     int a = i;

  6.     printf("i = %d", a);
  7.     __asm {
  8.         mov dword ptr [ebp-4], 20h
  9.     }

  10.     int b = i;
  11.     printf("i = %d", b);
  12. }
复制代码
分别在 Debug 和 Release 版本运行程序,输出都是:
i=10
i=32

这说明这个 volatile 关键字发挥了它的作用。其实不只是内嵌汇编操纵栈"这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:
  • 1) 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
  • 2) 多任务环境下各任务间共享的标志应该加 volatile;
  • 3) 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义;
2、volatile 指针
和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:
修饰由指针指向的对象、数据是 const 或 volatile 的:
  1. const char* cpch;
  2. volatile char* vpch;
复制代码
注意:对于 VC,这个特性实现在 VC 8 之后才是安全的。
指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:
  1. char* const pchc;
  2. char* volatile pchv;
复制代码
注意:
  • (1) 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
  • (2) 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
  • (3) C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。
3、多线程下的volatile
有些变量是用 volatile 关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:
volatile  BOOL  bStop  =  FALSE;
(1) 在一个线程中:
  1. while(  !bStop  )  {  ...  }  
  2. bStop  =  FALSE;  
  3. return;   
复制代码
(2) 在另外一个线程中,要终止上面的线程循环:
  1. bStop  =  TRUE;  
  2. while(  bStop  );  //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
复制代码
这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:
  1. ...  
  2. int  nMyCounter  =  0;  
  3. for(;  nMyCounter<100;nMyCounter++)  
  4. {  
  5. ...  
  6. }  
  7. ...
复制代码
在此段代码中,nMyCounter 的拷贝可能存放到某个寄存器中(循环中,对 nMyCounter 的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1; 这个操作中,对 nMyCounter 的改变是对内存中的 nMyCounter 进行操作,于是出现了这样一个现象:nMyCounter 的改变不同步。


C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。
C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。
所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。
这样看起来好象很酷, 很方便。 但在某些情况下(见下面权威的例子), 却违背了我们(程序员)的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。
explicit构造函数的作用
解析:
explicit构造函数是用来防止隐式转换的。请看下面的代码:
  1. class Test1
  2. {
  3. public:
  4.     Test1(int n)
  5.     {
  6.         num=n;
  7.     }//普通构造函数
  8. private:
  9.     int num;
  10. };
  11. class Test2
  12. {
  13. public:
  14.     explicit Test2(int n)
  15.     {
  16.         num=n;
  17.     }//explicit(显式)构造函数
  18. private:
  19.     int num;
  20. };
  21. int main()
  22. {
  23.     Test1 t1=12;//隐式调用其构造函数,成功
  24.     Test2 t2=12;//编译错误,不能隐式调用其构造函数
  25.     Test2 t2(12);//显式调用成功
  26.     return 0;
  27. }
复制代码
Test1的构造函数带一个int型的参数,代码23行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此代码24行会出现编译错误。
普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则