1、第五章 变量和常量5.1 从类型到变量5.1.1 公孙龙的“白马非马”5.1.2 定义变量5.1.3 如何为变量命名5.1.4 如何初始化变量5.1.4.1 什么时候需要给变量初始化?5.1.4.2 初始化变量的两个时机5.1.4.3 通过计算得到初始值5.1.4.4 变量的取值范围5.2 变量与内存地址5.3 常量5.3.1 几种数据类型常数的表达方法5.3.1.1 整型常数的表达5.3.1.2 实型常数的表达5.3.1.3 字符常量的表达5.3.1.4 字符串常量5.3.2 用宏表示常数5.3.3 常量定义5.3.4 枚举常量5.3.4.1 为什么需要枚举类型5.3.4.2 定义枚举类型的
2、基本语法5.3.4.3 关于枚举常量的输出效果5.1 从类型到变量5.1.1 公孙龙的“白马非马” (该版课程的内容更新及订正均已停止)旧版课程打包下载-想看涵盖“面向对象”、“图形编程”、“泛型编程”的“最新 2008 年版 白话 C+”课程,请点击! (另有: 博客版) 故事是春秋时的公孙龙先生说的。城门上告示:“马匹不得入城”。公孙龙同志骑白马而来,遭拒入。公孙龙一脸正色:“告示上写的是马,而我骑的是白马,难道 马等于 白马吗?”守门士兵觉得白马还真不是马,于是放行。依公孙龙先生的理论认为:如果白马是马,黑马也是马,那么岂不白马等于黑马,所以,不能说白马是马。“白马非马”是中国哲学史上的
3、一桩公案。不过,若是我们从程序的角度上说,可以认为:马在这里表示一种类型,而白马,黑马它们的类型都是马。白马,黑马具有相同的“数据类型”,但它们都相对独立的个体。从这点说,别说有白黑之分,就算同样是白马,这一匹和哪一匹白马,也是相对独立的个体。在程序中,“类型”和“变量”的关系正是“马”和“白马”的关系。如果 C 或 C+有这种数据类型: Horse,那么,定义一匹“白马”的变量应该这样:Horse AWhiteHorse;以后我们说不定真的有机会自已定义 Horse,不过现在,我们在上一章的学的数据类型是:char,int,bool 等等。假设我们需发使用一个有关年龄的变量,在 C 或 C+
4、中是这样定义的:int age;现在让我们来事先建立一个空的工程,随着本章课程的进展,我们需要不断地在工程中加入零星的代码,及时实践。仍然是一个空的控件台程序。方法是以前我们讲过,忘了就看前面章节吧。代码文件 Unit1.cpp 中,手工加入以下的黑体部分:/-#include #pragma hdrstop/-#pragma argsusedint main(int argc, char* argv)getchar();return 0;/-5.1.2 定义变量语法:数据类型 变量名;“张三”既可以指张三这个人,也可以是张三的名字。同样,上面的“变量名”,其实也就是变量本身。举上一节的例子:
5、int age;其中,int 是数据类型(整型),而 age 是变量名,更多的时候,我们就说是变量 age。最后是一人分号。它表示定义一变量在 C 或 C+里一句完整的语句。因为 C+的语言总是以分号结束。如果要声明一个字符类型变量:char letter;声明一个 bool 类型的变量:bool do_u_love_me;其它类型,除了 void 不能直接定义一个变量以外,格式都是一样的。void avoid; /错!void 类型无法直接定义一个变量。有时同一时候同一数据类型需要多个变量,此时可以分别定义,也可以一起定义:int a;int b;int c;下面采用一起定义,会更省事:in
6、t a,b,c;一起定义多个同类型变量的方法是:在不同变量之间以逗号(,)分隔,最后仍以分号(;)结束。让我们来试试变量定义,另外,我们还要趁此机会,看看一个变量在仅仅进行定义之后,它的值会是什么。继续上一小节的代码。仍然是加入黑体部分,当然 / 及其后面的内容是注释,你可以不输入。int main(int argc, char* argv)/定义变量/以下定义三个变量:a,b,cint a; double b,c; /a,b,c 仅仅被定义,它的值会是什么?我们用 cout 输出三个变量:cout 125 41 ) 63 ? 126 42 * 64 127 DEL (Delete 键)43
7、+ 65 90 A Z (其中,031 都是一些不可见的字符,所以这里只列出值为 0 的字符,值为 0 的字符称为空字符,输出该字符时,计算机不会有任何反应。我们以后会学习 0 字符的特殊作用。)4). 转义符的使用根据前面的说法,单引号应该表达为:char c = ; 但这是错误的。C、C+不认识 ,因为它容易引起歧义。另外,有一些字符属于不可见的字符,无法直接写出,比如键盘上大大的回车键,在输入文档时,用它可以输入一个回车换行,显然我们不能这样在 C/C+里表示一个回车换行:char c = 在第一个和第二个之间夹了一个换行,这样的表示方法不仅不方便,C 和 C+也不认。类似这样的问题还有
8、制表符(键盘上的 Tab 键)等等。解决的方法是使用转义符.C/C+使用反斜杠作为转义符。如: : 表示单引号;“ : 表示双引号;n : 表示换行(n : line);下面列出常用的 C、C+特殊字符:字符 数值 意义a 7 响铃(输出该字符时,屏幕无显示,但喇叭发音)n 10 换行(n: line )t 9 制表符(横向跳格)r 13 回车(return) 92 输出转义符 / 本身“ 34 双引号 39 单引号这里顺便解释一下“回车换行”是什么,尽管我们对这个词耳熟得很。“回车换行”是“回车”加“换行”。换行好理解,但什么叫“回车”呢?它和“换行”又有什么关系?原来,“回车换行”的概念源
9、于早先的打字机。类似于现在打印机中有一个打印头,这个打印头平常停在打印机内的某一端。在打印一行时,则需要向外移动,打印一行结束后,打印头需要回到原来位置。由于打印头在英文中用“车”来表示,所以这个动作就称为“回车”,用金山词霸的中的解释就是:“将打印或显示位置移到同行起始位置的运动。”所以对于打印机,假设有两行字,两行之间若光有“回车”,那么这两行字将重叠在一起(对于控制台程序的屏幕,则将回到行首)。如果光有“换行”,则第二行将不从起始位置打起,样子如下:这是第一行这是第二行。只有既输出“回车”又输出“换行”,才是我们常见的换行结果。当然,对于当今的大都软件,往往都把单独的回车或换行直接解释于
10、二者的结合。转义符的另外一种用法是直接接数值。但必须采用 8 进制或 16 进制。这里暂不讲解。如果需要使用数值表示,最直接的还是使用类似: c = 120; 的方法。比如要让变量 c 的值为单引号,我们至少可以有以下 2 种方法:char c = ; /使用转义符char c = 39; /直接赋给字符的 ASCII 的值。转义符的内容,看上去怪怪的?不过,多用几次我们就会明白。/char 类型/int k = 120;char j = 120;cout “k(int) = “ k “ j(char) = “ j endl;char l = A;char m = l + 1;cout “l
11、= “ l “ m = “ m endl;/转义符/cout “TAB:“ t “AA“ endl;cout “换行:“ n “AA“ endl;cout “回车:“ r “AA“ endl;cout “听到 BEEP 声了吗?“ a endl;cout endl;cout “ endl;cout endl;getchar();在执行之前,有必要稍作解释。首先那是“AA“做什么用。因为制表符、回车、换行等特殊字符,其输出效果是改变光标位置,所以我们需要一些上下文来显示出光标位置改变效果,这里就随便写个“AA”了事。然后是在 cout 语句中,平常我们叫是使用双引号输出一行话,但如果当前要输出只
12、是一个字符,我们也可以使用单引号。至于所谓“BEEP”声,你可别抱太多期望,它只是计算机内置的小喇叭短促一个声音,听起来并不美妙。现在来看结果(请只关心转义符部分):关于输出结果的几点说明:1、需要注意的是 t 在控制台窗口的输出效果,如果前面的字符长度已超过一个制表位,那么后面的第一个t将是没有效用的。(要理解这一点,你可以将代码中“TAB”加长一点,如“TABTAB“)。2、“AA 车” 的输出结果是怎么来的呢?请大家考虑考虑。试验程序在这里结束。5.2 变量与内存地址前面讲到“白马、黑马”时,我们说一匹白马和一匹黑马具有共同的数据类型“马”,但二者是相对独立的个体。现在我们以共熟悉的“人
13、”来继续这个话题,最终引出变量与内存地址的关系。张三和李四的数据类型都是“人类”。但张三和李四显然是独立的量:张三吃了两块蛋糕,李四不可能因此就觉和肚子饱了;而李四在下班的路上捡到一个钱包,虽然正好是张三的,两人似乎通过钱包有了点关系,但谁得谁失仍然不容混淆。这一切都很好理解。张三和李四之所以是不同的个体,根本原因在于两人有着不同的肉身。如果是一对连体婴儿,虽然也是两个人,但当左边的婴儿被蚊子咬一口时,右边婴儿是否也会觉得痒,就不好说了。现在我们困难的是,如何理解两个不同的变量,也是互相独立的呢?答案就在“内存地址”,“内存地址”就是变量的肉身。不同的变量,拥有不同的内存地址。譬如:char
14、a;char b;上面有两个字符类型的变量 a 和 b,a 拥有自已的内存地址,b 也拥有自已的内存地址,二者绝不相同。而 a、b 只不过分别是那两个内存地址的“名字”,恰如“张三、李四”。让我们看图解:看,内存就像是开宾馆的。不过这有宾馆有点怪。首先它每一个“房间”的大小都是一个字节(因此,计算机能单独处理的最小内存单位为字节)。它的门牌号也不叫房号,而是叫内存地址。在左图中,“房客”,变量 a 住在内存地址为 1000002 的内存中,而变量 b 则住在它的隔壁,地址为 100003 的内存中。另外,如果你足够细心,你还会发现:内存地址由下往上,从小到大排列。变量的内存地址是在程序运行时,
15、才由操作系统决定。这就好像我们住宾馆。我们预定一个房间,但房间号由宾馆根据情况决定,我们可以改变变量的值,但变量的地址我们无法改变。对照宾馆一说,就是我们订了房间,可以不去住,还可以决定让谁去住在那个房间里。(当然,现实生活中宾馆可能不会允许你这么做)。在前面图示的例子中,a、b 是字符(char)类型。各占用 1 个字节。如果是 int类型,那么应该占 4 个字节。这 4 个字节必须是连续的。让我们再来看一个例子:int a;int b;char c;这回,我们声明了两个 int 类型和一个 char 类型的变量。同时和上面一样,我们事实上是假设了这三个变量被依次序分配在相邻的内存地址上(真
16、实情况下,这和其它因素,如指定的字节对齐方式等有关)。从右图中可以看到整型变量 a占用了 10000011000004 这 4 个字节。在我们已学习的数据类型中,long double 占用 10 个字节,是占用内存空间最大的一种数据类型。以后我们学习数组,或者用户自定数据类型,则可能要求占用相当大的,并且同样必须是连续的空间。因此,如果操作系统仅仅通过简单的“按需分配”的原则进行内存管理,内存很快就会宣告不足。事实上,操作系统的内存管理相当复杂。幸好,一个普通的程序员并不要求去了解这些内幕。更多的有关内存管理的知识,我们会在下一部课程中学习。但是本章中有关内存的内容却相当重要。让我们来看看我
17、们学了什么:1、不同的变量,存入在不同的内存地址,所以变量之间相互独立。2、变量的数据类型决定了变量占用连续的多少个字节。3、变量的内存地址在程序运行时得以确定。变量的内存地址不能改变。除了这些以外,我们现在还要增加几点:现在,我们可以明白,为什么需要变量,显然,这又是一个讨好人类的做法。在汇编和机器语言,就是只对内存进行直接操作。但你也看到了,内存地址是一堆长长的数,不好记忆;另外,管理内存复杂易错,让程序员直接管理内存显示不可能。而通过变量,不仅让内存地址有了直观易记的名字,而且程序员不用直接对内存操作,何乐而不为呢?事实上,这是所有高级语言赖于实现基础。既然变量只不过是内存地址的名称,所
18、以:4、对变量的操作,等同于对变量所在地址的内存操作。第五点是反过来说:5、对指定内存地址的内存操作,等同对相应变量的操作。尽管这简直就是在重复。但这一条却是我们今后理解 C、C+语言相对于其它很多高级语言的,最灵活也最难学的“指针”概念的基石。5.3 常量说完变量,我们来学常量。看一段代码片段。省略号表示可能会有的其它操作。int a = 3;a = 100;代码中,a 是变量。一开始我们初始化为 3。后来出于什么需要,我们又将它赋值为 100。a 的值也就从 3 变成了 100。代码中,3 和 100 就是一种常量。像这种直接在代码写出大小的量,称为立即数,也称为常数,而常数是常量的一种。
19、常量的定义:常数,或代表固定不变值的名字。5.3.1 几种数据类型常数的表达方法5.3.1.1 整型常数的表达用 10 进制表示,当然是最常用也是最直观的了。如:7,356,-90,等等。 C,C+语言还允许我们使用 8 进制或 16 进制表示。这里且不讲。至于 2 进制形式,虽然它是计算机中最终的表示方法,但它太长,而且完全可以使用 16 进制或 8 进制方便地表达,所以计算机语言不提供用 2 进制表达常数的方法。有时,你也会看到一些老的代码中,在一些整型常后面加一个大写或小写的 L 字母。如:989L 这是什么意思呢?原来,一个常数如果其范围允许,那么计算机默认将其认为是 int 类型的,
20、那么要让计算机把某个数认为是 long int 类型,就可以在其后面加 L 或 l。不过,这在以前的 16 位机器才有意义了。现在,我们的机器都是 32 位,long int 和 int 完全一样,都是占用 4 个字节,所以,我们没有必要这样用了。5.3.1.2 实型常数的表达实型常数一般只用 10 进制表示。比如 123.45,或 .123。后者是 0.123 的简写。不过我个人认为,少写一个 0 的代价是很容看错。实型数还可以使用科学计数法,或曰指数形式,如:123e4、或 123E4 都表示 123 * 10 4,即 1230000。我们学过的实数数据类型有:float,double,l
21、ong double。在 C+中,默认的常数类型是 double。比如你写:1.234;那么,C+按 double 类型为这个数分配内存,也就是说为它分配 8 个字节。如果要改变这一点,可以通过加后缀字母来实现。加 f 或 F,指定为 float 类型。加 l 或 L, 指定为 double 类型。以下示例:12.3f /float 类型12.3 /默认类型(double)12.3L /long double 类型12.3e400 /long double 类型,因为值已不在 double 类型的取值范围内5.3.1.3 字符常量的表达关于字符的表示方法,我们已经在 5.1.3.4 节中的第
22、3 点讲过。这里简单重复。字符常量用单引号括起来的一个字符,如:a,b,c, ,A。等。可以用转义符来表示一些无法打出的符号,如 n,r,t。这里补充一点:值为 0 的字符,称为空字符,或零字符。它用 0 表示。注意,它和阿拉伯数字字符 0完全是两个数。(请大家查一前面常用字符 ASCII 值表中,后者的值是多少)5.3.1.4 字符串常量字符串由字符组成。在+语言中,字符串的是由一对双引号括起的来的字符序列。如:“Hello, world!“How do you do?“Hello“上面 3 行都是字符串常量。注意,双引号是英文字符。字符串是由一个个字符排列而成,所以在 C/C+中,字符串在
23、内存中的存储方式,我想你可以想象得出。每个字符占用一个字节,一个接一个排列。但是,双引号本身并不存储,因为双引号是为了表达方便,并不是实际内容。下面是示意图:(为了简单,我们选了例子中最短的字符串)不过,字符串的存储工作还有一点点事情需要做。举一个直观的例子。如果在上图中的 Hello 后,内存地址为 120006正好还存放一个字符:o。那么,程序就会把 Hello 和 o 连起来认作是一个字符串“Helloo”。为什么呢?前面我们讲字符类型,整型,等变量或常量时,根据数据类型的不同,它们都有已知的,固定的大小。字符串是由字符组成的,虽然每个字符的大小固定 1 个字节,但字符串的大小却是可变的
24、。所以必须有一个方法来表示一个字符串在哪里结束。空字符(值为 0 字符)担起这个责任。因为空字符没有任何输出效果,所以正常的字符串中是不会出现空字符的。因此,用它来表示一个字符串的结束,再合适不过了。以下是带有字符串在真正的内存存储示意图。记住,空字符用 0表示。有两个字符串:“Hello“What“假设二者在内存中存储的位置正好是连续的,那么内存示意就为:(为了结束版本,我这里横向表示,并且不再写出仅用于示意的内存地址)H e l l o 0 W h a t 0从表中,我们可以看出空字符 0是如何起到标志一个字符结束的作用。5.3.2 用宏表示常数假如我们要写一个有关圆的种种计算的程序,那么
25、(3.14159)值会被濒繁用到。我们显然没有理由去改的值,所以应该将它当成一个常量对待,那么,我们是否就不得不一遍一遍地写 3.14159 这一长串的数呢?必须有个偷懒的方法,并且要提倡这个偷懒,因为多次写 3.14159,难免哪次就写错了。这就用到了宏。宏不仅可以用来代替常数值,还可以用来代替表达式,甚至是代码段。(宏的功能很强大,但也容易出错,所以其利弊大小颇有争议。)今天我们只谈其中代替常数值的功能。宏的语法为:#define 宏名称 宏值比如要代替前面说到的值,应为:#define PAI 3.14159注意,宏定义不是 C 或 C+严格意义上的语句,所以其行末不用加分号结束。宏名称
26、的取名规则和变量名一样,所以我们这里用 PAI 来表示,因为 C、C+不能直接使用字符。有了上面的语句,我们在程序中凡是要用到 3.14159 的地方都可以使用 PAI 这个宏来取代。作为一种建议和一种广大程序员共同的习惯,宏名称经常使用全部大写的字母。假设原来有一段代码:double zc = 2 * 3.14159 * R; /求圆周长,其中 R 是代表半径的变量double mj = 3.14159 * R * R; /求圆面积在定义了宏 PAI 以后,我们就可以这样使用:#define PAI 3.14159double = 2 * PAI * R; /求圆周长,其中 R 是代表半径的
27、变量double = PAI * R * R; /求圆面积用宏来取代常数,好处是:1)让代码更简洁明了当然,这有赖于你为宏取一个适当的名字。一般来说,宏的名字更要注重有明确直观的意义,有时宁可让它长点。2)方便代码维护就如前面说的 3.14159。哪天你发现这个值精度不够,想改为 3.1415926,那么你只修改一处宏,而不是修改代码中的所有宏。原来的宏:#define PAI 3.14159修改后的宏:#define PAI 3.1415926对宏的处理,在编译过程中称为“预处理”。也就是说在正式编译前,编译器必须先将代码出现的宏,用其相应的宏值替换,这个过程有点你我在文字处理软件中的查找替
28、换。完成预处理后,所有原来的“PAI”都成了立即数 3.1415926。所以在代码中使用宏表达常数,归根结底还是使用了立即数,并没有明确指定这个量的类型。这容易带来一些问题,所以 C+使用另一更稳妥的方法来代替宏的这一功能。 5.3.3 常量定义常量定义的格式为: const 数据类型 常量名 = 常量值; 相比变量定义的格式,常量定义必须以 const 开始,另外,常量必须在定义的同时,完成赋值。 const float PAI = 3.1415926; const 的作用就是指明这个量(PAI)是常量,而非变量。 常量必须一开始就指定一个值,然后,在以后的代码中,我们不允许改变 PAI 的
29、值,比如: const float PAI = 3.14159; double zc = 2 * PAI * R; PAI = 3.1415926; /错误!,PAI 不能再修改。 double mj = PAI * R * R; 如果一个常量是整型,可以省略指出数据类型,如: const k = 100; 相当于 const int k = 100; 反过来说,如果不指定数据类型,则编译器将它当成整型。比如: const k = 1.234; 虽然你想让 k 等于一个实型数,然而,最终 k 的值其实是 1。因为编译器把它当成整型常量。 我们建议在定义变量时,明确指出类型,不管它是整型或其它类
30、型。 const int i = 100; const double di = 100.0; 5.3.4 枚举常量5.3.4.1 为什么需要枚举类型生活中很多信息,在计算机中都适于用数值来表示,比如,从星期一到星期天,我们可以用数字来表示。在西方,洋人认为星期天是一周的开始,按照这种说法,我们定星期天为 0,而星期一到六分别用 1 到 6 表示。 现在,有一行代码,它表达今天是周 3: int today = 3; 很多时候,我们可以认为这已经是比较直观的代码了,不过可能在 6 个月以后,我们初看到这行代码,会在心里想:是说今天是周 3 呢,还是说今天是 3 号?。其实我们可以做到更直观,并且
31、方法很多。 第一种是使用宏定义: #define SUNDAY 0 #define MONDAY 1 #define TUESDAY 2 #define WEDNESDAY 3 #define THURSDAY 4 #define FRIDAY 5 #define SATURDAY 6 int today = WEDNESDAY; 第二种是使用常量定义: const int SUNDAY = 0; const int MONDAY = 1; const int TUESDAY = 2; const int WEDNESDAY = 3; const int THURSDAY = 4; const
32、 int FRIDAY = 5; const int SATURDAY = 6; int today = WEDNESDAY; 第三种方法就是使用枚举。 枚举也是我们将学习的第二种用户自定义数据类型的方法。上回我们学过 typedef。typedef 通过为原有的数据类型取一别名来获得一新的数据类型。枚举类型的原有数据只能是 int 或 char 类型(有些编译器则只支持 int,如 VC)。 枚举类型是在整数的取值范围中,列出需要的个值作为新数据类型的取值范围。 这就像 bool 类型,其实它是 char 类型,但它只需要 0 或 1 来代表 false 或 true。 这里的例子中,我们用
33、整型来表示周一到周日。整型的取值范围是多少来的?反正很大,可我们只需 0 到 6,并且我们希望这 7 个值可以有另外一种叫法,以直观地表示星期几。 5.3.4.2 定义枚举类型的基本语法enum 枚举类型名 枚举值 1,枚举值 2, ; enum : 是定义枚举类型的关键字。 枚举类型名 :我们要自定义的新的数据类型的名字。 枚举值 :可能的个值。 比如: enum Week SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY; 这就定义了一个新的数据类型:Week。 Week 数据类型来源于 int 类型(默认)。 Week 类型
34、的数据只能有种取值,它们是:SUNDAY,MONDAY,TUESDAYSATURDAY。 其中 SUNDAY = 0,MONDAY = 1SATURDAY = 。也就是说,第个枚举值代表,第个枚举值代表,这样依次递增。 不过,也可以在定义时,直接指定某个或某些枚举值的数值。比如,对于中国人,可能对于用表示星期日不是很好接受,不如用来表示星期天。这样我们需要的个值就是 1,2,3,4,5,6,7。可以这样定义: enum Week MONDAY = 1,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY; 我们希望星期一仍然从开始,枚举类型默认枚举值从开始,所以我们直接指定 MONDAY 等于,这样,TUESDAY 就将等于,直接到 SUNDAY 等于。 枚举值,我们就称为格举常量,因为它一经定义以后,就不可再改变,以下用法是错误的! TUESDAY = 10; /错误!我们不能改变一个枚举值的数值。 用枚举常量来完成表达今天是星期三: Week today = TUESDAY; 再举一例。在计算机中,颜色值也是使用整数来表示的。比如红色用 255 表示,绿色用 65280 表示,而黄色则用