Learning C++ 之 2.9 常量,常量表达式和常量符号

Const 变量

到目前为止,我们碰到的所有的变量都不是常量变量。也就是说变量的值可以任意改变。例如:

int x { 4 }; // initialize x with the value of 4
x = 5; // change value of x to 5

然而,有些时候定义一些不能改变值的变量是非常有用的。比如,地心引力的值:9.8 m/s^2.这个值就不会轻易改变。把这个值定义成为一个常量,以防止误改。

定义一个常量变量,只需要把const放到变量类型的前面或者后面就可以了。

const double gravity { 9.8 }; // preferred use of const before type
int const sidesInSquare { 4 }; // okay, but not preferred

尽管const放到哪里都可以,但是我们建议放到变量类型前面。因为这是符合英语语法的,把修饰词放到被修饰的物品前面。

常量变量必须初始化,之后不能随意更改其值。声明一个const变量,以防止我们随意改变其值。

const double gravity { 9.8 };
gravity = 9.9; // not allowed, this will cause a compile error

定义const变量而不初始化会导致编译错误。

const double gravity; // compiler error, must be initialized upon definition

常量变量的初始化可以来自于普通变量。

std::cout << "Enter your age: ";
int age;
std::cin >> age;
 
const int usersAge (age); // usersAge can not be changed

常量通常会用在函数的参数值中

void printInteger(const int myValue)
{
    std::cout << myValue;
}

把函数的参数定义为常量有两个意义。首先它告诉调用函数的人不要更改myValue;其次他可以保证函数不会改变myValue的值。

如果函数参数是通过值传递的,那么我们一般不关心函数是否修改了该参数的值(因为最终随着函数结束,会被自动释放)。因此,我们一般不会把值传递的参数作为常量。但是后面我们会学习其他类型的函数参数,更改参数的值会改到传入的参数的值。对于这一类参数,判断是否使用const是非常重要的。

编译时间和运行时间:

当你的程序在编译的时候,叫做编译时间。在编译时间里,编译器会确保你的程序语法证确,将代码文件转换为对象文件。

当你的程序在运行的时候,叫做运行时间。在运行时间里,你的程序将会一行一行地执行。

constexpr:

C++有两种类型地常量:编译时间常量和运行时间常量

运行时间常量表示只有在程序运行的时候才会初始化,如上面的UserAge,MyValue变量都是在运行的时候输入初始化的。

编译时间常量是在编译时间就被初始化了,例如gravity变量,无论什么时候使用都是9.8的值。

在大多数场景下,编译时间常量还是运行时间常量是可以互换的。然而在一些特殊场景下,C++需要一些编译时间常量,比如我们需要定义一个固定长度的数组的时候。因为变量可能是编译时间常量也可能是运行时间常量,所以需要编译器来跟踪究竟是哪一种。

为了作出区分,C++11新定义了一个关键字constexpr定义编译时间常量。

constexpr double gravity (9.8); // ok, the value of 9.8 can be resolved at compile-time
constexpr int sum = 4 + 5; // ok, the value of 4 + 5 can be resolved at compile-time
 
std::cout << "Enter your age: ";
int age;
std::cin >> age;
constexpr int myAge = age; // not okay, age can not be resolved at compile-time

命名常量变量

一些程序员喜欢使用全大写来定义常量,还有一些喜欢使用k前缀来定义。我们建议和普通变量一样的方法定义就行了,因为常量也是普通变量的一种,使用方法也跟普通变量差不多,只不过不能改变其值,所以普通命名就OK了。

符号常量:

在上一章节中我们研究了数字常量,程序中使用魔数来表示常数量。既然魔数是不好的,那么我们该怎么来替代呢?使用符号常量。符号常量是文字常量的一个名称。有两种方式定义符号常量,一种是好的,另一种是不好的。下面具体展示一下:

Bad:使用类似对象的宏来替换魔数常量:

首先我们介绍一下不太令人满意的方式:在老的code里面,还经常用到,所以你会经常见到类似方法。在1.10章节中,你已经学习了宏的两种格式,一种是带有替换参数的,另一种是没有带替换参数的。这里我们探讨的是带有替换参数的。

#define identifier substitution_text

预处理器遇到这种指令的时候,会直接把代码中所有的‘identifier’替换成‘substitution_text’。其中identifier一般使用全大写字母表示,下划线链接。

#define MAX_STUDENTS_PER_CLASS 30
int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS;

当你编译你的代码的时候,预处理器会把代码中所有的MAX_STUDENTS_PER_CLASS替换成30,然后再进行编译。

你会发现这比使用魔数有更多的优势。首先,MAX_STUDENTS_PER_CLASS不需要备注就能让读者明白。其次,如果其值改变了,我们只需要改变这一个地方就OK了。

#define MAX_STUDENTS_PER_CLASS 30
#define MAX_NAME_LENGTH 30
 
int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS;
setMax(MAX_NAME_LENGTH);

在这个例子中,两个变量都是使用的30,但是使用宏的方式,就能区分出这是完全两个独立的变量。

所以为什么不使用#define来定义常量呢?有以下两个问题:

首先,宏定义会被预处理器处理,因此在调试的时候并不会显示数据的值,而是宏的名字。所及,即使表示int max_students = numClassrooms * 30,但是调试器里面显示的确是int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS。这样不利于调试。

其次:#define宏定义往往会有文件范围域,假如你在一个文件里定义了一个宏,然后又在另外一个函数里定义了另外一个,往往会报冲突。

#include <iostream>
 
void a()
{
// This define value is now available for the rest of this file
#define x 5
	std::cout << x;
}
 
void b()
{
// Even though we're intending this x to be local to function b()
// it conflicts with the x we defined inside function a()
#define x 6
	std::cout << x;
}
 
int main() {
 
	a();
	b();
 
	return 0;
}

在上面的代码里,我们在函数a()里面定义了一个宏,#define x,目的是想让它在函数内部使用。然而在整个文件里都可以使用这个宏。因此我们在函数b()里面再次定义一个宏的时候,就有了相应的命名冲突上报。即使不报错,x的值也是5,而不是6.

Good:使用const定义常量变量

一个比较好的创建符号常量的方式是使用const来定义:

constexpr int maxStudentsPerClass { 30 };
constexpr int maxNameLength { 30 };

这种方式让变量可以在debugger中正常显示,也可以遵循所有正常变量的有效范围。

在整个程序中使用符号常量

在多数程序里,一个符号常量需要从头用到尾。这种包括数学或者物理上的一些常数。与其每次都定义一遍,比如直接在一个核心位置定义一次,其他地方随时使用。这样,如果你需要改动的话,只需要改动一个地方就OK了。

在C++里有很多种方式来实现这一点,但是下面的是最容易的:

  • 创建一个头文件来包含这些常量
  • 在这个头文件里定义一个命名空间
  • 在你的命名空间里加上你的所有常量
  • 当你使用的时候#include你的头文件
#ifndef CONSTANTS_H
#define CONSTANTS_H
 
// define your own namespace to hold constants
namespace constants
{
    constexpr double pi(3.14159);
    constexpr double avogadro(6.0221413e23);
    constexpr double my_gravity(9.2); // m/s^2 -- gravity is light on this planet
    // ... other related constants
}
#endif

使用范围操作符(::)来引用

#include "constants.h"
double circumference = 2 * radius * constants::pi;

如果你同时有物理学的常量和app的调优值,你可以弄两个文件。一个是永远不变的物理常量,另一个是可能有变化的调优值。这样物理常量你可以到处使用了。

在后续的课程4.2中,我们使用了一种全局变量来定义符号常量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值