2022-11-06

运算符,表达式和语句

循环简介

用#define 指令创建符号常量和用 const 限定符创建在程序运行过程中不可更改的变量。

while循环

while循环的原理:当程序第1次到达while循环时, 会检查圆括号中的条件是否为真。 该程序中, 条件表达式如下:

shoe < 18.5

符号<的意思是小于。 变量shoe被初始化为3.0, 显然小于18.5。 因此,该条件为真, 程序进入块中继续执行, 把尺码转换成英寸。 然后打印计算的结果。 下一条语句把 shoe增加1.0, 使shoe的值为4.0:

shoe = shoe + 1.0;

程序返回while入口部分检查条件。这条语句的下面是右花括号( }) , 代码使用一对花括号( {}) 来标出while循环的范围。 花括号之间的内容就是要被重复执行的内容。 花括号以及被花括号括起来的部分被称为块( block) 。因为4小于18.5, 所以要重复执行被花括号括起来的所有内容( 用计算机术语来说就是, 程序循环这些语句) 。 该循环过程一直持续到shoe的值为19.0。 此时, 由于19.0小于18.5, 所以该条件为假:

shoe < 18.5

出现这种情况后, 控制转到紧跟while循环后面的第1条语句。

基本运算符

用运算符(operator) 表示算术运算。+运算符使在它两侧的值加在一起。基本算术运算的运算符: =、 +、 -、 *和/(C 没有指数运算符。不过, C 的标准数学库提供了一个pow()函数用于指数运算

赋值运算符:=

在C语言中, =并不意味着“相等”, 而是一个赋值运算符。

bmw = 2002;

把值2002赋给变量bmw。 也就是说, =号左侧是一个变量名, 右侧是赋给该变量的值。 符号=被称为赋值运算符。赋值行为从右往左进行。

i = i + 1;

找出变量 i 的值, 把该值加 1, 然后把新值赋值变量i

2002 = bmw;

2002 被称为右值(rvale) , 只能是字面常量。 不能给常量赋值, 常量本身就是它的值。=号左侧的项必须是一个变量名。 实际上, 赋值运算符左侧必须引用一个存储位置。 最简单的方法就是使用变量名。C 使用可修改的左值(modifiable lvalue)标记那些可赋值的实体。

赋值表达式语句的目的是把值储存到内存位置上。 用于储存值的数据存储区域统称为数据对象(data object) 。左值(lvalue) 是 C 语言的术语, 用于标识特定数据对象的名称或表达式。 因此, 对象指的是实际的数据存储, 而左值是用于标识或定位存储位置的标签。

右值(rvalue) 指的是能赋值给可修改左值的量, 且本身不是左值。

bmw = 2002;

bmw是可修改的左值, 2002是右值。右值可以是常量、 变量或其他可求值的表达式(如, 函数调用) 。 实际上, 当前标准在描述这一概念时使用的是表达式的值(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) 。 运算对象是运算符操作的对象。

加法运算符:+

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

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

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

4 + 20

相加的值(运算对象) 可以是变量, 也可以是常量。

income = salary + bribes;

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

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

减法运算符:-

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

takehome = 224.00 – 24.00;

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

符号运算符:-和+

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

rocky = –12;

smokey = –rocky;以这种方式使用的负号被称为一元运算符(unary operator) 。 一元运算符只需要一个运算对象

乘法运算符:*

C没有平方函数, 如果要打印一个平方表,可以使用乘法来计算平方。

指数增长

除法运算符

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

four = 12.0/3.0;

整数除法和浮点数除法不同。 浮点数除法的结果是浮点数, 而整数除法的结果是整数。 整数是没有小数部分的数。在C语言中, 整数除法结果的小数部分被丢弃, 这一过程被称为截断(truncation)

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

运算符的优先级

乘法和除法的优先级比加法和减法高, 所以先执行乘法和除法。 如果两个运算符的优先级相同它们处理同一个运算对象, 则根据它们在语句中出现的顺序来执行。 对大多数运算符而言, 这种情况都是按从左到右的顺序进行(=运算符除外) 。

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)

让加法运算在乘法运算之前执行

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

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

优先级和求值顺序

运算符优先级为表达式中的求值顺序提供重要的依据, 但是并没有规定所有的顺序。

y = 6 * 12 + 5 * 20;

当运算符共享一个运算对象时, 优先级决定了求值顺序。12是*和+运算符的运算对象。 根据运算符的优先级, 乘法的优先级比加法高, 所以先进行乘法运算。C 语言把主动权留给语言的实现者, 根据不同的硬件来决定先计算前者还是后者。

圆括号的优先级最高。 圆括号的最高优先级意味着, 在子表达式-(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

其他运算符

sizeof运算符和size_t类型

sizeof运算符以字节为单位返回运算对象的大小(在C中, 1字节定义为char类型占用的空间大小。 过去, 1字节通常是8位, 但是一些字符集可能使用更大的字节) 。 运算对象可以是具体的数据对象(如, 变量名) 或类型。 如果运算对象是类型(如,float) , 则必须用圆括号将其括起来。

C语言规定, sizeof 返回 size_t 类型的值。 这是一个无符号整数类型,但它不是新类型。size_t是语言定义的标准类型。 C有一个typedef机制, 允许程序员为现有类型创建别名。

typedef double real;

这样, real就是double的别名。 现在, 可以声明一个real类型的变量:

real deal; // 使用typedef

编译器查看real时会发现, 在typedef声明中real已成为double的别名, 于是把deal创建为double 类型的变量。 类似地, C 头文件系统可以使用 typedef把 size_t 作为 unsigned int 或unsigned long的别名。 这样, 在使用size_t类型时, 编译器会根据不同的系统替换标准类型。

求模运算符

求模运算符(modulus operator) 用于整数运算。 求模运算符给出其左侧整数除以右侧整数的余数(remainder) 。求模运算符只能用于整数, 不能用于浮点数。

canf()为变量sec获取一个新值。 只要该值为正, 循环就继续。 当用户输入一个0或负值时, 循环退出。 这两种情况设计的要点是, 每次循环都会修改被测试的变量值。

负数求模如果第1个运算对象是负数, 那么求模的结果为负数; 如果第1个运算对象是正数, 那么求模的结果也是正数

递增运算符:++

递增运算符(increment operator) 执行简单的任务, 将其运算对象递增1。 该运算符以两种方式出现。 第1种方式, ++出现在其作用的变量前面,这是前缀模式; 第2种方式, ++出现在其作用的变量后面, 这是后缀模式。两种模式的区别在于递增行为发生的时间不同。

紧凑结构的代码让程序更为简洁, 可读性更高。 这些运算符让程序看起来很美观。

递增运算符的另一个优点是, 通常它生成的机器语言代码效率更高, 因为它和实际的机器语言指令很相似。

递增运算符还有一个在某些场合特别有用的特性

a和b都递增了1, 但是, a_post是a递增之前的值, 而b_pre是b递增之后的值。 这就是++的前缀形式和后缀形式的区别。

a_post = a++; // 后缀: 使用a的值乊后, 递增a

b_pre= ++b; // 前缀: 使用b的值乊前, 递增b

单独使用递增运算符时(如, ego++;) , 使用哪种形式都没关系。但是, 当运算符和运算对象是更复杂表达式的一部分时(如上面的示例) , 使用前缀或后缀的效果不同。

递减运算符

每种形式的递增运算符都有一个递减运算符(decrement operator) 与之对应, 用--代替++即可:

--count; // 前缀形式的递减运算符

count--; // 后缀形式的递减运算符

优先级

递增运算符和递减运算符都有很高的结合优先级, 只有圆括号的优先级比它们高。 因此, x*y++表示的是(x)*(y++), 而不是(x+y)++。 不过后者无效, 因为递增和递减运算符只能影响一个变量(或者, 更普遍地说, 只能影响一个可修改的左值) , 而组合x*y本身不是可修改的左值。

y = 2;

n = 3;

nextnum = (y + n++)*6;

nextnum的值是多少? 把y和n的值带入上面的第3条语句得:

nextnum = (2 + 3)*6 = 5*6 = 30

n的值只有在被使用之后才会递增为4。 根据优先级的规定, ++只作用于n, 不作用与y + n。 除此之外, 根据优先级可以判断何时使用n的值对表达式求值, 而递增运算符的性质决定了何时递增n的值。

如果n++是表达式的一部分, 可将其视为“先使用n, 再递增”; 而++n则表示“先递增n, 再使用”。

如果一个变量出现在一个函数的多个参数中, 不要对该变量使用递增或递减运算符;

如果一个变量多次出现在一个表达式中, 不要对该变量使用递增或递减运算符。

表达式和语句

表达式(expression) 由运算符和运算对象组成。

运算对象可以是常量、 变量或二者的组合。 一些表达式由子表达式(subexpression) 组成(子表达式即较小的表达式) 。

每个表达式都有一个值

C 表达式的一个最重要的特性是, 每个表达式都有一个值。 要获得这个值, 必须根据运算符优先级规定的顺序来执行操作。 在上面我们列出的表达式中, 前几个都很清晰明了。 但是, 有赋值运算符(=) 的表达式的值是什么? 这些表达式的值与赋值运算符左侧变量的值相同。 因此, 表达式q = 5*2作为一个整体的值是10。 那么, 表达式q > 3的值是多少? 这种关系表达式的值不是0就是1, 如果条件为真, 表达式的值为1; 如果条件为假, 表达式的值为0。

语句

语句(statement) 是C程序的基本构建块。 一条语句相当于一条完整的计算机指令。 在C中, 大部分语句都以分号结尾。

legs = 4只是一个表达式(它可能是一个较大表达式的一部分) 。

legs = 4;是一条语句

最简单的语句是空语句:; //空语句

语句可以改变值或调用函数:并不是所有的指令都是语句。

x = 6 + (y = 5);

该语句中的子表达式y = 5是一条完整的指令, 但是它只是语句的一部分。 因为一条完整的指令不一定是一条语句, 所以分号用于识别在这种情况下的语句(即, 简单语句) 。

声明创建了名称和类型, 并为其分配内存位置。 注意, 声明不是表达式语句。 也就是说, 如果删除声明后面的分号, 剩下的部分不是一个表达式, 也没有值:

int port /* 不是表达式, 没有值 */

赋值表达式语句在程序中很常用: 它为变量分配一个值。 赋值表达式语句的结构是, 一个变量名, 后面是一个赋值运算符, 再跟着一个表达式, 最后以分号结尾。 注意, 在while循环中有一个赋值表达式语句。 赋值表达式语句是表达式语句的一个示例。

函数表达式语句会引起函数调用。 在该例中, 调用printf()函数打印结果。 while语句有3个不同的部分。 首先是关键字while; 然后,圆括号中是待测试的条件; 最后如果测试条件为真, 则执行while循环体中的语句。 该例的while循环中只有一条语句。 可以是本例那样的一条语句,不需要用花括号括起来, 也可以像其他例子中那样包含多条语句。 多条语句需要用花括号括起来。 这种语句是复合语句

while语句是一种迭代语句, 有时也被称为结构化语句, 因为它的结构比简单的赋值表达式语句复杂。

副作用和序列点

我们再讨论一个C语言的术语副作用(side effect)。 副作用是对数据对象或文件的修改。

states = 50;

它的副作用是将变量的值设置为50。从C语言的角度看, 主要目的是对表达式求值。 给出表达式4 + 6, C会对其求值得10; 给出表达式states = 50, C会对其求值得50。 对该表达式求值的副作用是把变量states的值改为50。递增和递减运算符也有副作用, 使用它们的主要目的就是使用其副作用

调用 printf()函数时, 它显示的信息其实是副作用(printf()的返回值是待显示字符的个数) 。

序列点(sequence point) 是程序执行的点, 在该点上, 所有的副作用都在进入下一步之前发生。 在 C语言中, 语句中的分号标记了一个序列点。 意思是, 在一个语句中, 赋值运算符、 递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。 任何一个完整表达式的结束也是一个序列点 任何一个完整表达式的结束也是一个序列点

完整表达式(full expression) , 就是指这个表达式不是另一个更大表达式的子表达式。 例如, 表达式语句中的表达式和while循环中的作为测试条件的表达式, 都是完整表达式。

序列点有助于分析后缀递增何时发生。

while (guests++ < 10)

printf("%d \n", guests);

表达式guests++ < 10是一个完

整的表达式, 因为它是while循环的测试条件, 所以该表达式的结束就是一个序列点。 因此, C 保证了在程序转至执行 printf()之前发生副作用(即, 递增guests) 。 同时, 使用后缀形式保证了guests在完成与10的比较后才进行递增。

你可能感兴趣的:(2022-11-06)