C / C ++ Rarer关键字 – register,volatile,extern,explicit

您能否快速了解这四个关键字的用途以及原因?

我理解谷歌会在注册和易失性方面告诉你的基础知识,但想了解更多(仅仅是一个实用的概述)。 虽然做了相当低级的嵌入式系统代码,但是我从来没有找到过自己使用它们的理由,因此外部和明确地让我感到困惑。 再一次,我可以谷歌,但我更喜欢专家的快速,实用的总结,所以它坚持我的想法。

EXTERN

extern超载用于多种用途。 对于全局变量,它意味着它声明变量,而不是定义变量。 这对于将全局变量放在标头中很有用。 如果你把它放在标题中:

 int someInteger; 

包含该标头的每个.cpp文件都会尝试拥有自己的someInteger 。 这将导致链接器错误。 通过extern声明它,你所说的只是在代码中的某个地方会有一个someInteger

 extern int someInteger; 

现在,在.cpp文件中,您可以定义int someInteger ,以便只有一个副本。

还有extern "C" ,用于指定某些函数使用C-linkage规则而不是C ++。 这对于与编译为C的库和代码的接口很有用。

在C ++ 0x中,还会有extern template声明。 这些与显式模板实例化相反。 当你这样做:

 template class std::vector; 

你告诉编译器现在就实例化这个模板。 通常,实例化会延迟到第一次使用模板为止。 在C ++ 0x中,您可以说:

 extern template class std::vector; 

这告诉编译器永远不要在这个.cpp文件中实例化这个模板。 这样,您可以控制实例化模板的位置。 明智地使用它可以大大缩短编译时间。

明确的

这用于防止类型的自动转换。 如果你有一个类ClassName与以下构造函数:

 ClassName(int someInteger); 

这意味着如果你有一个带有ClassName的函数,用户可以使用int调用它,并且转换将自动完成。

 void SomeFunc(const ClassName &className); SomeFunc(3); 

这是合法的,因为ClassName有一个带整数的转换构造函数。 这就是采用std::string函数也可以使用char* ; std::string有一个带char*的构造函数。

但是,大多数情况下,您不希望像这样进行隐式转换。 您通常只希望转换是明确的。 是的,它有时像std :: string一样有用,但你需要一种方法来关闭不合适的转换。 输入explicit

 explicit ClassName(int someInteger); 

这将阻止隐式转换。 你仍然可以使用SomeFunc(ClassName(3));SomeFunc(3)将不再有效。

顺便说一句:如果对你来说很少见,那么你就不会使用它了。 您应该始终使用它,除非您特别想要转换。 这不常见。

挥发物

这可以防止某些有用的优化。 通常情况下,如果你有一个变量,C / C ++会假设它的内容只有在明确改变它们时才会改变。 所以如果你声明一个int someInteger; 作为一个全局变量,C / C ++编译器可以在本地缓存该值,而不是每次使用它时都不经常访问该值。

有时,你想要阻止它。 在这些情况下,你使用volatile ; 这可以防止这些优化。

寄存器

这只是一个提示。 它告诉编译器尝试将变量的数据放入寄存器中。 这基本上是不必要的; 编译器比决定什么应该和不应该是一个寄存器更好。

register用作编译器的提示,即变量应存储在寄存器中而不是存储在堆栈中。 编译器经常会忽略它并做任何他们想做的事情; 如果可能的话,变量将被分配到寄存器中。

volatile指示内存可能会在程序实际执行任何操作时更改。 这是编译器的另一个提示,它应该避免优化对该位置的访问。 例如,如果您有两次连续写入同一位置而没有插入读取,则编译器可能会优化掉第一个。 但是,如果您要写入的位置是硬件寄存器,则需要完成所有写操作,完全按照写入。 如此volatile就像是说“只相信我”。

extern表示定义发生在当前文件之外。 对全局变量有用,但无论如何通常都隐含在声明中。 正如Blindy所说,它对于指示一个函数应该具有C链接也很有用。 具有C链接的函数将使用其实际名称作为其输出可执行文件中的符号进行编译。 C ++函数包含更多信息,例如符号中参数的类型。 这就是为什么重载在C ++中工作但在C中不工作的原因。

explicit适用于C ++构造函数。 这意味着不应该隐式调用构造函数。 例如,假设您有一个Array类,其构造函数接受整数capacity 。 您不希望整数值被隐式转换为Array对象,因为它有一个整数构造函数。

register现在大多被忽略了,但它应该向编译器暗示你宁愿将变量放在寄存器而不是堆栈中。 优化器现在做得很好,现在它无关紧要。

volatile告诉编译器不要假设该值仅从当前线程更改,因此它将始终读取实际内存值,而不是在读取之间缓存它。 它对multithreading应用程序很有用,但最好还是使用OS原语(事件,信号量等)。

extern没有定义值,它只声明它。 它主要用于从DLL或共享库( .a )导出和导入函数。 副作用还允许您使用extern "C"关闭C ++的名称修改。

而且explicit允许你指定一个构造函数必须是显式的,而不是隐式转换构造函数(你可以写CMyClass val=10;如果它有一个带有int的隐式构造函数)。

register用于指示编译器使用寄存器来存储该值,现代编译器优化以在for循环期间使用它等。

volatile是可由外部进程或multithreading应用程序运行时更改的变量。

extern告诉链接器该变量是在不同的文件中定义的。

explicit指示编译器不允许隐式转换类型。

1:当您希望强制将值保存在寄存器中而不是保存在RAM中时,使用寄存器。 例如,您可能会执行以下操作:

 register int x; 

这将让编译器知道您希望将此int放在CPU寄存器中,这样可以更快地访问它。 但是,允许您的编译器在优化过程中忽略此关键字,并且大多数时候,如果需要的话,优秀的编译器会将变量放在寄存器中。 现在我认为它主要是一个冗余关键字。

2:对编译器的易失性提示:变量将定期更改,例如,如果将某个float定义为volatile:

 volatile float flt; 

这告诉编译器您要执行特定于定期更改变量的优化。 它还声明变量可能会在没有活动程序输入的情况下发生变化。 同样,现在是一个主要冗余的关键字(multithreading编程除外)。

3:Extern告诉编译器该定义是在另一个文件中声明变量的文件,例如你可能有一些通用的头文件,你在大多数文件中都包含这个文件,这里你可能想要声明一些全局指针,所以你会做以下事情:

 extern MyClass* g_pClassPointer; 

然后,您将继续在cpp文件的顶部声明MyClass的实现:

 MyClass* g_pClassPointer = nullptr; 

extern关键字还用于向编译器声明您正在使用原始C代码或ASM代码,例如,您可以执行以下操作:

 extern __asm { mov eax, 2 mov ebx, 3 add eax, ebx } 

或者如果你只是想使用原始C代码,你可以使用extern "C" ,你的编译器就会认识到这一点。

4:显式是在您不希望在构造函数中进行隐式转换时使用,有关更多信息,请参阅此线程 。 它主要用于调试目的,并确保在执行OOP时遵守更严格的规则。