重要的关键字(一)

✏️ 1、auto

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量。C++11中,auto有了全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。通俗地讲,auto关键字是可以自动推导变量类型的。

auto不是一个类型的“声明”,而是一个“占位符”,编译器在编译期会将auto替换为变量实际的类型。使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。它自动推导变量类型是根据“=”右侧的变量类型决定的。

🖋️ 1.1、使用规则

1. auto与指针和引用结合起来使用

auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&

2. 在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对 第一个类型进行推导,然后用推导出来的类型定义其他变量。

3. auto不能作为函数的参数

参数要被编译成指令,但是开辟空间时候需要知道空间大小,auto做参数不知道多大,那么栈帧也不知道开多大。

4. auto不能直接用来声明数组

因为数组也涉及大小,在下面的例子中,a的类型严格来说是 int [3],所以b的大小也不确定。

int a[] = {1,2,3};
auto b[3] = a;

5. auto在实际中最常见的优势用法是C++11提供的新式for循环,还有lambda表达式等进行配合使用。

6. auto不能定义类的非静态成员变量。

7. 实例化模板时不能使用auto作为模板参数。

✏️ 2、mutable

mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。

被const关键字修饰的函数的一个重要作用就是为了能够保护类中的成员变量。即:该函数可以使用类中的所有成员变量,但是不能修改他们的值。然而,在某些特殊情况下,我们还是需要在const函数中修改类的某些成员变量,因为要修改的成员变量与类本身并无多少关系,即使修改了也不会对类造成多少影响。

🖋️ 2.1、Lambda 表达式 & mutable

✏️ 3、static

✏️ 4、volatile

C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。当要使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile int i=10;
int a = i;
 ... // 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i 是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。注意,在 VC 6.0 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。

#include <stdio.h>	 
void main(){
   int i = 10;
   int a = i;
   printf("i = %d ", a);	 
   // 下面汇编语句的作用就是改变内存中 i 的值
   // 但是又不让编译器知道
   __asm {
      mov dword ptr [ebp-4], 20h  
      // EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,
      // 该指针永远指向系统栈最上面一个栈帧的底部。
   }
   int b = i;
   printf("i = %d", b);
}

分别在 Debug 和 Release 版本运行程序,输出都是:i = 10 i = 10

如果将变量i声明成volatile int i = 10;,则在 Debug 下输出为i = 10 i = 10,在Release下输出为i = 10 i = 32。(测试环境:Visual Studio 2019——MSVC16.0

说明这个 volatile 关键字发挥了它的作用。其实不只是“内嵌汇编操纵栈”这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;

  2. 多任务环境下各任务间共享的标志应该加volatile;

  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

🖋️ 4.1、volatile与指针

和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:

  • 修饰由指针指向的对象、数据是 volatile 的:volatile char *vpch;

  • 指针自身的值——一个代表地址的整数变量,是 volatile 的:char* volatile pchv;

注意:

  1. 可以把一个非 volatile int 赋给 volatile int,但是不能把非 volatile 对象赋给一个 volatile 对象。

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

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

🖋️ 4.2、 多线程下的volatile

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

volatile bool bStop = FALSE;  
// (1) 在一个线程中:  
    while( !bStop ) { ... }  
    bStop  =  FALSE;  
    return;    
// (2) 在另外一个线程中,要终止上面的线程循环:  
    bStop = TRUE;  
    while( bStop ); //等待上面的线程终止,如果bStop不使用volatile申明,
                    //那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,
                    //寄存器中bStop的值永远不会变成FALSE,加上volatile,
                    //程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。

这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度。

✏️ 5、extern

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。

🖋️ 5.1、外部声明

🐹 5.1.1、引用同一个文件中的全局变量

在C语言中,程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

#include <stdio.h>
int max(int x,int y);

int main(void)
{
    int result;
    /*外部变量声明*/
    extern int g_X;
    extern int g_Y;
    result = max(g_X,g_Y);
    printf("the max value is %d\n",result);
    return 0;
}
/*定义两个全局变量*/
int g_X = 10;
int g_Y = 20;
int max(int x, int y)
{
    return (x>y ? x : y);
}

代码中,全局变量 g_X 与 g_Y 是在 main 函数之后声明的,因此它的作用范围不在 main 函数中。如果我们需要在 main 函数中调用它们,就必须使用 extern 来对变量 g_X 与 g_Y 作“外部变量声明”,以扩展全局变量的作用域。也就是说,如果在变量定义之前要使用该变量,则应在使用之前加 extern 声明变量,使作用域扩展到从声明开始到本文件结束。

注:extern关键字只需要指明类型和变量名,不能再重新赋值,初始化需要在定义的地方进行,如果不进行初始化的话,全局变量会被编译器自动初始化为 类型对应的默认值

🐹 5.1.2、引用另一个文件中的全局变量

如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。

/*  max.cpp */
int g_X = 10;
int g_Y = 20;
int max(int x, int y)
{
    return (x>y ? x : y);
}

/*main.cpp*/
#include <stdio.h>
extern int max(int x,int y);

int main(void)
{
    int result;
    /*外部变量声明*/
    extern int g_X;
    extern int g_Y;
    result = max(g_X,g_Y);
    printf("the max value is %d\n",result);
    return 0;
}

在一个文件中定义了变量和函数, 在其他文件中要使用它们, 可以有两种方式:

  1. 使用头文件,然后声明它们,然后其他文件去包含头文件。

  2. 在其他文件中直接extern

extern的引用方式比包含头文件要简洁得多,extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程。

注:定义和声明的类型必须对应,如:

int g_Array[10];
extern int g_Array[]; // 不能省略 []

🐹 5.1.3、模块化

对于模块化的程序文件,可在其文件中预先留好外部变量的接口,也就是只采用 extern 声明变量,而不定义变量。

/****max.c****/
#include <stdio.h>
/*外部变量声明*/
extern int g_X ;
extern int g_Y ;
int max()
{
    return (g_X > g_Y ? g_X : g_Y);
}

/***main.c****/
#include <stdio.h>
/*定义两个全局变量*/
int g_X=10;
int g_Y=20;
int max();  // extern可省略
int main(void)
{
    int result;
    result = max();
    printf("the max value is %d\n",result);
    return 0;
}

通常,这些外部变量的接口都是在模块程序的头文件中声明的,当需要使用该模块时,只需要在使用时具体定义一下这些外部变量即可。

注:不过,需要特别注意的是,由于用 extern 引用外部变量,可以在引用的模块内修改其变量的值,因此,如果有多个文件同时要对应用的变量进行操作,而且可能会修改该变量,那就会影响其他模块的使用。

🖋️ 5.2、链接之extern "C"

在C++环境下使用C函数的时候,常常会出现编译器无法找到obj模块中的C函数定义,从而导致链接失败的情况,应该如何解决这种情况呢? C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。

比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL 输出(Export) 的函数,你需要用extern "C" 来强制编译器不要修改你的函数名(即头文件中声明的函数)。头文件中声明函数时要用条件编译包含起来,下面是一个标准的写法:

//在.h文件中
#ifdef __cplusplus
#if __cplusplus
extern "C"{
 #endif
 #endif /* __cplusplus */ 
 …
 …
 //.h文件结束的地方
 #ifdef __cplusplus
 #if __cplusplus
}
#endif
#endif /* __cplusplus */ 

如果该头文件是别人写好,则无法修改。这个时候就要定义C++自己的头文件,文件名为"CStack.h":

// CStack.h
extern "C" {
    #include "Stack.h";
}

最后更新于