C++ 修饰符const、static、extern、ref、volatile、explicit总结
2017-07-15 13:09
597 查看
C++里面有不少知识点是与其本身的关键字紧密结合的。本文即讲到了常用的const、static、ref、enum,也会介绍一些不太常用或者一些较新的关键词:extern、volatile、auto、decltype、constexpr、explicit,最后再附带介绍了一下C++正则regex方面的知识。这里注意ref包括左值引用、右值引用两大部分,其中右值引用一般较少见。
目录:
const
修饰变量
与类结合
ref
左值引用
右值引用
static
extern
共享变量
C语言命名问题
volatile
explicit
auto_decltype
enum
regex
constexpr
在商业化编程中,熟练使用const是一门必要的基本功。它的主要使用场景有两大部分,
修饰变量或者参数,使其达到某个常量目的;
与类相结合。
然而,最恼火的是指针的出现,伴随着一串const与pointer的结合。心中一万只野马在奔腾。
初次见到上面这4行代码。如果不是对const十分熟悉的话,十有八九会搞晕。针对这种情况,极为有必要实践并做处相关的笔记。
首先,准备了几个备用变量。
接下来,就陆续给出实践结果。
实践1: const int* c_ptr = &c;
结果:*c_ptr 值是常量,c_ptr指针可以修改。
实践2:int const* c_ptr2 = &c;
结果:*c_ptr 2值是常量,c_ptr2指针可以修改。(也就是说实践1与实践2,两种写法是一样的效果),那么,继续看….
实践3:int* const c_ptr3 = &c;
结果:c_ptr3指针是常量,*c_ptr3值是可以修改的。(同以上2种实践结果恰好相反)。继续…
实践4:const int const* c_ptr4 = &c; //warning C4114
结果:*c_ptr 4值是常量,c_ptr4指针可以修改。该写法被给予了警告:warning C4114: 多次使用同一类型限定符,同时也可以看出这种写法的效果和实践1、2是一致的,直接使用实践1或者2的写法就完全可以,并且不会出现警告。
实践5:const int* const c_ptr5 = &c;
结果:*c_ptr 5值是常量,c_ptr5指针也是常量。这种修饰方式十分稳定,任何内容都是无法修改的。
小结:观察了这5组实践,很快就得出结论了。去掉类型,一眼就能知道谁是常量、谁是可以修改的。举个栗子吧,
e.g.
去掉类型,
第一个const后面修饰的是 *c_ptr5;第二个const修饰的则直接是c_ptr5。如此下来,就将整个内容全部表示为常量了。以此类推,相信很容易解释其他几个实践了。
修饰某个函数;
修饰成员变量。
那么,这些到底有什么作用呢? 纸上得来终觉浅,绝知此事要躬行。
实践是检验真理的唯一标准。
实践1:const int m_const;
结论:const修饰成员变量时,必须使用“冒号语法”对其进行初始化。对该处需要更深入了解的可以阅读《C++ 构造函数执行原理》一文,简明的阐述了构造函数是如何传参、开辟内存、赋初值以及初始化变量的。
实践2:void printConst() const;
结论:以上两个函数属于函数重载。那么这两个函数分别是何时调用呢? 只能再实践一次了。
实践3: const函数何时调用?
结论:如果对象被const修饰的话,将会调用const函数。那么,又需要下一个验证了,
实践4:const修饰的对象,是否可以调用普通函数?
在类中,增加一个普通函数print和普通成员函数m_data。
接下来,我们试图在const修饰的函数中,修改m_data的值,会发生什么呢?
结论:const修饰的函数中,①不能修改任何成员变量值,但是函数中的局部变量是可以修改的;②const修饰的对象,不能调用普通函数。
实践1:左值引用?
结论:引用必须给赋初值,而不是初始化。 同时,给引用二次初始化时,将无法达到引用效果。
之所以称为右值引用,也可以顾名思义——引用的是一个右值。那么,何为右值?[int a = 123;] 这行代码中,a称为左值,123则称为右值。
一般而言,右值是指常量或者是临时变量。
实践2:右值引用?
结论:右值引用可以简单来说,是近年来推出的一种对常量或者临时变量的引用技术。
从上可以看出,static变量是只会被初始化一次的。也就是说不管my_static_test()函数被调用几次
在static技术上想深入研究的,推荐阅读《GoF23设计模式(0)单例模式Singleton》
一文,该文从类的角度讨论了static的使用方式。
第一种方式,
在
在任何处使用
其他文件需要使用nCount变量的话,直接使用步骤1声明即可。(不需要再定义)
还有另外一种方式,
在.h中使用
在其他文件中需要使用nCount变量的话,一律使用第一种方式的步骤1。
将会产生一个func2@@YAHPAD@Z类似中间名称,以区分重载的不同函数。然而,在C语言中是不存在中间名称的。
一旦,混合编程的话,将会报错: error LNK2019: 无法解析的外部符号 “int __cdecl func2(char *)” (?func2@@YAHPAD@Z),该符号在函数 _main 中被引用。
这时候就必须将
但是,我们并不知道当前环境是不是cpp,这时候就需要做个宏判断,
很多时候,由于编译器对代码的优化,将出现不可思议(毁灭性的)的结果。
一个很经典的场景,
将编译器更改至Release状态下,像以上这两个函数,for循环执行了N次空操作,编译器将直接优化为i = 1000000000,一行代码。除非使用volatile 对其明确指出,不用优化。让我们看一下运行时间,
从上,可以看出,果不其然!两个几乎同样的函数,运行时间截然不同。
然而,这种好心的优化,却不总是给程序猿带来好处。特别是在多线程中,要保持十分谨慎。要敢于向编译器提出禁止优化volatile 。看一个多线程的特例,
在没禁止优化的情况下,线程中的输出总是1000000000,这明显是一个错误的数据。
这里,对第二个类构造函数显示添加了explicit 关键字。接下来,测试一下效果:
很明显,Explicit e2 = 10; //error ,这种隐式转换已经被阻止了。
auto可以根据
枚举体一般为整数,也可以显示指定类型;
直接使用枚举作为判断条件时,必须进行强制转换。
实践1:显示指定类型?
实践2: 强制类型转换?
需要明白C++也是有正则这个知识点就可以了,具体使用时,可以即学即用。
本文源码:cpp_keywords
参考文献:
Marc, Gregoire. C++高级编程(第3版)[M]. 北京:清华大学出版社, 2015.
目录:
const
修饰变量
与类结合
ref
左值引用
右值引用
static
extern
共享变量
C语言命名问题
volatile
explicit
auto_decltype
enum
regex
constexpr
const
const是constant的缩写,顾名思义——常量。在商业化编程中,熟练使用const是一门必要的基本功。它的主要使用场景有两大部分,
修饰变量或者参数,使其达到某个常量目的;
与类相结合。
修饰变量
一种最普遍是使用方式,const修饰变量,这样将无法再次修改该变量的值。int const a = 1; //a = 10; //error
然而,最恼火的是指针的出现,伴随着一串const与pointer的结合。心中一万只野马在奔腾。
const int* c_ptr = &c; int const* c_ptr2 = &c; int* const c_ptr3 = &c; const int const* c_ptr4 = &c;//warning C4114 const int* const c_ptr5 = &c;
初次见到上面这4行代码。如果不是对const十分熟悉的话,十有八九会搞晕。针对这种情况,极为有必要实践并做处相关的笔记。
首先,准备了几个备用变量。
int const a = 1; int const b = 2; int c = 10, d = 20;
接下来,就陆续给出实践结果。
实践1: const int* c_ptr = &c;
const int* c_ptr = &c; //*c_ptr = 11; //error c_ptr = &d;
结果:*c_ptr 值是常量,c_ptr指针可以修改。
实践2:int const* c_ptr2 = &c;
int const* c_ptr2 = &c; //*c_ptr2 = 12; //error c_ptr2 = &d;
结果:*c_ptr 2值是常量,c_ptr2指针可以修改。(也就是说实践1与实践2,两种写法是一样的效果),那么,继续看….
实践3:int* const c_ptr3 = &c;
int* const c_ptr3 = &c; //c_ptr3 = &d; //error *c_ptr3 = 13;
结果:c_ptr3指针是常量,*c_ptr3值是可以修改的。(同以上2种实践结果恰好相反)。继续…
实践4:const int const* c_ptr4 = &c; //warning C4114
const int const* c_ptr4 = &c;//warning C4114: 多次使用同一类型限定符 //*c_ptr4 = 14; //error c_ptr4 = &d;
结果:*c_ptr 4值是常量,c_ptr4指针可以修改。该写法被给予了警告:warning C4114: 多次使用同一类型限定符,同时也可以看出这种写法的效果和实践1、2是一致的,直接使用实践1或者2的写法就完全可以,并且不会出现警告。
实践5:const int* const c_ptr5 = &c;
const int* const c_ptr5 = &c; //*c_ptr5 = 15; //error //c_ptr5 = &d; //error
结果:*c_ptr 5值是常量,c_ptr5指针也是常量。这种修饰方式十分稳定,任何内容都是无法修改的。
小结:观察了这5组实践,很快就得出结论了。去掉类型,一眼就能知道谁是常量、谁是可以修改的。举个栗子吧,
e.g.
const int* const c_ptr5 = &c;
去掉类型,
const * const c_ptr5 = &c;
第一个const后面修饰的是 *c_ptr5;第二个const修饰的则直接是c_ptr5。如此下来,就将整个内容全部表示为常量了。以此类推,相信很容易解释其他几个实践了。
与类结合
与类结合之后,可能会面临两个问题,修饰某个函数;
修饰成员变量。
class Constants{ public: void printConst(); void printConst() const; private: const int m_const; };
那么,这些到底有什么作用呢? 纸上得来终觉浅,绝知此事要躬行。
实践是检验真理的唯一标准。
实践1:const int m_const;
Constants::Constants() : m_const (0) { }
结论:const修饰成员变量时,必须使用“冒号语法”对其进行初始化。对该处需要更深入了解的可以阅读《C++ 构造函数执行原理》一文,简明的阐述了构造函数是如何传参、开辟内存、赋初值以及初始化变量的。
实践2:void printConst() const;
void Constants::printConst() { m_data = 100; std::cout << "printConst()." << std::endl; } void Constants::printConst() const { //m_data = 100; //error std::cout << "printConst() const." << std::endl; }
结论:以上两个函数属于函数重载。那么这两个函数分别是何时调用呢? 只能再实践一次了。
实践3: const函数何时调用?
void my_const_class() { Constants c; c.printConst();//printConst(). const Constants c2; c2.printConst();//printConst() const. }
结论:如果对象被const修饰的话,将会调用const函数。那么,又需要下一个验证了,
实践4:const修饰的对象,是否可以调用普通函数?
在类中,增加一个普通函数print和普通成员函数m_data。
class Constants{ public: void print(); //... private: int m_data; //... };
接下来,我们试图在const修饰的函数中,修改m_data的值,会发生什么呢?
void Constants::print() { m_data = 100; std::cout << "print()." << std::endl; } void Constants::printConst() const { //m_data = 100; //error int a = 10; a = 20; std::cout << "printConst() const." << std::endl; } void my_const_class() { Constants c; c.print();//print(). const Constants c2; //c2.print(); //error c2.printConst();//printConst() const. }
结论:const修饰的函数中,①不能修改任何成员变量值,但是函数中的局部变量是可以修改的;②const修饰的对象,不能调用普通函数。
ref
引用是一门艺术,减少了很多不必要的拷贝构造,几大的加快了参数的传递速度。随着C++11的推进,有出现了一种右引用的技术。左值引用
左值引用就是我们常见的引用使用方式,符号(&),比如:int a = 10; int& b_ref = a;
实践1:左值引用?
void my_left_ref()
{
int a = 10; int& b_ref = a;
//int& c; //error
int c = 11;
b_ref = c;
b_ref = 12;
std::cout << a << "," << b_ref << "," << c << std::endl;//12,12,11
char* str_a = "aaa";
char*& str_ref_b = str_a;
char* str_c = "ccc";
str_ref_b = str_c;
str_ref_b = "ref";
std::cout << str_a << "," << str_ref_b << "," << str_c << std::endl;//ref,ref,ccc
}
结论:引用必须给赋初值,而不是初始化。 同时,给引用二次初始化时,将无法达到引用效果。
右值引用
右值引用是这几年才发展起来的,符号(&&),它的出现是由于存在这类现象,比如:int&& a = 10;
之所以称为右值引用,也可以顾名思义——引用的是一个右值。那么,何为右值?[int a = 123;] 这行代码中,a称为左值,123则称为右值。
一般而言,右值是指常量或者是临时变量。
实践2:右值引用?
void right_ref_params(int&& val){ std::cout << val << std::endl;//3 } void my_right_ref(){ int&& a = 10; std::cout << a << std::endl;//10 right_ref_params(1 + 2); }
结论:右值引用可以简单来说,是近年来推出的一种对常量或者临时变量的引用技术。
static
static静态技术,在实际编程中也使用的较为广泛。主要利用了static变量具有永恒生命周期——除非进程(exe)退出了,否则static变量一旦初始化之后,就不会被释放。void my_static_test(){ static int count = 1; std::cout << count << std::endl; ++count; } void my_static(){ my_static_test();//1 my_static_test();//2 }
从上可以看出,static变量是只会被初始化一次的。也就是说不管my_static_test()函数被调用几次
static int count = 1;这行代码都只会被执行一次。
在static技术上想深入研究的,推荐阅读《GoF23设计模式(0)单例模式Singleton》
一文,该文从类的角度讨论了static的使用方式。
extern
如何共享一个变量?或者说如何使用别的.cpp中的变量?C和C++命名差异如何解决?答案在这里extern。共享变量
在项目设计中,其实extern也是很常见的使用技术,一个变量如何在多处共享呢?第一种方式,
在
.h中使用
extern int nCount;声明nCount变量为外部共享变量。
在任何处使用
int nCount;或者
int nCount = 0真正定义该变量,注意这行语句仅且只能出现一次。
其他文件需要使用nCount变量的话,直接使用步骤1声明即可。(不需要再定义)
还有另外一种方式,
在.h中使用
extern int nCount = 0;声明为外部共享变量的同时,定义该变量。但是注意,由于定义了变量,那么该语句也只能出现一次。同时,不需要再进行第一种方式中的第2步了。
在其他文件中需要使用nCount变量的话,一律使用第一种方式的步骤1。
C语言命名问题
在C语言与C++混合编程中,如果你使用了.c文件,命名问题是一个突出问题。在C++中,由于存在函数重载技术,编译链接时存在一个中间名称,比如以下函数:
int func2(char* str);
将会产生一个func2@@YAHPAD@Z类似中间名称,以区分重载的不同函数。然而,在C语言中是不存在中间名称的。
一旦,混合编程的话,将会报错: error LNK2019: 无法解析的外部符号 “int __cdecl func2(char *)” (?func2@@YAHPAD@Z),该符号在函数 _main 中被引用。
这时候就必须将
.c文件的头文件中,所有内容使用extern “C”括起来,
extern "C"{ }
但是,我们并不知道当前环境是不是cpp,这时候就需要做个宏判断,
#ifdef __cplusplus #if __cplusplus extern "C"{ #endif #endif /* __cplusplus */ void func1(); int func2(char* str); #ifdef __cplusplus #if __cplusplus } #endif #endif /* __cplusplus */
volatile
当见到,volatile关键词时,应立即想到编译器优化功能。而实际volatile关键词的作用就是——禁止编译器对其修饰的内容进行优化。很多时候,由于编译器对代码的优化,将出现不可思议(毁灭性的)的结果。
一个很经典的场景,
void my_volatile_yes() { for (volatile int i = 0; i < 1000000000; i++); } void my_volatile_no() { for (int i = 0; i < 1000000000; i++); }
将编译器更改至Release状态下,像以上这两个函数,for循环执行了N次空操作,编译器将直接优化为i = 1000000000,一行代码。除非使用volatile 对其明确指出,不用优化。让我们看一下运行时间,
void my_volatile() {//release long begin = clock(); my_volatile_yes(); long end = clock(); std::cout << end - begin << std::endl;//2154ms begin = clock(); my_volatile_no(); end = clock(); std::cout << end - begin << std::endl;//0ms }
从上,可以看出,果不其然!两个几乎同样的函数,运行时间截然不同。
然而,这种好心的优化,却不总是给程序猿带来好处。特别是在多线程中,要保持十分谨慎。要敢于向编译器提出禁止优化volatile 。看一个多线程的特例,
/*volatile*/ int g_val = 0; unsigned int __stdcall run_volatile_thread(void* context){ std::cout << g_val << std::endl;//1000000000 return 0; } void my_volatile_impl(){ for (g_val = 0; g_val < 1000000000; ++g_val); } void my_volatile2(){ _beginthreadex(NULL, 0, run_volatile_thread, NULL, 0, NULL); my_volatile_impl(); Sleep(1000); }
在没禁止优化的情况下,线程中的输出总是1000000000,这明显是一个错误的数据。
explicit
该关键字,一般伴随着C++类而存在。常见的作用是:阻止不应该允许的经过转换构造函数进行的隐式转换的发生。class ExplicitNo{ public: ExplicitNo(int val); ~ExplicitNo(); }; class Explicit{ public: explicit Explicit(int val); ~Explicit(); };
这里,对第二个类构造函数显示添加了explicit 关键字。接下来,测试一下效果:
void my_explicit() { ExplicitNo en(10); ExplicitNo en2 = 10; Explicit e(10); //Explicit e2 = 10; //error }
很明显,Explicit e2 = 10; //error ,这种隐式转换已经被阻止了。
auto_decltype
这是两个关键字,auto与decltype。主要用于类型推导。void my_auto_decltype() { auto x = 123; //auto : int decltype(x) y = 456; //decltype(x) : int std::cout << x << "," << y << std::endl;//123,456 }
auto可以根据
123自动推导出int,而decltype(x)却可以根据x自动推导出int。这在模板中,发挥着极大作用。
enum
枚举体,这里主要介绍两个功能:枚举体一般为整数,也可以显示指定类型;
直接使用枚举作为判断条件时,必须进行强制转换。
实践1:显示指定类型?
enum class Fruit : unsigned long { apple, orange, peer = 7ul, mango, };
实践2: 强制类型转换?
void my_enum() { Fruit fruit = Fruit::mango; //if (fruit == 8UL) //error if ((unsigned long)fruit == 8ul){ std::cout << "mango" << std::endl; } }
regex
正则内容很是庞杂,这里一笔带过….需要明白C++也是有正则这个知识点就可以了,具体使用时,可以即学即用。
void my_regex(){ std::regex r("\\d{4}"); char* str = "20170714"; bool ret = std::regex_match(str, r); if (ret){ std::cout << "valid number." << std::endl; } else{ std::cout << "invalid number." << std::endl; } }
constexpr
constexpr是为了解决动态数组问题而设计的。笔者目前使用的是Microsoft Visual Studio 2013,只是可以识别该关键字,功能还无法支持。但是其大概用法应如下,//vs2013 constexpr int get_arr_size(){ return 3; } void my_constexpr(){ int arr[get_arr_size()]; }
本文源码:cpp_keywords
参考文献:
Marc, Gregoire. C++高级编程(第3版)[M]. 北京:清华大学出版社, 2015.
相关文章推荐
- C/C++中修饰符const、extern、static、volatile的用法【解决了】
- C/C++中修饰符const、extern、static、volatile的用法
- C/C++中修饰符const、extern、static、volatile的用法【解决了】
- C/C++中修饰符const、extern、static、volatile的用法【解决了】
- Static、Extern、Volatile及Const关键字总结
- C++ 关键字 static register const volatile extern
- Static、Extern、Volatile及Const关键字总结
- 关键字:auto、static、register、const、volatile 、extern 总结
- (转)C/C++中 const,extern,static,volatile的使用
- auto、static、register、const、volatile 、extern 总结【转贴】
- 关键字:auto、static、register、const、volatile 、extern 总结
- C/C++中存储类型修饰符的区别(auto、static、register、extern、volatile、restrict)
- overlay/static/register/atuo/extern/volatile/const 修饰符的用法
- C/C++中存储类型修饰符的区别(auto、static、register、extern、volatile、restrict)
- 关键字:auto、static、register、const、volatile 、extern 总结
- 关键字:auto、static、register、const、volatile 、extern 总结【转贴】
- C/C++中 const,extern,static,volatile的使用(转帖)
- C/C++中 const,extern,static,volatile的使用 200
- 关键字:auto、static、register、const、volatile 、extern 总结
- C语言关键字:auto、static、register、const、volatile 、extern 总结 <转>