[cpp primer随笔] 03.常量

1. 常量

const可以定义常量对象,这种对象一经定义,在程序运行期间无法被修改。也因此,常量对象必须初始化。

const int i = 0; // 常量
i = 5; // cannot assign to variable 'i' with 
       // const-qualified type 'const int'
       // 无法对具有const限定类型的变量'i'进行赋值操作

2. 常量表达式

const强调运行时对象的不可更改,也就是说你可以在运行时现定义一个const,而constexpr强调编译时确定结果。const无法像constexpr一样在编译阶段就对使用的地方进行替换,效率上有所不如,因此推荐将所有你认定为常量表达式的存在都用constexpr进行定义。

注意,常量和变量在c++里不是相对的概念。常量是指用const修饰,从而使其值无法更改的变量。

要定义常量表达式,首先要用const符修饰,其次要用另一个常量表达式对其进行初始化,可以是字面量,也可以是字面量的运算表达式。

int getsize(){
	return 20;
}
const int size = 20; // 常量表达式
const int size_1 = size + 1; // 常量表达式
const int size_2 = getsize(); // 不是常量表达式

2.1 constexpr关键字(C++ 11)

可以看到,虽然getsize函数返回值固定为20,但是size_2依旧无法被判定为常量表达式,这是因为编译器无法判定getsize的结果是否满足要求。这个时候,可以使用constexpr代替const来定义所有你认定为是常量表达式的符号。

constexpr int getsize(){
	return 20;
}
constexpr int size = 20; // 常量表达式
constexpr int size_2 = getsize(); // 常量表达式

需要注意三点:

  • 只有用constexpr修饰的函数,其返回值才能被用于给其他constexpr进行初始化。
  • 使用constexpr定义指针时,仅表明该指针是一个常量表达式,与其所指向对象无关。
  • 对于使用constexpr定义,但不符合常量表达式要求的情况,会产生编译时错误。例如:error: type name does not allow constexpr specifier to be specified。*

2.2 与const的区别

非常量表达式的const对象是变量,无法像constexpr一样在编译阶段就对使用的地方进行替换,效率上有所不如,因此推荐将所有你认定为常量表达式的存在都用constexpr进行定义。

下面写一段程序,并对其进行编译,加深理解。

constexpr int getsize(){
    return 20;
}

int getsize_2(){
    return 20;
}

constexpr int i = getsize_2();

void constexprTest(){
    int j = i;	// 使用非常量对象初始化
    int k = getsize(); // 使用常量表达式初始化
}

得到汇编代码如下:

constexprTest()     # @constexprTest()
# %bb.0:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     dword ptr [rbp - 4], 20 # 直接替换为20
        call    getsize_2() # 需要调用函数才能确定j的值
        mov     dword ptr [rbp - 8], eax
        add     rsp, 16
        pop     rbp
        ret         # -- End function

3. 常量对象的文件作用域

常量一般只在定义它的文件内有效。 要想在一个文件定义,在其他文件声明并使用它,可以使用extern关键字。

// a.cpp
extern const int size = 20;
// b.h
extern const int size; // 声明,与a.cpp是同一个常量对象

4. 常量引用

绑定到const对象上的引用,称之为常量引用( r e f e r e n c e reference reference t o to to c o n s t const const)。

const i = 0;
const &j = i; // reference to const

4.1 引用非常量对象

上面的例子里,我们创建了一个绑定到const对象进的引用。而常量引用还可以绑定非常量对象,与const对象相同,该引用无法在程序运行期间对绑定的对象进行更改。

int i = 20;
const int &j = i;
j = 21; // error: cannot assign to variable 'j' 
        // with const-qualified type 'const int &'

4.2 引用字面量

有趣的是,常量引用是可以引用字面量的,而非常量引用做不到这一点。

const int &i = 20; // 正确
int &j = 20; // error: non-const lvalue reference to type 
             // 'int' cannot bind to a temporary of type 'int'

下面写一段程序,并进行编译,来看下原理。

void constReference(){
    const int &i = 20;
}

汇编代码如下:

constReference():                    # @constReference()
# %bb.0:
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 12], 20 # 临时量对象
        lea     rax, [rbp - 12]
        mov     qword ptr [rbp - 8], rax # 对临时量对象进行引用
        pop     rbp
        ret                          # -- End function

可以看到程序先在栈空间上创建了一个临时量对象(没有命名的对象)20,再获取该临时量对象的地址以对其进行引用。

5. 指针常量与常量指针

这是个虽然基础,但是比较经典的知识点。但是我发现网络上对于此二者的解释和cpp primer上是相反的。这是翻译的问题,无关对错,我个人比较倾向于网上的说法。除此之外,我觉得与英文结合理解要清楚一点。

5.1 常量指针(pointer to const)

常量指针,即用于指向常量的指针。与无法使用非常量引用去绑定常量一样,要想存储常量对象地址,也必须使用具有所谓常量限定类型const-qulified的指针。

const int i = 5;
const int *p = &i; // 指针常量

const intint应视作两种不同的类型。因为指针类型与其指向对象类型必须相符,因此在用非常量对象的地址去初始化常量指针时,就会发生错误。

int i = 5;
const int *p = &i; // cannot initialize a variable of type 
                   // 'const int *' with an lvalue of type 'int'

5.2 指针常量(const pointer)

int i = 5;
int *const p = &i;

回想上一篇提过的就近原则,const限定符与变量名p最近,因此p首先是一个常量,再者是一个指向int类型对象的指针。常量类型使得该指针的值初始化后就无法改变,即无法指向其他对象。

上述二者也可以嵌套使用,例如:

const int i = 5;
const int *const p = &i;

此时,p是一个指向const int类型对象的指针常量。
此外,结合这种嵌套场景,cpp primer里面提出了两个名词,在之后会经常用到。

  • 顶层const:指针本身是常量。
  • 底层const:指针所指向的,或引用所绑定的,那个对象是常量。

你可能感兴趣的:(C++,c++)