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是一门必要的基本功。它的主要使用场景有两大部分,

  1. 修饰变量或者参数,使其达到某个常量目的;
  2. 与类相结合。

修饰变量

一种最普遍是使用方式,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。如此下来,就将整个内容全部表示为常量了。以此类推,相信很容易解释其他几个实践了。

与类结合

与类结合之后,可能会面临两个问题,

  1. 修饰某个函数;
  2. 修饰成员变量。
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也是很常见的使用技术,一个变量如何在多处共享呢?

第一种方式,

  1. .h中使用extern int nCount;声明nCount变量为外部共享变量。
  2. 在任何处使用int nCount;或者int nCount = 0真正定义该变量,注意这行语句仅且只能出现一次。
  3. 其他文件需要使用nCount变量的话,直接使用步骤1声明即可。(不需要再定义)

还有另外一种方式,

  1. 在.h中使用extern int nCount = 0; 声明为外部共享变量的同时,定义该变量。但是注意,由于定义了变量,那么该语句也只能出现一次。同时,不需要再进行第一种方式中的第2步了。
  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. 枚举体一般为整数,也可以显示指定类型;
  2. 直接使用枚举作为判断条件时,必须进行强制转换。

实践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.

阅读全文
0 0
原创粉丝点击