C++总结:C++中的const和constexpr(转)

C++总结:C++中的const和constexpr

C++中的const可用于修饰变量、函数,且在不一样的地方有着不一样的含义,现总结以下。html

const的语义

C++中的const的目的是经过编译器来保证对象的常量性,强制编译器将全部可能违背const对象的常量性的操做都视为error。数组

对象的常量性能够分为两种:物理常量性(即每一个bit都不可改变)和逻辑常量性(即对象的表现保持不变)。C++中采用的是物理常量性,例以下面的例子:缓存

1安全

2函数

3优化

4this

5spa

6指针

7code

struct A {

    int *ptr;

};

int k = 5, r = 6;

const A a = {&k};

a.ptr = &r;

*a.ptr = 7;

a是const对象,则对a的任何成员进行赋值都会被视为error,但若是不改动ptr,而是改动ptr指向的对象,编译器就不会报错。这实际上违背了逻辑常量性,由于A的表现已经改变了!

逻辑常量性的另外一个特色是,const对象中能够有某些用户不可见的域,改变它们不会违背逻辑常量性。Effective C++中的例子是:

1

2

3

4

5

6

7

8

9

class CTextBlock {

public:

    ...

    std::size_t length() const;

private:

    char *pText;

    std::size_t textLength;           

    bool lengthIsValid;               

};

CTextBlock对象每次调用length方法后,都会将当前的长度缓存到textLength成员中,而lengthIsValid对象则表示缓存的有效性。这个场景中textLength和lengthIsValid若是改变了,实际上是不违背CTextBlock对象的逻辑常量性的,但由于改变了对象中的某些bit,就会被编译器阻止。C++中为了解决此问题,增长了mutable关键字。

本部分总结:C++中const的语义是保证物理常量性,但经过mutable关键字能够支持一部分的逻辑常量性。

const修饰变量

如上节所述,用const修饰变量的语义是要求编译器去阻止全部对该变量的赋值行为。所以,必须在const变量初始化时就提供给它初值:

1

2

3

const int i;

i = 5;

const int j = 10;

这个初值能够是编译时即肯定的值,也能够是运行期才肯定的值。若是给整数类型的const变量一个编译时初值,那么能够用这个变量做为声明数组时的长度:

1

2

3

4

const int COMPILE_CONST = 10;

const int RunTimeConst = cin.get();

int a1[COMPLIE_CONST];

int a2[RunTimeConst];

由于C++编译器能够将数组长度中出现的编译时常量直接替换为其字面值,至关于自动的宏替换。(gcc验证发现,只有数组长度那里直接作了替换,而其它用COMPILE_CONST赋值的地方并无进行替换。)

文件域的const变量默认是文件内可见的,若是须要在b.cpp中使用a.cpp中的const变量M,须要在M的初始化处增长extern:

1

2

3

4

5

extern const int M = 20;

extern const int M;

通常认为将变量的定义放在.h文件中会致使全部include该.h文件的.cpp文件都有此变量的定义,在连接时会形成冲突。但将const变量的定义放在.h文件中是能够的,编译器会将这个变量放入每一个.cpp文件的匿名namespace中,于是属因而不一样变量,不会形成连接冲突。(注意:但若是头文件中的const量的初始值依赖于某个函数,而每次调用此函数的返回值不固定的话,会致使不一样的编译单元中看到的该const量的值不相等。猜想:此时将该const量做为某个类的static成员可能会解决此问题。)

const修饰指针与引用

const修饰引用时,其意义与修饰变量相同。但const在修饰指针时,规则就有些复杂了。

简单的说,能够将指针变量的类型按变量名左边最近的‘*’分红两部分,右边的部分表示指针变量本身的性质,而左边的部分则表示它指向元素的性质:

const指针的解读规则差很少就是这些了……

指针自身为const表示不可对该指针进行赋值,而指向物为const则表示不可对其指向进行赋值。所以能够将引用当作是一个自身为const的指针,而const引用则是const Type * const指针。

指向为const的指针是不能够赋值给指向为非const的指针,const引用也不能够赋值给非const引用,但反过来就没有问题了,这也是为了保证const语义不被破坏。

能够用const_cast来去掉某个指针或引用的const性质,或者用static_cast来为某个非const指针或引用加上const性质:

1

2

3

4

int i;

const int *cp = &i;

int *p = const_cast<int *>(cp);

const int *cp2 = static_cast<const int *>(p);

C++类中的this指针就是一个自身为const的指针,而类的const方法中的this指针则是自身和指向都为const的指针。

类中的const成员变量

 

类中的const成员变量可分为两种:非static常量和static常量。

非static常量:

类中的非static常量必须在构造函数的初始化列表中进行初始化,由于类中的非static成员是在进入构造函数的函数体以前就要构造完成的,而const常量在构造时就必须初始化,构造后的赋值会被编译器阻止。

1

2

3

4

5

6

7

8

class B {

public:

    B(): name("aaa") {

        name = "bbb";

    }

private:

    const std::string name;

};

static常量:

static常量是在类中直接声明的,但要在类外进行惟一的定义和初始值,经常使用的方法是在对应的.cpp中包含类的static常量的定义:

1

2

3

4

5

6

7

8

class A {

    ...

    static const std::string name;

};

const std::string A::name("aaa");

一个特例是,若是static常量的类型是内置的整数类型,如char、int、size_t等,那么能够在类中直接给出初始值,且不须要在类外再进行定义了。编译器会将这种static常量直接替换为相应的初始值,至关于宏替换。但若是在代码中咱们像正常变量那样使用这个static常量,如取它的地址,而不是像宏同样只使用它的值,那么咱们仍是须要在类外给它提供一个定义,但不须要初始值了(由于在声明处已经有了)。

1

2

3

4

5

6

7

8

class A {

    ...

    static const int SIZE = 50;

};

const int A::SIZE = 50;

const修饰函数

C++中能够用const去修饰一个类的非static成员函数,其语义是保证该函数所对应的对象自己的const性。在const成员函数中,全部可能违背this指针const性(const成员函数中的this指针是一个双const指针)的操做都会被阻止,如对其它成员变量的赋值以及调用它们的非const方法、调用对象自己的非const方法。但对一个声明为mutable的成员变量所作的任何操做都不会被阻止。这里保证了必定的逻辑常量性。

另外,const修饰函数时还会参与到函数的重载中,即经过const对象、const指针或引用调用方法时,优先调用const方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class A {

public:

    int &operator[](int i) {

        ++cachedReadCount;

        return data[i];

    }

    const int &operator[](int i) const {

        ++size;

        --size;

        ++cachedReadCount;

        return data[i];

    }

private:

    int size;

    mutable cachedReadCount;

    std::vector<int> data;

};

A &a = ...;

const A &ca = ...;

int i = a[0];

int j = ca[0];

a[0] = 2;

ca[0] = 2;

这个例子中,若是两个版本的operator[]有着基本相同的代码,能够考虑在其中一个函数中去调用另外一个函数来实现代码的重用(参考Effective C++)。这里咱们只能用非const版本去调用const版本。

1

2

3

int &A::operator[](int i) {

    return const_cast<int &>(static_cast<const A &>(*this).operator[](i));

}

其中为了不调用自身致使死循环,首先要将*this转型为const A &,可使用static_cast来完成。而在获取到const operator[]的返回值后,还要手动去掉它的const,可使用const_cast来完成。通常来讲const_cast是不推荐使用的,但这里咱们明确知道咱们处理的对象实际上是非const的,那么这里使用const_cast就是安全的。

constexpr

constexpr是C++11中新增的关键字,其语义是“常量表达式”,也就是在编译期可求值的表达式。最基础的常量表达式就是字面值或全局变量/函数的地址或sizeof等关键字返回的结果,而其它常量表达式都是由基础表达式经过各类肯定的运算获得的。constexpr值可用于enum、switch、数组长度等场合。

constexpr所修饰的变量必定是编译期可求值的,所修饰的函数在其全部参数都是constexpr时,必定会返回constexpr。

1

2

3

4

5

6

7

constexpr int Inc(int i) {

    return i + 1;

}

constexpr int a = Inc(1);

constexpr int b = Inc(cin.get());

constexpr int c = a * 2 + 1;

constexpr还能用于修饰类的构造函数,即保证若是提供给该构造函数的参数都是constexpr,那么产生的对象中的全部成员都会是constexpr,该对象也就是constexpr对象了,可用于各类只能使用constexpr的场合。注意,constexpr构造函数必须有一个空的函数体,即全部成员变量的初始化都放到初始化列表中。

1

2

3

4

5

6

7

struct A {

    constexpr A(int xx, int yy): x(xx), y(yy) {}

    int x, y;

};

constexpr A a(1, 2);

enum {SIZE_X = a.x, SIZE_Y = a.y};

constexpr的好处:

  1. 是一种很强的约束,更好地保证程序的正确语义不被破坏。
  2. 编译器能够在编译期对constexpr的代码进行很是大的优化,好比将用到的constexpr表达式都直接替换成最终结果等。
  3. 相比宏来讲,没有额外的开销,但更安全可靠。

 

原文连接:http://www.cnblogs.com/fuzhe1989/p/3554345.html

相关文章
相关标签/搜索