前面我们熟悉了如何表示数据,接下来我们学习如何处理数据。C语言为处理数据提供了大量的操作,可以在程序中进行算术运算、比较值的大小、修改变量、逻辑的组合关系等。
1.基本运算符
C用运算符表示算术运算,下面将介绍以下用于基本运算的运算符:=、+、-、*、/及它们的优先级。
1.赋值运算符=
相信大家对这个很熟悉,每一个初学者都可能对这个感到困惑,这不是数学中的等于号吗?然而,在C语言中,=是一赋值运算符(==是C语言中的等于号),像int i = 1;
,把1赋值给int类型的变量i,其中1被称为右值,不能写成int 1 = i;
,实际上,赋值运算符的左边必须引用一个存储空间。当然,最简单的方法就是使用变量名;也可以用我们之前简单提到过的指针,指针的作用就是指向一个存储空间。
赋值表达式语句的目的是把值存储到内存位置上。用于存储值的数据存储区统称为数据对象,使用变量名是标识对象的一种方法。左值是C语言的术语,用于标识特定数据对象的名称或表达式,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签。但是由于后面的C标准新增了const限定符,用const创建的变量不可修改,这些左值不能用于赋值运算符的左侧,因此,左值这个术语不再普遍适用,于是C标准新增了一个术语:可修改的左值,用于表示可修改的对象,所以,赋值运算符的左侧应该是可修改的左值,当前标准建议,使用术语对象定位值。右值指的是能赋值给可修改左值的量,且本身不是左值。右值可以是常量、变量或其它可求值的表达式(如函数调用),实际上当前标准在描述这一概念时使用的是表达式的值,而不是右值。
const int THREE = 3;
int num1, num2, num3;
num1 = num2 = num3 = 3;
num1 = num1 + (num2 * num3);
THREE是左值,num1、num2、num3都是可修改左值(对象定位值),它们可用于赋值运算符的左侧和右侧;同时,3、(num2 * num3)是右值(表达式的值),它不能引用某指定内存位置。第一行表示只读变量的初始化,并非赋值;第三行是C语言种的三重赋值,许多编程语言会避免这种多重赋值,多重赋值的顺序是从右往左;第四行中,表达式(num2 * num3)是右值,该表达式不能引用某指定内存位置,而且也不能给它赋值,它只是程序计算的一个临时值,在计算完毕后便会被丢弃。
2.加法运算符+
加法运算符用于加法运算,使其两侧的值相加:
int sum, add1, add2;
scanf("%d, %d", add1, add2);
sum = add1 + add2;
sum,add1,add2都是对象定位符,而表达式add1 + add2是右值(表达式的值)。
3.减法运算符-
同加法运算符,-运算符使其左侧的值减去右侧的值,+和-运算符都是二元运算符,即这些运算符需要两个运算对象才能完成操作。
4.符号运算符-和+
这里的-和+是减法运算符,减号-可用于标明或改变一个值的代数符号,如negNum = -1;
,以这种方式使用的负号被称为一元运算符,一元运算符只需要一个运算对象。C90标准新增了一元+运算符,它不会改变运算对象的值或符号,与-相同,但在之前使用一元+运算符是不被允许的。
5.乘法运算符*
*运算符是二元运算符,*的左侧是被乘数,*的右侧是乘数。
6.除号运算符/
/运算符是二元运算符,/的左侧是被乘除数,/的右侧是除数。
整数除法和浮点数除法不同。浮点数除法的结果是浮点数,当除法运算中出现浮点数和整数时,编译器会将低字节的整型转为高字节的浮点型,实际上也是浮点数的除法;整数除法的结果是整数,当整数除法的结果有小数部分时,小数部分会被丢弃,这一过程被称为截断,这种截断也被叫做趋零截断。
2.运算符优先级求值顺序
执行各种操作的顺序很重要,C语言通过运算符优先级来确定操作顺序的问题。每个运算符都有自己的优先级,与我们熟知的数学运算符优先级相同,乘法和除法的优先级高于加法和减法,符号运算符次之,赋值运算符的优先级最低。如果两个运算符优先级相同时,如果它们处理同一个运算对象,则根据它们在语句中出现的顺序来执行。对于大多数运算符而言,这种情况都是按照从左到右的顺序执行。butter = 25.0 + 60.0 * 6.0 / 2.0;
思考这句代码的执行顺序,许多人喜欢用表达式树来表示求值的顺序,这与编译原理中的知识点重合。下表是运算符优先级:
运算符 | 结合律 |
() | 从左往右 |
-+(二元) | 从左往右 |
*/ | 从左往右 |
-+(一元) | 从右往左 |
= | 从右往左 |
3.其他运算符
1.sizeof运算符和size_t类型
sizeof运算符以字节为单位返回对象的大小,在C语言中,一字节定义为char类型占用的空间大小,通常1字节是8位,但是一些字符集可能使用更大的字节。C语言规定,sizeof返回size_t类型的值,这是一个无符号整数类型,它并不是一个新的类型,而是无符号整数类型。
2.求模运算符%
求模运算符用于整数运算,只能用于整数,不能用于浮点数;关于求模运算,实际规定:无论何种情况,只要a和b都是整数值,便可通过a-(a/b)*b来计算a%b。
3.递增运算符++
递增运算符++,作用很简单,就是将运算对象递增。该运算符以两种方式出现,第一种是++出现在其作用的变量前面,这是前缀模式;第二种出现在其作用的变量后面,这是后缀模式。至于这两种模式具体有什么区别,请看如下代码:
int i = 0, j = 0;
int a, b;
a = ++i; //a = 1; i先加1在运算
b = j++; //b = 0; j先运算再加1
其中,用前缀模式给a赋值,后缀模式给b赋值,可以看到,a被赋值为1,b被赋值为0,这就是前缀模式和后缀模式的区别。
4.递减运算符--
递增运算符和递减运算符都有很高的优先级,只有圆括号的优先级比它们高,如:a * b++相当于a * (b++),并不是(a * b)++,而本身后者也无效,因为递增运算符和递减运算符只能影响一个可修改的左值,(a*b)++属于表达式的值。
注意:在使用递增/递减运算符时,在一条语句中一定不要多次使用,尽量将递增/递减运算符放在单独的行。在C语言中,编译器可以自行选择先对函数中的哪个参数进行求值,这样做提高了编译器的效率,但是如果在函数的参数中使用了递增/递减运算符就可能出现问题。像这条语句,printf("%10d %10d", num++, num * num);
编译器可能从左到右开始运行,也可能从右到左进行运行;再比如(n=3)y = n++ + n++;
,y的值可能有两种情况出现6和7,在对y进行求值时,编译器可能使用两次n的旧值3,也可能使用一次n的旧值之后,对其中一个n加1,当然,这条语句无论怎么运行,运行这条语句之后,n最后的值都为5。
针对上述可能出现的问题,使用时应遵循以下原则:如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增/递减运算符;如果一个变量多次出现在一个表达式中,不要对该变量使用递增/递减运算符。