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
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++里有很多种方式来实现这一点,但是下面的是最容易的:
#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中,我们使用了一种全局变量来定义符号常量。