|
值 | 符号 | 值 | 符号 | 值 | 符号 |
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车” 的输出结果是怎么来的呢?请大家考虑考虑。
试验程序在这里结束。
前面讲到“白马、黑马”时,我们说一匹白马和一匹黑马具有共同的数据类型“马”,但二者是相对独立的个体。现在我们以共熟悉的“人”来继续这个话题,最终引出变量与内存地址的关系。
张三和李四的数据类型都是“人类”。但张三和李四显然是独立的量:张三吃了两块蛋糕,李四不可能因此就觉和肚子饱了;而李四在下班的路上捡到一个钱包,虽然正好是张三的,两人似乎通过钱包有了点关系,但谁得谁失仍然不容混淆。
这一切都很好理解。张三和李四之所以是不同的个体,根本原因在于两人有着不同的肉身。如果是一对连体婴儿,虽然也是两个人,但当左边的婴儿被蚊子咬一口时,右边婴儿是否也会觉得痒,就不好说了。
现在我们困难的是,如何理解两个不同的变量,也是互相独立的呢?
答案就在“内存地址”,“内存地址”就是变量的肉身。不同的变量,拥有不同的内存地址。譬如:
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++语言相对于其它很多高级语言的,最灵活也最难学的“指针”概念的基石。
说完变量,我们来学常量。
看一段代码片段。省略号表示可能会有的其它操作。
int a = 3;
....
a = 100;
代码中,a 是变量。一开始我们初始化为3。后来出于什么需要,我们又将它赋值为100。a的值也就从3变成了100。
代码中,3 和 100就是一种常量。像这种直接在代码写出大小的量,称为立即数,也称为常数,而常数是常量的一种。
常量的定义:常数,或代表固定不变值的名字。
用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个字节,所以,我们没有必要这样用了。
实型常数一般只用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.1.3.4 节中的第3点讲过。这里简单重复。
字符常量用单引号括起来的一个字符,如:'a','b','c',' ','A'。等。
可以用转义符来表示一些无法打出的符号,如 '\n','\r','\t'。
这里补充一点:值为0的字符,称为空字符,或零字符。它用 '\0' 表示。注意,它和阿拉伯数字字符 '0'完全是两个数。(请大家查一前面常用字符ASCII值表中,后者的值是多少)
字符串由字符组成。在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'是如何起到标志一个字符结束的作用。
假如我们要写一个有关圆的种种计算的程序,那么∏(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++使用另一更稳妥的方法来代替宏的这一功能。
常量定义的格式为:
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;
生活中很多信息,在计算机中都适于用数值来表示,比如,从星期一到星期天,我们可以用数字来表示。在西方,洋人认为星期天是一周的开始,按照这种说法,我们定星期天为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个值可以有另外一种叫法,以直观地表示星期几。
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 };
bool 类型,其实就是个地道的枚举类型。我们已经在前面程序中试过它的输出效果:false 输出 0,true 输出 1。假设我们有一个 TLightColor 类型的变量:
TLightColor redLamp = Red;
然后我们输出 redLamp:
cout << redLamp << endl;
很多学员可能会猜出,输出结果是 “255” 。 显然他们比笔者聪明。当时我学习枚举类型,就天真地认为屏幕会打出“Red”。(呵呵,别嘲笑我,其实,有时候,比起聪明来,天真更难得……)
我不在课程里给出如何定义,如何使用,如何输出枚举常量的例程了。我想,学到今天,你应该有兴趣,也有能力自已新建一个控制台工程,然后自已写代码实现这一切了。
别发愣,歇上10分钟,你是要再看看本章课程,还是启动CB,写个例程,自已决定吧。
[到页首]