|
第几位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 合计 |
权值 | 27=128 | 26=64 | 25=32 | 24=16 | 23=8 | 22=4 | 21=2 | 20=1 | |
2进制 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
10进制 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | 255 |
1 * 27 + 1 * 26 + 1 * 25 + 1 * 24 + 1 * 23 + 1 * 22 + 1 * 21 + 1* 20 = 255
(顺便说一句,如果你忘了20 等于多少有点迟疑,请复习一下初中的数学知识:任何数的0次方都等于1)
结果是:
11111111(b) = 255 (d)
(为不了互相混淆,我们在书中常用(b)来表示前面的数是2进制的,而(d)则表示该数是10进制数。同样地,另有8进制数用(o)表示,16进制用(h)表示。不过记住了,这只是在书中使用,在程序中,另有一套表示方法。)
以前我们知道1个字节有8位,现在通过计算,我们又得知:1个字节可以表达的最大的数是255,也就是说表示0~255这256个数。
那么两个字节(双字节数)呢?双字节共16位。 1111111111111111,这个数并不大,但长得有点眼晕,从现在起,我们要学会这样来表达二制数:
1111 1111 1111 1111,即每4位隔一空格。
双字节数最大值为:
1 * 215 + 1 *214 + 1* 213 + 1 * 212 + 1 * 211 + 1 * 210 + …… + 1 * 22 + 1 * 21 + 1* 20 = 65535
很自然,我们可以想到,一种数据类型允许的最大值,和它的位数有关。具体的计算方法方法是,如果它有n位,那么最大值就是:
n位二进制数的最大值:1 * 2(n-1) + 1 * 2(n-2) + ... + 1 * 20
任何一种基本数据类型,都有其范围。比如字符类型,它的最大值是255,那么,当一个数在其类型的范围已经是最大值时,如果再往上加1,就会照成“溢出”。
其实,有限定的范围的数量,并不只在计算机中出现。钟表就是一个例子。10点再加1点是11点,再加1点是12点,可是再加1点,就又回到1点。再如汽车的行程表,假设最多只能显示99999公里,当达到最高值后继续行驶,行程表就会显示为00000公里。
回头看上一节,我们所讲的数都是正数。同样是年纪和工资,前者不需要有负值,但后者可能需要——至少所有的老板都这样认为。
那么,负数在计算机中如何表示呢?
这一点,你可能听过两种不同的回答。
一种是教科书,它会告诉你:计算机用“补码”表示负数。可是有关“补码”的概念一说就得一节课,这一些我们需要在第6章中用一章的篇幅讲2进制的一切。再者,用“补码”表示负数,其实一种公式,公式的作用在于告诉你,想得问题的答案,应该如何计算。却并没有告诉你为什么用这个公式就可以和答案?
另一种是一些程序员告诉你的:用二进制数的最高位表示符号,最高位是0,表示正数,最高位是1,表示负数。这种说法本身没错,可是如果没有下文,那么它就是错的。至少它不能解释,为什么字符类型的-1用二进制表示是“1111 1111”(16进制为FF);而不是我们更能理解的“1000 0001”。(为什么说后者更好理解呢?因为既然说最高位是1时表示负数,那1000 0001不是正好是-1吗?)。
让我们从头说起。
1、你自已决定是否需要有正负。
就像我们必须决定某个量使用整数还是实数,使用多大的范围数一样,我们必须自已决定某个量是否需要正负。如果这个量不会有负值,那么我们可以定它为带正负的类型。
在计算机中,可以区分正负的类型,称为有符类型,无正负的类型(只有正值),称为无符类型。
数值类型分为整型或实型,其中整型又分为无符类型或有符类型,而实型则只有符类型。
字符类型也分为有符和无符类型。
比如有两个量,年龄和库存,我们可以定前者为无符的字符类型,后者定为有符的整数类型。
2、使用二制数中的最高位表示正负。
首先得知道最高位是哪一位?1个字节的类型,如字符类型,最高位是第7位,2个字节的数,最高位是第15位,4个字节的数,最高位是第31位。不同长度的数值类型,其最高位也就不同,但总是最左边的那位(如下示意)。字符类型固定是1个字节,所以最高位总是第7位。
(红色为最高位)
单字节数: 1111 1111
双字节数: 1111 1111 1111 1111
四字节数: 1111 1111 1111 1111 1111 1111 1111 1111
当我们指定一个数量是无符号类型时,那么其最高位的1或0,和其它位一样,用来表示该数的大小。
当我们指定一个数量是无符号类型时,此时,最高数称为“符号位”。为1时,表示该数为负值,为0时表示为正值。
3、无符号数和有符号数的范围区别。
无符号数中,所有的位都用于直接表示该值的大小。有符号数中最高位用于表示正负,所以,当为正值时,该数的最大值就会变小。我们举一个字节的数值对比:
无符号数: 1111 1111 值:255 1* 27 + 1* 26 + 1* 25 + 1* 24 + 1* 23 + 1* 22 + 1* 21 + 1* 20
有符号数: 0111 1111 值:127 1* 26 + 1* 25 + 1* 24 + 1* 23 + 1* 22 + 1* 21 + 1* 20
同样是一个字节,无符号数的最大值是255,而有符号数的最大值是127。原因是有符号数中的最高位被挪去表示符号了。并且,我们知道,最高位的权值也是最高的(对于1字节数来说是2的7次方=128),所以仅仅少于一位,最大值一下子减半。
不过,有符号数的长处是它可以表示负数。因此,虽然它的在最大值缩水了,却在负值的方向出现了伸展。我们仍一个字节的数值对比:
无符号数: 0 ----------------- 255
有符号数: -128 --------- 0 ---------- 127
同样是一个字节,无符号的最小值是 0 ,而有符号数的最小值是-128。所以二者能表达的不同的数值的个数都一样是256个。只不过前者表达的是0到255这256个数,后者表达的是-128到+127这256个数。
一个有符号的数据类型的最小值是如何计算出来的呢?
有符号的数据类型的最大值的计算方法完全和无符号一样,只不过它少了一个最高位(见第3点)。但在负值范围内,数值的计算方法不能直接使用1* 26 + 1* 25 的公式进行转换。在计算机中,负数除为最高位为1以外,还采用补码形式进行表达。所以在计算其值前,需要对补码进行还原。这些内容我们将在第六章中的二进制知识中统一学习。
这里,先直观地看一眼补码的形式:
以我们原有的数学经验,在10进制中:1 表示正1,而加上负号:-1 表示和1相对的负值。
那么,我们会很容易认为在2进制中(1个字节): 0000 0001 表示正1,则高位为1后:1000 0001应该表示-1。
然而,事实上计算机中的规定有些相反,请看下表:
二进制值(1字节) | 十进制值 |
1000 0000 | -128 |
1000 0001 | -127 |
1000 0010 | -126 |
1000 0011 | -125 |
... | ... |
1111 1110 | -2 |
1111 1111 | -1 |
首先我们看到,从-1到-128,其二进制的最高位都是1(表中标为红色),正如我们前面的学。
然后我们有些奇怪地发现,1000 0000 并没有拿来表示 -0;而1000 0001也不是拿来直观地表示-1。事实上,-1 用1111 1111来表示。
怎么理解这个问题呢?先得问一句是-1大还是-128大?
当然是 -1 大。-1是最大的负整数。以此对应,计算机中无论是字符类型,或者是整数类型,也无论这个整数是几个字节。它都用全1来表示 -1。比如一个字节的数值中:1111 1111表示-1,那么,1111 1111 - 1 是什么呢?和现实中的计算结果完全一致。1111 1111 - 1 = 1111 1110,而1111 1110就是-2。这样一直减下去,当减到只剩最高位用于表示符号的1以外,其它低位全为0时,就是最小的负值了,在一字节中,最小的负值是1000 0000,也就是-128。
我们以-1为例,来看看不同字节数的整数中,如何表达-1这个数:
字节数 | 二进制值 | 十进制值 |
单字节数 | 1111 1111 | -1 |
双字节数 | 1111 1111 1111 1111 | -1 |
四字节数 | 1111 1111 1111 1111 1111 1111 1111 1111 | -1 |
可能有同学这时会混了:为什么 1111 1111 有时表示255,有时又表示-1?所以我再强调一下本节前面所说的第2点:你自已决定一个数是有符号还是无符号的。写程序时,指定一个量是有符号的,那么当这个量的二进制各位上都是1时,它表示的数就是-1;相反,如果事选声明这个量是无符号的,此时它表示的就是该量允许的最大值,对于一个字节的数来说,最大值就是255。
这一节课,看似罗嗦,但我希望每位没有编程基础,或者以前对进制,对负值、补码、反码等概念,对数据类型理解不透彻的学员,都能多花时间反复阅读,直到看懂得文中的每一张图表的意思为止。如果有困难,请发信到问答处的专门信箱:wenda@bcbschool.com (为了方便我的查阅,请无关课程的问答或其它来信,不要发到这个邮址,谢谢)。
字符集和保留并不专属于“数据类型”的基础知识。它是一门语言最基础的东西。就像字母A-Z对于英语的作用一样。我把它放到这里,更多的是因为这是我们第一次要碰到它,在下一节,马上就要用了。幸好, 它的难度和学会26个字母差多少。
每种语言都使用-组字符来构造有意义的语句,组成C++程序的,最终的是以下这些字符(空格这个字符不好表示,就直接写上“空格”两字了,以后同):
26个字母的大小写形式:ABCDEFGHIJKLMNOPQRSTUVWXYZ,abcdefghijklmnopqrst;
10个阿拉伯数字:0123456789;
其它符号:+ - * / = , . _ : ; ? \ " ' ~ | ! # % & ( ) [ ] { } ^ < > (空格)
其它符号,包括汉字,则可能作为程序中字符串的内容,用于显示等。
最后,C/C++语言是区分大小的语言,也就是说ABC和abc并不相同。这一点我们将在下一章特别强调。
保留字也称关键字。它是预先定义好的标识符,这些标识符必须保留给C++语言自身专用。因为它们用来在编译过程中表示特殊的含义。比如,我们想定义一个量为整数类型,那么C++就必须有一个词来表示什么是整数类型,这个词就是一个关键字。
C,C++主要的关键字,我们在章末附表列出。下面先列出本章要用的关键字。
char :字符类型
int : 整型
float :单精度实型(浮点型)
double : 双精度实型
unsigned : 无符号类型
signed : 有符号类型
bool : 布尔类型
true : 布尔类型的真值
false : 布尔类型的假值
void : 无类型
sizeof : 取得指定类型的所占用的范围
typedef : 为某种类型取一别名
前面讲的一些有关数据类型的各种基本概念,下面是数据类型这一课真正开始的时候。如果在其中你有什么概念不能理解,最好的方法就是首先回头看本章前面的内容。
类型标识符 | 类型说明 | 长度 (字节) |
范围 | 备注 |
char | 字符型 | 1 | -128 ~ 127 | -27 ~ (27 -1) |
unsigned char | 无符字符型 | 1 | 0 ~ 255 | 0 ~ (28 -1) |
short int | 短整型 | 2 | -32768 ~ 32767 | 2-15 ~ (215 - 1) |
unsigned short int | 无符短整型 | 2 | 0 ~ 65535 | 0 ~ (216 - 1) |
int | 整型 | 4 | -2147483648 ~ 2147483647 | -231 ~ (231 - 1) |
unsigned int | 无符整型 | 4 | 0 ~ 4294967295 | 0 ~ (232-1) |
float | 实型(单精度) | 4 | 1.18*10-38 ~ 3.40*1038 | 7位有效位 |
double | 实型(双精度) | 8 | 2.23*10-308 ~ 1.79*10308 | 15位有效位 |
long double | 实型(长双精度) | 10 | 3.37*10-4932 ~ 1.18*104932 | 19位有效位 |
unsigned 用于修饰 int 和 char 类型。它使int 或 char 类型成为无符号类型。
signed 是 unsigned 反义词,如 signed int 表示有符号类型,不过signed可以省略,所以上面列出char,short int,int 都是有符号类型。
有 short int (短整型) ,所以也就有对应 long int (长整型)。long int 用于表示4个字节(32位)的整数。但是在我们现在普通使用的32位计算机中,int 默认就是4个字节,所以long也是可以省略的。
(较早几年,也就是Windows 3.1/DOS 流行的时候,那时的机器及操作系统都是16位的,这种情况下,int 默认是16位的。此时,如果想实现32位整数,就必须定义为这样的类型:long int)。
在浮点数方面,我们最常用的将是 double。它的精度适合于我们日常中的各种运算。当然,float的精度也在很多情况下也是符合要求的。
除字符型,整型,实型以外,布尔型和无类型也是较常用的两种数据类型。
布尔型(bool)
布尔类型是C++的内容,C语言没有这一类型。
布尔类型的数据只有两种值:true(真) 或 false(假)。
什么时候使用布尔型呢?
履历表中一般有“婚否”这一项,婚否这种数据就适于用真或假来表示。性别男女,有时也会用布尔值表示,(一般程序都不约而同地把男性设置“真”,女性设置为“假”。)
无类型(void)
这个类型比较怪“无”类型。是的,没有类型的类型。或者我们这样认为比较好接受:在不需要明确指定类型的时候,我们可能使用 void 来表示。
用法: typedef 原类型名 类型的别名;
为什么要给现成的数据类型起别名?当然也是为了迁就我们为人类。就像我们给人家起绰号一样,形象好记,不易混淆。
比如,我们在编程中需要使用一些年龄数据,应该使用整型(int)类型。另我们还使用到身高的数据,由于单位采用“厘米”,所以也可能使用int类型。两种数据属于同一数据类型,但所代表的内容却不容混淆。我们可以使用typedef来临时为int取两个别名:
typedef int AGE;
typedef int STATURE;
通过以上两行话(行末都需要以分号结束),我们获得了两种新的数据类型名称。它们的一切属性都和其原名 int的数据类型完全一致。只是名字变得很有意义一点而已。
这里为了说明问题举一简单的例子,事实上例子的情况并不值得使用typedef来取别名。typedef常用来为一些复杂的类型取一简单且意义明了的别名。比如定义函数的指针。在C++ Builder的控件编写中,更是有非常频繁地用typedef来为那些定义复杂的事件函数(类成员函数的指针)取别名。
用法: sizeof(数据类型)
sizeof(变量)
C/C++提供了关键字sizeof,用于在程序中测试某一数据类型的占用多少字节。sizeof有两种用法,效果一样,由于我们还没有学变量,所以我们先讲第一种。
sizeof是一种计算,计算的对象是指定的一种数据类型,计算的结果是该数据类型占用的字节数目。如:
sizeof(char) = 1;也就是说char类型占用1个字节。
sizeof(int) = 4;也就是说int类型占用4个字节。
下面我们来完成一个实例。这个例子使用sizeof将我们已学的所有数据类型的名称,占用字节数列出。
首先,新建一个控制台工程(详细图解请见: 第2章第3节 DOS 版 Hello World )
步骤:
1、选择 主菜单 File | New;
2、New Items 对话框,选 New 页中的 Console Wizard,然后点OK;
3、Console Wizard 对话框,Source Type中选中 C++ ,右边的分组中只选中 Console Application 。点 OK
完成以上步骤后,代码编辑窗口自动新建并打开Unit1.cpp,其代码如下:
//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
return 0;
}
//---------------------------------------------------------------------------
请保存整个工程,包括工程文件名(Project1.bpr)和一个CPP文件(Unit1.cpp),请参见第二章。不过为了工程名字有点意义,我决定将它另存为SizeOf.bpr。
按F9,编译并运行这个空白工程,会发现一人DOS窗口一闪而过,这我们在第二章就知道了。
请添加以下代码中的粗体部分:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
getchar();
return 0;
}
//---------------------------------------------------------------------------
在第二章中我们的加过类似的代码,目的一样:让程序执行以后不是自动结束,而是在我们敲一个回车键后,才结束,因为我们需要查看程序的执行结果。以后我们所有使用控制台形式的程序,都将需要这两行代码,不过以后不会特别指出了。如你还是有些模糊这两话的作用,最好的办法是现在再按一次F9键,运行这个程序。
下面开始我们的 sizeof 用法初试。
我们这程序目标是输出所有我们已学的数据类型:整数(含unsigned,short的修饰等),实型(含各精度),字符,bool等数据类型占用的字节数。但我们事实上还没有学过C/C++的任何语法,所以我想我们还是先来一个就一句话的,确保成功以后,再完成所有的语句。
请添加以下代码中的粗体部分:
为了省定版面,我只抄出需要变化的main那一部分,之前的代码由于不再需要变化,就不抄到这里了,省略号只用来示意前面还有代码,你别把……也输入代码中啊!
……
int main(int argc, char* argv[])
{
cout << "Size of char is : " <<
sizeof(char) << endl;
getchar();
return 0;
}
//---------------------------------------------------------------------------
这次输入的是这一行:
cout << "Size of char is : " << sizeof(char) << endl;
这是一行标准的C++ 语句。cout 在 C++里表示“标准输出设置”。曾经是每个学习C++的人必学的内容。在Windows的编程里,它已是昨日黄花,我想诸位就不用在花什么时间去学习它了,只要简单地弄明白它的基本用法就是。
cout 和之后的 << 合用。 后者是两小于号,你可别老眼昏眼看作是中文的书名号《 。<< 在C++里属于操作符重载,我们以后在学习时,还会在不同的场合里遇到见和它的另一位老兄:>> 。理解并且迅速记下 << 的作用是看它的箭头方向:
<< 的指向左边,它的意思是把它右边的东西给,或者是接到右边。现在,它的左边是cout,cout这里就是屏幕。给屏幕东西,专业的说法就是:输出到屏幕。
图示为:将 "Size of char is : " 输出到屏幕,当然,一对双引号是不会被输出的,因为它们用来表示这是一句话。
cout 不仅可以输出一句话(字符串),而且可以用来输出各种C/C++基本类型的数据。并且,通过 <<,它可以一个接一个地输出所需内容。在上面的那行代码中,它首先输出:"Size of char is : ";然后是输出 sizeof(char)的计算结果。最后输出 endl。 endl 表示换一新行(回车换行)。
理解了 cout 部分,那一行代码中我们惟一不明白就是行末的那个分号了。是啊你看到了没,所有C/C++语句行都在语句后有一个';'。当然,和其它C++语句的主要组成部分一样,它必须是英文符号。如果你打入一个中文的符号。现在改还来得及。并且从此刻起就记住了,所有C++语句中用到标点符号,除非是指定就要用中文作为输出等参数,否则,分号,逗号,冒号,感叹号,问号等等,全都必须是英文的。
现在,你可以把放心地把那句话原样照敲入你的代码中了。不过,聪明的学员已经完成“Ctrl + C"/"Ctrl + V"大法,直接粘贴了。不过,我想,如果你真的以前还没写过C/C++代码,还是动手输入。
按F9编译,运行。结果如图:
"Size of char is : 1 "中,最后的那个1就是 sizeof(char)的计算结果。
至于那个endl呢?你看,光标不是在下一行的行首闪烁吗?如果你把代码改成这样:
cout << "Size of char is : " << sizeof(char);
那么,输出结果几乎一模一样,除了光标改成在行末。 我们之所以需要换行,是因为后面还要输出。
你可能喜欢用中文来输出?好吧。
cout << "char(字符)占用的字节数: " << sizeof(char) << endl;
结果是:
我默认不采用中文的原因只是因为学生容易在切换中英输入法的关节出错,结果在不该使用中文的时候输入了一些中文字符,造成无谓的错误,白白浪费学习时间。
再接再厉,我们输出所有语句。包含我们用typedef自定义了数据类型的别名。
……
int main(int argc, char* argv[])
{
typedef int AGE; //为int 取一个别名
//cout << "char(字符)占用的字节数: " << sizeof(char) << endl;
cout << "Size of char is : " << sizeof(char) << endl;
cout << "Size of unsigned char is : " << sizeof(unsigned char) << endl;
cout << "Size of short int is : " << sizeof(short int) << endl;
cout << "Size of unsigned short int is : " << sizeof(unsigned short int) <<
endl;
cout << "Size of int is : " << sizeof(int) << endl;
cout << "Size of unsigned int is : " << sizeof(unsigned int) << endl;
cout << "Size of long int is : " << sizeof(long int) << endl;
cout << "Size of unsigned long int is : " <<
sizeof(unsigned long int) << endl;
cout << "Size of AGE is : " << sizeof(AGE) << endl;
cout << "Size of float is : " << sizeof(float) << endl;
cout << "Size of double is : " << sizeof(double) << endl;
cout << "Size of long double is : " << sizeof(long double) << endl;
cout << "Size of bool is : " << sizeof(bool) << endl;
getchar();
return 0;
}
为了看着清楚,我加了一些空行。
我很乐意承认,这回我也当了一次聪明人。写完成上面的代码过程中,一直在用“Ctrl + C”和“Ctrl + V”。关键是复制完之后要及时改变需要变的地方。
另外,检查是否改错的一个方法就是利用CBC的代码窗口对关键字变色的特征。sizeof 本身和 sizeof()括号中的数据类型关键字都会变色。如果你发现有一处不是这样,那你一定是输入错了。比如你把sizeof(char)写成 sizeof(cahr)。那么,cahr不会变色。
我们漏了一个类型:void。void 是无类型。sizeof无法对无类型取其字节数。这是void的特殊性。当作为“无类型”意义时,它字大小是0字节,如函数的返回值。当作为“未确定”的意义时,它的大小也是未确定的。当然,这也是sizeof的一知识点:它的计算对像必须确定的类型。
确保输入无误。F9。结果如下:
“这一章好长。这一夏好热!要收多少个学员的20元钱,才能省下钱买空调呢?”我把键盘往前一推,长吁一口气,幽幽地想。
最终我决定到楼下买根冰棍。
[到页首]