首页 > 代码库 > 你好,C++(8)如何表达那些始终保持不变的数据量?3.2.2 常量

你好,C++(8)如何表达那些始终保持不变的数据量?3.2.2 常量

3.2.2  常量

与变量可以用在程序中表达那些可能会发生变化的数据量相对应地,在C++中,我们用常量来表达那些始终保持不变的数据量。简单来讲,就是程序中直接使用的数值、字符、字符串以及const关键字修饰的常变量等。大多数时候,常量只需要被读取一次,所以它没有名字,无须定义而可以直接使用。又因为其数据只能读取,不能修改,所以通常用来给一个变量赋值或者直接参与运算。例如:

// 用常量180对变量nHeight赋值nHeight = 180;// 直接使用常量进行计算fArea = fR * fR * 3.1415926;

这里的“180”和“3.1415926”就是两个常量,分别用来对变量nHeight进行赋值和参与乘法运算。这样的常量只能使用一次,当完成赋值操作和乘法运算后,这两个常量也就不再有意义了。

C++中的常量主要包括数值常量(整型常数、浮点型常数)、字符常量、字符串常量。

1. 整型常数

整型常数就是以文字形式出现的整数。整型常数的表示形式最常见的是十进制,也可以根据需要采用八进制或十六进制表示。在程序中,我们可以根据数字的前缀来区分各种进制的整数。

l  十进制整数:没有前缀,例如0、123、-1等。

l  八进制整数:以0为前缀,数字不包含8和9。例如0123、-022等。

l  十六进制整数:以0x或0X为前缀,数字除了0~9十个数字之外,还包括A~F六个英文字母字符。例如0x123、-0X2等。

在程序中,我们可以直接使用这三种方式来表示某个整数数值:

nHeight = 173;         // 十进制常数nHeight = 0255;        // 八进制常数nHeight = 0xAD;        // 十六进制常数

上面的代码分别采用不同进制形式的常数对一个变量赋值。虽然这些常数的表现形式不同,但是它们所代表的数值都是173。

2. 浮点型常数

浮点型常数就是以文字形式出现的浮点数,也就是我们通常所说的小数。浮点数有两种表示形式:小数形式和指数形式。小数形式就是我们通常的小数书写形式,由数字和小数点构成,如1.0、0.1、.123等。而指数形式则是用科学记数法,将一个浮点数表示为一个小数与10的多少次方的乘积的形式。当一个浮点数较大或者较小时,使用指数形式来表示浮点数会更加方便。例如,1.3e9表示1.3×109,也就是1300000000,0.123E-4表示0.123×10-4等。

3. 字符常量

字符常量就是程序中使用的单个字符,如“a”、“A”、“!”等。在C++中,我们使用单引号(‘ ‘)来表示一个字符常量。例如:

// 用一个字符常量对变量aMark赋值char aMark = A;// 输出一个字符常量‘!’cout<<!<<endl;

除了上述常见的可在屏幕显示的字符外,C++还允许使用一类特殊的字符常量。这些字符无法通过键盘直接输入,也不能直接输出显示到屏幕,但是可以用来表示一些特殊的控制意义,比如计算机响铃(/a)、换行(/n)、回车(/r)等。这些字符都以“\”开始,表示将“\”后的字符转换成其他的含义,所以这些字符也被称为转义字符。表3-2列出了C++的常用转义字符。

表3-2  C++中的常用转义字符

转义字符

意    义

‘\a‘

响铃,用cout直接输出该字符时,屏幕无显示,但计算机喇叭会发出“滴”声,常用来提示用户程序完成某项操作

‘\n‘

换行(n: line),如果在一个字符串中有这个字符,转义字符后的字符串将换行输出

(还记得在2.2.2[Chen1] 小节中我们曾用到过这个转义字符吗?回顾一下吧!)

‘\t‘

制表符,输出位置将横向移动一个Tab的位置

‘\r‘

回车(r:return)

‘\\‘

转义字符“\”本身

‘\"‘

双引号

‘\‘‘

单引号。以上这三个转义字符组合起来,可以输出一些含有特殊符号的字符串。例如,我们想输出:

这些字符都以“\”开始

这样一个字符串,就需要用到这些转义字符来输出其中的特殊符号:

cout<<"这些字符都以\"\\\"开始"<<endl;

转义字符的使用跟可显示字符的使用相似,可以把转义字符放到一个字符串中,让它完成相应的控制功能,也可以单独输出某个转义字符,例如:

// 将“\n“放到一个字符串中,它将控制这个字符串输出为两行// 恭喜!// 任务完成!cout<<"恭喜!\n任务完成!"<<endl;// 直接输出““\a”转义字符,发出一个计算机响铃,提示用户任务完成cout<<\a<<endl;

4. 字符串常量

字符串常量就是由一对双引号(" ")括起来的字符序列,如"Hello World!"。注意,因为双引号是字符串的界限符,所以如果想在字符串中使用双引号,就要使用转义字符来表示。另外值得提醒的是,这里的双引号必须是英文的("")。因为与中文双引号(“”)在形式上非常相似,所以常常被初学者误用而导致错误。例如:

// 使用字符串常量对变量赋值strName = "ZengMei";// 输出字符串常量// 在输出一些特殊符号(比如,双引号,斜杠等)时,我们必须使用相应的转义字符// 这里使用了转义字符“\””来输出字符串中的双引号,最终输出结果如下// 你的名字是:”ZengMei”cout<<"你的名字是:\"ZengMei\""<<endl;

知道更多:原生字符串(raw string)标识

在字符串常量中,我们可以使用反斜杠(\)这个转义操作符来引入一些特殊字符,实现特殊的输出目的。然而,这却给正则表达式的书写带来了麻烦,因为在正则表达式中,反斜杠成了用于引入表示字符的特殊符号,并且使用非常频繁。如果想在正则表达式中表示反斜杠这个字符,我们不得不使用两个反斜杠来表示一个反斜杠字符。例如,我们要表达“被反斜杠(\)分隔开的两个单词”这样一个模式(\zeng\\\mei),在C++代码中就成了:

string s = "\\zeng\\\\\\mei";   // 这样的表示很不直观、且容易出错

我们注意到,在正则表达式中,反斜杠字符被表达为两个反斜杠的组合。为了表示一个反斜杠,我们必须在正则表达式中使用两个反斜杠来表示。第一个反斜杠表示这是一个转义字符,第二个才表示真正的反斜杠。这样的表达方式,会让我们的字符串变得非常复杂繁琐,很不直观,即使是经验丰富的程序员也很容易出错。

为了解决这个问题,C++11引入了原生字符串的机制,并使用原生字符串标识符R来表示一个原生字符串。在原生字符串中,每个字符都代表其最原始的字符意义,所见即所得。换句话说,也就是反斜杠(\)不再具有转义符的作用,一个反斜杠仅用一个反斜杠字符就可以表示。因而,上述的例子可以简化为:

string s = R"(\zeng\\mei)";        // 使用R"()"表示的原生字符串

原生字符串的R"(...)"记法相比于普通字符串的"..."记法会有一点点的冗长,但它的意义就在于它可以让字符串中的转义规则无效,所写即所得,所见即所得。当我们需要在字符串中频繁地表示各种特殊符号(反斜杠,引号等)的时候,原生字符串将非常简便,而这一点点书写上的冗长也是值得的。

无论是数值常量还是字符串常量,它们都像C++世界的“雷锋”,只做好事而不留名字。可是,这样也带来了一个麻烦:当我们在程序中需要重复多次地使用某个常量时,我们不得不在代码中一遍又一遍地书写同一个常量。例如,要编写一个有关圆的计算程序,无疑会多次用到3.14159这个浮点常数:

float fR = 19.82;  // 半径// 用常数3.14159计算面积float fArea = 3.14159 * fR * fR;// 用常数3.14159计算周长float fGirth = 2 * 3.14159 * fR;

这样的代码,不仅书写起来非常困难(多次重复书写同一个小数,难以保证正确性和一致性),在后期也难以维护(如果想改变这个常数,我们不得不修改所有用到这个常数的地方)。这一切,都是因为常量在程序中无名无份,每次都是直接使用引起的。那么,解决的办法自然就是给常量取一个名字,让我们可以通过这个名字方便地重复多次使用同一个常量。在C++中,给常量取一个名字的方法有两种:

1.用#define预编译指令将数值或字符串定义成宏,然后用宏来代替常量的直接使用

2.用const关键字将一个变量修饰成常变量,然后用常变量来代替常量的直接使用

我们首先来看如何用宏来代替常量。所谓的宏,就是将某个无明确意义的数值(例如,3.14159,知道的认为是圆周率,不知道的认为只是某个奇怪的数字)定义为某个有明确意义的标识符(例如,PI,所有人都会认为是圆周率)。然后,就可以在代码中使用这个有意义的标识符来代替无明确意义的数值,从而使代码更具可读性。在C++中,可以使用#define预编译指令来定义一个宏:

#define 宏名称 宏值

其中,“宏名称”就是要定义的宏,通常用一个大写的有意义的名称来表示。“宏值”就是这个宏所代表的内容,它可以是一个常数、一个字符串,甚至是一个更加复杂的语句。比如,可以用下面的语句将3.14159定义为一个宏PI:

// 将3.14159定义成宏PI#define PI 3.14159

有了常数3.14159所对应的宏PI之后,我们就可以在代码中直接使用PI来代替3.14159进行相应的计算。例如,上面的代码可以简化为:

// 将3.14159定义成宏PI#define PI 3.14159 float fR = 19.82;  // 半径// 用PI计算面积float fArea = PI * fR * fR;// 用PI计算周长float fGirth = 2 * PI * fR;

这里,使用PI代替了原本应该使用的3.14159也同样可以完成计算。那么,宏是如何做到这一点呢?这里的宏PI并不是真正地具有了它所代表的3.14159这个常数的值,从本质上讲,宏只是一种替换。当编译器对代码进行预编译处理的时候,它会将代码中的宏替换为它所代表的内容,换而言之,也就是上面代码中的PI会被替换为3.14159,最终参与编译的代码实际上仍旧是:

// 宏PI被替换为常数3.14159计算面积float fArea = 3.14159 * fR * fR;// 宏PI被替换为常数3.14159计算周长float fGirth = 2 * 3.14159 * fR;

从这里可以看到,宏的使用并没有减少代码中的常数,但是它用一种巧妙的方法,减少了重复输入某个常数的繁琐,避免了可能发生的书写错误。

最佳实践:使用宏提高代码的可读性与可维护性

除了减少代码重复避免书写错误之外,宏的使用还会给我们带来额外好处:

1. 让代码更简洁明了,更具可读性

一个意义明确的宏名称往往比一个复杂而无意义的常数数字包含了更加丰富的信息,可以增加代码的可读性;同时,宏比常数数字更简单,可以使代码更简洁。对比下面两段代码:

// 不使用宏的代码for( int i = 0; i < 1024; ++i ){    // ...}// 使用宏的代码#define MIN 0#define MAX 1024for( int i = MIN; i < MAX; ++i ){    // ... }

通过对比我们可以发现,虽然两段代码实现的功能是一样的,但是给代码阅读者的信息却不大相同。第一段代码只是表示这个循环是从0到1024之间,至于为什么是从0到1024,只能让代码阅读者自己去猜测了。第二段代码则通过宏的使用,明确地告诉了我们这个循环是在最小值和最大值之间进行的,这样可以从代码本身获得更加丰富的信息,增加了代码的可读性。

2. 让代码更加易于维护

如果我们在代码中直接多次使用某个常数数字,而恰好这个数字需要修改,那么我们不得不修改代码中所有使用这个数字的地方。而如果是将这个常数定义成宏,并在代码中使用宏来代替这个常数,当我们需要修改这个常数时,只需要修改宏的定义就可以了,而无需修改代码中所有使用这个宏的地方。例如,我们将3.14159这个常数定义成PI这个宏并用它参与计算,当我们需要降低精度使用3.14进行计算时,只需修改PI的定义,将3.14定义成PI即可:

// 修改PI的定义#define PI 3.14// 使用3.14作为圆周率计算面积float fArea = PI * fR * fR;

 

除了用#define定义的宏可以表示常数之外,C++还提供了const关键字,使用它可以将一个变量修饰成一个数值不可修改的常变量,也可以用来表示程序中的常数。

const关键字的使用非常简单,只需要在定义变量的时候,在数据类型前或后加上const关键字即可:

const 数据类型 常变量名 = 常量值;

这里的const关键字会告诉编译器,这个变量的数值不可修改(或者更严格地说,不可以通过这个变量名直接修改它所表示的数值,而通过其他方式间接地修改是可以的),这就使得这个变量具有了一个常数最基本的特征:不可修改。所以,经过const关键字的修饰,这个变量就成了一个常变量,可以用来表示程序当中的各种常数。需要特别注意的是,因为常变量的值在定义后便不可以修改,所以必须在定义常变量的同时完成它的赋值。例如:

// 定义常变量PIconst double PI = 3.14159;//// 用常量PI计算面积float fArea = PI * fR * fR;// 用常量PI计算周长float fGirth = 2 * PI * fR;

而在定义之后,如果试图通过这个常变量名来修改它所表示的值,则会导致一个编译错误,以此来保证这个变量的数值不会被修改而成为一个常变量。例如,如果想在程序中降低PI的精度,偷工减料是不行的:

// 错误:不能修改const常变量的值PI = 3.141;

既然宏和const关键字都可以用来给常数一个名分,那么该如何选择呢?要表示常数的时候,到底是用宏还是用const关键字?我们的回答是:应该更多地选择使用const关键字。比如,要想在程序中表示3.14159这个常数,可以采用以下两种方式:

// 宏方式#define PI 3.14159// const方式const double PI = 3.14159;

这两种方式在语法上都是合法的,在使用上也并没有什么太大区别。但是第二种方式要比第一种方式好,因为如果使用#define将这个常数定义成宏PI,PI会在代码的预编译阶段被预编译处理器替换成3.14159这个常数本身,这样就没有了编译器的数据类型检查, 并且,宏的名称不会出现在符号表中,这样会给代码后期的调试带来麻烦,可能会遇到一个数字,却不知道它从何而来,这就是我们常说的Magic Number(像拥有魔力一样不知从何而来的数字)。而使用const将这个常数表示成一个常变量,它是拥有数据类型的,编译器可以对其进行数据类型检查,避免错误的发生。同时这个常变量名也会出现在程序的符号表中,便于程序的调试。所以,我们总是优先使用const关键字修饰的常变量来表示程序中的常数。


 [Chen1]确认

你好,C++(8)如何表达那些始终保持不变的数据量?3.2.2 常量