C++学习笔记 —— const

1.使用const定义变量/常量

  • $const$定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个$const$限定的变量,不要将与常量混淆。

如下面$bufSize$就是一个常量:

const int bufSize = 512; 
  • 任何修改$bufSize$的操作都会导致编译错误。
  • 常量在定义后不能被修改,所以定义时必须初始化
const std::string hi = "hello!"; //OK
const int i; // error,i未被初始化!

2.const对象默认为文件的局部变量

  • 非$const$变量默认为$extern$,但全局作用域定义的$const$变量是该变量所在文件的局部变量,要使$const$变量能够在其他文件中访问,必须显式地指定它为$extern$。
//非const对象
//file_1.cpp,定义
int counter = 0;
//file_2.cpp,声明
extern int countrt;
//const对象
//file_1.cpp,定义
extern const int counter = 0;
//file_2.cpp,声明
extern const int countrt;
  • 设置默认情况的原因在于允许$const$变量定义在头文件中

3.const引用

  • $const$引用是指向$const$对象的引用
  • 被$const$限定的引用表示只读
  • $const$引用可以初始化为$const$对象非$const$对象
    非$const$引用只能初始化非$const$对象
const int ival = 1024;
const int &refVal = ival; //ok
int &ref2 = ival; //error
  • $const$引用可以初始化为不同但相关的类型的对象或者是右值,如字面值常量;非$const$引用只能初始化为与该引用同类型的对象

原因在于用不同类型的对象初始化引用会发生类型转换,产生中间变量,期望通过引用改变源对象值是不现实的,引用只会改变中间变量的值
基于上述原因,规定只有$const$引用才能初始化为不同类型的对象,因为$const$引用是只读的,没有修改值的需求,也就完全避免了上述问题。

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

编译器会将代码转换为如下形式:

int temp = 3; //产生一个int类型的中间变量
const int &ri = temp; //使用中间变量temp初始化ri

4.const对象可以定义在头文件中

  • 头文件用于声明而非定义,但有三种例外:类、值在编译时就已知的$const$对象和$inline$函数。
  • 若$const$整型变量通过常量表达式初始化,则这个$const$整型变量就是一个常量表达式,常量表达式需要编译器在编译时就能计算出结果,所以$const$整型变量要变为常量表达式,其初始化过程必须为编译器可见,于是一般都将这样的$const$变量定义在头文件中,保证编译器一直可以看到其初始化式。
  • $const$变量默认是定义它的文件的局部变量,所以将用常量表达式初始化的$const$整型变量定义在头文件中是合法的,不会导致编译器报重复定义的错误。
  • 若$const$变量不是用常量表达式初始化的,那么它就不应该在头文件中定义。相反,它应该和其他变量一样在源文件中定义并初始化,在头文件中添加$extern$声明,以使它能被多个文件共享。

5.const与指针

5.1 指向const对象的指针

  • C++语言强制要求指向$const$对象的指针也必须具有$const$特性
const double *cptr; //cptr是一个指向double类型const对象的指针

$cptr$是一个指向$double$类型$const$对象的指针,$const$限定$cptr$所指对象是$const$的,所以不能通过$cptr$修改其所指对象的值;但$cptr$本身不是$const$的,所以在定义$cptr$时可以不用进行初始化,而且也可以对$cptr$进行重新赋值,使其指向另一个$const$对象

  • 不能使用void*指针保存$const$对象的地址,必须加上$const$。
  • 不允许将$const$对象的地址赋给指向非$const$对象的指针。
const double pi = 3.14;
double *ptr = π //error
const double *cptr = π //ok
  • 允许将非$const$对象的地址赋给指向$const$t对象的指针。
double dval = 3.14;
const double *cptr = &dval; // ok

尽管$cptr$指向的是非$const$对象$dval$,但任何企图通过$cptr$指针修改$dval$对象的值的行为都会导致编译时错误。

  • 不能使用指向$const$对象的指针修改其所指对象的值,无论该对象是$const$的还是非$const$的。

本质上是因为系统没办法分辨指向$const$对象的指针所指的对象是否为$const$,系统会将指向$const$对象的指针所指的对象都视为$const$对象。

但当该对象是非$const$时可以通过其他方法修改对象的值,如直接给该对象赋值或间接地利用非$const$指针修改其值。

  • 总的来说,不能保证指向$const$对象的指针所指对象的值一定不可被修改。

可以将指向$const$对象的指针理解为“自以为指向$const$对象的指针”

  • 实际应用中,指向$const$的指针常用作函数的形参。

将函数形参定义为指向$const$对象的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。

5.2 const指针

int errNumb = 0;
int *const curErr = &errNumb; // ok
  • $const$指针必须在定义时初始化,且其本身的值不能被修改。
    任何企图给$const$指针赋值的行为都会导致编译时错误,即使是赋相同的值。
curErr = curErr; // error
  • $const$指针所指对象的值能否被修改却决于该对象是否为$const$。

5.3 指向const对象的const指针

  • 对象和指针都不允许修改。
const double pi = 3.14;
const double *const pi_ptr = π //ok

6.const与函数

6.1 const与函数形参

  • 非引用的形参限定为$const$意义不大,仅当形参是引用或指针时,形参是否为$const$才有影响。

非引用形参通过复制对应实参实现初始化,形参是实参的局部副本,形参的改变不影响实参。所以无论非引用形参是否为$const$,都可以用$const$实参初始化也可以用非$const$实参进行初始化。为了对C语言实现兼容,即使将函数形参限定为$const$,编译器仍然视其形参为非$const$。

void fcn(const int i){}
void fcn(int i){} // 重定义
  • 利用$const$引用避免复制。

如果使用引用形参的唯一目的是避免复制实参,则应该将形参定义为$const$引用。

  • $const$引用形参更灵活,应该将不需要修改的引用形参都定义为$const$引用。

普通的非$const$引用形参在使用时不太灵活,这样的形参既不能用$const$对象初始化,也不能用字面值或产生右值的表达式实参初始化,即非引用形参只能与完全同类型的非$const$对象关联

  • 可基于函数的引用形参否为$const$实现函数重载。

将引用形参定义为$const$来重载函数是合法的,编译器可以根据实参是否为$const$确定调用哪个函数。

  • 可基于函数的指针形参所指对象否为$const$实现函数重载。

但是不能基于指针本身是否为$const$来实现函数重载!

void fcn(int *i){}
void fcn(int *const i){} // 重定义

6.2 const与函数返回值

  • $const$修饰非引用返回值意义不大。

函数返回值绝大多数时候被赋给其他变量。

  • $const$修饰指针类型的返回值 同 5.constconst与指针。

7.const与类

7.1 const与类的数据成员

  • 类中的$const$数据变量必须通过构造函数的初始化列表进行初始化,且不能在构造函数的函数体中对$const$数据成员进行赋值。

原因在于构造函数的初始化列表用于对象的初始化,而函数体用于对象的赋值,可以初始化$const$对象但不能对它进行赋值。
从概念上讲,可以认为构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。
数据成员要在初始化阶段完成初始化,初始化发生在计算阶段之前。所以在开始执行构造函数的函数体之前要完成数据成员的初始化,而初始化$const$数据成员的唯一机会是在构造函数的初始化列表中。
上述描述也适用于引用类型的对象
从C++ 11标准开始允许$const$变量在定义时初始化

7.2 const与类的成员函数

  • 在类的成员函数的形参表后添加$const$表示该函数是常量成员函数

常量成员函数的声明与定义都必须明确指出是$const$的,若只出现在一处将导致编译时错误。

  • 只有常成员函数才有资格操作常量或常对象,没有使用$const$关键字的成员函数不能用来操作常对象。
  • 常量成员函数本值上是将$this$指针所指对象限定为$const$。
bool Class1 :: func1(const Class1 &rhs) const{...}

等价于

bool Class1 :: func1(const Class1 *const this, const Class1 &rhs) const{...}

此时的$this$是一个指向$const$对象的指针,所以不能通过常量成员函数的$this$指针修改类的任何对象成员。

普通的非$const$成员函数,$this$的类型是一个指向类类型的$const$指针。

  • 不能从$const$成员函数返回非$const$的对象。
  • $const$对象、指向 $const$对象的指针或引用只能调用其$const$成员函数,如果尝试用他们来调用非$const$成员函数将报错。

类似的,在$const$成员函数中也只能调用$const$成员函数。
非$const$成员函数没有这个限制,它既可以调用非$const$成员函数也可以调用$const$成员函数。

  • 可基于函数是否为$const$实现函数重载。

$const$对象默认调用$const$成员函数,非$const$对象默认调用非$const$成员函数。


扫描二维码或搜索“工科南”,关注公众号!

你可能也喜欢

发表评论

电子邮件地址不会被公开。