C用运算符(operator)表示算术运算。例如,+运算符使在它两侧的值加在一起。如果你觉得术语“运算符”很奇怪,那么请记住东西总得有个名称。与其叫“那些东西”或“运算处理符”,还不如叫“运算符”。现在,我们介绍一下用于基本算术运算的运算符:=、+、-、*和/(C没有指数运算符。不过,C的标准数学库提供了一个pow()函数用于指数运算。例如,pow(3.5, 2.2)返回3.5的2.2次幂)。

5.2.1 赋值运算符:=

在C语言中,=并不意味着“相等”,而是一个赋值运算符。下面的赋值表达式语句:

bmw = 2002;

把值2002赋给变量bmw。也就是说,=号左侧是一个变量名,右侧是赋给该变量的值。符号=被称为赋值运算符。另外,上面的语句不读作“bmw等于2002”,而读作“把值2002赋给变量bmw”。赋值行为从右往左进行。

也许变量名和变量值的区别看上去微乎其微,但是,考虑下面这条常用的语句:

i = i + 1;

对数学而言,这完全行不通。如果给一个有限的数加上1,它不可能“等于”原来的数。但是,在计算机赋值表达式语句中,这很合理。该语句的意思是:找出变量i的值,把该值加1,然后把新值赋值变量i(见图5.1)。

图5.1 语句i = i + 1;

在C语言中,类似这样的语句没有意义(实际上是无效的):

2002 = bmw;

因为在这种情况下,2002被称为右值(rvalue),只能是字面常量,不能给常量赋值,常量本身就是它的值。因此,在编写代码时要记住,=号左侧的项必须是一个变量名。实际上,赋值运算符左侧必须引用一个存储位置。最简单的方法就是使用变量名。不过,后面章节还会介绍“指针”,可用于指向一个存储位置。概括地说,C使用可修改的左值(modifiable lvalue)标记那些可赋值的实体。也许“可修改的左值”不太好懂,我们再来看一些定义。

几个术语:数据对象、左值、右值和运算符

赋值表达式语句的目的是把值存储到内存位置上。用于存储值的数据存储区域统称为数据对象(data object)。C标准只有在提到这个概念时才会用到对象这个术语。使用变量名是标识对象的一种方法。除此之外,还有其他方法,但是要在后面的章节中才学到。例如,可以指定数组的元素、结构的成员,或者使用指针表达式(指针中存储的是它所指向对象的地址)。左值(lvalue)是C语言的术语,用于标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签。

对于早期的C语言,提到左值意味着:

1.它指定一个对象,可以引用内存中的地址;

2.它可用在赋值运算符的左侧,左值(lvalue)中的l源自left。

但是后来,标准中新增了const限定符。用const创建的变量不可修改。因此,const标识符满足上面的第1项,但是不满足第2项。一方面C继续把标识对象的表达式定义为左值,一方面某些左值却不能放在赋值运算符的左侧。

为此,C标准新增了一个术语:可修改的左值(modifiable lvalue),用于标识可修改的对象。所以,赋值运算符的左侧应该是可修改的左值。当前标准建议,使用术语对象定位值(object locator value)更好。

右值(rvalue)指的是能赋值给可修改左值的量,且本身不是左值。例如,考虑下面的语句:

bmw = 2002;

这里,bmw是可修改的左值,2002是右值。读者也许猜到了,右值中的r源自right。右值可以是常量、变量或其他可求值的表达式(如,函数调用)。实际上,当前标准在描述这一概念时使用的是表达式的值(value of an expression),而不是右值。

我们看几个简单的示例:

int ex;
int why;
int zee;
const int TWO = 2;
why = 42;
zee = why;
ex = TWO * (why + zee);

这里,ex、why和zee都是可修改的左值(或对象定位值),它们可用于赋值运算符的左侧和右侧。TWO是不可改变的左值,它只能用于赋值运算符的右侧(在该例中,TWO被初始化为2,这里的=运算符表示初始化而不是赋值,因此并未违反规则)。同时,42是右值,它不能引用某指定内存位置。另外,why和zee是可修改的左值,表达式(why + zee)是右值,该表达式不能表示特定内存位置,而且也不能给它赋值。它只是程序计算的一个临时值,在计算完毕后便会被丢弃。

在学习名称时,被称为“项”(如,赋值运算符左侧的项)的就是运算对象(operand)。运算对象是运算符操作的对象。例如,可以把“吃汉堡”描述为:“吃”(运算符)操作“汉堡”(运算对象)。类似地可以说,=运算符的左侧运算对象应该是可修改的左值。

C的基本赋值运算符有些与众不同,请看程序清单5.3。

程序清单5.3 golf.c程序

/* golf.c -- 高尔夫锦标赛记分卡 */
#include <stdio.h>
int main(void)
{int jane, tarzan, cheeta;cheeta = tarzan = jane = 68;printf("                  cheeta   tarzan    jane\n");printf("First round score %4d %8d %8d\n", cheeta, tarzan, jane);return 0;
}

许多其他语言都会回避该程序中的三重赋值,但是C完全没问题。赋值的顺序是从右往左:首先把68赋给jane,然后再赋给tarzan,最后赋给cheeta。因此,程序的输出如下:

                  cheeta    tarzan      jane
First round score   68         68         68

5.2.2 加法运算符:+

加法运算符(addition operator)用于加法运算,使其两侧的值相加。例如,语句:

printf("%d", 4 + 20);

打印的是24,而不是表达式

4 + 20

相加的值(运算对象)可以是变量,也可以是常量。因此,执行下面的语句:

income = salary + bribes;

计算机会查看加法运算符右侧的两个变量,把它们相加,然后把和赋给变量income。

在此提醒读者注意,income、salary和bribes都是可修改的左值。因为每个变量都标识了一个可被赋值的数据对象。但是,表达式salary + bribes是一个右值。

5.2.3 减法运算符:-

减法运算符(subtraction operator)用于减法运算,使其左侧的数减去右侧的数。例如,下面的语句把200.0赋给takehome:

takehome = 224.00 – 24.00;

+和-运算符都被称为二元运算符(binary operator),即这些运算符需要两个运算对象才能完成操作。

5.2.4 符号运算符:-和+

减号还可用于标明或改变一个值的代数符号。例如,执行下面的语句后,smokey的值为12:

rocky = –12;
smokey = –rocky;

以这种方式使用的负号被称为一元运算符(unary operator)。一元运算符只需要一个运算对象(见图5.2)。

图5.2 一元和二元运算符

C90标准新增了一元+运算符,它不会改变运算对象的值或符号,只能这样使用:

dozen = +12;

编译器不会报错。但是在以前,这样做是不允许的。

5.2.5 乘法运算符:*

符号*表示乘法。下面的语句用2.54乘以inch,并将结果赋给cm:

cm = 2.54 * inch;

C没有平方函数,如果要打印一个平方表,怎么办?如程序清单5.4所示,可以使用乘法来计算平方。

程序清单5.4 squares.c程序

/* squares.c -- 计算1~20的平方 */
#include <stdio.h>
int main(void)
{int num = 1;while (num < 21){printf("%4d %6d\n", num, num * num);num = num + 1;}return 0;
}

该程序打印数字1~20及其平方。接下来,我们再看一个更有趣的例子。

1.指数增长

读者可能听过这样一个故事,一位强大的统治者想奖励做出突出贡献的学者。他问这位学者想要什么,学者指着棋盘说,在第1个方格里放1粒小麦、第2个方格里放2粒小麦、第3个方格里放4粒小麦,第4个方格里放8粒小麦,以此类推。这位统治者不熟悉数学,很惊讶学者竟然提出如此谦虚的要求。因为他原本准备奖励给学者一大笔财产。如果程序清单5.5运行的结果正确,这显然是跟统治者开了一个玩笑。程序计算出每个方格应放多少小麦,并计算了总数。可能大多数人对小麦的产量不熟悉,该程序以谷粒数为单位,把计算的小麦总数与粗略估计的世界小麦年产量进行了比较。

程序清单5.5 wheat.c程序

/* wheat.c -- 指数增长 */
#include <stdio.h>
#define SQUARES 64             // 棋盘中的方格数
int main(void)
{const double CROP = 2E16;  // 世界小麦年产谷粒数double current, total;int count = 1;printf("square     grains       total     ");printf("fraction of \n");printf("           added        grains    ");printf("world total\n");total = current = 1.0;        /* 从1颗谷粒开始   */printf("%4d %13.2e %12.2e %12.2e\n", count, current,total, total / CROP);while (count < SQUARES){count = count + 1;current = 2.0 * current;    /* 下一个方格谷粒翻倍 */total = total + current;    /* 更新总数 */printf("%4d %13.2e %12.2e %12.2e\n", count, current,total, total / CROP);}printf("That's all.\n");return 0;
}

程序的输出结果如下:

square        grains       total        fraction ofadded        grains       world total1         1.00e+00     1.00e+00     5.00e-172         2.00e+00     3.00e+00     1.50e-163         4.00e+00     7.00e+00     3.50e-164         8.00e+00     1.50e+01     7.50e-165         1.60e+01     3.10e+01     1.55e-156         3.20e+01     6.30e+01     3.15e-157         6.40e+01     1.27e+02     6.35e-158         1.28e+02     2.55e+02     1.27e-149         2.56e+02     5.11e+02     2.55e-1410         5.12e+02     1.02e+03     5.12e-14

10个方格以后,该学者得到的小麦仅超过了1000粒。但是,看看55个方格的小麦数是多少:

55         1.80e+16     3.60e+16     1.80e+00

总量已超过了世界年产量!不妨自己动手运行该程序,看看第64个方格有多少小麦。

这个程序示例演示了指数增长的现象。世界人口增长和我们使用的能源都遵循相同的模式。

5.2.6 除法运算符:/

C使用符号/来表示除法。/左侧的值是被除数,右侧的值是除数。例如,下面four的值是4.0:

four = 12.0/3.0;

整数除法和浮点数除法不同。浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。这使得5除以3很让人头痛,因为实际结果有小数部分。在C语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断(truncation)。

运行程序清单5.6中的程序,看看截断的情况,体会整数除法和浮点数除法的区别。

程序清单5.6 divide.c程序

/* divide.c -- 演示除法 */
#include <stdio.h>
int main(void)
{printf("integer division:  5/4   is %d \n", 5 / 4);printf("integer division:  6/3   is %d \n", 6 / 3);printf("integer division:  7/4   is %d \n", 7 / 4);printf("floating division: 7./4. is %1.2f \n", 7. / 4.);printf("mixed division:    7./4  is %1.2f \n", 7. / 4);return 0;
}

程序清单5.6中包含一个“混合类型”的示例,即浮点值除以整型值。C相对其他一些语言而言,在类型管理上比较宽容。尽管如此,一般情况下还是要避免使用混合类型。该程序的输出如下:

integer division:  5/4   is 1
integer division:  6/3   is 2
integer division:  7/4   is 1
floating division: 7./4. is 1.75
mixed division:    7./4  is 1.75

注意,整数除法会截断计算结果的小数部分(丢弃整个小数部分),不会四舍五入结果。混合整数和浮点数计算的结果是浮点数。实际上,计算机不能真正用浮点数除以整数,编译器会把两个运算对象转换成相同的类型。本例中,在进行除法运算前,整数会被转换成浮点数。

C99标准以前,C语言给语言的实现者留有一些空间,让他们来决定如何进行负数的整数除法。一种方法是,舍入过程采用小于或等于浮点数的最大整数。当然,对于3.8而言,处理后的3符合这一描述。但是-3.8会怎样?该方法建议四舍五入为-4,因为-4小于-3.8。但是,另一种舍入方法是直接丢弃小数部分。这种方法被称为“趋零截断”,即把-3.8转换成-3。在C99以前,不同的实现采用不同的方法。但是C99规定使用趋零截断。所以,应把-3.8转换成-3。

5.2.7 运算符优先级

考虑下面的代码:

butter = 25.0 + 60.0 * n / SCALE;

这条语句中有加法、乘法和除法运算。先算哪一个?是25.0加上60.0,然后把计算的和85.0乘以n,再把结果除以SCALE?还是60.0乘以n,然后把计算的结果加上25.0,最后再把结果除以SCALE?还是其他运算顺序?假设n是6.0,SCALE是2.0,带入语句中计算会发现,第1种顺序得到的结果是255,第2种顺序得到的结果是192.5。C程序一定是采用了其他的运算顺序,因为程序运行该语句后,butter的值是205.0。

显然,执行各种操作的顺序很重要。C语言对此有明确的规定,通过运算符优先级来解决操作顺序的问题。每个运算符都有自己的优先级。正如普通的算术运算那样,乘法和除法的优先级比加法和减法高,所以先执行乘法和除法。如果两个运算符的优先级相同怎么办?如果它们处理同一个运算对象,则根据它们在语句中出现的顺序来执行。对大多数运算符而言,这种情况都是按从左到右的顺序进行(=运算符除外)。因此,语句:

butter = 25.0 + 60.0 * n / SCALE;

的运算顺序是:

60.0 * n            首先计算表达式中的*或/(假设n的值是6,所以60.0*n得360.0)
360.0 / SCALE       然后计算表达式中第2个*或/
25.0 + 180          最后计算表达式里第1个+或-,结果为205.0(假设SCALE的值是2.0)

许多人喜欢用表达式树(expression tree)来表示求值的顺序,如图5.3所示。该图演示了如何从最初的表达式逐步简化为一个值。

图5.3 用表达式树演示运算符、运算对象和求值顺序

如何让加法运算在除法运算之前执行?可以这样做:

flour = (25.0 + 60.0 * n) / SCALE;

最先执行圆括号中的部分。圆括号内部按正常的规则执行。该例中,先执行乘法运算,再执行加法运算。执行完圆括号内的表达式后,用运算结果除以SCALE。

表5.1总结了到目前为止学过的运算符优先级。

表5.1 运算符优先级(从高至低)

注意正号(加号)和负号(减号)的两种不同用法。结合律栏列出了运算符如何与运算对象结合。例如,一元负号与它右侧的量相结合,在除法中用除号左侧的运算对象除以右侧的运算对象。

5.2.8 优先级和求值顺序

运算符优先级为表达式中的求值顺序提供重要的依据,但是并没有规定所有的顺序。C给语言的实现者留出选择的余地。考虑下面的语句:

y = 6 * 12 + 5 * 20;

当运算符共享一个运算对象时,优先级决定了求值顺序。例如上面的语句中,12是*和+运算符的运算对象。根据运算符的优先级,乘法的优先级比加法高,所以先进行乘法运算。类似地,先对5进行乘法运算而不是加法运算。简而言之,先进行两个乘法运算6 * 12和5 * 20,再进行加法运算。但是,优先级并未规定到底先进行哪一个乘法。C语言把主动权留给语言的实现者,根据不同的硬件来决定先计算前者还是后者。可能在一种硬件上采用某种方案效率更高,而在另一种硬件上采用另一种方案效率更高。无论采用哪种方案,表达式都会简化为72 + 100,所以这并不影响最终的结果。但是,读者可能会根据乘法从左往右的结合律,认为应该先执行+运算符左边的乘法。结合律只适用于共享同一运算对象的运算符。例如,在表达式12 / 3 * 2中,/和*运算符的优先级相同,共享运算对象3。因此,从左往右的结合律在这种情况起作用。表达式简化为4 * 2,即8(如果从右往左计算,会得到12/6,即2,这种情况下计算的先后顺序会影响最终的计算结果)。在该例中,两个*运算符并没有共享同一个运算对象,因此从左往右的结合律不适用于这种情况。

学以致用

接下来,我们在更复杂的示例中使用以上规则,请看程序清单5.7。

程序清单5.7 rules.c程序

/* rules.c -- 优先级测试 */
#include <stdio.h>
int main(void)
{int top, score;top = score = -(2 + 5) * 6 + (4 + 3 * (2 + 3));printf("top = %d, score = %d\n", top, score);return 0;
}

该程序会打印什么值?先根据代码推测一下,再运行程序或阅读下面的分析来检查你的答案。

首先,圆括号的优先级最高。先计算-(2 + 5) * 6中的圆括号部分,还是先计算(4 + 3 * (2 + 3))中的圆括号部分取决于具体的实现。圆括号的最高优先级意味着,在子表达式-(2 + 5) * 6中,先计算(2 + 5)的值,得7。然后,把一元负号应用在7上,得-7。现在,表达式是:

top = score = -7 * 6 + (4 + 3 * (2 + 3))

下一步,计算2 + 3的值。表达式变成:

top = score = -7 * 6 + (4 + 3 * 5)

接下来,因为圆括号中的*比+优先级高,所以表达式变成:

top = score = -7 * 6 + (4 + 15)

然后,表达式为:

top = score = -7 * 6 + 19

-7乘以6后,得到下面的表达式:

top = score = -42 + 19

然后进行加法运算,得到:

top = score = -23

现在,-23被赋值给score,最终top的值也是-23。记住,=运算符的结合律是从右往左。

本文摘自《C Primer Plus(第6版)中文版》

[美] 史蒂芬·普拉达(Stephen Prata) 著,姜佑 译

  • C语言程序设计入门自学教程
  • 近百万程序员的启蒙教材,被誉为C语言百科全书
  • 购书赠送99元e读电子书及在线编程环境,额外赠送199元C语言学习训练营

《C Primer Plus(第6版)中文版》是一本经过仔细测试、精心设计的完整C语言教程,它涵盖了C语言编程中的核心内容。《C Primer Plus(第6版)中文版》作为计算机科学的经典著作,讲解了包含结构化代码和自顶向下设计在内的程序设计原则。

与以前的版本一样,作者的目标仍旧是为读者提供一本入门型、条理清晰、见解深刻的C语言教程。作者把基础的编程概念与C语言的细节很好地融合在一起,并通过大量短小精悍的示例同时演示一两个概念,通过学以致用的方式鼓励读者掌握新的主题。

每章末尾的复习题和编程练习题进一步强化了*重要的信息,有助于读者理解和消化那些难以理解的概念。本书采用了友好、易于使用的编排方式,不仅适合打算认真学习C语言编程的学生阅读,也适合那些精通其他编程语言,但希望更好地掌握C语言这门核心语言的开发人员阅读。

《C Primer Plus(第6版)中文版》在之前版本的基础之上进行了全新升级,它涵盖了C语言*新的进展以及C11标准的详细内容。本书还提供了大量深度与广度齐备的教学技术和工具,来提高你的学习。

C语言有大约40个运算符,最常用的有这些相关推荐

  1. c++宏定义常量为什么使用移位_干货 | C语言系列3——常量,运算符,常用数学函数.........

    本期总结 1.符号常量和const常量: 2.运算符: 3.输入与输出: 4.常用数学函数. 往期回顾 干货 | C语言系列1--编程软件与做题平台 干货 | C语言系列2--程序结构与变量 符号常量 ...

  2. 干货 | C语言系列3——常量,运算符,常用数学函数......

    符号常量和const常量 1.符号常量 符号常量通俗来讲就是"替换",又称为宏定义.格式如下: #define 标识符 常量 宏定义可以定义单个变量为常量,也可以定义某个语句或片段 ...

  3. C语言基础之5:运算符、表达式和语句

    Tips1: 关键字:while.typedef 运算符:=.-.*./.%.++.--.(类型名) C语言的各种运算符,包括用于普通数学运算的运算符 运算符优先级以及语句.表达式的含义 while循 ...

  4. c语言从键盘输入一个百分制成绩score,C语言 基础练习40题

    一.题目 1.输入2个整数,求两数的平方和并输出. 2. 输入一个圆半径(r)当r>=0时,计算并输出圆的面积和周长,否则,输出提示信息. 3.函数y=f(x)可表示为: 4.编写一个程序,从4 ...

  5. sizeof是c语言的一种运算符,C语言位运算和sizeof运算符详解

    位运算和sizeof运算符 C语言中提供了一些运算符可以直接操作整数的位,称为位运算,因此位运算中的操作数都必须是整型的.位运算的效率是比较高的,而且位运算运用好的话会达到意想不到的效果.位运算主要有 ...

  6. python语言有哪些类型的运算符_python(4)-变量 数据类型和运算符

    第二章:变量.数据类型.运算符 思考:怎么理解变量和内存? 变量:就等同于上面图中的age name hobby 内存:存储位置里所装的内容 18 张三 打球 变量一定是存在某个地址,可以从变量名找到 ...

  7. c语音异或运算符_C语言中的按位异或运算符有什么用处?

    原标题:C语言中的按位异或运算符有什么用处? 想知道C语言中的按位异.运算符有什么用处,首先C语言中^为按位异或运算符,若两个二进制位相同,则结果为0,不同为1 例: #include "s ...

  8. C语言自加自减运算符(++i / i++) - C语言零基础入门教程

    目录 一.C 语言自加++ / 自减 – 运算符简介 1.前 ++ 后 ++ 的区别 2.前–后-- 的区别 二.C 语言自加++/自减–运算符实战 三.猜你喜欢 零基础 C/C++ 学习路线推荐 : ...

  9. python位运算符_详细介绍Python语言中的按位运算符

    按位运算符是把数字看作二进制来进行计算的.Python中的按位运算法则如下: 按位与 ( bitwise and of x and y ) & 举例: 5&3 = 1 解释: 101 ...

最新文章

  1. 百度小程序--支付功能
  2. c语言常用的字符串处理函数
  3. 【CV】YOLO算法最全综述:从YOLOv1到YOLOv5
  4. 数据结构期末复习之排序
  5. 03 tsung测试报告分析
  6. 四种方法下载网络文本数据到本地内存
  7. 【前端图表】echarts散点图鼠标划过散点显示信息
  8. tinymce中粘贴word文本时保留格式
  9. java将图书信息写入原有文件里_Java保存图书信息
  10. activiti 启动tomcat乱码_使用 IntelliJ IDEA 创建 Web 工程以及启动 Tomcat 乱码问题处理...
  11. 给“大学生IT博客大赛”参赛博主的一封信
  12. es6的模块化export和import
  13. 数据库课程设计:某自来水公司收费管理系统(SQL Server)
  14. CS61A 学习笔记Week1
  15. php mysql 去重,数据库文本去重
  16. 核心游戏系统架构设计
  17. 截止到某天的汇总报表_excel表格日数据汇总-excel表中如何将每日的数据汇总到每周...
  18. html站点是啥意思,html啥意思
  19. 内蒙古对口升学2018年计算机,2018年届对口升学考试计算机专业月考三试题.doc
  20. 关于软件功能点评估的问题(一)

热门文章

  1. Java 时间处理 ZoneId
  2. 关于keep,dont touch和max_fanout的用法建议
  3. CCNP 6 ISIS
  4. 华为智选 720 全效空气净化器 评测
  5. Android DVM简介
  6. a标签href的几种写法
  7. 唐迟长难句逻辑思维导图(含英语高分笔记)
  8. 雷泰RAYR3I2MSCL3+高温红外测温仪的参数指标
  9. Linux禅道安装步骤以及测试初认知
  10. java实例成员和类成员变量_Java 中成员变量又分为 和类成员变量。_学小易找答案...