一 操作符
(1) 算数操作符: + - × / %

(1)除了%操作符,其余几个操作符都是既适用于浮点类型又适用于整数类型。
(2)当 / 操作符的两个操作数都是整数时,它执行整除运算,在其他情况下则执行浮点数除法.
(3)%为取模操作符,它接受两个整型操作数,把左操作数除以右操作数,但它返回的值是余数,不是商。

(2)移位操作符 : >> <<

移位操作只是简单地把一个值的位向左或向右移动。在左移位中,值最左边的几位被丢弃,右边多出来的几个空位则由0补齐。下图是一个左移位的例子,它在一个8位的值上进行左移3位的操作, 以二进制形式显示。这个值所有的位均向左移3个位置,移出左边界的那几个位丢失,
右边空出来的几个位则用0补齐。
右移位操作存在一个左移位操作不曾面临的问题:从左边移入新位时,可以选择两种方案。一种是逻辑移位,左边移入的位用0填充; 另一种是算术移位, 左边移入的位由原先该值的符号位决定,符号位为1则移入的位均为1,符号位为0则移入的位均为0,这样能够保持原数的正负形式不变。如果值10010110右移两位,逻辑移位的结果是00100101, 但算术移位的结果11100101。算术左移和逻辑左移是相同的,它们只在右移时不同,而且只有当操作数是负值时才不一样。

左移位操作符为<<,右移位操作符为>>。左操作数的值将移动由右操作数指定的位数。两个操作数都必须是整型类型。

警告:(1) 标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底 是采用逻辑移位还是算术移位取决于编译器。你可以编写一个简单的测试程序,看看你的编译器使用哪种移位方式。但你的测试并不能保证其他的编译器也会使用同样的方式。因此,一个程序如果使用了有符号数的右移位操作, 它就是不可移植的。
(2)注意类似这种形式的移位:
a<<-5
左移-5位表示什么呢?是表示右移5位吗?还是根本不移位?在某台机器上,这个表达式实际执行左移27位的操作一你怎么也想不出来吧!如果移位的位数比操作数的位数还要多,会发生什么情况呢?
标准说明这类移位的行为是未定义的,所以它是由编译器决定的。然而,很少有编译器设计者会清楚地说明如果发生这种情况将会怎样, 所以它的结果很可能没有什么意义。因此,你应该避免使用这种类型的移位,因为它们的效果是不可预测的,使用这类移位的程序是不可移植的。

(3) 位操作符 : & | ^

 位操作符对它们的操作数的各个位执行AND、OR和XOR (异或)等逻辑操作。当两个位进行
 AND操作时,  如果两个位都是1,结果为1,  否则结果为0。当两个位进行OR操作时,如果
 两个位都是0,  结果为0,否则结果为1。最后,  当两个位进行XOR操作时,如果两个位不
 同,结果为1,  如果两个位相同,  结果为0。这些操作以图表的形式总结如下。

(4)赋值: =
赋值操作符 它用一个等号表示。赋值是表达式的一种,而不是某种类型的语句。所以,只要是允许出现表达式的地方,都允许进行赋值。下面的语句
x = y + 3;
包含两个操作符,+和=。 首先进行加法运算,所以=的操作数是变量x和表达式y+3的值。赋值操作符把右操作数的值存储于左操作数指定的位置。但赋值也是个表达式,表达式就具有一个值。赋值表达式的值就是左操作数的新值,它可以作为其他赋值操作符的操作数,如下面的语句所示:
a=x=y+3;
赋值操作符的结合性(求值的顺序)是从右到左,所以这个表达式相当于:
a=(x=y+3);
它的意思和下面的语句组合完全相同:
x=y+3;
下面是一个稍微复杂一-些的例子。
r=s+(t=u-v)/3;
这条语句把表达式u–v的值赋值给t,然后把t的值除以3,再把除法的结果和s相加,其结果再赋值给r。尽管这种方法也是合法的,但改写成下面这种形式也具有同样的效果。
t = u - v;
r = s + t / 3;
事实上,后面这种写法更好一些,因为它们更易于阅读和调试。人们在编写内嵌赋值操作的表达式时很容易走极端,写出难于阅读的表达式。因此,在你使用这个“特性”之前, 确信这种写法能带来一些实实在在的好处。

警告:在下面的语句中,认为a和x被赋予相同的值的说法是不正确的:
a = x = y +3;
如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符类型的变量中。那么a所赋的值就是这个被截短后的值。在下面这个常见的错误中,这种截短正是问题的根源所在:
char ch;
while( ( ch = getchar() ) != EOF ),
EOF需要的位数比字符型值所能提供的位数要多,这也是getchar ,返回一个整型值而不是字符值的原因。然而,把getchar的返回值首先存储于ch中将导致它被截短。然后这个被截短的值被提升为整型并与EOF进行比较。当这段存在错误的代码在使用有符号字符集的机器上运行时,如果读取了一个值为\377的字节时,循环将会终止,因为这个值截短再提升之后与EOF相等。当这段代码
在使用无符号字符集的机器上运行时这个循环将永远不会终止。

( 5 ) 复合赋值符: += -= *= /= %= <<= >>= &= |= ^=

我们只讨论+=操作符,因为其余操作符与它非常相似,只是各自使用的操作符不同而已。+=操作符的用法如下:
a += 1 它读作“把1加到a”,它的功能相当于下面的表达式:
a =a+( 1 )
唯一的不同之处是+=操作符的左操作数(此例为a)只求值一次。注意括号:它们确保表达式在执行加法运算前已被完整求值,即使它内部包含有优先级低于加法运算的操作符。
存在两种增加一个变K&R C法有何意义呢? C语言设计者认为复合赋值符可以让程序员把代码写得更清楚一些。另外,编译器可以产生更为紧凑的代码。现在,a =a+5和a+=5之间的差别不再那么显著,而且现代的编译器为这两种表达式产生优化代码并无多大问题。但请考虑下面两条语句,如果函数f没有副作用,它们是等同的。
a [ 2 * ( y - 6 * f ( x } ) ] = a [ 2 * ( y - 6 * f ( x ) ) ] + 1;
a [ 2 * ( y - 6 * f ( X ) ) ] + = 1;
在第1种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值 号的左边,另一次在赋值号的右边。由于编译器无从知道函数f是否具有副作用,所以它必须两次计算下标表达式的值。第2种形式效率更高,因为下标只计算一次。

提示: +=操作符更重要的优点是它使源代码更容易阅读和书写。读者如果想判断.上例第1条语句的功能,他必须仔细检查这两个下标表达式,证实它们的确相同,然后还必须检查函数f是否具有副作用。但第2条语句则不存在这样的问题。而且它在书写方面也比第1条语句更方便,出现打字错误的可能性也小得多。基于这些理由,你应该尽量使用复合赋值符。

(6)单目操作符: ! ++ - & ~ – * sizeof
! 操作符对它的操作数执行逻辑反操作;如果操作数为真,其结果为假,如果操作数为假,其结果为真。和关系操作符一样,这个操作符实际上产生一个整型结果,0或1。
~操作符对整型类型的操作数进行求补操作,操作数中所有原先为1的位变为0,所有原先为0的位变为1。
-操作符产生操作数的负值。
+操作符产生操作数的值;换句话说,它什么也不干。之所以提供这个操作符,是为了与-操作符组成对称的一对。
&操作符产生它的操作数的地址。例如,下面的语句声明了-一个整型变量和一一个指向整型变量的指针。接着,&操作符取变量a的地址,并把它赋值给指针变量。
int a, *b;
……
b = &a;
这个例子说明了你如何把一一个现有变量的地址赋值给一个指针变量。
* 操作符是间接访问操作符,它与指针一起使用,用于访问指针所指向的值。在前面例子中的赋值操作完成之后,表达式b的值是变量a的地址,但表达式*b的值则是变量a的值。
sizeof操作符判断它的操作数的类型长度,以字节为单位表示。操作数既可以是个表达式(常
常是单个变量),也可以是两边加上括号的类型名。这里有两个例子:
sizeof ( int) sizeof x
第1个表达式返回整型变量的字节数,其结果自然取决于你所使用的环境。第2个表达式返回
变量x所占据的字节数。注意,从定义上说,字符变量的长度为1个字节。当sizeof的操作数是个
数组名时,它返回该数组的长度,以字节为单位。在表达式的操作数两边加上括号也是合法的,
如下所示:
sizeof(x )
这是因为括号在表达式中总是合法的。’判断表达式的长度并不需要对表达式进行求值,所以sizeof(a=b+ 1)并没有向a赋任何值。
(类型)操作符被称为强制类型转换(cast),它用于显式地把表达式的值转换为另外的类型。例如,为了获得整型变量a对应的浮点数值,你可以这样写
(float)a
强制类型转换这个名字很容易记忆,它具有很高的优先级,所以把强制类型转换放在一个表达式前面只会改变表达式的第1个项目的类型。如果要对整个表达式的结果进行强制类型转换, 你必须把整个表达式用括号括起来。
最后我们讨论增值操作符++和减值操作符–。如果说有哪种操作符能够捕捉到C编程的“感觉”,它必然是这两个操作符之一。这两个操作符都有两个变型,分别为前缀形式和后缀形式。两个操作符的任一变种都需要一个变量而不是表达式作为它的操作数。实际上,这个限制并非那么严格。这个操作符实际只要求操作数必须是一个“左值”,但目前我们还没有讨论这个话题。这个限制要求++或–操作符只能作用于可以位于赋值符号左边的表达式。
前缀形式的++操作符出现在操作数的前面。操作数的值被增加,而表达式的值就是操作数增加后的值。后缀形式的++操作符出现在操作数的后面。操作数的值仍被增加,但表达式的值是操作数增加前的值。如果你考虑一下操作符的位置,这个规则很容易记住一在操作数之前的操作符在变量值被使用之前增加它的值;在操作数之后的操作符在变量值被使用之后才增加它的值。–操作符的工作原理与此相同, 只是它所执行的是减值操作而不是增值操作。
这里有一些例子:

上面的注释描述了这些操作符的结果,但并不说明这些结果是如何获得的。抽象地说, 前缀和后缀形式的增值操作符都复制一份变量值的拷贝。用于周围表达式的值正是这份拷贝(在上面的例子中,“周围表示式”是指赋值操作)。前缀操作符在进行复制之前增加变量的值,后缀操作符在进行复制之后才增加变量的值。这些操作符的结果不是被它们所修改的变量,而是变量值的拷贝,认识这一点非常重要。它之所以重要是因为它解释了你为什么不能像下面这样使用这些操作符:
++a = 10;
++a的结果是a值的拷贝,并不是变量本身,你无法向一个值进行赋值。

(7)关系操作符 : >= > < =< == !=

前4个操作符的功能一看便知。!=操作符用于测试“不相等”,而== 操作符用于测试“相等”。尽管关系操作符所实现的功能和你预想的一样,但它们实现功能的方式则和你预想的稍有不同。这些操作符产生的结果都是一个整型值,而不是布尔值。如果两端的操作数符合操作符指定的关系,表达式的结果是1,如果不符合,表达式的结果是0。关系操作符的结果是整型值,所以它可以赋值给整型变量,但通常它们用于if或while语句中,作为测值表达式。请记住这些语句的工作方式:表达式的结果如果是0,它被认为是假;表达式的结果如果是任何非零值,它被认为是真。所有关系操作符的工作原理相同,如果操作符两端的操作数不符合它指定的关系,表达式的结果为0。因此,单纯从功能上说,我们并不需要额外的布尔型数据类型。

警告:
如果说下面这个错误不是C程序员新手最常见的错误,那么它至少也是最令人恼火的错误。绝大多数其他语言使用 = 操作符来比较相等性。在C中,你必须使用双等于号==来执行这个比较,单个=号用于赋值操作。
这里的陷阱在于:在测试相等性的地方出现赋值符是合法的,它并非是一个语法错误”。这个不幸的特点正是C不具备布尔类型的不利之处。这两个表达式都是合法的整型表达式,所以它们在这个,上下文环境中都是合法的。

(8)逻辑操作符 : && ||

逻辑操作符有&&和||。这两个操作符看上去有点像位操作符,但它们的具体操作却大相径庭它
们用于对表达式求值,测试它们的值是真还是假。让我们先看一下 &&操作符。
expression1 && expression2
如果expressionl 和expression2的值都是真的,那么整个表达式的值也是真的。如果两个表达式中的任何一个表达式的值为假,那么整个表达式的值便为假。到目前为止,一切都很正常。
这个操作符存在一个有趣之处,就是它会控制子表达式求值的顺序。例如,下面这个表达式:
a > 5 && a < 10
&&操作符的优先级比>和<操作符的优先级都要低,所以子表达式是按照下面这种方式进行组合的:
( a > 5 ) && ( a < 10 )
但是,尽管&&操作符的优先级较低,但它仍然会对两个关系表达式施加控制。下面是它的工作原理: &&操作符的左操作数总是首先进行求值, 如果它的值为真,然后就紧接着对右操作数进行求值。如果左操作数的值为假,那么右操作数便不再进行求值,因为整个表达式的值肯定是假的,右操作数的值已无关紧要。|操作符也具有相同的特点,它首先对左操作数进行求值,如果它的值是真,右操作数便不再求值, 因为整个表达式的值此时已经确定。这个行为常常被称为“短路求值(short-circuited evaluation)”。

警告 :
位操作符常常与逻辑操作符混淆,但它们是不可互换的。它们之间的第1个区别是|和&&操作符具有短路性质,如果表达式的值根据左操作数便可决定,它就不再对右操作数进行求值。与之相反,|和&操作符两边的操作数都需要进行求值。
其次,逻辑操作符用于测试零值和非零值,而位操作符用于比较它们的操作数中对应的位。这里有一个例子if(a < b && c > d ) if ( a < b & c > d )
因为关系操作符产生的或者是0,或者是1,所以这两条语句的结果是一样的。但是,如果a是1而b是2,下一对语句就不会产生相同的结果。
if ( a && b )… if ( a & b )
因为a和b都是非零值,所以第1条语句的值为真,但第2条语句的值却是假,因为在a和b的位模式中,没有一个位在两者中的值都是1。

(9)条件操作符:
条件操作符接受三个操作数。它也会控制子表达式的求值顺序。下面是它的用法:
expression1 ? expression2 : express ion3
条件操作符的优先级非常低,所以它的各个操作数即使不加括号,一般也不会有问题。但是,为了清楚起见,人们还是倾向于在它的各个子表达式两端加上括号。

首先计算的是expression1, 如果它的值为真(非零值),那么整个表达式的值就是expression2的值, expression3不会进行求值。但是,如果expression1的值是假(零值), 那么整个条件语句的值就是expression3的值,expression2 不会进行求值。
如果你觉得记住条件操作符的工作过程有点困难,你可以试一~试以问题的形式对它进行解读。例如,
a > 5 ? b - 6 : c / 2
可以读作“a 是不是大于5?如果是,就执行b-6,否则执行c/2”。.语言设计者选择问号符来表示条件操作符决非一时心血来潮。

提示:
什么时候要用到条件操作符?这里有两个程序片段:

这两段代码实现的功能都相同,但是左边的代码需要写两次“b = ”,当然,这也没什么大不了,在这种场合使用条件操作符没什么优势,但是,请看下代码片

在这里,长长的下标表达式需要写两次,确实令人讨厌。如果使用条件操作符,看上去就清楚得多:
b [ 2 * c + d ( e / 5 ) ] = a > 5 ?3 : - 20 ;
在这个例子里,使用条件操作符就相当不错, 因为它的好处显而易见。在此例中,使用条件操作符出现打字错误的可能性也比前一种写法要低, 而且条件操作符可能会产生较小的目标代码。当
你习惯了条件操作符之后,你会像理解if语句那样轻松看懂这类语句,

(10)逗号操作符:
提起逗号操作符, 你可能都有点听腻了。但在有些场合,它确实相当有用。它的用法如下:
expressionI, expression2, . .,,expressionN
逗号操作符将两个或多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值。例如:
if ( b + 1 , c / 2 , d > 0 )
如果d的值大于0, 那么整个表达式的值就为真。当然,没有人会这样编写代码,因为对前两个表达式的求值毫无意义,它们的值只是被简单地丢弃。但是,请看下面的代码:

a = get_ value();
count_ value(a);
while (a > 0)
{
    ......
    a = get_ value();
    count__ valuel(a);
}

在这个while循环的前面,有两条独立的语句,它们用于获得在循环表示式中进行测试的值。这样,在循环开始之前和循环体的最后必须各有一份这两条语句的拷贝。但可以把这个循环改写为

whilea = get_ value(),count_ value(a),a > 0)

{
      ......
}

也可以使用内嵌的形式,如下:

while(count_ value(a = get_ value()),a > 0)

{
      ......
}

提示:
现在,循环中用于获得下一个值的语句只需要出现一次。逗号操作符使源程序更易于维护。如果用于获得下一个值的方法在将来需要改变,那么代码中只有一个地方需要修改。
但是,面对这个优点,我们很容易表现过头。所以在使用逗号操作符之前,你要问问自己它能不能让程序在某方面表现更出色。如果答案是否定的,你就不要使用它。顺便说一- 下, “更出色”并不包括“更炫”、“更酷” 或“令人印象更深刻”。
这里有一个技巧,你偶尔可能会看到:
while( x< 10 )
b += x,
x +=1;
在这个例子中,逗号操作符把两条赋值语句整合成一条语句,从而避免了在它们的两端加上花括号。不过,这并不是个好做法,因为逗号和分号的区别过于细微,人们很难注意到第1个赋值后面是一个逗号而不是个分号。

(11) 函数调用,下标引用和结构成员:
下标引用操作符是一对方括号。下标引用操作符接受两个操作数: 一个数组名和一个索引值。事实上,下标引用并不仅限于数组名。C的下标引用与其他语言的下标引用很相似,不过它们的实现方式稍有不同。C的下标值总是从零开始, 并且不会对下标值进行有效性检查。除了优先级不同之外,下标引用操作和间接访问表达式是等价的。这里是它们的映像关系:
array[下标
]
*{ array+(下标))
下标引用实际上是以后面这种形式实现的,当你越来越频繁地使用指针时,认识这一.点将会越来越重要。
函数调用操作符接受一个或多个操作数。它的第1个操作数是你希望调用的函数名,剩余的操作数就是传递给函数的参数。把函数调用以操作符的方式实现意味着“表达式”可以代替“常量”作为函数名,事实也确实如此。
. 和 -> 操作符用于访问一个结构的成员。如果s是个结构变量,那么s.a就访问s中名叫a的成员。当你拥有一个指向结构的指针而不是结构本身,且欲访问它的成员时,就需要使用->操作符而不是.
操作符。
(12) 表达式求值:
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
(13)隐式类型转换:
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,
和短整型操作数在使用之前被转换为普通整型,这种转换称为「整型提升」

//实例1
char a, b, c;
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后, 结果将被截断, 然后再存储于a中。

//实例2
a = (~a ^ b >> 1) << 1;

对于实例1的结果和使用8位算术运算的结果是一样的,但是实例2就不一样了。
由于存在求补和左移操作,所以8位的精度是不够的。标准要求进行完整的整型求值,所以对于这类表达式的结果, 不会存在歧义。

(14)算数转换:
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。

long double
long 
float
unsigned long int 
long int 
unsigned int 
int 

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一一个操作数的类型后执行运算。
警告:
但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失