Skip to content

Latest commit

 

History

History
110 lines (72 loc) · 6.28 KB

2-3.md

File metadata and controls

110 lines (72 loc) · 6.28 KB

固化态 - 常量限定符

世间有许多亘古不变的真理,人类不得撼动亦无法改变它的存在。不断地认识与利用真理,恰是人类文明不断进步的表现。

而在 C++ 中,同样也存在许多不得变化、不可变化的事物。我们要想使它们变得真正不可变,就需要我们主动对其作出限制。不可变是一层很好的"保护膜",可以帮助我们预防修改了不当修改的数据,也可以提醒我们数据的不可变性质。

接下来,我们便了解一下变量的不可变形态--常量。


常量相当于不可更改其中内容的变量,我们只得使用其中的数据,却不得修改其中的数据:

const double pi = 3.141592653;

这是最经典的应用了--定义一系列数学上的常量,这样我们进行数学计算时便方便多了:

double r = 3.5;
const double pi = 3.141592653;
std::cout < "S = " << pi * r * r << std:;endl;

上面的程序演示的是圆的面积计算,其中变量r是半径,常量pi则是圆周率。

而要想定义一个常量,很简单,在类型名前加const即可。请注意,const总是应当在所有具体的类型前出现。例如:

const long long MAX_SIZE = 99999999999999999999L;

即将重写:对顶层 const 与底层 const 认知有偏差 补充:const 与 constexpr 都需要初始化

const不仅可用于制造不可变的数据,它也可以作为可变的变量的一层"保护膜",阻止我们向变量赋值。这种机制既可用在指针上,亦可用在引用上:

int i = 3;								// 一个可变的变量
const int *pi = &i;		// 正确,这是一个顶层 const
const int &ri = i;			// 正确,这也是一个顶层 const
*pi = 5;								// 错误,const 指针不允许修改其指向的数据
ri = 8;								  	// 错误,const 引用不允许修改其指向的数据

像上述程序中最后两行所作的操作称为赋值,它是用于修改变量所储存的值的最为直接的手段。我们将在后续章节中具体了解有关赋值的内容。

在上述程序中,我们创建了一个变量,并分别创建了指向此变量的指针与引用。很显然,由于const的限制,我们既不能修改指针解引用后的值,亦无法修改引用直接关联到变量的值--它们在解引用后,会视原来的变量为const int类型,而不是int类型。

不过,本质上,这两个常量所指向的变量其实是可变的,不过是这两个常量在"故弄玄虚"罢了。像这样的const类型称作顶层 const,即实际指向的对象是可变的,但在对象的外围一层指针或引用却不允许我们修改这个对象。这是一种保证数据安全的方式,因为它可以帮助我们减少造成非法写入数据这类错误的几率,并且它也是一个醒目的"对象不可更改"的标志。


与顶层 const 相对的是底层 const,这类 const 直接作用于具体的某个变量上,而非指向该变量的指针或引用:

const int i = 3;				// 一个不可变的常量
int *pi = &i;						// 错误,指针解引用后得到的类型可变
int &ri = i;							// 错误,引用的类型也是可变的

带有const的非引用或指针的变量,是不可变到"骨子里"的。我们不仅不得修改它,就连创建指向它的可变类型的指针或引用也是禁止的--既然本体都不可变,难道指向它的"中介"能让它可变么?

所以,要正确声明指向这种本体不变的对象的指针与引用,这些指针与引用也需要带上 const 属性,但这个 const 却是在具体类型之后的:

const int i = 3;				// 一个不可变的常量
int *const pi = &i;		// 正确,这是一个指针,它指向的是一个类型为 int 的常量
int *const &ri = i;		// 正确,这是一个引用,它引向的是一个类型为 int 的常量

C++ 还提供了一个与底层 const 性质相似的另一类 const,它的关键字是constexpr。与const不同的是,constexpr的值是在程序尚且还在编译时就定下来的,在运行时不可能被改变。由于它是编译时就确定的,因此它甚至不需要占用额外的内存来创建空间、存储自身,直接写死在编译后的可执行程序中。不过,它也仅适用于定义在编译时便能确定下来的值,而在程序运行时才能确定的值是不能以constexpr定义的:

constexpr double pi = 3.1415926535;		//很显然,constexpr 比 const 更适合表达数学常量
constexpr double pi_square = pi * pi;			// 正确,参与初始化计算的对象均为 constexpr
constexpr double pi_half = pi / 2;					// 正确,字面量(数字)同样也相当于 constexpr

本质上,先前所学的诸如数字、字符串这类字面量就带有constexpr属性,而且理所应当--字面量本来就是在程序尚未编译时就确定下来的值。

由于带constexpr属性的常量与字面量均可在编译时就确定其值,因此我们还可以用这些值做一些符合运算,定义新的constexpr常量,就像刚刚的示例一样。

据此,我们也可以看到一个有趣的现象--C++ 编译器会在编译时完成尽可能多的常量计算,以此提升程序的运行性能,减少运行时不必要的计算:

std::cout << "4π = " << 4 * pi << std::endl
                 << " π * π * π = " << pi * pi * pi << std::endl;

在上面的例子中,编译器会在编译时就将4 * pipi * pi * pi的结果计算完成,然后直接"替换"掉原来的式子。这样一来,程序运行时就无需再大费周章地重算一遍,直接使用编译器提前算好的计算结果即可。


从字面量到变量,再从变量回到常量,无不是返璞归真的过程。善加利用常量,可以大幅提升程序的性能。


习题

  • 尝试口述下列程序的运行结果
    • std::cout << 1 + 2 + 3 + 4 << std::endl
    • constexpr int c = 3;
    • constexpr int cc = c * c + c;
    • std::cout << cc * c + cc << std::endl