首页 服务器 编程 必备知识 搜索引擎 圩日手册
站内搜索
最近浏览
推荐文章
热文排行

第五章 变量和常量


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 定义枚举类型的基本语法

    5.3.4.3 关于枚举常量的输出效果

5.1 从类型到变量

5.1.1 公孙龙的“白马非马” 

故事是春秋时的公孙龙先生说的。

城门上告示:“马匹不得入城”。公孙龙同志骑白马而来,遭拒入。公孙龙一脸正色:“告示上写的是‘马’,而我骑的是‘白马’,难道 ‘马’等于 ‘白马’吗?”

守门士兵觉得白马还真不是马,于是放行。

依公孙龙先生的理论认为:如果白马是马,黑马也是马,那么岂不白马等于黑马,所以,不能说白马是马。“白马非马”是中国哲学史上的一桩公案。不过,若是我们从程序的角度上说,可以认为:马在这里表示一种类型,而白马,黑马它们的类型都是马。

白马,黑马具有相同的“数据类型”,但它们都相对独立的个体。从这点说,别说有白黑之分,就算同样是白马,这一匹和哪一匹白马,也是相对独立的个体。

在程序中,“类型”和“变量”的关系正是“马”和“白马”的关系。

如果C或C++有这种数据类型: Horse,那么,定义一匹“白马”的变量应该这样:

Horse AWhiteHorse;

以后我们说不定真的有机会自已定义Horse,不过现在,我们在上一章的学的数据类型是:

char,int,bool 等等。

假设我们需发使用一个有关年龄的变量,在C或C++中是这样定义的:

int age;

 

现在让我们来事先建立一个空的工程,随着本章课程的进展,我们需要不断地在工程中加入零星的代码,及时实践。

 

仍然是一个空的控件台程序。方法是……以前我们讲过,忘了就看前面章节吧。

代码文件Unit1.cpp中,手工加入以下的黑体部分:

//---------------------------------------------------------------------------

#include <iostream.h>

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused

int main(int argc, char* argv[])]

{

  getchar();

  return 0;

}

//---------------------------------------------------------------------------

5.1.2 定义变量

语法:

 

数据类型 变量名;

 

“张三”既可以指张三这个人,也可以是张三的名字。同样,上面的“变量名”,其实也就是变量本身。

 

举上一节的例子:

 

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;

下面采用一起定义,会更省事:

 

int a,b,c;

一起定义多个同类型变量的方法是:在不同变量之间以逗号(,)分隔,最后仍以分号(;)结束。

 

让我们来试试变量定义,另外,我们还要趁此机会,看看一个变量在仅仅进行定义之后,它的值会是什么。

继续上一小节的代码。仍然是加入黑体部分,当然 // 及其后面的内容是注释,你可以不输入。

......

int main(int argc, char* argv[])]

{

 /////////////////定义变量//////////////////////////////////////////////////

  //以下定义三个变量:a,b,c

  int a;      

  double b,c; 

 

  //a,b,c仅仅被定义,它的值会是什么?我们用 cout 输出三个变量:

  cout << "a = " << a << " b = " << b << " c = " << c << endl;

 

  getchar();

  return 0;

}

 

最好先保存代码文件和工程文件。然后按F9运行。以下是笔者机器得到结果。

 

a 是1,b和c都像天文数字?嗯,从这里我们学到一个C,C++编程极其重要知识:未初始化的变量,它的值将是未确定的。所谓“未初始化”,就是指这个变量只有定义代码,而没有赋值。

(立即重复执行这段代码,得到结果可能都一样,但这并不说明这些值就是可以确定不变。)

5.1.3 如何为变量命名

C/C++的变量的名字中只能有以下字符:大小写字母、阿拉伯数字(但不能做为开头)、下划线 _。

汉字不能成为变量名。

不过,就算允许,又有谁会这么累呢,用汉字作变量名?

 

不能或不要

不能取名为C、C++的保留字。

如:

int char; //不行

这是不被允许的。char 是一个保留字。我们不能再拿来做变量。

 

不能超过250个字符。

这在BCB里有规定。是一个可以调整的值。

 

不能以数字开头

int 100;     //不行

int 100ren;  //不行

 

不能夹有空格

bool do you love me; //不行

你可以用下划线代替空格:

bool do_you_love_me; //OK

 

不能在同一作用范围内有同名变量 (仅C++)

比如:

int abc; 

int abcd; 

int abc;  //不行

 

在C里, 上面重复定义变量 abc是被允许的。

关于作用范围,我们以后会讲到。

 

不要和C、C++中已有的全局变量,函数,类型名取相同的名字。

double sin;  

这不合适。因为C库为我们提供了计算正弦值的函数,名字就叫 sin;

 

不要太长。

是的,250个字符其实太长了。如果有个变量名长达30个字母,我看不仅你自已写得累,别人看着也会觉得是在读外国小说,主人公的名字太长,烦人。

 

不要太短

这个我们放到后面说。

 

以上几点中,凡标为“不能”,意味如果你违反了,你的程序便会在编译时出错。而“不要”则仅属建议内容。真要这么做,程序也没错。

另外,我们还建议要为每个变量取一个有意义的名字。比如 NumberOfStudents,这个变量一看就明白是为“学生的人数”定义的。而如果定义为 aaa,cc,之类,就容易混淆。当然,有一些约定成俗的用法,如字母i,j,等常用来作循环流程中的计数变量。再者,有意义的名字并不是指一定要把变量所要代表的意思用英文句子写出,一般可以用简写,如 NumOfStudent,它同样意义明了,但更简短。而且,如果我们英文一般,那么有时也可以使用拼音。这里就不举例了,因为笔者连拼音都很次。

前面说到取名不要太短,说的就是避免像 aaa,cc之类的图输入方便,但毫无意义,不可读的变量命名习惯。

(你很快就会在教程中发现,笔者似乎自已违反了这个规定,用一些a,b,c作为变量名。这不能说是笔者的错。因为会有些时候变量的意义并不重要)

最后,C,C++是区要大小写的语言,所以变量 Num 和变量 num 完全是两个变量。大家在定义,使用变量要注意到这一点。

 

关于变量的命名,我们不做实践。下面附加说说编程中命名的一些风格。

 

附:关于命名变量的几种风格。

较早以前,现在仍流行于UNIX、Linux编程界,程序员喜欢用全部小写的单词来命名变量,如果两个单词,比如my car,常用的命名方法有两种:my_car或myCar。my_car自然看起清楚,不过输入频繁地下划线是件累事(根据指法,下划线必须使用小指头按)。后一种方法被称为“驼峰表示法”,原因是大写字母看起来想凸起的驼峰。

之所以不使用MyCar,原因是C/C++允许程序自定义数据类型,所以有必要从一个名字上很快地区分它是变量或是数据类型。方法是让自定义的数据类型都用大写开头。比如前面的说的“马”是程序员自定的数据类型,那么如果采用这里的命名规则,则应取名为:Horse,而“一匹白马”是变量,所以取名为:aWhiteHorse。

Horse aWhiteHorse;

在C++ Builder里,并没有限制大家如何为变量取名。所以为了仍可以很明显的做到上述的区分,CB的方法是对用户自定义的数据类型在前头加一个字母T(Type的首字母)。仍如 Horse,则改名为:THorse。前面我们写Windows版的hello world时,使用了一个Label控件,其实,检查代码你会发现,它的类名就叫:TLabel。

最后还有一种方法是匈牙利标记法(Hungarian notation)。该法要求在每人变量的前面加上若干个用于表示该变量的数据类型的小写字母。如iMyCar表示这个变量是整型(i表示int);而倘若是cMyCar,则表示这个变量是char类型。该法经过一段时间的训练熟悉以后,会带来一些好处。问题是如果对自定义的数据类型也按这种方法进行,就不是经过训练就能熟悉了。比如hoWhite,这个名字中的ho表示“马”,真有点强人所难。举上实际存在的例子,在Windows API中,如果你看到:

LPCITEMIDLIST pidlRoot;

想要一眼看明白 pidRoot,必须的要求是你很明白 ITEMIDLIST 是什么玩意儿了。

是的,Windows的API使用的是最后一种方法。在大多数情况下,它的变量的名字都看上去怪怪的。

在本部教程中,我们在正式程序中,最常使用的方法是简单的“驼峰”法。

5.1.4 如何初始化变量

5.1.4.1 什么时候需要给变量初始化?

int a;

声明了一个整型变量a。但这变量的值是多少?a等于0吗?还是等于100?我们都不知道。“不知道”的意思是:a 有值,但这个值的大小是随机的,所以我们无法确定。

无法确定一个变量值是常有的事,比如我们要求用户输入他的年龄。我们需要声明一个整型变量来存储用户年龄,但在用户输 入之前,我们是无法确认它的值。

但有些时候,我们可以事先给一个变量初始时的值。同样是年龄的问题,虽然我们不知道用户到底几岁,但我们知道他不可能是 0 ,所以我们把年龄一开始设为0。为什么要这样?用户有时不小心忘了输入年龄(就像我们在网上填表一样),我们就可以检查年龄是否为0来发现了。另外一种相反的用法是,我们发现大都数用户是8岁(比如一个小学生入学登记表),这时我们初始化年龄变量为8,目的是为了方便用户了。

那么,如果为一个变量赋值呢?

答案就像我们初中的代数:设 x = 10, y = 100。用等号。请记住:现实生活中,等号(=)有两个意义,但在C/C++里,= 只用来给一个变量赋值。

5.1.4.2 初始化变量的两个时机
  1. 在定义时初始化变量

int a = 0;

 

通过一个等号,我们让a的值等于0;

 

同时定义多个变量时也一样:

int a = 0, b= 1;

 

当然也可以根据需要,只给部分变量初始化。

int a = 0, b;

或:

int a,b = 1;

  1. 在定义以后赋值

int a;

a = 100;

 

5.1.4.3 通过计算得到初始值

给变量赋值,除了给一个直接的值以外,还可以通过计算获得。如:

int a = -3 + 200 - 5;

或者如:

int a = 9;

int b = 3;

int c = a / b;

(/ 表示除号)

 

现在来试试给变量赋值的几种方法。

 

......

int main(int argc, char* argv[])]

{

 /////////////////定义变量//////////////////////////////////////////////////

 //以下定义三个变量:a,b,c

  int a;      

  double b,c; 

 

  //用 cout 输出三个变量:

  cout << "a = " << a << " b = " << b << " c = " << c << endl;

 

/////////////////初始化变量////////////////////////////////////////////////

  int d = 0;

  float e =  1.234, f;

  f = 567.8 + 0.2;

 

  cout << "d = " << d << " e = " << e << " f = " << f << endl;

 

  getchar();

  return 0;

}

 

5.1.4.4 变量的取值范围

1). 变量允许取值范围 = 变量数据类型的范围

 

关于赋值的最后一个需要注意的是:变量的取值范围。

变量的取值范围?变量是有类型的,变量的允许的取值范围等同于其数据类型的范围。比如一个整型数,那么它的取值范围就是:-2147483648 ~ 2147483647。而如果是一个字符类型,它的取值就不能是 -129。

以下是示例:

int  a = 3000000000; //错误!

char b = -129; //错误!

 

我们来写代码实践一下。由于char类型计算机在输出将直接输出字符,我们不好观察结果,所以我们选择了int类型。

......

int main(int argc, char* argv[])]

{

  /////////////////定义变量//////////////////////////////////////////////////

 //以下定义三个变量:a,b,c

  int a;      

  double b,c; 

 

  //用 cout 输出三个变量:

  cout << "a = " << a << " b = " << b << " c = " << c << endl;

 

 /////////////////初始化变量////////////////////////////////////////////////
     int d = 0;

  float e =  1.234, f;

  f = 567.8 + 0.2;

 

  cout << "d = " << d << " e = " << e << " f = " << f << endl;


  /////////////////变量值越界//////////////////////////////////////////////////

  int g = 3000000000; //给g赋值超过int允许的范围,所以g的值不可能如愿等于3000000000

  cout << "g = " << g <<endl;

  getchar();

  return 0;

}

 

g 的值已经溢出,它的值究竟是多少?自已按F9运行,看结果吧。

 

2). bool 类型的特殊

 

至于 bool 类型,事实上它的内存空间范围和char一样是一字节的(因为计算机能直接处理的最小内存单位是字节),所以按说它也能存256个不同的值,但作为C++的一个规定,强制给它两个值: false,true。那么 false 是什么呢?其实它是0。而 true则是非0的数。也就是说,值0对应为false,而所有非0的数对应成true。

所以,你这样给bool类型的变量赋值自然没错:

bool do_u_love_me = false; //噢,你不爱我。

bool do_u_love_me = true;

但你也可以这样给来:

bool do_u_love_me = 0;

bool do_u_love_me = 1;

当然,我们建议使用第一种方法,那是“正规”的方法。

在C里,并没有bool这个数据类型,所以C程序员采用的方法是自定义:

typedef char BOOL;

如果你在某些书上碰到BOOL,你就可以明白它就是我们学的 bool.

 

false和true到底是什么?“假”或“真”,很简单啊。噢,我不是问二者的意思,C++之所以要加入bool类型,目的也是为了让程序员在处理那些只用于表示“真或假”、“是或否”等数据时,可以用直观的false和true来表示。而在计算机内部,二者仍然是数。让我们写几行代码,揭开bool类型的“假面”。

......

int main(int argc, char* argv[])]

{

 /////////////////定义变量//////////////////////////////////////////////////
    //以下定义三个变量:a,b,c

  int a;      

  double b,c; 

 

  //用 cout 输出三个变量:

  cout << "a = " << a << " b = " << b << " c = " << c << endl;

 

/////////////////初始化变量////////////////////////////////////////////////
     int d = 0;

  float e =  1.234, f;

  f = 567.8 + 0.2;

  cout << "d = " << d << " e = " << e << " f = " << f << endl;


   /////////////////变量值越界//////////////////////////////////////////////////
     int g = 3000000000; //给g赋值超过int允许的范围,所以g的值不可能如愿等于3000000000

  cout << "g = " << g <<endl;

 

/////////////////bool类型的"真面目"//////////////////////////////////////////
    bool h = false ,i = true;  

  cout << "h = " << h << " i = " << i << endl; 

  getchar();

  return 0;

}

运行后……原来,false是0,true是1。

 

3). char 类型的特殊

 

char 的范围是 -128 ~ 127

unsigned char 的范围是 0 ~ 255

那么按照前面的说法,我们可以为这样为一个字符类型的变量赋值:

 

char c = 120;

unsigned char uc = 250;

 

这样看来,所谓的“字符”类型,似乎除了取值范围小一点以外,和整型也没有什么区别。这句话的确没错。对于C、C++来说,字符类型完全可以当成一个整数来对待。

事实上,所有信息在计算机里,都是使用数字来表达。英文字母 'A' 在计算机里表示为 65; 字母 'B' 表示为66。所有你在键盘可以看到的字符,如大小写英文字母,阿拉伯数字符号,标点符号都可以有一个相应的数值表示。

但要让我们记住65就是'A',而33就 '!'等255个对应关系,显然很折磨人,所以,计算机高级语言允许我们直接为字符类型变量这样赋值:

char  c = 'A';  

char  d = '!'; //英文感叹号

char  e = '.'; //英文句号

char  f = ' '; //空格

即:将所要得到的字符用单引号括住。(引号''是英文状态下的,千万不要使用中文符号)

 

另外,对于一个数值类型,如果它等于120,那么输出时显示的120,如果是一个字符类型,输出却是120对应的字符。也就是说:

int  k = 120;

char j = 120;

二者虽然值都为120,但输出j时,计算机并不显示120这个值,而是120对应的字符。试试看!

 

为了不让教程中的代码重复占用版面,省略号省略掉的代码要多点了……

 

......

 

/////////////////char 类型///////////////////////////////////////////////////

int k = 120;

char j = 120;

cout << "k(int) = " << k << " j(char) = " << j << endl;

 

getchar();

......

输出结果,k当然是120,但j,原来120对应的字母是 'x'。写的是120,输出的却是x,很不直观对不?所以,除非我们故意要和自已或者其他看代码的人玩“密码”,否则,还是直接想要什么字符,就写哪个字符吧。

 

/////////////////char 类型///////////////////////////////////////////////////

int k = 120;

char j = 120;

cout << "k(int) = " << k << " j(char) = " << j << endl;

 

char l = 'A';

char m = l + 1;

cout << "l = " << l << " m = " << m << endl;

 

getchar();

......

输出结果,l为 'A',而 m 为 'B',想一想,为什么?学完后面内容就有答案。

 

单引号本身也是一个字符,如何表示单引号呢?是否用 ''' 来表示?看明白下面的常用字符ASCII码表以后再说。

 

(ASCII是指:American Standard Code for Information Interchange,美国信息交换标准码。)

符号 符号 符号
0 空字符 44 , 91 [
32 空格 45 - 92 \
33 ! 46 . 93 ]
34 " 47 / 94 ^
35 # 48 ~ 57 0 ~ 9  95 -
36 $ 58 : 96 `
37 % 59 ; 97 ~ 122 a ~ z
38 & 60 < 123 {
39 ' 61 = 124 |
40 ( 62 > 125 }
41 ) 63 ? 126 ~
42 * 64 @ 127 DEL (Delete键)
43 + 65 - 90 A ~ Z    

(其中,0~31都是一些不可见的字符,所以这里只列出值为0的字符,值为0的字符称为空字符,输出该字符时,计算机不会有任何反应。我们以后会学习0字符的特殊作用。)

 

4). 转义符的使用

 

根据前面的说法,单引号应该表达为:

char c = '''; 

但这是错误的。C、C++不认识 ''',因为它容易引起歧义。

另外,有一些字符属于不可见的字符,无法直接写出,比如键盘上大大的回车键,在输入文档时,用它可以输入一个回车换行,显然我们不能这样在C/C++里表示一个回车换行:

char c = '

'

在第一个'和第二个'之间夹了一个换行,这样的表示方法不仅不方便,C和C++也不认。

类似这样的问题还有制表符(键盘上的Tab键)等等。

解决的方法是使用转义符.C/C++使用反斜杠'\'作为转义符。如:

'\'' : 表示单引号;

'\"' : 表示双引号;

'\n' : 表示换行(n : line);

 

下面列出常用的C、C++特殊字符:

字符 数值 意义
'\a' 7   响铃(输出该字符时,屏幕无显示,但喇叭发音)
'\n' 10 换行(n: line)
'\t' 9 制表符(横向跳格)
'\r' 13 回车(return)
'\\' 92 输出转义符 '/' 本身
'\"' 34 双引号
'\'' 39 单引号

这里顺便解释一下“回车换行”是什么,尽管我们对这个词耳熟得很。

“回车换行”是“回车”加“换行”。

换行好理解,但什么叫“回车”呢?它和“换行”又有什么关系?

原来,“回车换行”的概念源于早先的打字机。类似于现在打印机中有一个打印头,这个打印头平常停在打印机内的某一端。在打印一行时,则需要向外移动,打印一行结束后,打印头需要回到原来位置。由于打印头在英文中用“车”来表示,所以这个动作就称为“回车”,用金山词霸的中的解释就是:“将打印或显示位置移到同行起始位置的运动。”

所以对于打印机,假设有两行字,两行之间若光有“回车”,那么这两行字将重叠在一起(对于控制台程序的屏幕,则将回到行首)。如果光有“换行”,则第二行将不从起始位置打起,样子如下:

 

这是第一行

         这是第二行。

 

只有既输出“回车”又输出“换行”,才是我们常见的换行结果。当然,对于当今的大都软件,往往都把单独的回车或换行直接解释于二者的结合。

 

转义符的另外一种用法是直接接数值。但必须采用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 = " << 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语句中,平常我们叫是使用双引号输出一行话,但如果当前要输出只是一个字符,我们也可以使用单引号。

至于所谓“BEEP”声,你可别抱太多期望,它只是计算机内置的小喇叭短促一个声音,听起来并不美妙。

 

现在来看结果(请只关心转义符部分):

 

 

关于输出结果的几点说明:

 

1、需要注意的是 '\t' 在控制台窗口的输出效果,如果前面的字符长度已超过一个制表位,那么后面的第一个'\t'将是没有效用的。(要理解这一点,你可以将代码中“TAB”加长一点,如"TABTAB")。

2、“AA车” 的输出结果是怎么来的呢?请大家考虑考虑。

 

试验程序在这里结束。

5.2 变量与内存地址

前面讲到“白马、黑马”时,我们说一匹白马和一匹黑马具有共同的数据类型“马”,但二者是相对独立的个体。现在我们以共熟悉的“人”来继续这个话题,最终引出变量与内存地址的关系。

张三和李四的数据类型都是“人类”。但张三和李四显然是独立的量:张三吃了两块蛋糕,李四不可能因此就觉和肚子饱了;而李四在下班的路上捡到一个钱包,虽然正好是张三的,两人似乎通过钱包有了点关系,但谁得谁失仍然不容混淆。

这一切都很好理解。张三和李四之所以是不同的个体,根本原因在于两人有着不同的肉身。如果是一对连体婴儿,虽然也是两个人,但当左边的婴儿被蚊子咬一口时,右边婴儿是否也会觉得痒,就不好说了。

现在我们困难的是,如何理解两个不同的变量,也是互相独立的呢?

答案就在“内存地址”,“内存地址”就是变量的肉身。不同的变量,拥有不同的内存地址。譬如:

 

char a;

char b;

 

上面有两个字符类型的变量a和b,a拥有自已的内存地址,b也拥有自已的内存地址,二者绝不相同。而a、b只不过分别是那两个内存地址的“名字”,恰如“张三、李四”。

 

让我们看图解:

看,内存就像是开宾馆的。不过这有宾馆有点怪。首先它每一个“房间”的大小都是一个字节(因此,计算机能单独处理的最小内存单位为字节)。它的门牌号也不叫房号,而是叫内存地址

在左图中,“房客”,变量a住在内存地址为1000002的内存中,而变量b则住在它的隔壁,地址为100003的内存中。另外,如果你足够细心,你还会发现:内存地址由下往上,从小到大排列

 

变量的内存地址是在程序运行时,才由操作系统决定。这就好像我们住宾馆。我们预定一个房间,但房间号由宾馆根据情况决定,

我们可以改变变量的值,但变量的地址我们无法改变。对照宾馆一说,就是我们订了房间,可以不去住,还可以决定让谁去住在那个房间里。(当然,现实生活中宾馆可能不会允许你这么做)。

 

 

 

 

在前面图示的例子中,a、b是字符(char)类型。各占用1个字节。如果是 int类型,那么应该占4个字节。这4个字节必须是连续的。让我们再来看一个例子:

 

int a;

int b;

char c;

 

这回,我们声明了两个int类型和一个char类型的变量。同时和上面一样,我们事实上是假设了这三个变量被依次序分配在相邻的内存地址上(真实情况下,这和其它因素,如指定的字节对齐方式等有关)。从右图中可以看到整型变量a占用了1000001~100004这4个字节。

 

在我们已学习的数据类型中,long double占用10个字节,是占用内存空间最大的一种数据类型。以后我们学习数组,或者用户自定数据类型,则可能要求占用相当大的,并且同样必须是连续的空间。因此,如果操作系统仅仅通过简单的“按需分配”的原则进行内存管理,内存很快就会宣告不足。事实上,操作系统的内存管理相当复杂。幸好,一个普通的程序员并不要求去了解这些内幕。更多的有关内存管理的知识,我们会在下一部课程中学习。但是本章中有关内存的内容却相当重要。

 

让我们来看看我们学了什么:

1、不同的变量,存入在不同的内存地址,所以变量之间相互独立。

2、变量的数据类型决定了变量占用连续的多少个字节。

3、变量的内存地址在程序运行时得以确定。变量的内存地址不能改变。

 

除了这些以外,我们现在还要增加几点:

现在,我们可以明白,为什么需要变量,显然,这又是一个讨好人类的做法。在汇编和机器语言,就是只对内存进行直接操作。但你也看到了,内存地址是一堆长长的数,不好记忆;另外,管理内存复杂易错,让程序员直接管理内存显示不可能。而通过变量,不仅让内存地址有了直观易记的名字,而且程序员不用直接对内存操作,何乐而不为呢?事实上,这是所有高级语言赖于实现基础。

既然变量只不过是内存地址的名称,所以:

4、对变量的操作,等同于对变量所在地址的内存操作。

第五点是反过来说:

5、对指定内存地址的内存操作,等同对相应变量的操作。

尽管这简直就是在重复。但这一条却是我们今后理解C、C++语言相对于其它很多高级语言的,最灵活也最难学的“指针”概念的基石。

5.3 常量

说完变量,我们来学常量。

看一段代码片段。省略号表示可能会有的其它操作。

int a = 3;

....

a = 100;

 

代码中,a 是变量。一开始我们初始化为3。后来出于什么需要,我们又将它赋值为100。a的值也就从3变成了100。

代码中,3 和 100就是一种常量。像这种直接在代码写出大小的量,称为立即数,也称为常数,而常数是常量的一种。

 

常量的定义:常数,或代表固定不变值的名字。

 

5.3.1 几种数据类型常数的表达方法

5.3.1.1 整型常数的表达

用10进制表示,当然是最常用也是最直观的了。如:7,356,-90,等等。 C,C++语言还允许我们使用8进制或16进制表示。这里且不讲。至于2进制形式,虽然它是计算机中最终的表示方法,但它太长,而且完全可以使用16进制或8进制方便地表达,所以计算机语言不提供用2进制表达常数的方法。

 

有时,你也会看到一些老的代码中,在一些整型常后面加一个大写或小写的 L 字母。如:989L 这是什么意思呢?原来,一个常数如果其范围允许,那么计算机默认将其认为是 int 类型的,那么要让计算机把某个数认为是 long int类型,就可以在其后面加 L 或 l。不过,这在以前的16位机器才有意义了。现在,我们的机器都是32位,long int 和 int 完全一样,都是占用4个字节,所以,我们没有必要这样用了。

5.3.1.2 实型常数的表达

实型常数一般只用10进制表示。比如 123.45,或 .123。后者是 0.123的简写。不过我个人认为,少写一个0的代价是很容看错。

实型数还可以使用科学计数法,或曰指数形式,如:123e4、或123E4 都表示 123 * 104,即 1230000。

我们学过的实数数据类型有:float,double,long 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 节中的第3点讲过。这里简单重复。

字符常量用单引号括起来的一个字符,如:'a','b','c',' ','A'。等。

可以用转义符来表示一些无法打出的符号,如 '\n','\r','\t'。

 

这里补充一点:值为0的字符,称为空字符,或零字符。它用 '\0' 表示。注意,它和阿拉伯数字字符 '0'完全是两个数。(请大家查一前面常用字符ASCII值表中,后者的值是多少)

5.3.1.4 字符串常量

字符串由字符组成。在C/C++语言中,字符串的是由一对双引号括起的来的字符序列。如:

"Hello, world!"

"How do you do?"

"Hello"

上面3行都是字符串常量。注意,双引号是英文字符。

字符串是由一个个字符排列而成,所以在C/C++中,字符串在内存中的存储方式,我想你可以想象得出。每个字符占用一个字节,一个接一个排列。但是,双引号本身并不存储,因为双引号是为了表达方便,并不是实际内容。下面是示意图:

(为了简单,我们选了例子中最短的字符串)

不过,字符串的存储工作还有一点点事情需要做。举一个直观的例子。如果在上图中的 Hello后,内存地址为120006正好还存放一个字符:‘o’。那么,程序就会把 Hello 和 o连起来认作是一个字符串“Helloo”。为什么呢?

前面我们讲字符类型,整型,等变量或常量时,根据数据类型的不同,它们都有已知的,固定的大小。字符串是由字符组成的,虽然每个字符的大小固定1个字节,但字符串的大小却是可变的。所以必须有一个方法来表示一个字符串在哪里结束。

空字符(值为0字符)担起这个责任。因为空字符没有任何输出效果,所以正常的字符串中是不会出现空字符的。因此,用它来表示一个字符串的结束,再合适不过了。以下是带有字符串在真正的内存存储示意图。

记住,空字符用 '\0'表示。

 

有两个字符串:

"Hello"

"What"

假设二者在内存中存储的位置正好是连续的,那么内存示意就为:

(为了结束版本,我这里横向表示,并且不再写出仅用于示意的内存地址)

H e l l o \0 W h a t \0

从表中,我们可以看出空字符 '\0'是如何起到标志一个字符结束的作用。

 

5.3.2 用宏表示常数

假如我们要写一个有关圆的种种计算的程序,那么∏(3.14159)值会被濒繁用到。我们显然没有理由去改∏的值,所以应该将它当成一个常量对待,那么,我们是否就不得不一遍一遍地写3.14159这一长串的数呢?

必须有个偷懒的方法,并且要提倡这个偷懒,因为多次写3.14159,难免哪次就写错了。

这就用到了宏。宏不仅可以用来代替常数值,还可以用来代替表达式,甚至是代码段。(宏的功能很强大,但也容易出错,所以其利弊大小颇有争议。)今天我们只谈其中代替常数值的功能。

宏的语法为:

 

#define 宏名称 宏值

 

比如要代替前面说到的∏值,应为:

 

#define PAI 3.14159

 

注意,宏定义不是C或C++严格意义上的语句,所以其行末不用加分号结束。

 

宏名称的取名规则和变量名一样,所以我们这里用PAI来表示∏,因为C、C++不能直接使用∏字符。有了上面的语句,我们在程序中凡是要用到3.14159的地方都可以使用PAI这个宏来取代。

作为一种建议和一种广大程序员共同的习惯,宏名称经常使用全部大写的字母。

 

假设原来有一段代码:

 

double zc = 2 * 3.14159 * R; //求圆周长,其中R是代表半径的变量

double mj = 3.14159 * R * R; //求圆面积

 

在定义了宏PAI以后,我们就可以这样使用:

#define PAI 3.14159

 

double = 2 * PAI * R; //求圆周长,其中R是代表半径的变量

double = PAI * R * R; //求圆面积

 

用宏来取代常数,好处是:

 

1)让代码更简洁明了

当然,这有赖于你为宏取一个适当的名字。一般来说,宏的名字更要注重有明确直观的意义,有时宁可让它长点。

2)方便代码维护

就如前面说的3.14159。哪天你发现这个∏值精度不够,想改为3.1415926,那么你只修改一处宏,而不是修改代码中的所有宏。

原来的宏:

#define PAI 3.14159

修改后的宏:

#define PAI 3.1415926

 

对宏的处理,在编译过程中称为“预处理”。也就是说在正式编译前,编译器必须先将代码出现的宏,用其相应的宏值替换,这个过程有点你我在文字处理软件中的查找替换。完成预处理后,所有原来的“PAI”都成了立即数3.1415926。所以在代码中使用宏表达常数,归根结底还是使用了立即数,并没有明确指定这个量的类型。这容易带来一些问题,所以C++使用另一更稳妥的方法来代替宏的这一功能。

5.3.3 常量定义

常量定义的格式为:

 

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

 

相比变量定义的格式,常量定义必须以 const 开始,另外,常量必须在定义的同时,完成赋值。

 

const float PAI = 3.1415926;

 

const 的作用就是指明这个量(PAI)是常量,而非变量。

常量必须一开始就指定一个值,然后,在以后的代码中,我们不允许改变PAI的值,比如:

 

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。因为编译器把它当成整型常量。

我们建议在定义变量时,明确指出类型,不管它是整型或其它类型。

 

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号?。其实我们可以做到更直观,并且方法很多。

第一种是使用宏定义:

 

#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 int FRIDAY = 5;

const int SATURDAY = 6;

 

int today = WEDNESDAY;

 

第三种方法就是使用枚举。

 

枚举也是我们将学习的第二种用户自定义数据类型的方法。上回我们学过typedef。typedef通过为原有的数据类型取一别名来获得一新的数据类型。枚举类型的原有数据只能是int或char类型(有些编译器则只支持int,如VC)。 枚举类型是在整数的取值范围中,列出需要的个值作为新数据类型的取值范围。

这就像bool类型,其实它是char类型,但它只需要0或1来代表false或true。

这里的例子中,我们用整型来表示周一到周日。整型的取值范围是多少来的?反正很大,可我们只需0到6,并且我们希望这7个值可以有另外一种叫法,以直观地表示星期几。

5.3.4.2 定义枚举类型的基本语法

enum 枚举类型名 {枚举值1,枚举值2,…… };

 

enum : 是定义枚举类型的关键字。

枚举类型名 :我们要自定义的新的数据类型的名字。

枚举值 :可能的个值。

 

比如:

enum Week {SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY};

 

这就定义了一个新的数据类型:Week。

Week数据类型来源于int类型(默认)。

Week类型的数据只能有7种取值,它们是:SUNDAY,MONDAY,TUESDAY……SATURDAY。

其中SUNDAY = 0,MONDAY = 1……SATURDAY = 6。也就是说,第1个枚举值代表0,第2个枚举值代表1,这样依次递增1。

不过,也可以在定义时,直接指定某个或某些枚举值的数值。比如,对于中国人,可能对于用0表示星期日不是很好接受,不如用7来表示星期天。这样我们需要的个值就是 1,2,3,4,5,6,7。可以这样定义:

 

enum Week {MONDAY = 1,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY};

我们希望星期一仍然从1开始,枚举类型默认枚举值从0开始,所以我们直接指定MONDAY等于1,这样,TUESDAY就将等于2,直接到SUNDAY等于7。

 

枚举值,我们就称为格举常量,因为它一经定义以后,就不可再改变,以下用法是错误的!

TUESDAY = 10; //错误!我们不能改变一个枚举值的数值。

 

用枚举常量来完成表达今天是星期三:

 

Week today = TUESDAY;

 

再举一例。在计算机中,颜色值也是使用整数来表示的。比如红色用 255 表示,绿色用 65280 表示,而黄色则用65535表示。(在自然界中,红 + 绿 = 黄,在计算机中也一样,你注意到了吗?)

假设我们在交警队工作,需要写一个有关红绿灯的程序,我们可以这样定义一个颜色的枚举类型:

 

enum TLightColor { Red = 255, Green = 65280, Yellow = Red + Green };

5.3.4.3 关于枚举常量的输出效果

bool 类型,其实就是个地道的枚举类型。我们已经在前面程序中试过它的输出效果:false 输出 0,true 输出 1。假设我们有一个 TLightColor 类型的变量:

 

TLightColor redLamp = Red;

 

然后我们输出 redLamp:

 

cout << redLamp << endl;

 

很多学员可能会猜出,输出结果是 “255” 。 显然他们比笔者聪明。当时我学习枚举类型,就天真地认为屏幕会打出“Red”。(呵呵,别嘲笑我,其实,有时候,比起聪明来,天真更难得……)

我不在课程里给出如何定义,如何使用,如何输出枚举常量的例程了。我想,学到今天,你应该有兴趣,也有能力自已新建一个控制台工程,然后自已写代码实现这一切了。

别发愣,歇上10分钟,你是要再看看本章课程,还是启动CB,写个例程,自已决定吧。

[wangjy17908]
添加时间:2008-04-12
版权所有(C)2005-2015