CPP基础--变量和基本类型之const限定符

作者 by adtxl / 2021-03-08 / 暂无评论 / 377 个足迹

const限定符

  • 因为const对象一旦创建后其值不能更改,因此创建时就必须初始化

  • 在不改变const对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要:

    int i = 42;
    const ci = i;   // correct
    int j = ci;     // correct

    尽管ci是整型常量,但无论如何ci中的值还是一个整型数。ci的常量特征仅仅在执行改变ci的操作时才会发挥作用。当用ci去初始化j时,根本无须在意ci是不是一个常量。拷贝一个对象的值并不会改变它,一旦拷贝完成,新的对象就和原来的对象没有关系了。

  • 默认状态下,const对象仅在文件内有效

当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。

某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其他(非常量)对象一样工作,也就是说,只在一个文件中定义const,而在多个文件中声明并使用它

解决办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了;

// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h头文件
extern const int bufSize;   // 与file_1.cc中定义的bufSize是同一个

file_1.h头文件中的声明也由extern做了限定,其作用是指明bufSize并非本文件所独有,它的定义将在别处出现。

如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字

1. const的引用

可以把引用绑定到const对象上,就像绑定到其它对象上一样,我们称之为对常用的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:

const int  ci = 1024;
const int &r1 = ci;     // correct,引用及其对应的对象都是常量
r1 = 42;                // 错误,r1是对常量的引用
int &r2 = ci;           // 错误,试图让一个非常量引用指向一个常量对象。假设该初始化合法,则可以通过r2来改变它引用对象的值,这显然是不正确的。
  • 初始化和对const的引用

引用的类型必须与其所引用的对象的类型一致,但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可,尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式:

int i = 42;
const int &r1 = i;      // 允许将const int&绑定到一个普通的int对象上
const int &r2 = 42;     // 正确: r1是一个常量引用
const int &r3 = r1 * 2; // 正确; // r3是一个常量引用
int &r4 = r1 * 2;       // 错误:r4是一个普通的非常量引用

可以这样做的原因如下,

double dval = 3.14;
const int &ri = dval;

编译器把上述代码变成了如下形式

const int temp = dval;  // 由双精度浮点数生成一个临时的整型常量
const int &ri = temp;   // 由ri绑定这个临时量

如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非dval。程序员既然让ri引用dval,就肯定想通过ri改变dval的值,否则干什么要给ri赋值呢?如此看来,既然大家都不会想着把引用绑定到临时量上,C++语言也就把这种行为归为非法。

  • 对const的引用可能引用一个并非const的对象
int i = 42;
int &r1 = i;            // 引用ri绑定对象i
const int &r2 = i;      // r2也绑定对象i,但是不允许通过r2修改i的值
r1 = 0;                 // r1并非常量,i的值修改为0
r2 = 0;                 // 错误:r2是一个常量引用

不允许通过r2修改i的值

2. 指针和const

指向常量的指针(pointer to const)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针:

const double pi = 3.14;
double *ptr = π          // error
const double *cptr = π   // correct
*cptr = 42;                 // error
  • const指针

常量指针(const pointer),将指针本身定为常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。

int errnumb= 0;
int *const curErr = &errNumb;   // curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π  // pip是一个指向常量对象的常量指针

3. 顶层const

用名词顶层const(top-level const)表示指针本身是个常量
用名词底层const(low-level const)表示指针所指的对象是一个常量

4.constexpr和常量表达式

常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。
以下均不是常量表达式;

int staff_size = 27;
const int sz = get_size();
  • constexpr变量

在一个复杂系统中,很难(几乎不能)分辨一个初始值到底是不是一个常量表达式。

C++11标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
一般来说,如果你认定变量是一个常量表达式,那就把它声明称constexpr类型。

  • 字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为"字面值类型"(literal type)

到目前为止,算术类型、引用和指针都属于字面值类型。自定义类Sales_item、IO库、string类型则不属于字面值类型,也就不能被定义成constexpr。

  • 指针和constexpr
    必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
    const int *p = nullptr;         // p是一个指向整型常量的指针
    constexpr int *q = nullptr;     // q是一个指向整数的常量指针

独特见解