1
一、运算符与表达式概述二、左值和右值三、运算符的优先级与结合性四、算术运算表达式五、混合运算时数据的类型转换六、赋值表达式和混合赋值
2
一、运算符与表达式概述表达式由操作数和运算符构成,操作数描述数据的状态信息,运算符限定了建立在操作数之上的动作。简单的运算符表达式对应着汇编语言中的一条指令。
在表达式后加一个分号,;“就构成表达式语句,
用于表达式的求值计算。例如 x=y 是赋值表达式,
x=y;是赋值表达式语句。
3
运算符主要有下面几类,
1,后置运算符 () [ ] ++ --
2,前置运算符 new delete sizeof ++ -- ! ~ - * &
3,算术运算符 * / % + -
4,位运算符 ~ << >> & ^ |
5,关系运算符 < > >= <= == !=
6,逻辑运算符 ! && ||
7,条件运算符?,
8,赋值运算符 = += -= *= /= %= >>= <<= &= ^= |=
9,逗号运算符,
4
其中脚标表示具体优先级别,未注明脚标的运算符排在前的一般拥有较高的优先级别。字符 @不属于 C/C++的字符集,字符 @来代替上面各种具体的运算符,作为运算符的抽象。对于双目运算符,其使用形式为:
左操作数 运算符 右操作数
leftOperand @ rightOperand
例如,4*5.2,6>3.1,x||y,x+= y;
其中 *,>,||,+=是双目运算符,而 4,6,x视为左操作数,5.2,3.1,y
视为右操作数。
5
对于单目运算符,其使用形式为:
运算符 操作数 @operand
例如,++x,~1,&x,!y;
表达式是操作数和运算符构成的算式,其结果是具有确定类型的数据值。
运算是分层次来进行的,由此分成源操作数和目标操作数,目标操作数可以作为下一个回合的源操作数。例如 !y中的是 y源操作数,运算的结果是目标操作数。对于赋值表达式存在两种等价的称谓:
左操作数 =右操作数 目标操作数 =源操作数
6
二、左值和右值表达式有两个特点,一个特点是表达式具有一定的数值,另一个特点是表达式具有类型属性。 表达式的类型属性用于指出其占有的内存大小。
根据操作数运算的来源与结果是否与内存相关,表达式分为左值和右值。
左值是对应内存数据的一个表达式,最常见的左值是变量。左值表达式最终必须通过变量定义语句分配内存。
允许刷新的数据区称为左值数据区。左值可多次出现在赋值运算符的左侧,右值不行。
7
右值出现在赋值运算符的右侧,仅含右值的操作数不改变内存的数据状态,右值不一定具有存储地址,而左值则拥有存储地址。左值可作为右值。
常见的右值为常数,例如数组名 a可作为右值不能作为左值,而数组元素 a[i]是左值表达式。变量的引用是左值。
但 const限定的名称是右值。
例如,对于 {int x,&y=x; const int c=1,&d=c;},x,y
是左值,c,d是右值。
8
算术、逻辑和关系运算符不要求左值操作数,计算的结果为右值。系统在编译阶段检查运算符与各操作数之间左值右值的匹配性。 例如:
double v=2.0; const double e=1.0;
表达式 v--,v=e*e,v+=e,(v+=e)- =1是正确的,而
++e,e= v,e-=v,e*v=4.0,(v+e)- =1等是错误的。
9
三、运算符的优先级与结合性运算符的优先级和结合性用于确定连接在一起的复杂表达式先后求值的次序性。
1,结合性 (associativity)
运算符结合性是指相同级别的运算符相关联的表达式各操作数之间的运算结合次序。例如算术运算加 +减 -具有相同的级别,下面的表达式如何计算?
1-2+3 或 i-j+k
C/C++规定加减乘除算术运算的结合性是从左到右,于是运算的次序进一步确定为:
(1-2)+3 或 (i-j)+k
10
因此 (1-2)+3的结果为 2而不是 1-(2+3)=-4。 i-j+k 确定为
(i-j)+k,而不是 i-(j+k)。同理 1/2*3处理为 (1/2)*3,结果是 0。
单目运算符和三目运算符是右结合性的。双目运算符除直接赋值和复合赋值运算符是右结合的以外,其余都是左结合的。
运算符结合性用于确定同一级别相关操作数绑定的紧密程度。
11
2,优先级 (precedence)
将复杂的表达式分解为简单的表达式过程中,运算符的优先级用于判定各表达式的先后执行次序。运算符的优先级越高,相关联的表达式就越先组合在一起完成求值计算。
圆括号 ( )运算符具有最高的优先级,如果圆括号是多层嵌套的,首先计算最内层配对的圆括号中的表达式。对于优先级相同的表达式序列,表达式求值的次序取决于同级操作符的结合性;左结合的操作符各表达式总体上从左到右依次求值,右结合的则相反。
12
不再内嵌其它表达式的操作数称为基本表达式。常见的基本表达式是常数、变量名、数组名和函数名。另一个值得关注的基本表达式是圆括号括起来的表达式如 (e),圆括号用于提高其中表达式 e的优先级。
例如,对于 (y/x)* (x*3.1),(y/x)就视为基本表达式,
(x*3.1)也作为基本表达式。
对于形如 (e1)@(e2)的表达式 C/C++语言一般并未规定基本表达式 e1和 e2的求值次序,即编译器可以先对 e2例如
(x*3.1)完成初始化计算,特别地当 e2对应函数调用时。
13
对于表达式:
r=1-4*3/2+6/3
根据运算符的优先级也就是数学上先乘除后加减的运算规则和结合性系统处理为:
r=((1-((4*3)/2))+(6/3))
这样 r的值可以无歧义地确定为 (1-6)+2=-3。
14
四、算术运算表达式算术运算就是加减乘除运算以及求余数运算,算术运算符是左结合的双目运算符,左操作数与右操作数的类型可以不一致,运算时执行常用的算术类型转换,运算的结果是右值。它们的格式为:
乘法运算,e1*e2 2*3=6,3*1.1=3.3
除法运算,e1/e2 6/5的结果是 1,2/4的结果是 0,
2.0/4的结果是 0.5。
求余运算,n% m m%(m+1)=m,(m+1)%m=1,
n=k*m+j,则 n%m的值为 j;
求余运算求两个整数相除的余数,求余数运算两个操作数必须是整型。
15
对于正整数之间的关系式 n=k*m+j,则 n%m的值为 j,其中 j为小于 m的整数。求余运算也称取模运算。
加法运算,e1+e2?减法运算,e1-e2
减法运算,e1-e2
加减法运算中的表达式有两种类型的操作数:一种是算术类型,算术类型的操作数得到的是两个操作数的和或差。
另一种是指针类型,对于 e1-e2,如果 e1为指针,则 e2为整型或者都为指针表达式。 e1+e2仅其中之一为指针表达式,另一个为整型表达式。
运算过程存在溢出,溢出是运算的结果超出该数据能表示的范围。
16
[例 ] 整型数据的溢出和格式转换函数
#include <stdio.h>
void main(void)
{ unsigned char k=(char)0xff;
signed char n=(char)0xff; //输出
printf ("%d,[%d,%x]\n",k,k+1,k+1);
printf ("%d,[%d,%x]\n",n,n+1,n+1); //-1,[0,0]
}
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 00x100'\xff' 1 1 1 1 1 1 1 1
17
五、混合运算时数据的类型转换
1,强制类型转换强制类型转换又称为显式类型转换,类型转换不管是隐含的还是显式的都具有强制的性质。 强制类型转换有两种形式,一种是 C++新增的函数表达式的形式:
类型名 (表达式 ) type(expression)
另一种是 C语言中采用已久的类型转换形式:
(类型名)表达式 (type)(expression)
两种形式在基本数据类型的情形具有相同的结果,但函数表达式的形式不适宜于指针和引用的强制类型转换。
18
强制类型转换的作用就是将表达式的类型转换为类型名指定的数据类型。 例如:
float x=5.9;
int k=int(x)+(int)x;
(int)x+x
(int)(x+x)
(int)6.5%4
对于简单的变量类型转换时推荐采用传统的 C语言形式。值得指出强制类型转换中出现在表达式中变量的值在转换中并没有发生变化。
19
2,常用的算术转换当两个操作数类型不相同时,常用的算术转换应用于双目运算符 (主要是加减乘除算术运算符 ) 其目的是产生一个公共的类型,它也是双目运算表达式结果的类型。
为了确定实际发生的转换,不妨简单地认为编译器初步根据变量类型的字节长度 1,2,4和 8或 10把算术类型数据从低到高分为多个级别,应用于表达式中的双目操作。
20
1,long double if某操作数为 long double,
另一操作数变为 longdouble.
2,double else if某操作数为 double,
另一操作数变为 double.
3,float else if某操作数为 float,另一操作数变为 float
4,unsigned long else if某操作数为 unsigned long,
另一操作数为 unsigned long
4,long else if某操作数为 long,另一操作数变为 long
5,unsigned int else if某操作数为 unsigned int,
另一操作数变为 unsigned int
5,int else 两个操作数变为 int。
6,unsigned short short
7,unsigned char char signed char
各种算术类型数据的顺序低高
21
在混合类型的表达式中,转换是自动将数据向较高级别类型转换的过程。双目操作数中,低类型的数据直接转换为高类型的数据。无符号整数的类型高于有符号的整数类型。
int数据类型以下的运算转换为 int类型处理。如 char和
short类型的运算都转换为 int数据类型。
例如,sizeof(‘c’/‘d’)= sizeof(int),sizeof(‘\\’/(short)1)=4。
long型数据与 double型数据运算,先将 long型数据转换为 double型,结果为 double型;一个 float型数据与一个
char型数据运算,直接将 char型数据转换成 float型数据,
结果为 float型数据。
即,表达式 11.0f*‘x’ 11.0f*(float)'x'。
22
浮点协处理器的缺省运算是 double 类型,这意味两个
float数的运算可优先转换为 double 类型,结果的类型可以是 float类型。如 sizeof(1.0f/2.0f)=4 而 sizeof(1.0/2.0f)=8。
例如,sizeof(1L/2.0f)= sizeof(float)=4是 vc6.0的结果。
当 long double型变量占 8个字节的内存时其数据类型的精度同 double。这是微软编译器的约定。
char,short和 long在早期的 C语言中都归属于整型,
关键字 int充当了整型变量的沟通联系的桥梁作用。脱胎于 C
语言的 C++依然保留了这一历史的痕迹。在 16位编程模式下
int占 2个字节的内存,32位编程模式下 int占 4个字节的内存。
23
3,隐含类型转换在运算符两边数据类型不同时,系统自动进行隐含类型转换。隐含类型转换的规则如下:
1,转换是按照运算表达式的优先级别分步进行的,优先级高的优先完成转换。
2,在同一级别表达式中,操作数先直接转换为数据 类型保值精度更强的形式。
3,源操作数的数据类型最终转换为目标的数据类型。
4,关系和逻辑运算结果为 0或 1,0,1的目标类型在 C中为 int型,C++为 bool型。
24
数据占有的内存字节数制约数据转换精度的得失。通常数据类型占有的字节多,表示的数据范围越大。数据类型表示的数据范围越大,其保值精度越强,相应地数据类型的级别高,操作数的数据总是从低类型的数据直接转换为高类型的数据。
整型、实型和字符型数据允许进行混合运算。
例如:
3+'c'+'z'*10f-5/2.0
等价于
int float
double
int
float
double
double
float
(( 3 + 'c' ) + (' z ' *10f ) ) - ( 5 /2.0 )
25
根据优先级和结合性,上面复杂表达式的运算在幕后可以分解为如下各种次序:
1,先乘除后加减,从左到右运算。
(编译器可以先算 e2=5/2.0,后算 e1='z'*10f)
float e1='z'*10f ;
double e2=5/2.0 ;
register int e3= 3+'c';
float e4=e3+e1;
double e5= e4 - e2;
26
2,对圆括号的基本表达式从左到右运算。 (各种分解都导致 e5= e4 - e2)
register int e3= 3+'c';
float e1='z'*10f ;
double e2=5/2.0 ;
float e4 = e3+ e1;
double e5= e4 - e2;
上面的两种分解得到同样的结果 e5=1319.50000。这也就是原表达式的结果。
下面运算说明优先级高的先完成数据的转换,
printf("%d,%d,%f\n",int(2.5)*4+5/4,int(2.5*4+5/4),2.5*4+5/4.0);
//输出结果,9,11,11.250000
27
六、赋值表达式和混合赋值其结果为左值表达式,结果的类型是左操作数的类型,其一般形式为:
变量 =运算表达式 lvalue=expre
赋值运算符左边的操作数必须是左值,右操作数可以是单一的常数、变量和非 void类型的函数调用。赋值运算符 =
是先右结合的,即 lvalue=expre理解为 lvalue=(expre),程序先取出右操作数的值,然后把右操作数的值送入左操作数对应的内存单元。数学公式 x=x+1是无意义的,但却是合法的表达式。其含义为先求出表达式 x+1的值,再将该值赋给 x。
28
在一个赋值表达式中,可以包含另一个表达式,其运算顺序是从右向左进行,例如:
i=j=k=l= 0; //右结合性等价于 i=(j=(k=(l=0)));
编译器分解为,l=0; k=l; j=k; i=j;
赋值运算符两边数据类型相同时赋值运算是精确的二进制数据拷贝。在运算符两边的数据类型不同时,系统自动进行类型转换。
类型转换的规则是源操作数的数据类型转换为目标操作数的数据类型。这里,赋值运算符的隐含转换规则是右边的数据类型转换为左边的类型。类型转换的规则如下:
29
1.同等长度的整型数据赋值或转换。
同长度类型数据有符号数和无符号数之间数据的赋值是精确的,左操作数具有与右操作数相同的内存状态。但对于数据的解释上存在差别,有符号整数其最高位用于鉴别其正负,最高位为 0表示该数为正,最高位为 1表示该数为负。而无符号数的最高位本身作为数据的一部分。负数以补码原码转换即间接求补显示。
signed1 short i='\xfe';
unsigned short k=i; 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
30
[例 ] 同长度整型数的赋值操作
#include<stdio.h>
void main (void) //输出
{ signed short i='\xfe';
printf ("%4hx,%d\n",i,i); //fffe,-2
unsigned short k=i ;
printf ("%4hx,%d\n",k,k); //fffe,65534
}
格式 %4hx表示以 4字符宽度输出 16进制的短整型的内存数据状态。
31
2.向长字节扩展。
整型数据可以转换为更长的有符号或无符号整数,这种转换称为向长字节扩展。无符号数扩展时以 0 进行扩展位的填充,简称 0 扩充。 这样立即数‘ \x7f’扩展为 0x0000007f,带符号数扩展时最高位为 1 时 (可强制 ) 以 1 进行扩展位的填充,
最高位为 0 时以 0 进行扩展位的填充,称为符号扩充。但简写的 int 立即数 0x9f 等价于 0x0000009f。
1 0 0 1 1 1 1 1
1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1
0 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1
'\x7f'(0扩展 ) '\x9f' (1扩展 )
32
[例 ] 短整型数向长整型数的扩展
#include <stdio.h>
void main (void)
{ short s= (signed char) 0x7f;
printf ("%08x,%d\n",s,s); //输出 0000007f,127
int k= 0x9f;
printf ("%08x,%d\n",k,k); //输出 0000009f,159
int i=(signed char) 0x9f;
printf ("%x,%d\n",i,i); //输出 ffffff9f,-97
}
33
3,向低字节截断转换整型数据可以转换为更短的有符号或无符号整数,这种转换称为向低字节转换。
长字节的整型数据赋给短字节的整型变量时,数据从低位到高位以字节为单位输送。
4 字节的 long整型转换为 2字节的 short整型,只将低 16
的数据转送。
2字节的 short整型赋给为 1字节的 char型变量,只将低 8
的数据转送。
如果源数据的高 16位或高 8位非 0,高位多余的字节对应的数据被截断,此时导致数据损失。
34
[例 ] 长整型数向短整型数的截断赋值
#include <stdio.h>
void main (void)
{ long n= 0xfedc8f63;
short s=n;
printf ("%08x,%d\n",s,s);
unsigned short u=n;
printf ("%08x,%d\n",u,u);
short t=0x8f63;
printf ("%08x,%d\n",t,t);
char c=s;
printf ("%c,%c,%x \n",'c',c,c);
}
35
n=0xfedc8f63
s=n,s=0x8f63
c=s,c=0x63 01 1 0 0 0 1 1
1 0 0 0 11 1 1 0 1 1 0 0 0 1 1
1 0 0 0 1 1 1 1 0 1 1 0 0 0 1
11 1 1 1 1 1 1 0 1 1 0 1 1 1 0 04字节高位
2字节
1字节低位
36
4.浮点和整型的转换当浮点类型的一个数据转换为一个整型变量时,小数部分截除,截除意味着一个像 1.53这样的数转换为 1,而 -1.37转换为 -1。
浮点数是有符号的,浮点和整型的相互转换时一般通过有符号的 long型数据作为中转站;同占 4个字节的 long和
float数据内存的存储格式不同,因此数据相互转换可存在精度损失。
例如,double转换为 unsigned short,首先转换 double
为 long型数据,long型数据再根据上面向低字节截断转换的规则转换为 unsigned short型数据。
37
少于 4字节的整型变量转换为 float 型的变量,数值一般不变,但内存的存储格式变动为浮点形式,4 个字节的 long
型变量赋给 double型的变量也能保持转换的原数据精度。
一个浮点类型数据可以转换为一个低精度的浮点类型,
转换中发生四舍五入。一个浮点类型的数据可以安全地转换为一个高精确的浮点类型,也就是该转换不造成损失。
例如从 float到 double或从 double到 long double的转换是安全的,而且值没有变化。 例如:
printf ("%g,%g\n",(float)8e40,8e40
double型数据 8e40超出 float型的数据范围,因此类型转换 (float)8e40导致溢出。
38