1.2 数据类型和基本输入/输出
程序的数据必须依附其内存空间方可操作,每个数据在内存中存储的格式及所占的内存空间的大小取决于它的数据类型。在C++中,数据可分为变量(对象)和常量两种,是贯穿整个程序的一种流,依托C++流的独特机制可对流进行输入和输出操作。
为了能精确表征数据在计算机内存中的存储(格式及大小)和操作,C++将数据类型分为基本数据类型、派生类型和复合类型三类,后两种类型又可统称为构造类型,如图1.4所示。
图1.4 C++的数据类型
1.2.1 基本数据类型
基本数据类型是C++系统内部预定义的数据类型;派生类型是将已有的数据类型定义成指针和引用;而复合类型是根据基本类型和派生类型定义的复杂数据类型,如数组、类、结构和联合等。这里先来介绍基本数据类型。
C++基本数据类型有int(整型)、float(单精度实型)、double(双精度实型)、char(字符型)和bool(布尔型,值为false或true,false用0表示,true用1表示)等。
对于上述基本数据类型,还可以使用short(短型)、long(长型)、signed(有符号)和unsigned (无符号)来区分,以便更准确地适应各种情况的需要。
short只能修饰int,写成“short int”,也可省略为short。在大多数计算机中,short int表示2字节长。
long只能修饰int和double。当为long int时,可略写成long,一般表示4字节。而long double一般表示10字节。
signed(有符号)和unsigned(无符号)只能修饰char和int。实型float和double一般总是有符号的,因此不能用unsigned来修饰。默认状态下,char、short、int和long本身是有符号(signed)的,它们可统称为整型。
表1.1列出了C++各种基本数据的类型、字宽(以字节数为单位,本书余同)和范围,它们是根据ANSI标准而定的。
表1.1 C++的基本数据类型
注:①此表的字宽和范围是32位系统的结果,若在16位系统中,则int、signed int、unsigned int的字宽为2字节,其余相同。
②出现[int]可以省略,即在int之前有signed、unsigned、short、long时,可以省略int关键字。
1.2.2 字面常量
根据C++程序中数据的可变性,可将数据分为常量和变量两大类。
在C++程序运行过程中,其值始终保持不变的数据称为常量。常量可分字面常量和标识符常量两类。所谓字面常量,是指能直接从字面形式判别其类型的常量,又称直接量。如1、20、0、-6为整数,1.2、-3.5为实数,‘a’、‘b’为字符,“C++语言”为字符串,等等。而标识符常量是用标识符来说明的常量,如const修饰的只读变量、#define定义的常量及enum类型的枚举常量等。下面先来介绍各种不同类型的字面常量的表示方法。
1.整数常量
C++中的整数可用十进制、八进制和十六进制来表示。其中,八进制整数是以数字0开头且由0~7的数字组成的数。例如045,即(45)8,表示八进制数45,等于十进制数37;-023表示八进制数-23,等于十进制数-19。而十六进制整数是以0x或0X开头且由0~9、A~F或a~f组成的数。例如0x7B,即(7B)16,等于十进制的123,-0X1a等于十进制的-26。
需要说明的是,整数后面还可以有后缀l、L、u、U等。若以L或其小写字母l作为结尾的整数表示长整型(long)整数,如78L、496l、0X23L、023l等;以U或u作为结尾的整数表示无符号(unsigned)整数,如2100U、6u、0X91U、023u等;以U(u)和L(或小写字母l)的组合作为结尾的整数表示无符号长整型(unsigned long)整数,如23UL、23ul、23LU、23lu、23Ul、23uL等。默认情况下,如果一个整数没有后缀,则可能是int或long类型,这取决于该整数的大小。
2.实数常量
实数即浮点数,它有十进制数和指数两种表示形式。
十进制数形式是由整数部分和小数部分组成的(注意必须有小数点)。例如0.12、.12、1.2、12.0、12.、0.0都是合法的十进制数形式实数。
指数形式采用科学表示法,它能表示出很大或很小的实数。例如1.2e9或1.2E9都表示1.2×109,注意字母E(或e)前必须有数字,且E(或e)后面的指数必须是整数。
需要说明的是:若实数是以F(或f)结尾的,如1.2f,则表示单精度浮点数(float),以L (或小写字母l)结尾的,如1.2L,表示长双精度浮点数(long double)。默认情况下,若一个实数没有任何后缀,则表示双精度浮点数(double)。
3.字符常量
在C++中,用单引号将其括起来的字符称为字符常量,如‘B’、‘b’、‘%’、‘’等。但若只有一对单引号‘’则是不合法的,因为C++不支持空字符常量。注意‘B’和‘b’是两个不同的字符。
本书约定:由于阅读时,书中的空格难以看出,故用符号表示一个空格。
除了上述形式的字符常量外,C++还可以用“\”开头的字符序列来表示特殊形式的字符。例如在以前程序中的‘\n’,它代表回车换行,即相当于按【Enter】键,而不是表示字母n。这种将反斜杠(\)后面的字符转换成另外意义的方法称为转义序列表示法。‘\n’称为转义字符,“\”称为转义字符引导符,单独使用没有任何意义,因此若要表示反斜杠字符,则应为‘\\’。表1.2列出了常用的转义序列符。
需要说明的是:
(1)当转义字符引导符后接数字时,用来指定字符的ASCII码值。默认时,数字为八进制,此时数字可以是1位、2位或3位。若采用十六进制,则需在数字前面加上X或x,此时数字可以是1位或多位。例如,‘\101’和‘\x41’都是表示字符‘A’,若为‘\0’,则表示ASCII码值为0的字符。
注意:
ANSI/ISO C++中由于允许出现多字节编码的字符,因此对于“\x”或“\X”后接的十六进制的数字位数已不再限制。
(2)不是每个以转义序列表示的字符都是有效的转义字符,当C++无法识别时,就会将该转义字符解释为原来的字符。例如,‘\A’和‘\N’等虽都是合法的转义字符,但却都不能被C++识别,此时‘\A’当做‘A’,‘\N’当做‘N’。
(3)注意0、‘0’和‘\0’的区别:0表示整数,‘0’表示数字0字符,‘\0’表示ASCII码值为0的字符。
表1.2 C++中常用转义序列符
4.字符串常量
C++语言除了允许使用字符常量外,还允许使用字符串常量。字符串常量是由一对双引号括起来的字符序列,简称为字符串。字符串常量中除一般字符外,还可以包含空格、转义序列符或其他字符(如汉字)等。例如:
"Hello, World!\n" "C++语言"
都是合法的字符串常量。字符串常量的字符个数称为字符串长度。若只有一对双引号“”,则这样的字符串常量的长度为0,称为空字符串。
由于双引号是字符串的分界符,因此如果需要在字符串中出现双引号则必须用“\"”表示。例如:
"Please press \"F1\" to help!"
这个字符串被解释为:
Please press "F1" to help!
字符串常量应尽量在同一行书写,若一行写不下,可用“\”来连接,例如:
"ABCD \ EFGHIGK…"
注意不要将字符常量和字符串常量混淆不清,它们主要的区别如下。
(1)字符常量是用单引号括起来的,仅占1字节;而字符串常量是用双引号括起来的,至少需要2字节,但空字符串除外,它只需1字节。例如,字符串“a”的字符个数为1,即长度为1,但它所需要的字节大小不是1而是2,因为除了字符a需要1字节外,字符串结束符‘\0’还需1字节,如图1.5所示。
图1.5 "a"和'a'的区别
(2)内存中,字符是以ASCII码值来存储的,因此可将字符看做整型常量的特殊形式,它可以参与常用的算术运算,而字符串常量则不能。例如:
int b='a'+3; // 结果b为100,这是因为将'a'的ASCII码值97参与运算
1.2.3 变量及其命名规则
变量是指在程序执行中其值可以改变的量。变量的作用是存取程序中需要处理的数据,它“对应”某个内存空间。变量有3个基本要素:C++合法的变量名、变量的数据类型和变量的数值。
1.变量名命名
变量名须用标识符来标识。所谓标识符,是指用来标识变量名、函数名、数组名、类名、对象名等的有效字符序列。标识符命名的好坏直接影响程序的可读性,下面几个原则是命名时所必须注意的。
(1)合法性。C++规定标识符由大小写字母、数字字符(0~9)和下划线组成,且第一个字符必须为字母或下划线。任何标识符中都不能有空格、标点符号及其他字符,例如下面的标识符是不合法的:
93Salary, Peter.Ding, $178, #5f68, r<d
而且,用户定义的标识符不能和系统的关键字同名。以下是48个C++的标准关键字:
当然,ANSI/ISO C++还不止这些关键字,它们还有bool、const_cast、dynamic_cast、explicit、export、namespace、wchar_t等,另外,C++中标识符的大小写是有区别的。例如,data、Data、DaTa、DATA等都是不同的标识符。尽管如此,也不要将两个标识符定义成字母相同、大小写不同的标识符。
(2)有效性。因为有的编译系统只能识别前32个字符,也就是说,前32个字符相同的两个不同标识符被有的系统认为是同一个标识符。因此,虽然标识符的长度(组成标识符的字符个数)是任意的,但最好不要超过32个。
(3)易读性。在定义标识符时,若能做到“见名知意”就可以达到易读性的目的。通常,在每个变量名前面加上表示数据类型的小写字符,变量名中每个单词的首字母均大写。例如,用nWidthOfTable或iWidthOfTable(桌子的宽度)表示整型(int)变量。
2.变量定义
C++中,定义变量的最简单方法是先写数据类型,然后写变量名。数据类型和变量名之间必须用1个或多个空格来分隔,最后以分号来结尾,即如下列的格式:
<数据类型> <变量名1>[, <变量名2>, …];
本书约定:若格式中出现了尖括号“< >”,表示括号中的内容是必须指定的,若为方括号“[ ]”,则括号中的内容是可选的。
数据类型是告诉编译系统要为由变量名指定的变量分配多少字节的内存空间,以及变量中要存取的是什么类型的数据。例如:
double x; // 双精度实型变量
这样,x占用了8字节连续的内存空间,存取的数据类型是double型,称为双精度实型变量。再如:
float y; // 单精度实型变量
则y占用了4字节连续的内存空间,存取的数据类型是float型,称为单精度实型变量。此后,变量x、y就分别对应各自的内存空间,换句话说,开辟的那块8字节的内存空间就叫x,那块4字节的内存空间就叫y。又如:
int nNum1; // 整型变量 int nNum2; // 整型变量 int nNum3; // 整型变量
则nNum1、nNum2、nNum3分别占用4字节的存储空间,其存取的数据类型是int型,称为整型变量。由于它们都是同一类型的变量,因此为使代码简洁,可将同类型的变量定义在一行语句中,不过同类型的变量名要用逗号(,)分隔(逗号前后可以有0个或多个空格)。例如这三个整型变量可这样定义(注意:只有最后一个变量nNum3的后面才有分号):
int nNum1, nNum2, nNum3;
需要说明的是:
(1)除了上述整型变量、实型变量外,还可有字符型变量,即用char定义的变量。这些都是基本数据类型变量。实际上,只要是合法的C++数据类型,均可以用来定义变量。例如:
unsigned short x, y, z; // 无符号短整型变量 long double pi; // 长双精度实型变量
(2)在C++中没有基本数据类型的字符串变量。字符串变量是用字符类型的数组、指针或string类来定义的(以后会讨论)。
(3)在同一个作用域(以后会讨论)中,不能对同一个变量重新定义。或者说,在同一个作用域中,不能有两个或两个以上的相同变量名。例如:
float x, y, z; // 单精度实型变量 int x; // 错误,变量x重复定义 float y; // 错误,变量y重复定义
(4)C++变量满足即用即定义的编程习惯,也就是说,变量定义的位置可以不固定,比较自由,但一定要遵循先定义后使用的原则。例如:
int x; x = 8; int y; // 即用即定义 cout<<z<<endl; // 错误,z还没有定义
3.变量赋值和初始化
变量一旦定义后,就可以通过变量名引用变量来进行赋值等操作。所谓引用变量,就是使用变量名来引用变量的内存空间。也就是说,变量名是对内存空间的标识,对变量名的操作也是对其内存空间的操作。例如:
int x, y; x=8; // 给x赋值 y=x; // 将x的值赋给y
“x = 8;”和“y = x;”都是变量的赋值操作。由于变量名x和y是它们的内存空间的标识符(名称),因此,“x = 8;”是将运算符“=”右边的数据8存储到左边变量x的内存空间中。而“y= x;”这一操作则包括两个过程:先获取x的内存空间中存储的值(此时为8),然后将该值存储到y的内存空间中。
当首次引用一个变量时,变量必须要有一个确定的值,这个值就是变量的初值。在C++中,可用下列方法给变量赋初值。
(1)在定义变量后,使用赋值语句来赋初值。如前面的“x = 8;”和“y = x;”,使得x和y的初值都设为8。
(2)在定义变量的同时赋给变量初值,这一过程称为变量初始化。例如:
int nNum1=3; // 指定nNum1为整型变量,初值为3 double x=1.28; // 指定x为双精度实变量,初值为1.28 char c='G'; // 指定c为字符变量,初值为'G'
(3)也可以在多个变量的定义语句中单独对某个变量进行初始化,如:
int nNum1, nNum2=3, nNum3;
表示nNum1、nNum2、nNum3为整型变量,但只有nNum2的初值为3。
(4)在C++中,变量的初始化还有另外一种形式,例如:
int nX(1), nY(3), nZ;
表示nX、nY和nZ都是整型变量,其中紧随nX和nY后面的括号中的数值1和3分别为nX和nY的初值。
注意:
一个没有初值的变量并不表示它所在的内存空间没有数值,而是取决于编译为其开辟内存空间时的处理方式,它可能是系统默认值或者该内存空间以前的操作留下来的无效值。
1.2.4 标识符常量
标识符常量,有时又称为符号常量,它用一个标识符来代替一个数值。由于标识符总比数值常量本身更具意义,因而在程序中使用标识符常量不仅可以提高程序的可读性,且修改方便,并能预防程序出错。例如,整个程序的许多地方都要用一个常数π,每次输写时都得写上3.14159,如果在某些地方写错这个值,则会导致计算结果的错误。若给这个π取一个名字PI(标识符),每处需要的地方都用PI来代替,不仅输写方便,而且即便写错了,编译系统一般也能检查出来。如果π值不需要太精确的话,那么只要修改PI的值(如3.1416)即可。事实上,布尔常量中的false和true就是系统内部预定义的两个标识符常量,即用false代替0,用true代替1。
与变量相似,标识符常量在使用前同样需要先作声明。在C++中,标识符常量一般有const修饰的只读变量、#define定义的常量及enum类型的枚举常量3种形式。
1.const只读变量
在变量定义时,可以使用关键字const来修饰,这样的变量是只读的,即在程序中对其只能读取,不能修改。由于不可修改,因而它是一个标识符常量,且在定义时必须初始化。需要说明的是,通常将标识符常量中的标识符写成大写字母以与其他标识符相区别。例如:
const float PI=3.14159265f; // 指定f使其类型相同,否则会有警告错误
因 π 字符不能作为C++的标识符,因此这里用PI来表示。PI被定义成一个float类型的只读变量,由于float变量只能存储7位有效位精度的实数,因此PI的实际值为3.141592。若将PI定义成double,则全部接受上述数字。事实上,const还可放在类型名之后,如下列语句:
double const PI=3.14159265;
这样,就可在程序中使用PI这个标识符常量来代替3.14159265了。
【例Ex_PI】 用const定义标识符常量
#include <iostream.h> const double PI=3.14159265; //PI是一个只读变量 int main() { double r = 100.0, area; area=PI*r*r; // 引用PI cout<<"圆的面积是:"<<area<< "\n"; return 0; // 指定返回值 }
程序运行结果如下:
圆的面积是:31415.9
2.#define标识符常量
在C++中,允许程序用编译预处理指令#define来定义一个标识符常量。例如:
#define PI 3.14159265
注意行尾没有分号。在程序编译时,编译系统首先将程序中的PI用3.14159265来替换,然后再进行代码编译,故将#define称为编译预处理指令。
显然,#define定义的常量不是真正的标识符常量,因为在编译预处理完成后,标识符PI的生命期也就结束了,不再属于程序中的元素名称。而且,标识符后面的内容实际上是一个字符串,编译系统本身不会对其进行任何语法检查,仅在程序中与标识符作简单替换。例如,若有:
#define PI 3.141MNP+59
虽是一个合法的定义,但它此时已经失去了一个标识符常量的真正作用。正因为如此,在C++编程中,标识符常量都用const来定义,而不使用#define。
1.2.5 枚举常量
枚举常量是在由关键字enum指定的枚举类型中定义的。枚举类型属于构造类型,它是一系列有标识符的整型常量的集合,因此枚举常量实质上是整型标识符常量。
定义时,先写关键字enum,然后是要定义的枚举类型名、一对花括号({}),最后以分号结尾。enum和类型名之间至少要有一个空格,花括号里面是指定的各个枚举常量名,各枚举常量名之间要用逗号分隔,即如下格式:
enum <枚举类型名>{<枚举常量1, 枚举常量2, …>};
例如,有下列枚举常量定义:
enum COLORS{Black, Red, Green, Blue, White};
其中COLORS是要定义的枚举类型名,通常将枚举类型名写成大写字母以与其他标识符相区别。它有5个枚举常量(又称枚举值、枚举元素)。系统默认为每一个枚举常量对应一个整数,并从0开始,逐个增1,也就是说枚举常量Black等于0,Red等于1,Green等于2,以此类推。
当然,这些枚举常量默认的值可单独重新指定,也可部分指定,例如:
enum COLORS{Black=5, Red, Green=3, Blue, White=7};
由于Red没有赋值,则其值自动为前一个枚举常量值增1,即为6。同样,Blue为4,这样各枚举常量的值依次为5,6,3,4,7。以后就可直接使用这些枚举常量了,例如:
int n=Red; // n的初值为6 cout<<Blue+White<<endl; // 输出11
事实上,若不需要用枚举类型定义一个变量,则在枚举定义时可不指定枚举类型名。例如:
enum{Black=5, Red, Green=3, Blue, White=7};
显然,用enum一次可以定义多个标识符常量,不像const和#define每次只能定义一个。又如,若在程序中使用TRUE表示true,FALSE表示false,则可定义为:
enum {FALSE, TRUE}; // 或enum {TRUE=true, FALSE=false};
1.2.6 基本输入/输出
通过对前面程序的学习,已经对C++的标准输入流cin和标准输出流cout有所了解。所谓“流”是从数据的传输(流动)抽象而来的,可以把它理解成“特殊的文件”;从操作系统的角度来说,每一个与主机相连的输入/输出设备都可以看做一个文件。例如,终端键盘是输入文件(输入流),显示器和打印机是输出文件(输出流)。
cin和cout是C++预先定义的流对象,分别代表标准输入设备(键盘)和标准输出设备(显示器)。这里将进一步介绍用cin和cout进行输入/输出的方法。
1.输入流(cin)
cin可以获得多个键盘的输入值,它具有下列格式:
cin>><对象1>[>><对象2> …];
其中,提取运算符“>>”可以连续写多个,每个提取运算符后面跟一个获得输入值的变量或对象。例如:
int nNum1,nNum2,nNum3; cin>>nNum1>>nNum2>>nNum3;
要求用户从键盘上输入三个整数。输入时,必须在三个数值之间加上一些空格来分隔,空格的个数不限,最后用回车键结束输入;或者在每个数值之后按回车键。例如,上述输入语句执行时,用户可以输入:
本书约定:书中出现的“↵”表示按一次回车键。
此后变量nNum1、nNum2和nNum3的值分别为12、9和20。需要说明的是,提取运算符“>>”能自动将cin输入值转换成相应变量的数据类型,但用键盘输入数据的个数、数据类型及顺序,必须与cin中列举的变量一一匹配。
2.输出流(cout)
与cin相对应,通过cout可以输出一个整数、实数、字符及字符串,如下列格式:
cout<<<对象1>[<<<对象2> …];
cout中的插入运算符“<<”可以连续写多个,每个后面可以跟一个要输出的常量、变量、转义序列符及表达式等。
【例Ex_Cout】 cout的输出及endl算子
#include <iostream.h> int main() { cout<<"ABCD\t"<<1234<<"\t"<<endl; return 0; // 指定返回值 }
程序运行结果如下:
ABCD 1234
程序中,转义字符‘\t’是制表符,endl是C++中控制输出流的一个操作算子(预定义的对象),它的作用和‘\n’等价,都是结束当前行,并将屏幕输出的光标移至下一行。
3.使用格式算子oct、dec和hex
格式算子oct、dec和hex能分别将输入或输出的整数转换成八进制、十进制及十六进制。
【例Ex_ODH】 格式算子的使用
#include <iostream.h> int main() { int nNum; cout<<"Please input a Hex integer:"; cin>>hex>>nNum; cout<<"Oct\t"<<oct<<nNum<<endl; cout<<"Dec\t"<<dec<<nNum<<endl; cout<<"Hex\t"<<hex<<nNum<<endl; return 0; }
程序运行结果如下:
Please input a Hex integer:7b↵
Oct 173
Dec 123
Hex 7b
综上所述,在外观上,提取运算符“>>”和插入运算符“<<”好比是一个箭头,它表示流的方向。显然,将数据从cin流入一个变量时,则流的方向一定指向变量,即如“cin…>>a”格式;而将数据流入cout时,则流的方向一定指向cout,即如“cout<<…”格式。