C++ 修饰符const、static、extern、ref、volatile、explicit总结
来源:互联网 发布:sql中什么是主键 编辑:程序博客网 时间:2024/05/16 17:21
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是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 C4114const 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; //errorc_ptr = &d;
结果:*c_ptr 值是常量,c_ptr指针可以修改。
实践2:int const* c_ptr2 = &c;
int const* c_ptr2 = &c;//*c_ptr2 = 12; //errorc_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; //errorc_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 __cplusplusextern "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,只是可以识别该关键字,功能还无法支持。但是其大概用法应如下,
//vs2013constexpr int get_arr_size(){ return 3;}void my_constexpr(){ int arr[get_arr_size()];}
本文源码:cpp_keywords
参考文献:
Marc, Gregoire. C++高级编程(第3版)[M]. 北京:清华大学出版社, 2015.
- C++ 修饰符const、static、extern、ref、volatile、explicit总结
- C/C++修饰符static、const、extern
- C/C++中修饰符const、extern、static、volatile的用法【解决了】
- C/C++中修饰符const、extern、static、volatile的用法
- C-static,const,volatile,extern
- Objective-C 语法修饰符 static extern const
- auto、static、register、const、volatile 、extern 总结
- Static、Extern、Volatile及Const关键字总结
- Static、Extern、Volatile及Const关键字总结
- Static、Extern、Volatile及Const关键字总结
- const,static,extern,volatile
- overlay/static/register/atuo/extern/volatile/const 修饰符的用法
- objectiveC【语法】修饰符 static extern const
- objectiveC【语法】修饰符 static extern const
- objectiveC【语法】修饰符 static extern const
- 修饰符 static extern const (转载)
- objectiveC修饰符 static extern const
- objectiveC【语法】修饰符 static extern const
- 面向对象和面向过程
- Java中的网络编程
- MySQL中的触发器insert、update
- HashSet、LinkedHashSet、TreeSet的区别
- Maven入门指南12:将项目发布到私服
- C++ 修饰符const、static、extern、ref、volatile、explicit总结
- Groovy学习:第四章 Groovy特性深入
- jQuery部分疑问及小结
- 深度学习数据整理——Python读写xml文件
- 今日头条开通,分享我爱的数码科技
- Guava之controller中使用缓存cache
- 高内聚低耦合的介绍
- 区别:javascript:void(0);javascript:;
- Spring学习笔记(7)——Bean的基本配置