第四章 运算符、表达式和语句
4.1 基本运算符
C使用运算符来表示算术运算。基本的算术运算符:=、+、-、*,以及/(C没有指数运算符。标准C的数学库为此提供了一个pow()函数。例如,pow(3.5,2.2)返回3.5的2.2次幂)。
1.赋值运算符:=
=号左边必须是一个变量的名字。赋值运算符左边必须指向一个存储位置。最简单的方法是使用变量的名字,但是以后您会看到,“指针”也可以用于指向一个存储位置。更普遍地,C使用术语“可修改的左值”来标志那些我们可以为之赋值的实体。
几个术语:数据对象、左值、右值和操作数
“数据对象”是泛指数据存储区的术语,数据存储区能用于保存值。C的术语左值指用于标识一个特定的数据对象的名字或表达式。
因为不是所有的对象都是可以更改的,所以C使用术语“可修改的左值”来表示那些可以被更改的对象。所以,赋值运算符的左边应该是一个可修改的左值。术语“右值”指的是能赋值给可修改的左值的量。右值可以是常量、变量或者任何一个可以产生一个值的表达式。
我们称之为“项目”的东西(比如在“符号=左边的项目”中的“项目”)的正确术语是“操作数”。操作数是运算符操作的对象。
2.算术运算符
+和-运算符被称为二元运算符或者双值运算符,这表示它们需要两个操作数。
除法运算符/,在C中,整数除法结果的小数部分都被丢弃。这个过程被称为截尾。
注意,没有把整数除法运算符运算的结果四舍五入到最近的整数,而是进行截尾,即舍弃整个小数部分。当您对整数与浮点数进行混合运算时,结果是浮点数。实际上,计算机不能真正用整数去除浮点数,所以编译器将两个操作数转变成一致的类型。C99要求使用“趋零截尾”,所以应该把-3.8转换成-3。
3.优先级和求值顺序
当两个运算符共享一个操作数时,优先级规定了求值的顺序。两个乘法运算6*2和5*20在加法运算之前进行。优先级没有确定的是这两个乘法运算中到底哪个先进行。C将这个选择权留给实现者,这是因为可能有一种选择在一种硬件上效率更高,而另一种选择在另一个硬件上效率更高。
4.2.其他运算符
1.sizeof运算符和size_t运算符
sizeof运算符以字节为单位返回其操作数的大小(在C中,1个字节被定义为char类型所占用的空间的大小。在过去一个字节通常是8位,但是一些字符集可能使用更大的字节)。操作数可以是一个具体的数据对象(例如一个变量名),或者一个类型。如果它是一个类型(如float),操作数必须被括在括号里。
//2022年4月6日07:29:26
//使用C99的%z修饰符。如果不能使用%zd,请用%u或%lu
#include <stdio.h>
int main(void)
{
int n = 0;
size_t intsize;
intsize = sizeof(int);
printf("n = %d,n has %zd bytes; all ints have %zd bytes.\n",n,sizeof n,intsize);
return 0;
}
结果
n = 0,n has 4 bytes; all ints have 4 bytes.
C:\Users\51670\Desktop\C Program\sizeof\x64\Debug\sizeof.exe (进程 13928)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
C规定sizeof返回size_t类型的值。这是一个无符号整数类型,但它不是一个新的类型。相反,与可移植类型(如int32_t等)相同,它是根据标准类型定义的。C有一个typedef机制,它允许您为一个已有的类型创建一个别名。例如:
typedef double real;
使real成为double的别名。您现在可以声明一个real类型的变量:
real deal;
编译器看到单词real,回想起typedef语句把real定义为double的别名,于是它把deal创建为一个double类型的变量。与此相似,C的头文件系统可以使用typedef来使size_t在系统中作为unsigned int或unsigned long的同义词。这样当您使用size_t时,编译器会用适合您的系统的标准类型代替之。C99更进一步,把%zd作为用来显示size_t类型值得printf()说明符。如果您的系统没有实现%zd,您可以进一步试着使用%u或者%lu代提它。
2.取模运算符:%
在C99为整数除法规定“趋零截尾”,如果第一个操作数为负数,那么得到的模也为负数;如果第一个操作数为正数,那么得到的模也为正数:
11/5 is 2 and 11%5 is 1
11/-5 is -2 and 11%-2 is 1
-11/-5 is 2 and -11%-5 is -1
-11/5 is -2 and -11%5 is -1
3.增量运算符和减量运算符:--和++
优点:通常产生更高效的机器语言代码,因为它与实际的机器语言指令相似。然而,随着商家推出更好的C编译器,这个好处可能会消失。一个智能编译器能识别出x=x+1,并把它与++x相同对待。
①优先级
增量运算符和减量运算符有很高的结合优先级;只有圆括号比它们的优先级高。所以,x*y++代表(x)*(y++)而不是(x*y)++。幸亏后者无效,增量运算符和减量运算符只能影响一个变量(或者更一般地讲,一个可修改的左值);而组合x*y不是一个变量。
使用原则:
●如果一个变量出现在同一个函数的多个参数中时,不要将增量或者减量运算符用于它上面。
●当一个变量多次出现在一个表达式里时,不要将增量或减量运算符运用到它的上面。
4.表达式和语句
副作用和顺序点:
副作用是对数据对象或文件的修改。如果是一个表达式的话,不仅算出一个值,还修改了环境。跟赋值运算符一样,增量运算符和减量运算符也有副作用,它们主要由于副作用而被使用。
一个顺序点是程序执行中的一个点;在该点处,所有的副作用都在进入下一步前被计算。在C中,语句里的分号标志了一个顺序点。它意味着在一个语句中赋值运算符、增量运算符及减量运算符所做的全部改变必须在程序进入下一个语句前发生。
一个完整的表达式是这样一个表达式-------它不是一个更大的表达式的子表达式。完整表达式的例子包括一个表达式语句里的表达式和在一个while循环里作为判断条件的表达式。
顺序点帮助阐明后缀增量动作何时发生。
总结:表达式和语句
表达式:
表达式是运算符和操作数的组合。最简单的表达式只有一个常量或一个变量而没有运算符。
语句:
语句是对计算机的命令。有简单语句和复合语句。简单语句以一个分号结束。
复合语句或代码块由一个或多个括在花括号里的语句(这些语句本身也可能是复合语句)构成。
4.3类型转换
基本规则
1.当出现在表达式里时,有符号和无符号的char和short类型都将自动转换为int,在需要的情况下,将自动被转换为unsigned int(如果short和int有相同的大小),那么unsigned short比int大;在那种情况下,将把unsigned short转换为unsigned int)。在K&R C下,但不是当前的C下,float将自动被转换为double。因为是转换成较大的类型,所以这些转换被称为提升。
2.在包含两种数据类型的运算里,两个值都被转换成两种类型里较高的级别。
3.类型级别从高到低的顺序是long double、double、float、unsigned long long、longlong、unsigned long、long、unsigned int、int。一个可能的例外是当long和int具有相同大小时,此时unsigned int比long的级别更高。之所以short和char类型没有出现在此清单里,是因为它们已经被提升到int或也可能被提升到unsigned int。
4.在赋值语句里,计算的最后结果都被转换成将要被赋予值的那个变量类型。像规则1中一样,这个过程可能导致提升;但也可能导致降级,降级是一个更低级的类型。
5.当作为函数的参数被传递时,char和short会被转换成int,float会=被转换成double。可以通过函数原型来阻止自动提升的发生。
提升是一个平滑的无损害的过程,但是降级可能导致真正的问题。
指派运算符
通常您应该避免自动类型转换,尤其是避免降级。但是倘若您小心使用,有时候它对做类型转换很方便。然而,您也有可能需要准确的类型转换,或者需要在程序中表明您是知道您正在做类型转换的。完成这一任务的方法被称为指派。
4.4带有参数的函数
参数与参量
尽管术语参数和参量可以互换地使用,但C99文档已经规定:对实际参数或实际参量使用术语参数,对形式参数或者形式参量使用术语参量。遵循这个约定,我们可以说参量是变量,而参数是由函数调用提供的值,并且将它赋给相对应的参量。
假设您漏掉了类型指派,如果使用现代C,程序将为您自动完成类型指派。
void pound(int n);
原型是一个函数声明,它描述了函数的返回值和它的参数。这个函数原型说明了关于pound()函数的两件事情:
●函数没有返回值
●函数接收一个int类型的参数
4.5总结
表达式是运算符和操作数的组合。在C里,每一个表达式都有一个值,其中包括赋值表达式和比较表达式。运算符优先级的规则帮助决定当对表达式进行求值时,如何组合表达式里的各项。当两个运算符共共享一个操作数时,具有较高优先级的运算符先被运算。如果运算符有相同的优先级,结合性(从左到右或从右到左)决定了那个运算符先被应用。
语句是对计算机的完整指示,在C中通过一个分号来标识。到目前,您已经使用了声明语句、赋值语句、函数调用语句和控制语句。包含在一对花括号里的语句构成了一个复合语句或代码块。
在C里,许多类型转换会自动发生。当char和short类型出现在表达式里或者作为函数的参数时,它们都将被提升为int类型。当float类型作为一个函数参数时被提升为double类型。在K&R C(而不是ANSI C)下,当float用于表达式里时也被提升为double类型。当把一种类型的值赋值给另一种类型的变量时,该值被转换成和那个变量相同的类型。当较大类型的值被转换成较小类型的值时,它们可能会丢失数据。在混合类型的算术运算的情况下,较小的类型被转换成较大的类型。