1
第 3章 数据类型、运算符与表达式
2
3.1 C的数据类型在计算机领域中,信息载体的各种符号等都看作为数据,程序设计语言中使用的各种数据都有与之相联系的数据类型,而数据类型是语言中描述数据结构的机制。
数据类型刻划了数据符号所能具有的值及在其上可施行的操作。 程序中使用的所有数据都必须指定其数据类型。
程序设计语言中为数据指定数据类型的另一目的是为数据 分配规定的存储空间、正确解释与使用数据。
3
4
3.2 常量与变量
1) 常量在程序运行期间其值已知且不能改变的量。
直接常量 值和类型由书写形式本身确定,不需进行任何说明便可直接使用。
例如,10,3.14,10e5
符号常量 是对直接常量规定了一个名字的常量 (用一个标识符来代替一个直接常量 ),要求先定义后使用。
例如,#define PRICE 30
5
第一,含义清楚,如上面的例子,看到 PRICE 就知道它代表价格。因此定义符号常量名时应考虑 见名知意 。
为提高程序可读性,提倡少用直接常量,多用符号常量。
第二、在需要改变一个常量的值时能做到 一改全改 。
如在程序中多处用到某物品的单价,若用直接常量表示价格,则在价格调整时,就需要在程序中作多处修改;
若用符号常量 PRICE 代表价格,只需改动,
#define PRICE 21.5
一处即可。以后在程序中所有以 PRICE代表的价格就会一律自动改为 21.5 。
(a) 使用符号常量的优点
6
(b) 符号常量定义的一般形式:
#define 符号常量名 字符序列例如:
#define SIZE 80
#define END?\0‘
#define FORMAT ―%d%d%d‖
#define NULL 0
一旦定义了符号常量则在程序中可以使用它们。
例如:
int a = SIZE ;
printf ( FORMAT,x,y,z) ;
if ( fp==NULL) printf ( ―open error!‖ ) ;
7
(c) 定义符号常量的注意事项
1) 必须以 #开头;
2) 因为它们是 C编译系统的预处理程序的宏定义命令,并不是 C语言语句,所以不要以分号? ;?结束。
否则将分号作为常量的一部分;
3) 通常使用大写字母序列作为符号常量的名字。但可用小写字母;
4) 符号常量一般在源文件的开始定义,也可以在源文件的任意行上定义,但 必须在使用它们的位臵之前定义 ;
5) 所有的符号常量的名字不能相同。
6) 在 #define和 符号常量名 的后面至少有一个空格 。
8
2) 变量
1,变量的含义在程序运行期间其值可以改变的量。
2,变量的作用变量用来表示一个数据对象,保存初值,保存运算结果、作为运算分量参与运算。
3,变量名每一个变量都 有一个名字以供 识别 (故称之为 标识符 )
并用它 引用 该变量。 变量名的定义规则:
① 字母、数字、下划线组成,首字符必须是字母或下划线。
② 字母区分大小写。
③ C 标准没有规定变量名的长度,各编译系统自行规定。
Turbo C 允许 32个字符。
9
sum
_total
class
student_name
SIC
_8_8
ok_
_ _doub
下列都是正确的标识符:
10
下列都是错误的标识符:
M,D,john,不能用于组成标识符
123 非字母、下划线开头
-1-2-3 - 是连字符,与下划线不同
$33 $ 是特殊字符,非字母
c/s / 不能用于组成标识符
tax rate 中间有空格,是两个标识符
11
2) 不能用已被 C系统赋予特殊意义的标识符 (关键字 )
作为变量名。 C 定义的关键字,[见教材附录 P375]
auto break case char const continue default
do double else enum extern float for goto
if int long register return short signed sizeof
static struct switch typedef union unsigned void
volatile while
4,定义变量名 (标识符)的注意点,
1) 不要使用 预处理命令 标识符作为变量名 。
例,include,define,undef,ifdef,ifndef,endif、
line等 。
12
② 使用有含义的英文单词或汉语拼音。
3) 不要使用 C系统的库函数名 作为 变量名 。
例,printf,scanf 等。详见教材 [附录 Ⅴ P381]。
其实 C语言 系统允许使用 系统的库函数名作为变量名,
并不会产生语法错误,但 会发生含义覆盖,失去原来的定义。除非你在程序中不使用该函数,因此 不宜使用 。
4) 选择变量名应注意到? 见名知意?,选用有意义的变量名。
例如,IntCount,CharName,StudentNumber
通常使用两种命名方法:
① 匈牙利命名法若干个英文单词组成,每个单词首字母大写,标识符前面的若干字符表示数据类型。
13
若程序中的变量名长度大于本系统规定的长度,则只有前面一些字符有效,后面的不被识别。
如对于下列两个标识符:
student_name 与 student_number
由于前 8个字符相同,在 MSC 系统中则认为这两个标识符是一样的,不能用来代表两个不同的对象。为提高程序的可移植性,应尽量使用较短的标识符。
5) 因 C 标准没有规定标识符的长度。各个编译系统有自己的规定。如:
MSC 8个字符
Turbo C 32个字符
14
6) C函数库中通常使用一个或两个下划线开始的标识符(如 _ _ kcab),C标准称这样的标识符为 保留字
(保留给系统使用的) 。 虽然可使用这样的标识符,
但可能导致名字的混 乱,最好避免。
7)符号常量名也是标识符,因此符号常量的命名也按标识符规则确定。
15
5,变量的存储与地址变量存储在主存储器中,也可指定存放到寄存器中。
但不能指定具体的存放位臵。因此,一个变量总是与 一定数量的存储单元相关联。
不同类型的 变量占用的 存储单元的多少不同,这 由 C
编译系统按变量的类型进行分配 。
变量名代表它所占用的存储空间,及代表该存储空间中存储的内容,并不代表它的存储起始位臵(地址)。
要获得它的存储地址可用 C的地址运算符 &,通过地址运算获得。 例如,要得到变量 val 的 起始存储地址,可通过计算 &val 而获得 。
16
17
6、变量的值与存取变量的值指当前存放在变量所占用的存储区域中的数据,
也称变量的内容。
向变量中存数据就是把数据放入变量占用的存储区域中去,或者说改变变量的值。首次向变量中存值称之为变量的初始化。有三种基本手段:
定义变量时初始化 例如,int val = 50 ;
赋值 例如,val = 10 ;
从外部读入 例如,scanf ( ―%d‖,&val ) ;
必须注意:
无论哪种方法,当前存入的数据总是替换掉 (冲掉 ) 变量中当前存在的内容。
18
从变量中取数据就是引用(使用)变量的当前值。如:
sum = val + 100 ;
意即:取出变量 val 的内容加上 100 后存入变量 sum中去。
必须注意的是:
( 1) 取变量的值只是取的变量的一个副本,并不清除变量中的内容。
( 2) 在使用变量的值之前变量必须已存过值(已初始化过),否则不能使用。未经初始化过的变量的值是 无定义的 (其值不清楚,系统给变量分配存储单元时并不将其空间清除,并把原来存在的数据作为该变量的初始化值)。
( 3) 程序设计者必须清楚变量中当前存放的是何值,
以便正确地使用变量的值 。
19
main( )
{
int a = 5,b,c,d ;
c = a+5 ;
d = a+c+b ;
printf ( ―a=%d\nb=%d\nc=%d\nd=%d\n‖,a,b,c,d ) ;
}
程序的运算结果将是,
a=5
b=64 /* b的内容未初始化,是一个不确定的数 */
c=10
d=79
20
7、变量的声明程序中任何变量都必须,先声明,后引用 。其好处是:
(1) 有利于避免拼写错误。如在程序中声明了 int student;
而在程序中错写成,sdudent=30; 编译时发现 sdudent 未经说明,将输出,? 变量 sdudent未经声明?的信息,这样有利于程序设计者及时发现错误。
(2) 便于编译系统检查对其运算的合法性。例如,C允许对整型变量 a和 b作求余运算 a%b,若将 a,b指定为实型变量,
则编译系统很容易发现该错误。
(3) 为每一变量指定确定的数据类型,编译系统就能根据该类型分配确定大小的存储空间,正确的存储、获取、解释该数据。
21
㈠ 变量声明的一般格式
[存储类型 ] 数据类型 变量名 1 [ =表达式 1 ]
[,变量名 2 [ =表达式 2 ] ]
……
[,变量名 n [ =表达式 n ] ] ;
其中,
[……] 表示可缺省项 。 因此,变量声明的最简单的 形式是:
数据类型 变量名;
变量名 i 是要声明的变量的名字。可以一 次声明多个变量,
它们之间用逗号隔开。 在声明变量的同一作用域中,一个变量名只能被定义成一种类型,不能重复定义。
22
main( )
{
int a ;
float a ;
……
}
程序编译时,将会给出如下的错误信息,
Error C:\TC\NONAME.C 5,Redeclaration of 'a' in function main
23
char 字符型
int 基本整型
float 单精度浮点数
double 双精度浮点数数据类型在该位臵处指定变量的数据类型。用来指出给变量分配多大的存储空间;规定数据的存储形式,或者说对变量所占用 的存储空间中的数据如何解释。
可使用的基本数据类型的关键字是:
24
存储类型用来指出变量存储的地方。变量可被存储到三种区域中:
静态数据区、堆栈区和寄存器。
静态数据区 存储空间分配在编译阶段进行,存于其中的数据对象相对程序的执行是永久的(一直有效)。
堆栈区 存储空间的分配在函数或复合语句被执行时进行,存于其中的数据对象相对函数或复合语句的执行是临时的 ( 函数或复合语句一旦执行结束,它们用的空间立即释放,
存于其中的数据不再有效)。
寄存器 C 语言允许将变量存放到指定的寄存器中。
声明变量时若要指定存储类型,可用如下存储类型关键字进行:
auto static register extern
25
其中:
声明了存储类型为 auto 型的变量称之为? 动态变量?。
对于这种存储类型:
a) 只用于局部 变量的声明(不能用于外部变量,即全局变量)。所谓局部变量,是指变量的作用域仅是定义它的函数或复合语句。
b) C编译程序总是将 auto 型变量存储到堆栈区。
c) 因此,这种变量具有局部性、动态性、临时性。
d) 声明变量时若未给出存储类型关键字,对局部变量:其缺省值为 auto ; 而对全局变量或外部变量,其缺省值为
extern 。
26
main ( )
{
auto int a ;
a = 1 ;
if ( a >= 1) {
auto int b ;
……
}
……
}
float fun ( auto int c,float d)
{
auto double e ;
……
}
auto int k ; /* 错误,外部变量声明 不能使用 auto存储类型 */
main ( )
{
auto int a ;
a = 1 ;
if ( a >= 1) {
auto int b ;
……
}
……
}
27
声明了存储类型为 register型的变量 称之为? 寄存器型变量?。对于这种存储类型:
a) 用来要求把声明的变量存储到计算机的寄存器中去。
b) 这种变量存取速度快。
c) 只能用于局部变量及函数的形式参数变量定义。 register
型变量与 auto型变量具有相同的性质。
d) 若声明的这种变量的个数超过机器可供使用的寄存器数,
C编译系统会自动地将超过的部分作为 auto型变量处理,
存放到堆栈区中。
e) 因寄存器长度有限,不能将 long double 型的浮点数定义成 register型变量。
f) 不允许对 register型变量进行取地址? &?操作。
28
例,
#include <stdio.h>
main ( )
{
register int a ; /*可简写为 register a ; */
scanf ( ―%d‖,&a ) ; /*这里取 a的地址运算是错误的 */
……
}
注意,当 register关键字用于 int 局部变量声明时,其后的
int关键字可以缺省不写。
29
声明了存储类型为 static 型的变量,称之为? 静态型变量?。
c) C语言允许定义静态局部变量和静态全局变量。
b) 这种变量在整个程序的执行期间一直有效 ;
a) C编译程序总是将这种变量存储到到静态数据区 ;
对于这种存储类型:
30
静态局部变量静态局部变量的局部特性与 auto 一样,与 auto的区别在于,
(1) 存储的内存区域不同,auto变量存储在堆栈区,static
型 局部变量存储在静态数据区;
(2) static 型局部变量不随函数或复合语句的进入 /退出而建立 /消亡。形象地说 static 型局部变量 有? 继承性?,
即本次进入函数或复合语句使用的 static型局部变量的值是上一次该函数或复合语句执行结束退出时的该函数或复合 语句的值。
31
main( )
{
int a,b ;
int fun ( int a ) ;
a = 1 ;
b = fun ( 1 ) ;
printf ( "%d\n",b ) ;
b = fun ( 1 ) ;
printf ( "%d\n",b ) ;
}
int fun ( int a )
{
static int b =1 ;
b = b+a ;
return b ;
}
程序输出是,
2 /*第一次输出 */
3 /*第二次输出 */
main( )
{
int a,b ;
int fun ( int a ) ;
a = 1 ;
b = fun ( 1 ) ;
printf ( "%d\n",b ) ;
b = fun ( 1 ) ;
printf ( "%d\n",b ) ;
}
int fun ( int a )
{
int b =1 ;
b = b+a ;
return b ;
}
程序输出是,
2 /*第一次输出 */
2 /*第二次输出 */
32
静态全局变量静态全局变量是其作用域仅在声明该变量的源文件范围之内的一种全局变量。
这种变量对属于同一程序的其他源文件中的函 数来说是不可见的,且在定义它的文件中,也仅仅在其声明位臵后面的那些函数能直接存取它们 ; 而在其声明位臵前面的那些函数若要使用它们,可用下面将要介绍的 extern 说明进行。
例如,假定程序设计者希望只让源文件中的两个函数
fun_b 和 fun_c知道外部变量 buf的存在,并能直接存取它,那么可以将 buf声明为静态全局变量,以如下形式安排在该源文件 file.c 中:
33
源文件 file.c 的内容是:
fun_a (……)
{
……
}
static int buf = 1000 ;
fun_b (……)
{
……
}
fun_c (……)
{
……
}
34
存储类型关键字 extern 的主要作用是用来说明要使用的外部变量是在程序中别的地方定义的 ( 扩大外部变量作用域 ) 。
当然,也可用它来声明外部变量,
注意,声明 和 说明 的含义是不同的:
(1) 声明是指定义变量,一定会给要求定义的变量分配存储空间;而 extern 说明只是指出需要使用本源文件中或该程序的某个源文件中已经声明 (定义 )过的变量,并不 对说明的变量分配存储空间。
(2) 在程序中,一个外部变量只能在函数的外部声明 (定义 )
一次,而不管它在何处被定义 ; 但却可以用 extern 对同一个外部变量进行多次说明,不但可以在函数的外部说明,也可以在函数的内部说明。
35
fun_a (……)
{
……
}
static int buf = 1000 ;
fun_b (……)
{
……
}
a) 用 extern说明的变量一定是外部对象。
例,在下面的源文件中,静态外部变量 buf 对函数 fun_a
是不可 访问 的,但若 fun_a 需要访问它,有三种办法 解决。
36
fun_a (……)
{
extern static int buf ; /*增加的引用 buf的 extern说明 */
……
}
static int buf = 1000 ;
fun_b (……)
{
……
}
方法一,
在函数 fun_a 的说明部分增加引用 buf的 extern说明。
37
extern static int buf ; /*增加的引用 buf的 extern说明 */
fun_a (……)
{
……
}
static int buf = 1000 ;
fun_b (……)
{
……
}
方法二,
在函数 fun_a 的前面增加引用 buf 的 extern 说明。
38
static int buf = 1000 ;
fun_a (……)
{
……
}
fun_b (……)
{
……
}
方法三,
将 buf的声明移到函数 fun_a的前面,这样一来不需增加引用 buf 的 extern 说明。
39
b) 不能用 extern说明来说明局部变量 ( 即不能用 extern说明来扩大局部变量的作用域 )。
fun_a (……)
{
extern int a ; /* 错误说明 */
……
}
fun_b (……)
{
int a ; /*局部变量声明 */
……
}
40
c) 可以用 extern 声明一个外部对象。 但若用 extern 声明一个外部变量时 必须同时给出初始化值。 通常声明外部变量时不使用 extern 说明符 ( 因声明外部变量时缺省? 存储类型? 即为 extern 类型,且此时可以不指定初始化值 ) ;
相反,用 extern 说明 一个变量时则不能对说明的变量指定初始化值。
extern int x=2 ;
float b ;
main ( )
{
x=x+2 ;
printf("%d\n",x) ;
}
extern int buf=10 ; /*错误说明 */
fun_a (……)
{
……
}
int buf ;
fun_b (……)
{
……
}
41
d) 用 extern 说明或者声明一个变量时,若说明或者声明的变量的数据类型为为 int,则关键字 int可以省略不写。
extern x=2 ;
float b ;
main ( )
{
x=x+2 ;
printf("%d\n",x) ;
}
extern buf=10 ; /*错误说明 */
fun_a (……)
{
……
}
int buf ;
fun_b (……)
{
……
}
42
e) 编译阶段遇到 extern 时,先在本文件中找外部变量的定义,
如果找到,就在本文件中扩展作用域 ;如果找不到,再从其他文件中找外部变量的定义 (如果有其他源程序文件的话 ),如果找到,就将作用域扩展到那个文件,如果找不到外部变量的定义,将作为变量无定义处理。例如:
extern int x ;
main ( )
{
x=2 ;
printf ( "%d\n",x ) ;
}
运行该程序时将输出如下出错信息:
Linker Error,Undefined symbol '_x' in module NONAME.C
43
extern j=17 ; /* 缺省 int,外部变量 定义 */
main( )
{
extern k ; /* 缺省 int,外部变量引用 说明 * /
int i=10 ;
printf ( "i=%d\n",i+k+j ) ;
}
int k=3 ; /* 缺省 extern的外部变量定义 */
程序输出:
i=30
例 1:
44
extern int i=12 ; /* 使用了 extern的外部变量 定义 */
main( )
{
extern int j ; /* 说明 使用后面定义的外部变量 j */
……
}
int j ; /* 缺省 extern的外部变量定义,可不指定初值 */
double pi=3.14 ; /* 缺省 extern且指定初值的外部变量定义 */
int fun ( int k )
{
……
}
例 2:
45
int max ( int x,int y ) /* 定义 max 函数 */
{
int z ;
z=x>y? x,y ;
return (z) ;
}
void main( )
{
extern int a,b ; /* 引用外部变量 说明 */
printf ( ―%d‖,max ( a,b) );
}
int a=13,b = -8 ; /* 外部变量定义 */
例 3:
46
例如,
float pi = 3.14 ;
int a,b = 2,c = 3 ;
register d = -5 ;
double e = 3.1415926 ;
static char character =?a‘ ;
= 表达式在声明变量的同时,允许在变量名的后面以:
= 表达式的形式给变量指定初值,这称为变量的初始化。
47
需要注意,在定义变量时,对不同类型变量指定的初始化值,有不同的规定和要求:
1) static 型变量和外部变量的初始化值 必须为常量或常量表达式 (由一个或若干个全部是直接常量或符号常量组成的计算式)。例如:
#define PI 3.14 /* PI 是定义的符号常量名 */
static int a = 2 ;
static float b = 2.35 + 4.2/PI ;
但下面的声明是错误的:
static int c =2+5,d = c + 3 ;
因为在 d = c + 3 中含有变量名 c
48
(2) auto,register型局部变量的初始化值可以是,常量、符号常量、变量名、函数调用及由它们组成的表达式 。 但如果含有变量名,则必须是在这之前已经指定了初始化值的变量名。例如:
double x = 1.5,y = sin(x),z = x + y ;
(3) 在用 extern 说明使用某个外部变量时,不能对说明使用的变量指定初始化值 。因为变量的初始化值只能在声明时给出。
(4) C 不允许在定义变量的同时,以如下形式对同一类型的若干个变量臵相同的初值:
int x = y = z =100 ;
49
(5) 不能为函数的形式参数指定初始化值。函数的形式参数的初始化值是由调用它的函数那里传递过来的。
例如,下面的语法是错误的:
fun ( int p=20,int q=3 )
{
……
}
这里 p 和 q 都是函数 fun 的形式参数,不能给它们指定初始化值。
50
(6) 由于 static 型局部变量具有?继承性?,所以函数和复合语句中声明的带有初始化值的 static 型局部变量 仅在编译时对它们初始化一次,以后每次进入将不再为它们重新分配存储单元和初始化。
int fun ( int a )
{
static int b =1 ; /* 仅在编译时对它初始化一次 */
b = b+a ;
return b ;
}
main ( )
{
int a,b ;
a = 1 ;
b = fun ( 1 ) ;
printf ( "%d\n",b ) ;
b = fun ( 1 ) ;
printf ( "%d\n",b ) ;
}
51
(7) 若在定义 auto,register 型局部变量时为它们指定了初始化值,则对它们的初始化处理是当 每次进入 定义它们的函数和复合语句时,为它们分配了存储单元后进行的。亦即 每次进入时,都要对它们初始化一次。
(8) 系统将 自动 对没有指定初始化值的外部变量和 static
型变量 (无论局部和全局 ) 初始化为 0值,因此这些变量总是有定义的,可以直接使用 ; 而系统不自动为
auto,register 型局部变量初始化,因此在没有给出初始值前,它们保持无定义,不能直接使用!
52
㈡ 声明变量的位臵与变量的作用域在 C 语言程序中不同位臵处声明的变量具有不同的作用域、生命期。
作用域指程序中的标识符能够被引用的范围 。
(1) 在函数外部声明的变量相对函数而言称之为? 外部变量?,也称为? 全局变量? 。这种变量在程序运行期间一直保持有定义。除特别说明外,其作用域是?整个程序?,
所以也称为 文件作用域 。 因其全局性,所以不能定义同名的外部变量。函数名也是外部对象,所以程序中函数名也不能同名。
总之,外部标识符都不能同名。
53
如果定义了同名外部对象,在编译阶段并不能发现这种错误,因为 程序是以源文件为单位编译的,这只能在连接装配阶段才能发现。
如果定义了与库函数同名的对象,则库函数失效。
前面曾提及外部变量的作用域是?整个程序?,其实这并不准确,我们只能说外部变量对程序中的函数是存在的。
这就如同一个城市中的所有电影院对公众来说是存在的,但并非任何人都能无限制地进入电影院看电影的道理一样,事实上,C语言中函数对外部变量的存取是有限制的:
54
b) 若一个外部对象不是在源文件首部声明的,而是在两个函数之间的位臵声明的,那么,只有该变量的 定义点之后 的那些函数可以直接存取该变量;而在该定义点之前的那些函数若要存取这个外部对象,则必须在使用点之前,用 extern 进行特别引用说明。
c) 同理,一个程序中的某个函数若要存取该程序的另一个源文件中声明的外部对象,必须在该函数中或在该函数的前面的某个外部位臵,用 extern进行特别的引用说明。
a) 在源文件 首部声明 的外部对象,对该源文件中的所有函数来说都是可以立即存取的,无需任何特别的说明;
55
(2) 如果把函数外部声明的变量称之为?全局变量?,那么在函数内部声明的变量称之为?局部变量? 。而在函数中不同位臵处声明的局部变量的作用域、生命期也是不同的。归结起来,可以在函数中三个不同位臵处声明局部变量:
在函数体的说明部分声明在该位臵声明的局部变量的作用域局部于整个函数(称为 函数作用域 )。在该函数内的语句可以直接使用它们。若是 auto型变量,每次进入该函数总是重新为其分配存储单元,
但并不自动初始化。当退出函数时取消其定义。
56
在该位臵声明的变量称为形式参数变量。也是一种局部变量,其性质及其作用域与在函数体的说明部分定义的变量 相同,区别在于声明形式参数变量不能为其指定初值,也不能定义为 static 类型。 事实上,形式参数变量的声明可以在两个不同的位臵。例如:
在函数的形式参数声明部分声明
57
在复合语句开始位臵声明所谓复合语句即一对大括号 { …… } 括住的程序块 ( 也称分程序 )。
在函数中复合语句被看作为一条单一的语句。
在复合语句中声明的变量是一种作用域更小的局部变量。作用域仅限于该复合语句(称为 块作用域 ),对函数的其他部分是不可见、不可存取的。
每当执行进入该复合语句时,若其中有 auto 型变量声明,则总是重新为其分配存储单元,但并不自动初始化,
当退出复合语句时取消其定义。
58
归结起来,程序中的标识符有四种不同的作用域,
a) 文件作用域
b) 函数作用域
c) 块作用域
d) 函数原型作用域
59
main ( )
{
int a ;
……
}
fun ( )
{
float a ;
……
}
由于局部变量有自己的作用域,所以:
a) 不同函数中可以声明同名的局部变量;例如:
60
b) 同一函数的不同复合语句内可以声明同名的局部变量 ;
main ( )
{
int a,b ;
……
if ( a>b) {
float k ;
……
}
else {
double k ;
……
}
……
}
61
c) 同一函数中局部于整个函数的局部变量(包括形式参数局部变量)可以与该函数中任何复合语句中的局部变量同名 ;
main ( )
{
int k,b ;
……
if ( a>b) {
float k ;
……
}
}
62
d) 同一程序中的全局变量与函数中的局部变量可以同名。
char k ;
main ( )
{
int k,b ;
……
if ( a>b) {
float k ;
……
}
}
无论上述哪种情况,同名变量之间都互不冲突,各自占用不同的存储空间,存储不同类型的数据。
63
关于同名情况的处理规则:
如果全局变量与局部变量同名,那么该全局变量在与之同名的局部变量的作用域中将失去效用。在局部变量的作用域中使用的这个同名变量,其值是局部变量的值。
同理,若一个函数的复合语句中声明的局部变量与局部于该函数的一个局部变量同名,则在复合语句中使用的这个同名变量,其值是复合语句中该变量的值。
而若一个函数的复合语句中声明的局部变量不但与一个全局变量同名,且也与所在函数作用域中的一个局部变量同名,则在复合语句中使用的这个同名变量,其值是复合语句中该变量的值。
下面是一个典型的源文件中声明变量的各种位臵的示例,
64
65
main ( )
{
int a,b ;
b = 2*a ; /* 变量 a 此时还没有赋过值,是无定义的 */
scanf ( ―%d‖,&a ) ;
……
}
㈢ 声明、使用变量时的常见错误例 1,
编译 warning:
possible use of ―a‖ before definition in main
原因,a 的值不确定
66
main ( )
{
int a ;
a = 3 ;
int b ;
b=2 ;
……
}
main ( )
{
a = 3 ;
int a;
……
}
例 2:
函数的声明在动作语句之后,位臵不对。
67
main ( )
{
int a,int b ;
……
}
例 3,
声明语句没有按语法书写。
应写成:
int a,b ;
或干脆写成两行:
int a ;
int b ;
68
main ( )
{
int a,b ;
double b ;
……
}
例 4,
把同一个标识符在同作用域中声明成不同类型的对象。
69
3.3 整型数据
(a) 按日常使用的十进制整数形式书写( 0 ~ 9,+,- )
(b) 正数前面的 +号可有可无
(c) 不能以数字 0开头(因八进制整数必须以 0开头,以与八进制整数相区别 )
(d) 中间不能含有分节号和空格。
1)整型常量的表示十进制整数直接量例如:下面的两个整数直接量的写法是不允许的。
16 ′ 532′751
12 34 5
70
(e) 十进制整数通常用两种长度的内存空间来表示:
16 bit (2字节 ) 表示 ——基本整数表示范围为 -215 ~ 215 –1 ( 即 –32768 ~ 32767 )
32 bit (4字节 ) 表示 ——长整数表示范围为 -2 31~ 231 –1 (即 -2147483648 ~ 2147483647)
书写的十进制整数直接量若不大于基本整数的表示范围按基本整数表示,否则 自动 按长整数表示。
但是,若给出的值超出了长整数的表示范围,C将不报错,自动进行 溢出 处理(注,C标准没有定义溢出处理规则!
这里给出的只是 Turbo C2.0 中对溢出的处理。)
71
若程序中给出的数是 2147483649 则:
溢出处理后的值 = - 1 × 1× 2147483648 + 1
= - 2147483647
若程序中给出的数是 - 2147483649 则:
溢出处理后的值 = - 1× ( - 1) × 2147483648 –1
= + 2147483647
(f) 可以在十进制整数直接量后附加字母 l 或 L,让 C编译程序将其按长整数存储。例如,128L,256L,尽管它们不是足够的大,但由于后缀了 L,总是按 32位存储。
例如:
72
(g) 很多场合不需要使用负整数,为了扩大整数的表示范围,
C编译程序允许使用无符号整数直接量 (16位或 32位全部用来表示数值,不表示也不需表示符号),只要在整数直接量后面放臵字母 U或 u 如 123U,456u
这样无符号整数直接量的表示范围将分别是:
0 ~ 65535( 16位)和 0 ~ 4294967295 ( 32位)
若要指定长整数无符号直接量,则在后面再加上字母 l 或 L,如 123LU,456Lu (L与 U前后顺序任意) 。
无符号整数直接量溢出处理比较简单:
溢出处理后的值 = 溢出值 mode 4294967296
73
(1) 组成八进制数的数字只能是 0 ~ 7,例如,0786 是错误的,其中含有 8这样的数字。
(2) 与十进制整数直接量类似,也可以在八进制数后面后缀 L或 l,U或 u及 UL等构成基本八进制数、长八进制数、无符号八进制数、长无符号八进制数等。它们也分别用 16位和 32
位表示。
(3) 事实上,C中八进制数总是按无符号整数处理,但可使用带符号的八进制数。 例如,-077 。如果给出一个负的八进制数,则相当于对原数的各位取反加 1。例如:
八进制整数直接量八进制整数直接量是在组成八进制数的数字串前缀一个数字 0的直接量。例如,0200,0777。
74
假定给出 了 -077这样的八进制数,则表示的实际八进制数为,0177701,即 –077 等价于 0177701 。
因为在机器内 077 的表示形式为:
0000000000111111 ( 16位)
按规则,则 -077在机器内的表示形式应为:
1111111111000001
1 7 7 7 0 1
若将该数解释成有符号十进制整数,其值为,-63
若将该数解释成无符号十进制整数,其值为,65473
75
(4) 八进制数的数值表示范围:
76
c) 与八进制整数直接量类似,也可以在十六进 制数后面后缀 L或 l,U或 u 及 UL等构成长十六进制数、无符号十六进制数、长无符号十六进制数等。它们也分别用 16位和 32位表示。
十六进制整数直接量
(1) 十六进制整数直接量的表示形式,
a) 0x( 或 0X) 开始的 十六进制 数字串。例如:
0x400 0x7FF 0xAfe
b) 组成 十六进制 数的数字可以是,
0 ~ 9,a ~ f 或 (A ~F) a ~ f 表示 10 ~ 15
77
注意:提供不同的数制系统是为了方便使用,并不影响数的存储。如对于 16,020,0x10 在机器内部的存储都是相同的,使用的效果也相同。例如对于:
x=16 ; x=020 ; x=0x10 ;
三者都是等价的。
(2) 十六进制数的数值表示范围:
78
2) 整型变量用于存放整数的变量。
(1) 整型变量的种类用数据类型关键字 int 声明的变量称之为 基本整型变量,int型变量用来存储 C编译程序所在的宿主计算机所能表示的有符号整数。
C编译程序通常为 int型变量分配的一个机器字的存储空间 (在 Turbo C 2,0 中分配 16位,主要考虑兼容性的需要)。
数以 补码表示 。
79
short ( 短整型)
long ( 长整型)
unsigned ( 无符号型)
signed ( 有符号型)
为满足存储较大的整数及节省存储空间、及与相应的整型常量对应需要,C编译程序允许在 int之前加两类四种限定性关键字,
归纳起来,共有以下 6 种整型说明符的形式,
80
其中:
(1) [ …… ] 表示可缺省不写。
(2) 若既没有指定 signed,也没有指定 unsigned,则隐含为
signed。
因此,整型变量声明有如下简单形式:
81
(3) short 类型用于数值比较小的场合,以节省存储空间。
按标准,数用 8位表示,但多数 C并没有按标准实现,
仅保证 short不会比 int长。如 Turbo C 的 short就用 16
位表示。
(4) 下表是 Turbo C定义的存储空间大小及数值表示范围。
82
(5) 如果整数太大,超出了整数类型的表示范围,将会产生溢出,系统并不会给出提示,而自动按 C系统的溢出处理方法处理(处理方法与整型直接量的处理方法相同)。
例 1:
(7) 整型变量声明例
(6) 整数直接量有十、八 及十六进制之分,但整型变量只有十进制,而 没有对应的八进制与十六进制变量类型 。
83
例 2 整型数据的溢出
84
3.4 实型数据日常生活中使用的实数在计算机中称为浮点数。 浮点数在计算机内部的一般表示形式是:
85
其中:
阶码 即是指数部分,只能是整数,用来指出小数点的位臵,所以称为浮点数。表示位数的多少决定表示数的范围。
尾数 即是小数部分,是一个纯小数,用来决定有效数字,
位数越多精度越高。
数符 指出数的符号情况,通常用一位表示,1为正,0为负。
浮点数在不同的计算机中表示可能是不同的,因此语言中只关心表示位数的多少(通常用 32位和 64为表示)。
浮点数在计算机中 总是近似表示 的。
86
一、实型常量
(1) 实型常量有两种书写形式,
十进制小数形式十进制小数的一般书写形式是:
± a,b
当 a或 b为 0时可省略,a与 b不能同时省略,小数点不能省略(必须有),中间不能夹有空格。
例:
12.3 - 12.3,123 -1,-,2
0.0 0.123 +31.45 79.0 000.321
87
十进制指数形式十进制指数的一般书写形式:
± a.be ± c 或 ± a.bE ± c
a 是整数部分,为 0时可省略。
b 是小数部分,b=0且 a≠ 0时可省略,并可同时省略小数点。
c 是指数部分,不能省略且必须为 整数直接量
a与 b不能同时缺省(仅有一个小数点是错误的)。
+号可以不写。
例如:
-1.23e-2 表示 -1.23× 10 –2
1E6 表 示 1.0× 10 6
0.234e+1 表 示 0.234× 10 1
88
下面也是一些 正确的 浮点数直接量,
6.43e0 10e2 0e0 0.e0,0e0
注意,像 10e2 这样的浮点数直接量,虽然值是整数,但因其书写形式决定了它的类型,仍然是浮点数。
下面是一些 错误的 浮点数直接量,
e10 缺少尾数部分
100.e15.2 阶码部分为实数
.e5 不能仅有小数点。
(2) 规格化浮点数形式小数点前只有一位非 0数字。 例如,1.23e-2
89
(3) 浮点数直接量的存储浮点数直接量一定是有符号数,在机器中都用 64位存储(称之为双精度浮点数),数值表示范围大约在 –1.7×
10308 ~ 1.7× 10 308左右,约有 14位有效数字。
为节约存储空间,C允许在浮点数直接量后面后缀一个字母 F或 f,强制 C编译程序将其按 32位存储( 称之为 单精度浮点数 ),数值表示范围约在 –3.4× 1038 ~ 3.4× 1038
左右,约有 7位有效数字。
为适应实际应用的需要,C还允许在浮点数直接量的后面后缀一个字母 L或 l,强制 C编译程序将其按 80位存储
(称之为 扩充精度浮点数或长双精度浮点数 ),数值表示范围大约在 –3.4× 104932 ~ 1.1× 10 4932左右,约有 18位有效数字。
90
(1) 实型变量的类型
float 单精度 ( 32位表示 )
double 双精度 ( 64位表示 )
long double 长双精度 ( 80位表示 )
(2) 实型变量的声明程序中使用的每一个实型变量都必须声明,声明的形式、
位臵、初始化等都与整型变量相同。例如:
float x,y ; 指定 x,y为单精度实数
double z ; 指定 z为双精度实数
long double t ; 指定 t为长双精度实数二、实型变量
91
例 1,将一个数加上 1再减去原数,结果为 1。若数很小可以使用整型运算,这不会错;但我们的问题给出的数很大,使用整型无法计算,必须使用浮点计算,但使用浮点计算,则可能会给出错误的结果。例如:
三,浮点数计算的舍入误差由于浮点数是近似表示的,且只用有限的存储空间存储,因此 只能提 供有限的有效数字,有效数字以外的数字是不准确的,计算时将被舍去,经多次运算,就可能产生误差。
92
#include <stdio.h>
main ( )
{
float a,b ;
b = 2.0e18 + 1.0 ;
a = b - 2.0e18 ;
printf ( ―%f \n‖,a) ;
}
程序运行后的输出结果是:
-31386501120.000000
93
出现这种奇怪结果的原因是:
浮点数 2.0e18 为 2后面加 18个零,对它加 1是加到它的第
18位上,即变化的是第 18位。
如果要正确计算至少需要存储 18位有效数字,而 float是单精度浮点数,只有 7位有效数字。所以,如此计算肯定是不正确的 。即使把 a,b 改成 double型,结果也还是错误的,
double型浮点数也只有 14 位有效数字。若把 a,b 改成 long
double型,便可得到正确的结果。
其实,long double型也只有 18位有效数字,因此需要有更多有效数字位的浮点数。为此,C99 中提供了 long long
double浮点数据类型( Turbo C 中允许这种类型,但实际使用中,它只等同于 long double型,并没有实际实现) 。
程序设计 建议,不要将一个很大的数与一个很小的数直接相加或相减,以免?丢失? 有效数字 。
94
该程序中将一个最大的 float型数 3.4e38f 乘上 100.0f,其结果显然超过了 float 型数的表示范围,这种情况的处理在较老的 C中没有规定,但现在的 C将为 toobig赋予一个代表无穷大的值。 printf 函数将此值显示为,+INF
四,浮点数的上溢与下溢
(1) 上溢 在浮点计算中,当计算结果是一个大得机器不能表达的数时,就会发生上溢。例如:
main ( )
{
float toobig = 3.4e38f * 100.0f ;
printf ( ―%e\n‖,toobig ) ;
}
程序的输出结果是,+INF ( infinity )
95
另一个特殊的浮点值称为 NaN (Not a Number),也是一种溢出值。例如,给 asin( )函数的参数大于 1,此时 asin( )函数返回 NaN。 不过 Turbo C中 给出的信息是,DOMAIN error
(2) 下溢有这样一个浮点数,它有最小的指数和最小的尾数。现在将此数除以 2,通常的做法是将指数减 1(在机器中指数和尾数都是二进制表示的),但是此时指数已经是最小值,所以只好将尾数部分右移一位,以达到操作要求,但这样就会丢掉最后一位上的二进制数字,损失了一位有效数字。如果除以足够大的数,将使得所有的位都是 0,此过程称之为下溢,
而将此值称为? 低于正常的值 ( subnormal) ‖。
96
3.5 字符型数据字符型数据指的是 C语言中表示字母、标点符号,特殊符号等之类的数据类型。
一、字符常量(字符直接量)
(1) 用一对单引号括住的一个字符。一对单引号仅作为界限符。例如:
‘ A‘?a‘?1‘?+‘
(2) 因单引号 (‘ ’ )和反斜杠 (\)在 C中有特别的用途 ( 如单引号用作为字符直接量界限符 ),所以它们不能直接括在单引号中作为字符常量使用。如果这样使用将出错。
若必须,则应使用 ‘ \\’ 和?\‘‘ 这种形式表示。
97
(3) 一个 字符常量用一个字节 ( 8位) 存储,存储的是字符的
ASCII编码。
例如字母 A 的 ASCII编码是十进制数 65,那么存储字符常量 ‘ A‘,实际上就是将 65放到分配的存储单元中。因此 ‘ A‘和 65是等同的,即可把 ‘ A‘看作 65,反之也可把 65看作 ‘ A‘,并且 ‘ A‘可以作为一个整数参与数值运算。 程序中任何使用整数的地方都可用对应的字符常量代替。
事实上 C中把字符数据按整型数据 (? 短整型? ) 处理。
若作为无符号数处理,数值表示范围为 0~ 255;若 作为有符号数处理,则数值表示范围为 -128~ 127 。
注意,由于不同的计算机可能使用不同的字符集,字符常量 ‘ A‘在某些机器上其值未必就是 65。 因此在编写可移植程序时应尽量使用字符常量,而不应该用具体的数值来代替字符常量。
98
(4) 转义字符(转义序列)
字符集中的字符分为两大类:
可显示字符 (可输入、显示,打印);
非打印字符 (控制字符、回车、换行等)。
可显示字符很容易写成字符直接量。例如:
‘ $’ ‘ &’ ‘ *’ ‘ #’ ‘ a‘?9‘?0‘
等。
而非打印字符不可辨认,无法写成字符直接量。但程序设计中要求使用这类字符的情况很多,例如:要求换行、退格、走纸等。为满足程序设计的需要,于是 C编译程序提供了表示这类字符的手段,转义序列 。 即以一个反斜杠开始,
后跟一个 约定的 字符或所要表示字符的八进制或十六进制编码。
99
例如,\n 表示换行,\r 表示回车,\f 表示换页等等。
使用时只要括上单引号,如,‘ \n‘。 下表列出了常用的转义序列。
100
其中的 \0表示字符 NULL,其 ASCII编码为 0。因此‘ \0’
与数值 0等价,在 C程序中经常用到。使用时应注意它与空格的区别,空格的编码是 32。
表中转义序列 \ddd 头和 \xhhh 是用字符的 ASCII编码来表示字符的专用方法。
其中的 ddd 表示 1~3位八进制数字,hhh 表示 1~3位十六进制数字,它们就是要表示的字符的编码。
例如:字母 A 的 ASCII编码是 65,对应的八进制数为
101,十六进制数为 41。则字母 A的字符常量可写成:
\101‘,?\x41‘
再如空格的编码是 32对应的八进制数为 40,十六进制数为 20。则空格字符的常量可写成:
‘ \40’,‘ \x20‘
101
使用转义序列时应注意:
a) 若在 \后给出的字符是非约定的字符,例如因没有 \k这样的约定转义序列,此时将忽略 \,因此 字符常量?\k‘ 等价于字符常量?k‘ 。
b) 在 \后可以跟 1~3位八进制数字或十六进制数字。但其值不能大于最大的字符编码。否则将出现
Numeric constant too large
的错误。
c) 连续两个 \ \表示反斜杠字符。
d) 单引号的转义序列必须写成:‘ \’ ’
102
二、字符变量用来存储一个字符的变量,对应字符常量。在内存中用一个字节( 8位二进制位 ) 存储字符的 ASCII 编码(而不是存储字符本身)。
字符变量说明的一般形式:
char 字符变量名 1,字符变量名 2,…,字符变量名 n ;
char前面可前缀 signed关键字 ( 指定按有符号整数处理,其表示范围 -128~ 127) 或 unsigned关键字 ( 指定按无符号整数处理,其表示范围 0~ 255) 。例如:
char c1,c2 ;
char a=?a‘,b=?b‘,c=c‘ ;
unsigned char d=255 ;
103
假定有 char ch ; 声明语句,它定义了字符变量 ch。
要往 ch中存储同一个字符有很多方法,例如要向 ch中存放字符 A,则可以:
ch=?A‘ ; /* 直接赋字符常量 */
ch=65 ; /* 赋字符 A的 ASCII编码 */
ch=?\101‘ ; /* 用字符 A的八进制换码序列 */
ch=?\x41‘ ; /* 用字符 A的十六进制换码序列 */
ch=?a‘-32 ; /*?a‘为 97减 32为 65 */
请思考,
若有赋值语句 ch=256; 则变量 ch中存储的值是什么? A:NULL
104
main ( )
{
char c1,c2;
c1=?a‘ ;
c2=?b‘ ;
c1= c1-32;
c2= c2-32 ;
printf( ―%c %c\n‖,c1,c2);
}
例 1 大小写字母的转换。
运行结果为,A B
注 1,‘ a‘的 ASCII码为 97,比‘ A‘大 32。
注 2,允许字符数据与整数直接进行算术运算,如例中的
c1= c1-32 与 c2= c2-32 的运算。
105
main ( )
{
int i ;
char c;
i=?a‘; /* 相当于把整数 97赋给 i */
c= 97; /* 相当于把字符?a‘赋给 c */
printf ( ―%c,%d\n‖,c,c ) ;
printf ( ―%c,%d\n‖,i,i ) ;
}
例 2 字符数据与整型数据互相赋值。
程序输出:
a,97
a,97
106
―C Language programming‖ /* 串中的空格有意义 */
―#12345‖
―a\\n‖ /* 其中 \\是反斜杠的转义序列,含 3个字符 a\n */
―‖ /* 串中无任何字符 */
\n\007 Pay to attention of this!‖ /* 含有转义序列 */
三、字符串常量
C语言允许使用由一串字符组成的直接量,这样的直接量称为 字符串直接量 。
(1) 字符串直接量由括在一对双引号中的 0 个或多个 除双引号、反斜杠 之外的字符或转义序列组成。例如:
(2) 因双引号用作字符串直接量的界限符,反斜杠用于换码序列,所以若字符串直接量中含有它们,则只能使用它们的换码序列。例如:
107
The dentist said,\‖ open wide! \‖ ‖
―The path is \\root\\usr\\device‖
(3) 特例,
\2389? /* 包含 3个字符,/* \23,8和 9 */。
\sr‖ /* 包含 2个字符,s和 r,\ 无效 */
―\t‖ /* 包含 1个字符,即一个 Tab字符,并非一串空格 */
(4) 字符串存储 当 C编译程序 扫描到一个字符串时,将其含有的字符的编码存储到一片连续的存储单元中,并在最后自动加上一个 NULL字符作为字符串的结束符。例如:
This is a string‖
在内存中的存储形式是:
108
a) \0 与 空格 字符不同。 编码不同,分别是 0 和 32 。
\0不可显示,不可输入;空格可显示、可输入。
b) 空字符 \0作为 字符串结束标志,是系统 自动 加上的,
虽占用一个字节存储单元,但不作为串的内容,不计入字符串长度。例如 strlen(―abcd‖) 的返回值是 4而不是 5。
c) 从键盘 输入 字符串时 不需加 ‘ \0’ (也无法输入 ),
直接输入字符串包含的内容,也不要输入引号。
d) 输出时仅作为 字符串结束的 识别标志,一旦遇到
\0,字符串 输出立即 结束 (后面有无字符不管! ),并不 输出 \0。
e) 以后会知道,程序中可向字符串中写入多个 \0。
(5) 关于空字符 \0 (NULL)
109
5) ‘ X‘ 与? X‖ 的区别
a) 书写形式不同,前者单引号作界限符,后者用双引号。
b)?x‘ 可作为整数参与数值运算;而? x‖ 不能 。
c)?x‘ 的值是 整型值 ; 而? x‖ 的 值是地址 (存储空间的起始地址)。因此,它是指针类型。
d)?x‘ 有对应的字符变量 ; 而? x‖ 没有对应的简单字符串变量,以后会知道,可以用字符数组或字符指针来实现这种对应。
e) 前者用一个字节存储,后者要用两个字节存储 。
110
6) 字符串的跨行书写在书写一个很长的字符串时,如果一行写不下,可以跨行书写。方法有两个:
方法一 在要求换行的位臵给出一个反斜杠,按回车后,后续部分从 下一行的开始位臵 书写。例如:
A string is a sequence of characters\
surrounded by double quotes‖
该方法的缺点是新的一行必须从下一行的开始书写,否则会把新行前面的空格也作为续行内容。
方法二 把一个长串 分成多个子串 书写。例如上例可写成:
111
A string is a sequence of characters‖
surrounded by‖
double quotes‖
A string is a sequence of characters surrounded bydouble quotes‖
其效果相当于书写如下一个字符串,
112
3.6 变量赋初值例如,int a = 3 ; 相当于 int a ; a = 3 ;
又如,int a,b,c = 5 ; 相当于 int a,b,c ; c = 5 ;
C允许在声明变量的同时使变量初始化。如:
int a,b = 3,d ; /* 对 一部分赋初值 */
float f = 3.56 ;
char c =?a‘ ;
全局变量和静态变量的初始化是在编译阶段完成的;而自动变量(动态变量)是在程序运行时执行本函数时赋予初值的,相当于执行了一个赋值语句。
再次强调,int a = b = c = 3; 这样 的初始化是错误的 !
113
3.7 各类数值型数据间的混合运算
1) 整型,实型,字符型数据之间可以混合运算。例如:
10 + ‘ b‘ + 1.5 - 8765.1234 *?b‘
在进行某种运算时,系统先 自动 把参与运算的两个同类型的数据 转换成同一类型,然后再进行计算。
转换规则 如下图所示:
114
2) 类型转换规则的说明
① 图中横向向左的箭头表示 必定 进行的转换;
② 纵向的箭头表示运算对象为不同类型时转换的方向;
③ 箭头方向只表示数据级别的高低,由低向高转换。纵向转换不是逐级进行,而是直接将低类型转成高类型。
运算结果总是为高级类型 ;
④ 类型转换是由 系统自动 进行的,只是产生一个临时的数据参与运算,并不改变操作数本身 ;
⑤ 两个操作数类型相同时,结果仍为该类型 。如,1/5
的值是 0。
115
3) 类型转换例:
例 1,假定有如下变量声明,
int i = 1 ; float f = 3.1 ;
double d = 2.71 ; long e = 100 ;
则表达式 10 +?a‘ + i * f – d / e 的运算次序为:
① 将 i 与 f 都转换成 double 型 ( f 为 float 型,所以 f 必定先转换成 double),进行 i*f 的运算,结果为 double型 ;
② 将 e 转换成 double 型,d/e 结果为 double型 ;
③ 将 ‘ a‘ 转换成 int型,其值为整数 97,计算 10+97为 107 ;
④ 再将整数 107转换成双精度数,与 i*f的积相加,结果为
double型 ;
⑤ 将 10+‘ a‘+i*f 的结果与 d/e的商相减,结果为 double型。
116
3.8 算术运算符和算术表达式
1,算术运算符 + - * / %
2,关系运算符 > >= < <= == !=
3,逻辑运算符 ! && | |
4,位运算符 << >> ~ | ∧ &
5,赋值运算符 = 及其组合赋值运算符
6,条件运算符?,
7,逗号运算符,
8,指针运算符 * &
9,求字节数运算符 sizeof
10,强制类型转换运算符 (类型 )
11,分量运算符 →
12,下标运算符 [ ]
一,C 运算符简介 (分类 )
117
- 单目减(反号)运算符。如 -3
+ 单目加运算符。如 +3
* 乘法运算符。如 3*5
/ 除法运算符。如 5/3
% 模运算符,或称求余运算符,%两侧均应为整型数据,如 7%4的值为 3
+ 双目加法运算符,如 3+5
- 双目减法运算符,如 5-2
二、算术运算符和算术表达式
(1) 基本的算术运算符
118
(2) 算术表达式用算术运算符和括号(小括号) 将运算对象(也称操作数,即常量、变量、函数等) 按 C 语言语法规则连接起来的计算式称为算术表达式。例如:
a * b/c - 1.5 +?a‘
单一的常量,变量和 函数调用是最 简单 表达式,如:
1.5,100,‘ A‘,sin(x),b
全由常量组成的表达式称为 常量 表达式 。 如:
1.5/3 +?a‘ * 2 -?\x41‘
C 语言的算术表达式类似于数学中的代数式。 除了乘号和除号的区别外,在书写和计算方面还有一些差别:
119
a) a*b 不能写成 a× b,ab,a · b
b) c/d 不能写成 c÷ d
c) e -( ( f + ( g - h ) / ( i – j ) ) + k ) / 2 不能写成:
e - { [ f + ( g – h ) / ( i - j) ] +k } / 2
因为 [ ] 和 { } 在 C语言中另作它用。
d) 3.14 *g*g 不能写成 π*g**2或 3.14*g^2 等。
f ) ln x 应写成 log(x),而 log10x 应写成 log10(x) 。
e) sin(x) 不能写成 sin x,其他三角函数也如此,并且其中的 x 是 双精度型数的弧度值 。
C语言中没有乘方运算符,要计算乘方可以采用连乘的办法。另可调用计算 xy的数学函数 pow ( x,y )。
120
注意,常用的数学库函数详见教材附录 Ⅴ,若程序中调用 了数学库函数,那么在源文件首部必须加上如下的 C预处理命令行:
#include < math.h >
或 #include ― math.h ‖
g) 因 – 号可作为单目运算,它可改变操作数的符号,得到相反数。 无符号数的相反数是 2n减去它的值 。 n 是无符号数存储空间的 bit数。例如,unsigned int a=30;
那么 –a相当于 (-1)*a,即计算 216–30 也即计算 65536-30
而得到无符号数 65506,它的机内表示是,
1111111111100010
这就是有符号数 -30。不难看出,求无符号的相反数只是简单地将机内表示的无符号数的各位取反后加 1。
121
h) 程序设计中,对两个操作数都是整数的除法运算情况应予注意。因运算结果仍为整型,所以 7/4的结果为 1,
而不是 1.75。 无论结果为正还是为负,总是简单的去掉小数部分。
i ) 每个表达式都有一个确定的值和类型,由参与运算的操作数和运算符决定。
例如:
‘ A‘ /* 其值为 65,类型由 操作数 决定,为 int */
‘ a‘+1.5 /* 其值为 98.5,类型由 操作数 决定,为 double */
(int)1,/* 其值为 1,类型由 运算符 决定,为 int */
122
(3) 算术运算符的优先级与结合性
a) 算术运算符的运算优先级与数学规则相同,先乘除后加减,有括号先算括号里面的。
b) 由 同优先级 运算符组成的表达式的求值按运算符和操作数出现的顺序,从左到右顺序结合处理 。 这种结合规则在 C语言中称之为? 左结合?,即运算符优先与左边的运算对象结合 如对于表达式 a-b+c 先做 a-b 然后再做加 c 。
c) 由于 + 和 - 允许单目运算(只有一个操作数)它们作为单目运算时在算术运算符中优先级最高,且结合方向从右往左。 这种结合规则在 C语言中称之为? 右结合?,即运算符优先与右边的运算对象结合。 如表达式 c+ -b 在 C中是允许的(连续两个运算符)。 先计算 –b,然后 c 再与 –b
的结果相加。
123
再如表达式 –a/-b 当然也是允许的。 先计算 –a,然后再计算 –b,最后再计算 –a 除以 –b 。 能很好地说明右结合性的例子是赋值表达式 x=y=3 的计算过程:第二个 =
先与右边的 3结合完成 y=3运算;然后第一个 =再 y=3的结果结合 完成 x=(y=3)运算。
d) C语言没有规定像 * 和 + 这样满足结合律和交换律运算符组成的表达式的求值顺序。对它们的求值,C总是按 C认为最有效的方式处理。如对 a+b+c可能按 a+(b+c)来处理,甚至像 a+(b+c) 这样指定了求值顺序的表达式,也可能按 (a+b)+c 来进行计算。这样做是在不影响计算结果的前提下进行的。因此,要强调按某种顺序求值,程序中应采用显式的临时变量的方法处理之。如对于 a+(b+c) 可先计算 b+c 放入一个 临时变量中,temp=b+c ; 然后再计算
a+temp 。
124
(4) 取余(模)运算符取余(模)运算符 % 用来取整数除法的余数。
a) % 的两个操作数都必须是整数(或字符型数据),若非整数,C语言编译 不会自动转换,将作为非法操作数出错处理 。必要时可用下节将要讨论的强制类型转换运算符对其进行转换处理。
b) 余数与被除数有相同的符号 。例如,若整型变量 a的值为 -30,b的值为 -7,则 a%b 的值为 -2,而非 2,因为被除数为负数。
c) 取余运算符的优先级与 * 和 / 运算符相同,结合性也相同,从左往右。
d) % 运算应用例。 把秒数转换成分钟的程序
125
#include <stdio.h>
main ( )
{
int sec,min,left ;
printf (―Enter the numer of seconds:‖ ) ;
scanf ( ―%d‖,&sec ) ;
min = sec/60 ;
left = sec%60 ;
printf(―%d seconds is %d minutes,%d seconds.\n‖,sec,min,left);
}
程序运行及输出:
Enter the numer of seconds:234 /*这是输入的秒数 */
234 seconds is 3 minutes,54 seconds,/*这是输出的结果 */
126
(5) 自增、自减运算符自增运算符 ++ 和自减运算符 --是一种特殊且很有用的单目算术运算,其功能是将 变量 的值增一( ++)或减一( - -)。
a) 它们的操作数只能是具有确定存储空间的对象(即可获得其地址的对象,常称为 左值对象,(例如变量),而 不能用于常量或表达式计算 。 例如,5++,(a+b)++ 都是错误的。
b) 只有一个操作数。
c) 参加运算的操作数既可位于运算符的前面,也可位于运算符的后面。例如,++a a++ --a a-- 。 但其计算顺序与效果不同。
127
若 ++/ -- 运算符 位于操作数之前,则在求整个表达式的值之前 先将其操作数的值增 1/减 1,然后将变值后操作数再参与所在表达式的运算;
若 ++/ --运算符 位于操作数之后,那么 先用操作数的当前值 参与所在表达式的运算; 待整个表达式运算完成后再将其操作数的值增 1或减 1。
例如:假定 int 变量 n 的值为 5,那么执行 x = n++ ;
其操作顺序是:
先将 n → x,然后再计算 n+1 → n
结果,x 的值是 5,n 的值是 6
而若执行 x = ++n ; 其操作顺序是:
先计算 n+1 → n,然后再将 n → x
结果,x 的值是 6,n 的值是 6
128
上面的例子比较简单,下面是一个较为复杂的例子。
假定 int 变量 a,b 的当前值分别为 1 和 2 。那么对于:
r = a++ + a++ * b ;
计算过程:
a+a*b → r ( 即 1+1*2→r )
a+1 → a ( 即 1+1→ a,第一个 a++ )
a+1 → a ( 即 2+1→a),第二个 a++ )
计算结果,r 为 3,a 为 3,b 为 2(不变)
而若执行,r = ++a + ++ a * b ;
计算过程:
a+1 → a ( 即 1+1→ a,第一个 ++a )
a+1 → a ( 即 2+1→a,第二个 ++a )
a+a*b → r ( 即 3+3*2→r )
计算结果,r 为 9,a 为 3,b 为 2(不变)
129
注意,上面的例子若按浮点数运算,计算过程不同,
结果也不同。 假定 a,b 为 float 型,当前值分别为 1 和 2 。
则下列语句,
r = a++ + a++ * b ;
的运算结果,r 为 4,a 为 3
其计算过程是:
第一步 先计算完 a++ * b,得到具体的值参与以后的运算,后面不再计算与使用其中的 a++
a*b ( 即 1*2,得 2 ),
a+1 → a ( 1+1→a )
第二步 计算 a++ + 2 → r (即 2+2→r )
a+1 → a ( 即 2+1→a )
130
同理,若 a,b 为 float 型,当前值分别为 1 和 2,则,
r = ++a + ++ a * b ;
的运算结果,r 为 7,a 为 3
其计算过程是:
第一步 先计算完 ++a * b,得到具体的值参与以后的运算,
后面不再计算与使用其中的 ++a 。
a+1 → a ( 即 1+1→a ) /* 因 ++在前,先算 */
a*b ( 即 2*2,得 4 )
第二步 计算 ++a + 4
a+1 → a ( 即 2+1→a,得 3 )
a+4 → r ( 即 3+4→r )
131
a++b 理解为 a ++ b (错误)
a+++b 理解为 a++ +b (正确 )
a++++b 理解为 a++ ++b (错误 )
a+++++b 理解为 a++ ++ +b (错误 )
++a+++b 理解为 ( (++a) ++ ) + b (错误,非左值 )
d) ++ 和 -- 也是单目运算符,与作单目运算符的 +和 -具有相同的优先级和结合性( 右结合 )。
例如,-a++ 的运算顺序为 a++,-a 而不是 ( -a)++ 。
后者显然不对,因为 –a 是表达式非左值对象 (非变量 ) 。
特别像 a+++b 这种模棱两可的处理顺序,C 编译程序总是将其理解为 ( a++)+b,而不是 a+(++ b) 。
如果表达式中有多个连续的 + 号,编译程序总是首先识别 ++ 。例如:
132
例,
若有变量声明,int a=1,b=0 ; 则:
(a) b = -a++ ;
正确,-1 → b (++ 优先于 -)
(b) b = (-a)++;
错误 ( (-a) 是表达式,非左值对象 )
(c) b = -++a; (++优先于 -)
正确,-2 → b
(d) b = ++-a;
错误( -a是表达式,非左值对象,-优先于 ++)
(e) b = --++a;
错误( ++a是表达式,非左值对象)
133
(6) 强制类型转换运算符在某些运算(如取余运算 %) 的计算过程中,如果不进行自动类型转换,可以利用强制类型转换运算符将一个表达式的值转换成所需的类型。
a) 强制类型转换运算语法:
(类型关键字)(表达式)
该运算的作用是将右边括号中的表达式值的类型转换成左边括号中指定的类型。例如:
(double) a 将 a 的值 转换成 double 型
(int) (x+y) 将 x+y 的值转换成 int 型
(float) (5%3) 将 5%3 的值转换成 float 型
134
b) 当被转换的 表达式 是单一的常量、变量名、函数调用时,两边可以不加括号。否则必须加上括号!例如:
( int ) x+y
其实际的效果是:只将 x转换成整型,然后与 y相加。
c) 强制类型转换时,得到一个所需类型的中间结果值参与运算,并不改变原来变量的类型 。
例如:设 x 为 float型,对其实施强制类型转换运算:
( int ) x /* 不能写成 int ( x ) */
后,得到一个临时的 int型的中间量,它的值等于 x 的整数部分,而 x的类型不变,仍为 float型。
d) 强制类型转换运算是单目运算,优先级与结合性与
++,--,单目运算符 - 等相同。
135
e) 转换时由于截断等处理,所以转换的结果可能与原来的值有差别。
例如:
main ( )
{
float x,y ; int k ;
x=3.6 ; y = k = (int) x ;
printf ( ―x=%f,k=%d,y=%f\n‖,x,k,y ) ;
}
运行结果为,x=3.600000,k=3,y=3.000000
x的类型仍为 float型,值仍等于 3.6 。
f ) 相容数据类型之间才可转换。
136
3.9 赋值运算符和赋值表达式所谓赋值是指把一个数据存放到指定的对象 ( 如变量名、数组元素名等)中去的过程。
(1) 赋值运算表达式的 一般形式
v = e
其中,= 是 赋值运算符读作? 赋值? 。
e 是表达式,可以是任何表达式。
v 称之为左值对象,通常为变量名。
而全部 v=e 则称为 赋值表达式。
例如,a=100 读为? 把 100赋给变量 a ‖,其动作是 100→ a 。 不应读成? a等于 100? 。赋值号两边的类型可以不同,但必须相容 。
137
(2) 赋值表达式的 执行过程
① 计算出右边表达式 e的值;
② 把求得的 e的值转换成左边变量 v的类型,转换动作是自动完成的
③ 将经类型转换后的值存到左部变量 V中去。
例如对于 表达式,a=a+1
在数学上永远不会成立,但在 C语言中是一个正确的 赋值表达式。它 执行过程是,取出变量 a中的内容,加上 1,
再存入变量 a中去。
138
(3) 赋值表达式的值和类型既然 v=e 本身是一个表达式,那么它就有值和类型。
它的值就是最后 赋给变量 v的值,它的类型就是 变量 v的类型 。 因此,它可以作为运算分量参与某个表达式的运算,
这是 C语言的特别之处。例如:
程序的输出 a=100,b=30,c=-20 这个程序等价下列程序:
main ( )
{
int a,b,c ;
c = ( a = 100 ) + ( b=30 ) - b*5 ;
printf ( ―a=%d,b=%d,c=%d\n‖,a,b,c ) ;
}
139
main ( )
{
int a,b,c ;
a = 100 ;
b = 30 ;
c = a+b-b*5 ;
printf ( ―a=%d,b=%d,c=%d\n‖,a,b,c ) ;
}
程序的输出是:
a=100,b=30,c=-20
140
因为赋值运算符的优先级很低,当把赋值表达式当作另一个表达式的一个运算分量参与计算时应该加上括号否则将出现错误 。比如上例,若去掉括号将变成:
c = a = 100 + b = 30 – b * 5 ;
这样一来,存在两个问题:
第一 该表达式中 *运算符的优先级最高,所以先算乘法 b*5,但此时的 b 是无定义的,直接使用其值参与运算是错误的。
第二 假如先前 b已经有值,则可计算 30-b*5,然后将求值结果赋给赋值号 = (第三个赋值号 ) 的左边,但该赋值号的左边不是一个变量名,而是一个表达式 100+b,
这是非左值对象,所以出现赋值错误。
141
(4) 嵌套的赋值运算
C语言允许将一个数值连续赋给多个不同的对象,即允许如下的赋值表达式形式:
e1 = e2 = e3 = e4 = …… = E
这种赋值形式称为嵌套的赋值运算。
其中的 e1,e2,e3,e4,…… 不必具有相同的数据类型,但必须都是左值对象 (变量名)。 如果它们都具有相同的数据类型,运算的结果使得 e1,e2,e3,e4,…… 都具有 E的值,否则未必。
例如:
a = b = c = d =100
这样的嵌套赋值表达式的执行等价于:
a = ( b= ( c = ( d = 100 ) ) )
142
(5) 复合赋值运算对于形如 a=a+b 这样的赋值运算,其执行过程是分别取出变量 a,b的内容,使两数相加并把和存入变量 a 。
为简化书写、提高编译效率,C语言允许将其写成如下形式:
a += b
这种在赋值运算符前面加上另一个运算符所构成的运算符称之为 复合赋值运算符,它实现了一种新的运算功能。
如 += 这个运算符能同时完成加法和赋值运算。
C 语言允许将所有的 算术运算符 和 位逻辑运算符 与 赋值运算符 组合成复合赋值运算符。如算术复合赋值运算符有:
+= -= *= /= %=
143
复合赋值运算符都是二目的,由它们组成的表达式的处理过程是,先取出第一操作数;再算出复合赋值运算符右边的第二操作数;然后把得到的两个值按前面的运算符进行运算;最后将运算的结果赋给第一操作数。
显然,第一操作数必须为左值对象(变量),第二操作数可以是任何表达式 。例如:
a %= b+c/d
其中的 a 必须是变量,而 b+c/d 可以是一般表达式。它等价于:
a = a%( b+c/d )
无论复合赋值运算符右边的操作数如何复杂,只要将其作为一个整体运算分量看待,就较容易分析和理解它的运算顺序。例如,
144
其中 a+=b*=(++b - c++) 的计算顺序是,
步 1 计算 ++b - c++ 即 b+1→ b,b-c (4-2得 2)
步 2 计算 b *= 2 即 b*2→ b (4*2→ b)
步 3 计算 a += b 即 a+b→ a (2+8→ a)
步 4 计算 c++ 即 c+1→ c (3→ c)
main ( )
{
int a=2,b=3,c=2 ;
a+=b*=(++b - c++) ;
printf ( ―a=%d,b=%d,c=%d\n‖,a,b,c ) ;
}
程序的输出,a=10,b=8,c=3
145
为什么要使用复合赋值运算符? 既不直观又易搞错,其实使用它至少有两方面的好处,
首先,能简化程序的书写 。例如像,
x[ y[ p3+p4 ] + y[ p1+p2 ] ]+=2
这样的表达式,若不使用 复合赋值运算符 +=,则必须写成,
x[ y[p3+p4]+y[p1+p2] ]=x[ y[p3+p4]+y[p1+p2] ]+2
显然,前者简洁的多。
其次,有助于提高运行效率 。这从 a[i]+=2与 a[i]=a[i]+2
的计算过程就可看出来。前者的 a[i] 仅被计算一次,而后者的 a[i]则要计算两次,况且数组下标的计算是很花时间的。
复合赋值运算符 的优先级与结合性 与赋值运算符相同,
除高于将要介绍的逗号运算符外,比所有其他运算符的优先级都低,结合方向从右往左( 右结合 )。
146
(7) 赋值运算中的算术数据类型转换如果赋值号两边的操作数都是算术型操作数,那么在赋值表达式的求值过程中有两种不同阶段的数据类型转换,
其一是赋值号右边的表达式求值过程中的数据类型转换 (表达式求值类型转换 ),转换规则前面已经介绍了;
其二是当右边的表达式求值完毕,但其类型与赋值号左边的左值对象的类型不一致时的类型转换(称之为 赋值转换 )。 赋值转换规则很简单,总是把赋值号右边的表达式的结果类型转换成赋值号左边的左值对象的类型 。
赋值转换规则的具体处理有下面几种情况:
147
char→int
将字符数据( 8位)放到 int整型变量的低 8位中。
有两种情况:
1) 当所用系统将 char 型数据当作无符号数或被转换数据本身被定义为 unsigned char 型变量时,则将 8位字符数据放到 int 的低 8位,高 8位补零;
2) 当所用系统 (如 Turbo C)将字符处理为 signed char时,
进行? 符号扩展? 。即若被转换数据的最高位为 0,则
int的 高 8位补 0 ; 若被转换数据的最高位为 1,则 int 的 高
8位全补 1。
int→ char
总是把 int型数据的高位部分截去,只留下低 8位作为转换的结果。
148
进行? 符号扩展?,高位补上足够的符号位的值。例如:
int a=-2;
long b;
b=a;
……
有符号短整型 → 有符号长整型
149
signed char a;
int b= - 526;
a=b;
……
有符号长整型 → 有符号短整型简单地把多余的高位部分截去。留下与短整型相同长度的低位部分的位数。例如:
150
int a= -1 ;
unsigned int b ;
b=a ;
……
有符号整型 → 无符号整型这种转换分两种情况:
长度相同 —— 不发生任何位模式的变化,按存储 形式原样赋值。例如:
151
—— 对于长度不同的转换又分为两种情况,长度不同有符号 长 整型 → 无符号 短 整型按前面已介绍的 有符号长整型 → 有符号短整型 规则转换; (简单地把多余的高位部分截去 !)
有符号 短 整型 → 无符号 长 整型按前面已介绍的 有符号短整型 → 有符号长整型 规则转换。 (符号扩展 )
152
unsigned char a=255 ;
int b ;
b=a ;
……
无符号整型 → 有符号整型这种转换分两种情况:
长度相同 —— 不发生任何位模式的变化,按存形式原样赋值。
长度不同 —— 对于长度不同的转换又分为两种情况,
无 符号 短 整型 → 有符号 长将高位部分补 0 。例如:
153
unsigned int a=0xfdf2 ; /* -526 */
signed char b ;
b=a ;
……
无符号 长 整型 → 有符号 短 整型仅取低位部分,截去多余的高位部分。例如:
154
float a =,123 ;
double b ;
b=a ;
……
float → double
将尾数部分的有效数字扩展到 18位,形成 64位的 double
型数据。(注意:未必在后面添 0),
例如:
0.12300000339746475200
则 b的值为,
155
double a = 0.123123648 ;
float b ;
b=a ;
……
取 6位有效数字,从第 7位有效数字四舍五入。例如:
double→float
系统将输出,Floating point error,Overflow.
float a ;
double b=8.6e42 ;
a=b ; /* 转换结果超过 1038 */
……
注意,如果 double型数据太大,用 float型数据无法表达时,将产生浮点数溢出错误。例如:
则 b的值为,0.123124
156
整型数 → 浮点数数 的 值不变,以浮点数形式(右补 0)存入变量中。
浮点数 → 整型数去掉 浮点数 的小数部分,仅取整数部分。如果整数部分太大,将自动进行溢出处理 。但这里溢出处理比较简单,仅仅按,溢出结果 = 溢出数 %2n ( n 是整型数的存储位数 )
例如:
程序的输出将是,a=3
main ( )
{
int a ;
float b=65539.123 ;
a=b ;
printf ( ―a=%d\n‖,a) ;
}
157
3.10 逗号运算符和逗号表达式
C语言允许用逗号?,? 将两个表达式连接起来,指出两个表达式的运算顺序,所以逗号?,?运算符被称为
顺序求值运算符? 。而用逗号运算符将两个表达式连接起来构成的计算式称为逗号表达式。
使用逗号运算的目的主要是希望在同一个表达式计算中分别得到多个表达式的值。
(a) 逗号表达式的一般形式:
表达式 1,表达式 2
例如:
3+5,6+8
158
(b) 逗号表达式的求值过程先求表达式 1,再求表达式 2 。 最后求解的表达式的值就是整个逗号表达式的值。
例如,
3+5,6+8
该逗号表达式的值为 14
(c) 逗号运算符的优先级结合性逗号运算符的优先级最低,左结合性。
例如:
a=3,a=3*5,a*4
该逗号表达式的值为 60
159
main ( )
{
int a ;
( a = 3*5,a*4 ),a+5 ;
printf ( ―a=%d\n‖,a ) ;
}
先计算 ( a=3*5,a*4) 的值为 60,a的值为 15;再计算
a+5得 20,整个表达式的值即为 20 。
(d) 嵌套的逗号表达式一个逗号表达式又可以与另一个表达式组成一个新的逗号表达式。
例如:
160
main ( )
{
int a ;
a =( 3*5,a*4),a+5 ;
printf ( ―a=%d\n‖,a ) ;
}
但若把程序写成,
是错误的。因为做 a*4 时,此时的 a 无定义。若 a 有定义,
则将 a*4的结果送入 a( 而不是 a+5 !),因赋值运算的优先级高于逗号运算。
161
%d,%d,%d ‖
( a=1,b=2,c=a+b)
b
c
(e) 注意 1
int a,b,c ;
再如作为函数参数分隔符的逗号也不是逗号运算符。
printf (? %d,%d,%d‖,(a=1,b=2,c=a+b),b,c ) ;
但 (a=1,b=2,c=a+b) 中的逗号是逗号运算符,它是一个逗号表达式,它的值等于 c的值。
并非程序中任何地方出现的逗号都是逗号运算符。例如变量声明中的逗号就不是逗号运算符。
调用该函数给出了四个参数。即,
162
逗号运算符是 序列点运算符。
所谓序列点 ( sequence point ),即在表达式的求值过程中的某一点,在该点前面的运算 (包括副作用 ),在进入下一步前都已被计算,这样的计算点便称之为序列点。
因此逗号运算符保证由它分开的表达式按从左到右的顺序计算。换句话说,逗号运算符左边所有可能产生的副作用都在计算到逗号右边之前产生(或都已计算过)。例如:
a++,b=a*c ;
其中,按正常计算 a*c 时,应取 a 的当前值乘以 c,整个表达式做完后再使 a增 1。 但因其前面的逗号运算符是序列点,按序列点处理规则,虽然 ++在变量 a的后面,但还是先算好 a++,这样计算 a*c时使用的是 a的新值 。
(f) 注意 2
163
比较下面的两条语句:
price = 123,456 ;
price = (123,456) ;
它们的效果是不同的:
前者把 123赋给 price,整个表达式的值是逗号右边的表达式的值,即 456;
后者把 456赋给 price,整个表达式的值是 price的值,
也为 456。
而语句 a = ( b = 3,(c = ++b + 2 ) + 5 ) ; 的计算过程是:
3 → b
b+1 → b
b+2 → c
c+5 → a
计算结果是,a为 11,b 为 4,c 为 6