(原创)谭浩强C++程序设计学习笔记:第二章 数据类型与表达式
数据结构 指的是数据的组织形式。例如,数组就是一种数据结构。
C++ 可以使用的数据类型如下:
布尔型就是逻辑型,空类型就是无值型。
C++ 的数据包括常量与变量,常量与变量都具有类型。
例如:利用指针和结构体类型可以构成表、树、栈等复杂的数据结构。
说明:
(1) 整型数据分为长整型 (long int) 、 一般整型 (int) 和 短整型 (short int)。 在 int 前面加 long 和 short 分别表示长整型和短整型。
(2) 整型数据的存储方式为按二进制数形式存储,例如十进制整数 85 的二进制形式为 1010101 ,则在内存中的存储形式如图 2.1 所示。
(3) 在整型符号 int 和字符型符号 char 的前面, 可以加修饰符 signed(表示 “有符号” ) 或 unsigned( 表示 “无符号” )。如果指定为 signed, 则数值以补码形式存放,存储单元中的 最高位(bit) 用来表示数值的符号。如果指定为 unsigned, 则数值没有符号,全部二进制位都用来表示数值本身。例如短整型数据占两个字节,见图 2.2:
有符号时,能存储的最大值为 2^15 -1,即 32767,最小值为 -32768。无符号时,能存储的最大值为2^16 -1,即 65535,最小值为0。有些数据是没有负值的,可以使用 unsigned, 它存储正数的范围比用 signed 时要大一倍。
(4) 浮点型(又称实型)数据分为单精度(float)、 双精度(double) 和 长双精度(long double) 3种,并且 float 和 double 的数值范围不同。对 float 分配 4 个字节,对 double 和 long double 分配 8 d个字节。
(5) 表中类型标识符一栏中,方括号[]包含的部分可以省写,如 short 和 short int 等效, unsigned int 和 unsigned 等效。
常量
常量的值是不能改变的,一般从其字面形式即可判别是否为常量。常量包括两大类,即 数值型常量(即常数) 和 字符型常量。
这种从字面形式即可识别的常量称为 “字面常量” 或 “直接常量”。
数值常量就是通常所说的常数。
为什么将数值常量区分为不同的类别呢?因为在进行赋值或函数的参数虚实结合时要求数据类型匹配。
整数
一个整型常量可以用 3 种不同的方式表示:
* 十进制整数 * 八进制整数 * 十六进制整数
浮点数
一个浮点数可以用两种不同的方式表示:
(1) 十进制小数形式。
(2) 指数形式(即浮点形式)
如 3.14159 可以表示 为 0.314159 × 10^1 , 3.14159 × 10^0 ,31.4159 × 10^-1 , 314.159× 10^-2等形式。在程序中应表示为:0.314159e1, 3.14159e0,31.4159e-1 ,314.159e-2,用字母 e表示其后的数是以 10 为底的幂,如 e12 表示 10^12.
在程序中不论把浮点数写成小数形式还是指数形式,在内存中都是以指数形式(即浮点形式) 存储的。如图2.3所示:
< 数字部分必须小于 1 ,同时,小数点后面第一个数字必须是一个非 0 数字。存储单元分为两部分,一部分用来存放数字部分,一部分用来存放指数部分。>
字符常量
用单撇号括起来的一个字符就是字符型常量。如 ′a′。
注意:① 字符常量只能包括一个字符,如 ′AB′是不合法的。 ② 字符常量区分大小写字母,如 ′A′ 和 ′a′ 是两个不同的字符常量。 ③ 撇号( ′ )是定界符,而不属于字符常量的一部分。如 cout<<′a′; 输出的是一个字母 “a”, 而不是 3个字符′ “ ′a′ ”。
C++ 还允许用一种特殊形式的字符常量,就是以 “\”开头的字符序列,转义字符;
将一个字符常量存放到内存单元时,是将该字符相应的 ASCII 代码放到存储单元中。
既然字符数据是以 ASCII 码存储的,它的存储形式就与整数的存储形式类似。这样,在 C++ 中字符型数据和整型数据之间就可以通用。一个字符数据可以赋给一个整型变量,反之,一个整型数据也可以赋给一个字符变量。也可以对字符数据进行算术运算,此时相当于对它们的 ASCII 码进行算术运算。
// test1 // 将字符赋给整型变量 #include <iostream> using namespace std; int main() { int i, j; i = 'A'; j = 'B'; cout << i << ' ' << j << endl; return 0; } // 输出:65 66
可以看到:在一定条件下,字符型数据和整型数据是可以通用的。但是应注意字符数据只占一个字节,它只能存放 0~255范围内的整数。
// test2 // 字符数据与整数进行算术运算--将小写字母转换为大写字母 #include <iostream> using namespace std; int main() { char c1, c2; c1 = 'a'; c2 = 'b'; c1 = c1 - 32; c2 = c2 - 32; cout << c1 << ' ' << c2 << endl; return 0; } // 输出:A B
从 ASCII 代码表中可以看到每一个小写字母比它相应的大写字母的 ASCII 代码大 32。
字符串常量
用双撇号括起来的部分就是字符串常量,如 ″abc″。字符串常量 ″abc″ 在内存中占 4 个字节(而不是 3 个字节),见图 2.5。
编译系统会在字符串最后自动加一个 ′\0′作为字符串结束标志。
注意:″a″ 和 ′a′ 代表不同的含义,″a″ 是字符串常量,′a′ 是字符常量。前者占两个字节,后者占 1 个字节。
请思考:字符串常量 ″abc\n″ 包含几个字符? 不是 5个而是 4个字符,其中 “\n” 是一个转义字符。但它在内存中占 5 个字节(包括一个 “\0” 字符)。
如果在一个字符串中最后一个字符为 “\”,则表示它是续行符,下一行的字符是该字符串的一部分,且在两行字符串间无空格。如
符号常量
在 C++ 程序设计中,常用一个符号名代表一个常量,称为符号常量,即以标识符形式出现的常量。
// test3 // 符号常量的使用 #define PRICE 30 // 注意这不是语句,末尾不要加分号 int main() { int num, total; num = 10; total = num * PRICE; cout << "total = " << total << endl; return 0; }
程序中用预处理命令 #define 指定 PRICE 在本程序单位中代表常量 30 ,此后凡在本程序单位中出现的 PRICE 都代表 30 ,可以和常量一样进行运算。
请注意符号常量虽然有名字,但它不是变量。它的值在其作用域(在本例中为主函数)内是不能改变的,也不能被赋值。
使用符号常量的好处是:
(1)含义清楚。
(2)在需要改变一个常量时能做到 “一改全改”。
变量
在程序运行期间其值可以改变的量称为变量。
请注意区分变量名和变量值这两个不同的概念,见图 2.6
用来标识变量、符号常量、函数、数组、类型等实体名字的有效字符序列称为标识符( identifier)。
C++ 规定标识符只能由字母、数字和下划线 3种字符组成,且第一个字符必须为字母或下划线。
应注意变量名不能与 C++ 的关键字、系统函数名和类名相同。
C++ 没有规定标识符的长度(字符个数),但 有的系统取 32 个字符,超过的字符不被识别。
在 C++ 语言中,要求对所有用到的变量作强制定义,也就是必须 “先定义,后使用 ”。
C++ 要求对变量作强制定义的目的是:
(1) 凡未被事先定义的,不作为变量名,这就能保证程序中变量名使用得正确。例如,如果在声明部分写了 int student;而在执行语句中错写成 statent 。 如 statent = 30;在编译时检查出 statent未经定义,作为错误处理。输出 “变量 statent未经声明 ”的信息,便于用户发现错误,避免变量名使用时出错。
(2) 每一个变量被指定为一确定类型,在编译时就能为其分配相应的存储单元。如指定a和b为 int 型,一般的编译系统对其各分配 4 个字节,并按整数方式存储数据。
(3) 指定每一变量属于一个特定的类型,这就便于在编译时,据此检查该变量所进行的运算是否合法。例如,整型变量 a 和 b , 可以进行求余运算:a%b ,% 是 “求余”,得到 a/b 的余数。如果将 a 和 b 指定为实型变量,则不允许进行 “求余”运算,在编译时会给出有关的出错信息。
允许在定义变量时对它赋予一个初值,这称为变量初始化。
如果对变量未赋初值,则该变量的初值是一个不可预测的值,即该存储单元中当时的内容是不知道的。
初始化 是在程序运行时执行本函数时赋予初值的,相当于执行一个赋值语句。
常变量
在定义变量时,如果加上关键字 const, 则变量的值在程序运行期间不能改变,这种变量称为常变量 (constant variable)。 例如,
const int a=3; // 用 const 来声明这种变量的值不能改变,指定其值始终为 3
在定义常变量时必须同时对它初始化(即指定其值),此后它的值不能再改变。
常变量又称为只读变量(read-only-variable)。
请区别用 #define 命令定义的符号常量和用 const 定义的常变量:
* 符号常量只是用一个符号代替一个字符串,在预编译时把所有符号常量替换为所指定的字符串,它没有类型,在内存中并不存在以符号常量命名的存储单元。
* 常变量具有变量的特征,它具有类型,在内存中存在着以它命名的存储单元,可以用 sizeof 运算符测出其长度。
C++运算符
C++ 提供了以下运算符:
(1) 算术运算符
+(加 ) -(减) *(乘 ) /(除 ) %(整除求余 ) ++(自加 ) --(自减)
(2) 关系运算符
>(大于) <(小于 ) ==(等于) >=(大于或等于) <=(小于或等于) !=(不等于)
(3) 逻辑运算符、
&&(逻辑与 ) ||(逻辑或 )!(逻辑非)
(4) 位运算符
<<(按位左移 ) >>(按位右移 ) &( 按位与 ) |(按位或 ) ∧(按位异或 ) ~(按位取反)
(5) 赋值运算符 (=及其扩展赋值运算符)
(6) 条件运算符 (?:)
(7) 逗号运算符 ( ,)
(8) 指针运算符 ( * )
(9) 引用运算符和地址运算符 (& )
(10) 求字节数运算符(sizeof)
(11) 强制类型转换运算符(( 类型 ) 或类型 ( ) )
(12) 成员运算符 ( . )
(13) 指向成员的运算符( ->)
(14) 下标运算符([])
(15) 其他(如函数调用运算符())
+(加法运算符,或正值运算符。如 3+5, +3)
-(减法运算符,或负值运算符。如 5-2, -3)
*(乘法运算符。如 3 * 5 )
/(除法运算符。如 5/3)
%(模运算符,或称求余运算符,%两侧均应为整型数据,如7%4的值为3)。
需要说明,两个整数相除的结果为整数,如 5/3 的结果值为 1,舍去小数部分。但是,如果除数或被除数中有一个为负值,则舍入的方向是不固定的。例如, -5/3 在有的 C++ 系统上得到结果 -1,有的 C++ 系统则给出结果 -2。多数编译系统采取 “向零取整” ” ”
的方法,即 5/3 的值等于 1, -5/3 的值等于 -1,取整后向零靠拢。
C++ 在运算时对所有 float 型数据都按 double 型数据处理。
算术表达式
用算术运算符和括号将运算对象(也称操作数)连接起来的、符合 C++ 语法规则的式子,称 C++ 算术表达式。
“自左至右的结合方向 ”又称 “左结合性 ”,即运算对象先与左面的运算符结合。以后可以看到有些运算符的结合方向为 “自右至左 ”,即右结合性(例如赋值运算符)。
在进行运算时,不同类型的数据要先转换成同一类型,然后进行运算。转换的规则按图 2.7所示。
自增和自减运算符
++i( 在使用 i 之前,先使 i 的值加 1 ,如果 i 的原值为 3,则执行 j=++i 后,j 的值为 4)
--i (在使用 i 之前,先使 i 的值减 1,如果 i的原值为 3,则执行 j=--i 后,j 的值为 2)
i++ (在使用 i 之后,使 i 的值加 1,如果 i 的原值为 3,则执行 j=i++ 后,j 的值为 3,然后 i 变为 4)
i--(在使用 i 之后,使 i 的值减 1,如果 i 的原值为 3,则执行 j=i-- 后,j 的值为 3,然后 i 变为 2)
++i 是先执行 i = i+1 后,再使用 i 的值;而 i++ 是先使用 i 的值后,再执行 i= i+1。
请注意:
(1) 自增运算符 (++) 和 自减运算符 (--) 只能用于变量,而不能用于常量或表达式。
(2) ++ 和 -- 的结合方向是 “自右至左” 。
(3) 自增运算符( ++)和 自减运算符( --)使用十分灵活,但在很多情况下可能出现歧义性,产生 “意想不到 ”的副作用。
(4) 自增(减)运算符在 C++ 程序中是经常见到的,常用于循环语句中,使循环变量自动加 1。也用于指针变量,使指针指向下一个地址。
强制类型转换
强制类型转换的一般形式为
(类型名)(表达式)
( int)( x+y)( 将 x+y的值转换成整型)
(int)x+y 则只将x转换成整型,然后与y相加。
C++ 还增加了以下形式:类型名(表达式),如 int(x) 或 int(x+y)。
需要说明的是在强制类型转换时,得到一个所需类型的中间变量,但原来变量的类型未发生变化。
// test4 // 强制类型转换 #include <iostream> using namespace std; int main() { float x; int i; x = 3.6; i = (int)x; cout << "x = " << x << ", i = " << i << endl; return 0; } // 输出:x = 3.6, i = 3
由上可知,有两种类型转换,一种是在运算时不必用户指定,系统自动进行的类型转换,如 3+6.5。
第二种是强制类型转换。当自动类型转换不能实现目的时,可以用强制类型转换。此外,在函数调用时,有时为了使实参与形参类型一致,可以用强制类型转换运算符得到一个所需类型的参数。
赋值运算符
赋值符号 “=” 就是赋值运算符,它的作用是将一个数据赋给一个变量。
如果赋值运算符两侧的类型不一致,但都是数值型或字符型时,在赋值时会自动进行类型转换。
(1) 将 浮点型数据(包括单、双精度)赋给 整型 变量时,舍弃其小数部分。
(2) 将 整型 数据赋给 浮点型 变量时,数值不变,但以指数形式存储到变量中。、
(3) 将一个 double 型数据赋给 float 变量时,要注意数值范围不能溢出。
(4) 字符型 数据赋给 整型 变量,将字符的 ASCII 码赋给整型变量。
(5) 将一个 int 、 short 或 long 型数据赋给一个 char 型变量,只将其低 8 位原封不动地送到 char型变量(发生截断)。例如
short int i=289;
char c;
c=i; //将一个 int型数据赋给一个 char 型变量
赋值情况见图 2.8。为方便起见,以一个 int 型数据占两个字节 (16位) 的情况来说明。
(6) 将 signed(有符号)型数据赋给长度相同的 unsigned(无符号)型变量,将存储单元内容原样照搬(连原有的符号位也作为数值一起传送)。
// test5 // 将有符号数据传送给无符号变量 #include <iostream> using namespace std; int main() { unsigned short a; short int b = -1; a = b; count << " a = " << a << endl; return 0; } // 输出:65535
不同类型的整型数据间的赋值归根结底就是一条:按存储单元中的存储形式直接传送。
复合赋值操作符
赋值表达式
由赋值运算符将一个变量和一个表达式连接起来的式子称为 “赋值表达式 ”。
对赋值表达式求解的过程是:先求赋值运算符右侧的 “表达式 ”的值,然后赋给赋值运算符左侧的变量。一个表达式应该有一个值。
赋值运算符左侧的标识符称为 “左值 ”(left value, 简写为 lvalue)。
出现在赋值运算符右侧的表达式称为 “右值 ”(right value, 简写为 rvalue)。
逗号运算符