C操作符(上)

C提供了所有你希望编程语言应该拥有的操作符,它甚至提供了一些你意想不到的操作符,事实上,C被人所诟病的一个缺点就是它品种繁多的操作符。C的栅格特点使它很难被精通。另一方面,C的许多操作符具有其它语言操作符无可抗衡的价值,这也是C适用于开发范围极广的运用程序的原因之一。

一、操作符

为了便于解释,我们对操作符进行了分类。

1.算术操作符

    +    -   *   /   %

    除了%操作符,其它几个都是既适用于浮点又适用于整型的操作符。当'/'操作符两个操作数都是整型时,它将执行整除运算(例如,5/2结果是2),其它情况下将执行浮点数除法。'%'为取模操作,它接受两个整型操作数,返回的是余数(5%2结果是1)。

2.移位操作符

常使用汇编的人对于移位操作肯定是非常熟悉。这里对移位操作作简单的介绍。移位操作只是简单地把一个值的位向左或向右移动。在左移位中,值最左边的几位被丢弃,右边多出来的几个空位则由0补齐。下图是一个左移的例子。

它在一个8位的值上进行左移3位的操作,以二进制显示。

右移操作存在一个左移不曾面临的问题:从左边移入新位时,可以选择两种方案。一种是逻辑移位,左边移入的位用0填充;另一种是算术移位,左边移入的位由原先该值的符号决定,符号位为1则移入的位均为1,符号位为0则移入的位均为0,这样能够保证原数的正负性不变。

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

3.位操作符

位操作符对它们的操作数各个位执行&(and,与)、|(OR,或)和^(XOR,异或)等逻辑操作。同样,这类操作也是汇编中常用的,为了照顾初学者,这边作简单介绍。当两个位进行&操作时,如果两个位都是1,结果为1,否则为0。执行|操作时,如果两个位都位0,结果为0,否则为1。当执行^操作时,如果两个位不同,结果为1,否则为0,这些操作以图表的形式如下,

4.赋值操作符

赋值操作符用一个等号表示。赋值是表达式的一种,所以只要是允许出现表达式的地方,都允许进行赋值,下面语句:

x = y + 3;

包含两个操作符,'+'和'='。首先进行加法运算,所以'='的操作数是变量x和表达式y+3的值。赋值操作符把右操作数的值存储于左操作数指定的位置。但赋值也赋值本身也是个表达式,表达式就具有一个值。赋值表达式的值就是左操作数的新值,它可以作为其它赋值操作符的操作数,如下语句所示:

a = x = y + 3;

赋值操作符的结合性(求值的顺序)是从右到左,所以这个表达式相当于:

a = ( x = y + 3);

它的意思也和下面语句相同:

x = y + 3;

a = x;

下面是一个稍复杂的例子:

r = s + (t = u - v) / 3;

这条语句把表达式u-v的值赋给t,然后把t的值除以3,再把除法的结果和s相加,其结果再赋给r。尽管这种方法也是合法的,但下面的这种写法更好一些。

t = u - v;

r = s * t / 3;

事实上,我们几乎看不到前面的写法,因为后面的方法更易于阅读和调试。人们在编写内嵌赋值操作的表达式时很容易走极端,写出难以阅读的表达式。因此,在你使用这个特性前,你得确信这种难懂的写法能带来实在的好处(否则,毕竟代码的阅读者通常是人,写的更易懂易维护才是合理的)。

5.复合赋值符

到目前为止介绍的操作符都还有一种复合赋值的形式:

+=   -=   *=   /=   %=

<<=   >>=   &=   ^=   |=

我们只讨论+=操作符,因为其余操作符与它非常相似,只是各自使用的操作符不同而已。+=操作的用法如下:

a += expression;

它读作“把expression加到a”,它的功能相当于下面的表达式:

a = a + (expression);

唯一的不同之处是+=操作符的左操作数(这里就是变量a)只求值一次。注意括号,它能确保表达式在执行加法运算前已被完整求值,即使它内部包含有优先级低于加法运算的操作符。

那么,存在两种增加一个变量值的方法有什么意义呢?

K&R C的设计者认为这个复合赋值符可以让程序员代码写得更清楚一点。另外,编译器可以产生更为紧凑的代码。现在,a = a + 5和 a += 5之间的差别不那么显著,现代的编译器为这两种表达式产生优化代码并无多大问题。但请考虑下面两条语句,如果f(x)无副作用,它们是等同的:

a[2 * (y - 6 * f(x))] = a[2 * (y - 6 * f(x))] + 1;

a[2 * (y - 6 * f(x))] += 1;

在第一种形式中,用于增值的表达式书写了两次。由于编译器无法知道f(x)是否具有副作用(副作用可以理解为通过全局、成员、静态变量等,可能对预期的结果产生影响),所以它必须两次计算数组加标表达式的值。第二种形式效率更高,因为下标只计算一次。

6.单目操作符

C具有单目操作符,也就是只接受一个操作数的操作符。它们是:

!   ++   -   &   sizeof

~   --   +   *   (类型)

我们逐个介绍这些操作符。

!操作符对它的操作数进行逻辑反操作;如果操作数为真,其结果为假,若操作数为假,结果为真。这个操作符实际上产生一个整型结果0或1。

~操作符对整型类型的操作数进行求补操作,操作数中所有原先为1的位变为0,为0的变为1。

-操作符,产生操作数的负值。

+操作符,产生操作数的值,换句话说,它什么也不干。

&操作符,产生它的操作数的地址,此时读作取地址符。例如,下面的语句声明了一个整型变量和一个指向整型变量的指针。接着,&操作符取变量a的地址,并把它赋给指针变量。

int a, *b;

...

b = &a;

这个例子说明了你如何把一个现有变量的地址赋给一个指针变量。

*操作符,是间接访问操作符,它与指针一起使用,用于访问指针所指向的值。在前面例子中的赋值操作完成之后,表达式b的值是变量a的地址,但表达式*b的值则是变量a的值。

sizeof操作符判断它的操作数的类型长度(sizeof是C中唯一一个以单词形式出现的操作符,并且它还是关键字),以字节为单位表示。操作数既可以是个表达式(常常是单个变量),也可以是两边加上括号的类型名。这里有两个例子:

sizeof(int )    sizeof(x)

第一个表达式返回类型变量的字节数,其结果自然取决于你的使用环境。第二个表达式返回变量x所占据的字节数。注意,从定义上来说,字符变量的长度为一个字节。当sizeof的操作数是个数组名时,它返回数组的长度,以字节为单位。在表达式的操作数两边加上括号也是合法的。这是因为括号在表达式中总是合法的。判断表达式的长度并不需要对表达式进行求值,所以sizeof(a = b + 1)并没有向a赋值。

(类型)操作符被称为强制类型转换(cast),它用于显式地把表达式的值转换为另外的类型。例如,为了获得整型变量a对应的浮点数值,你可以这样写,

(float) a;

强制类型转换这个名字很容易记住,它具有很高的优先级,所以把强制类型转换放在一个表达式前面只会改变表达式第一个项的类型,如果要对整个表达式的结果进行强制转换,你必须把整个表达式用括号括起来。

最后我们讨论一波增值操作符++和减值操作符--。这两个操作符都有两个变型,分别是前缀形式和后缀形式。两个操作符的任一变种都需要一个变量而不是表达式作为它的操作数。实际上,这个限制并非那么严格。这个操作符实际只要求操作数必须是一个“左值”,但目前我们还没有讨论这个话题。这个限制要求++或--操作符只能作用于可以位于赋值符号左边的表达式。

前缀形式的++操作符出现在操作数的前面。操作数的值被增加,而表达式的值就是操作数增加后的值。后缀形式的++操作符出现在操作数的后面,操作数的值仍被增加,但表达式的值是操作数增加前的值。如果你考虑一下操作符的位置,这个规则很容易记住----在操作数之前的操作符在变量被使用之前增加它的值;在操作数之后的操作符在变量值被使用之后才增加它的值。--操作符工作原理与之相同。

举个栗子,

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

++a = 10;

++a的结果是a值的拷贝,并不是变量本身,你无法向一个拷贝值进行赋值。

7.关系操作符

这类操作符用于测试操作数之间的各种关系。C提供了所有常见的关系操作符。不过,这组操作符里面存着一个陷阱。它们是:

>    >=    <    <=    !=   ==

前四个操作符的功能一看便知。!=操作符用于测试“不相等≠”,而==操作符用于测试“相等”。

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

C用整数来表示布尔型值,这直接产生了些简写方法,它们在表达式测值中极为常用。

if(expression != 0) ...

if (expression) ...

if(expression == 0) ...

if (!expression) ...

在每对语句中,两条语句功能是相同的。测试“不等于0”既可以用关系操作符来实现,也可以简单地通过测试表达式的值来完成。类似,测试“等于0”也可以通过测试表达式的值,然后再取结果值的逻辑反实现。你喜欢使用哪种形式纯属风格问题,但在使用最后一种形式时必须多加小心。由于!操作符的优先级很高,所以如果表达式内包含了其他操作符,你最好把表达式放在一对括号内(根据个人的了解,还是推荐第一种写法,新的C/C++标准已经对第二种写法有所区分和限制)。

8.逻辑操作符

逻辑操作符有&&和||。这两个操作符看上去有点像位操作符,但它们的具体操作缺大相径庭----它们用于对表达式求值,测试它们的值是真是假。我们看下&&操作符,

expression1 && expression2

如果expression1和expression2的值都是真的,那么整个表达式的值也是真的。若两个表达式中任一一个表达式值为假,那么整个表达式值便为假。

这个操作符有一个有趣之处,就是它会控制子表达式求值的顺序,例如,下面这个表达式:

a > b && a < 20

&&操作符的优先级比>和<操作符的优先级都要低,所以表达式是按照下面这种方式组合的:

(a > b) && (a < 20)

但是,尽管&&操作符的优先级比较低,但它仍然会对两个关系表达式施加控制。下面是它的工作原理:&&左操作数总是首先进行求值,若为真,然后对右操作数求值。若为假,那么右操作数不再进行求值,因为整个表达式的值肯定是假,右操作数的值已无关紧要。||也具有相同特点。这个行为常常被称为“短路求值(short-circuited evaluation)”。

9.条件操作符

条件操作符(有时也称三目运算符)接受三个操作数,它也会控制子表达式的求值顺序。它的用法:

expression1 ? expression2 : expression3

条件操作符的优先级非常低,所以它的各个操作数即使不加括号,一般也不会有问题。但是,为了清楚起见,还是倾向于在它的子表达式上加上括号。

首先计算expression1,若它的值为真,那么整个表达式的值就是expression2,expression3就不会求值。但是,若expression1的值为假,那么整个条件语句的值就是expression3的值,expression2不会进行求值。

如果你觉得记住条件操作符有点困难,你可以试试以问题形式对它进行解读,如,

a > 5 ? b - 6 : c / 2

可以读作“a是不是大于5?”,如果是,就执行b-6,否则执行c/2。语言设计者选择问号符表示条件操作符绝非心血来潮。

10.逗号运算符

提起逗号运算符,你可能听腻了。它的用法如下,

expression1, expression2......expressionN

逗号操作符将两个或多个表达式分隔开。这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值。如:

if ( b * 1, c / 2, d > 0 )

如果d的值大于0,那么整个表达式的值就为真。当然,没有人会这样写代码。因为对前两个表达式的求值毫无意义,它们的值只是被简单地丢弃。

11.下标引用、函数调用和结构成员

剩余的操作符,我会另起文详细讨论。

发布了60 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/101196539