首页 04-const限定符
文章
取消

04-const限定符

首先列举出一些关于const限定符的特点:

  • 限定符不可随意赋值,因为它的值在定义之初就已经被告知不能改变。

  • 默认状态下,const对象仅在文件内有效。因此当多个文件出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。如果有必要在文件之间共享,那就再加一个extern;

    1
    2
    3
    4
    5
    
    // file_1.cpp定义并初始化了一个常量,该常量能被其他文件访问
    extern const int bufSize = fcn();
    // file_1.h头文件
    extern const int bufSize; // 两份文件中定义的bufSize是同一个
    // 头文件的声明是指明bufSize并非本文件所独有,它的定义将在别处出现。
    

指向常量的指针

1
2
3
const double pi = 3.14;
double *ptr = π  // 这种做法是错误的,const类型得用指向常量(const)的指针去指向
const double *cptr = π // 正确

底层const: 指向常量(const)的指针一般认为是名词底层const,以上述代码为例:

  • cptr是可以被允许改变的;
  • *cptr是不能允许改变的,受到了const的限制;

常量(const)指针

主要的形式是将*放在const关键字之前,用以说明指针是一个常量,这样的书写隐含着一层意味,即不变的是指针本身的值而非指向的那个值。

1
2
3
4
int errNumb = 0;
int *const curErr = &errNumb;	// curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π	// pip为一个指向常量对象的常量指针

顶层const: const指针一般认为是名词顶层const,以上述代码为例:

  • curErr是不能改变的;
  • 但是*curErr

同时,联系到后面的章节,我们需要记住的是,顶层const不影响传入函数的对象:

  • 这一点是第二次提及;

Notes: 对于普通变量作为形参而言,不区分底层还是顶层const,这些针对的是指针;

1
2
3
//-------上下两部分的const声明都等价-------//
Record lookup(Phone*);
Record lookup(Phone* const);	// 忽略了顶层const

怎么理解顶层const不影响传入函数的对象: 下面那个指向Phone类型变量的指针,已经是一个确定的的地址了,但我们调用的是对象,对象的值修改与否与那个const指针无关;

1
2
3
4
5
6
7
8
9
Record lookup(Phone&);

// Phone是一种类似int的类型,一个是常量引用,一个是常规引用。
Record lookup(const Phone&);
//-------上下两部分的const声明都不等价-------//
Record lookup(Phone*);

// 上面是指向Phone类型的指针,下面是指向常量的Phone类型的指针
Record lookup(const Phone*);

底层const会影响传入函数的对象: 因为两个变量本质的类型是不容的,一个是const变量类型,一个是常规类型。

通过使用const指针我们可以实现的一个效果是,因为指针永远是指向那个常量,所以当指针的值发生了变化,那么相对应的它指向的那个常量的值也会发生变化。

1
*errNumb = 1; // 这么一个操作就将errNumb的值改变了。

一串相关代码展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const int x = 123;  // const代表了变量的值不能被修改,但仍然代表了一个变量。
x = 27;	x++;        // 不合法的表达
extern const int bufsize; // 不代表bufsize真的是const变量,但在这里当作const。
/*-------------------------------------------------------------------------*/
char * const q = "abc";   // q指针为const类型,因此q不能做运算。
char *(const q) = "abc";  // 改成这样更容易理解
*q = 'c'; // 是合法的,因为地址q指向的地址是可以变化的。
q++;      // 不合法,因为q为const变量。

const char *p = "ABCD"; // 这里代表了(char *p)是一个const变量,也就是地址不能改变。
*p = 'c'; // *p是不能随便变的,因此是该行是不合法的。
p++;      // 合法的,因为p变量并不是const变量。
    
Person p1("Fred", 200);
const Person* p = &p1;	// Person对象是const(区分标志,看*在const前面还是后面)
Person const* p = &p1;	// Person对象是const
Person *const p = &p1;	// p是const
const *Person const p = &p1;	// 指针p与const对象都是const
/*-------------------------------------------------------------------------*/
int f3() { return 1; }
const int f4() { return 1; }
int main() {
    const int j = f3(); // 没任何问题
    int k = f4();       // 同样没问题
}
/*-------------------------------------------------------------------------*/
int get_day () const;   // 意味着this是const。

如果成员变量中有const变量,那么这个变量不能用作数组下标(因为要发生变化)

函数体周围的const

  • const func():表示函数的返回值不能修改;
  • func() constconst修饰的是this指针,里面的成员不能够修改;

constexpr和常量表达式

常量表达式的定义

常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式

  • 首先需要是常量;
  • 其次需要在编译过程中就得到结果;

C++11新标准规定,允许将变量声明constexpr类型以便由编译器来验证变量的值是否是一个常量表达式;

声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,因此也称为字面值类型

算术类型、引用和指针都属于字面值类型;

尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制:

  • 一个constexpr指针的初始值必须是nullptr或者0,或者某个存储于固定地址中的对象。

同时需要明确的是,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。

1
2
3
4
5
6
7
8
// 一个指向常量的指针。
const int *p = nullptr;

// 一个const指针(constexpr所作用的?),限定符constexpr仅对指针有效,与指针所指的对象无关。
constexpr int *q = nullptr;

// 要求size是一个constexpr函数,该声明才正确。
constexpr int sz = size();

constexpr函数是C++11标准库定义的一个足够简单,在编译时就能得到结果的函数;

字面值

字面值的定义: 是指在程序中无需变量保存,可直接表示为一个具体数字或字符或字符串的值;

  • 字面值本身会带着const类型,因此字面值无法做左值,也无法进行随意转换;

Notes: 不能用字面值初始化一个非常量的引用,举个例子理解:

1
int &ref = 42; // 引用的对象必须有一个明确的内存位置,而字面值是没有明确的内存位置的

关于const的内容就介绍到这里;


本文由作者按照 CC BY 4.0 进行授权

03-表达式类型推断

05-引用的介绍