GUN C 手册 第七章

7 赋值表达式

作为编程中的一般概念,赋值是一种将新值存储到可以存储值的位置的构造,例如,在变量中。这些位置被称为左值,因为它们是保存值的位置。

在C中,赋值是一个表达式,因为它有一个值;我们称之为赋值表达式。一个简单的赋值看起来像

lvalue = value-to-store

它将表达式value-to-store的值分配给位置 lvalue。

然而,这不是使用左值的唯一方法,也不是所有左值都可以被赋值。要在赋值的左侧使用左值,它必须是可修改的。在C中,这意味着它不是用类型限定符const声明的。

赋值表达式的值是新值存储在其中后的左值。这意味着您可以在其他表达式中使用赋值。赋值运算符是右关联的,因此

x = y = z = 0;

相当于,

x = (y = (z = 0));

另一方面,

((x = y) = z) = 0;

将无效,因为赋值表达式(如x=y)作为左值无效。

警告:如果将赋值嵌套在另一个表达式中,请在其周围写括号,除非该表达式是条件表达式、逗号分隔序列或另一个赋值。

7.1 简单赋值

简单赋值表达式计算右侧操作数的值,并将其存储到左侧的lvalue中。下面是一个简单的赋值表达式,它将5存储在i中:

i = 5

我们说这是给变量i赋值,它给i赋值5。它没有分号,因为它是一个表达式(所以它有一个值)。在结尾添加分号将使其成为语句。

下面是简单赋值表达式的另一个示例。它的操作数并不简单,但赋值是简单赋值。

x[foo ()] = y + 6

如果可能,使用两种不同数据类型的简单赋值将右侧数值转换为lvalue的类型。它可以将任何数字类型转换为任何其他数字类型。

一些非数字类型也允许简单赋值:指针、结构和联合。

警告:数组不允许赋值,因为C中没有数组值;C变量可以是数组,但数组不能作为整体进行操作。

7.2 左值

标识保存值的内存空间的表达式称为左值,因为它是可以保存值的位置。

左值的标准类型为:

  • 变量。
  • 使用一元操作符“*”的指针解引用表达式。
  • 使用“.”的结构字段引用,如果结构值是左值。
  • 使用“->”的结构字段引用。这始终是一个左值,因为“->”含有指针解引用的意思。
  • 使用“[…]”的数组元素引用,如果数组是左值。

如果表达式的最外层操作是任何其他运算符,则该表达式不是左值。因此,变量x是左值,但x+0不是左值,即使这两个表达式计算相同的值(假设x是数字)。

数组可以是左值(上面的规则确定它是否为左值),但在表达式中使用数组会自动将其转换为指向第一个元素的指针。转换的结果不是左值。因此,如果变量a是一个数组,则不能使用a本身作为赋值的左操作数。但你可以给a的元素赋值,例如a[0]。这是左值,因为a是左值。

7.3 Modifying Assignment

您可以缩写通用构造

lvalue = lvalue + expression

as

lvalue += expression

这称为modifying assignment。例如,

i = i + 5;
i += 5;

两个语句是等价的。第一个使用简单赋值;第二个使用modifying assignment。

例如,你可以像这样从左值中减去一些东西,

lvalue -= expression

或者将其乘以一定量,

lvalue *= expression

或者像这样移位一定量。

lvalue <<= expression 
lvalue >>= expression

在大多数情况下,此特性不会让语言更强大,但它提供了极大的方便。此外,当左值包含具有副作用的代码时,简单赋值执行这些副作用两次,而修改赋值执行一次。例如,

x[foo ()] = x[foo ()] + 5;

函数foo调用了两次,每次都可能返回不同的值。如果foo()第一次返回1,第二次返回3,则效果可能是将x[3]和5相加并将结果存储在x[1]中,或者将x[1]和5相加并存储在x[3]中。我们不知道两个中哪一个会发生,因为C没有指定哪个foo调用首先被计算。

这样的语句是不明确的,不应该使用。

相比之下

x[foo ()] += 5;

是明确的:它只调用foo一次,以确定要调整x的哪个元素,并通过向其加5来调整该元素。

7.4 递增和递减运算符

运算符“++”和“--”是递增和递减运算符。当用于数值时,它们加或减1。我们不认为它们是赋值,但它们等同于赋值。

在左值之前使用“++”或“--”作为前缀称为预增量或预减量。这将加或减1,其结果成为表达式的值。例如,

#include    /* Declares printf. */

int
main (void)
{
  int i = 5;
  printf ("%d
", i);
  printf ("%d
", ++i);
  printf ("%d
", i);
  return 0;
}

打印包含5、6和6的行。表达式++i将i从5增加到6,并具有值6,因此该行上printf的输出是“6”。

7.5 后增量和后减量

在左值后面使用“++”或“--”会做一些特殊的事情:它直接从左值中获取值,然后递增或递减。因此,i++的值与i的值相同,但i++“稍后”会递增i。这称为后增或后减。

例如

#include /* Declares printf. */

int
main (void)
{
  int i = 5;
  printf ("%d
", i);
  printf ("%d
", i++);
  printf ("%d
", i);
  return 0;
}

打印包含5、5和6的行。表达式i++的值为5,这是当时i的值,但稍后它将i从5增加到6。

“晚一点”是多久?这是灵活的。增量必须在下一个序列点发生。在简单的情况下,这意味着语句的结尾。

如果一元运算符位于后增或后减表达式之前,则增量嵌套在内层:

-a++ is equivalent to -(a++)

因为-a不是左值,因此不能递增。

7.7 陷阱:子表达式中的赋值

在C语言中,表达式各部分的计算顺序是不固定的。除少数特殊情况外,操作可以按任何顺序计算。如果表达式的一部分具有对x的赋值,而表达式的另一部分使用x,则结果是不可预测的,因为该使用可能在赋值之前或之后计算。

下面是一个模棱两可的代码示例:

x = 20;
printf ("%d %d
", x, x = 4);

如果第二个参数x是在第三个参数x=4之前计算的,则第二个变量的值将为20。如果按其他顺序计算,则第2个参数的值将是4。

这里有一种方法可以使代码明确:

y = 20;
printf ("%d %d
", y, x = 4);

这是另一种方式,具有另一种含义:

x = 4;
printf ("%d %d
", x, x);

此问题适用于所有类型的赋值,以及与赋值等效的递增和递减运算符。

但是,在if条件或while测试中把赋值跟逻辑运算符一起使用可能很有用。

7.7 在单独的语句中赋值

在if条件中编写赋值通常很方便,但这会降低程序的可读性。下面是一个避免的示例:

if (x = advance (x))
  …

这里的想法是推进x并测试值是否为非零。但是,读者可能会忽略它使用“=”而不是“=”的事实。事实上,在条件中写入“=”是一个常见的错误,因此当“=”以某种方式显示为错误时,GNU C可以发出警告。

将赋值写为单独的语句更清楚,如下所示:

x = advance (x);
if (x != 0)
  …

这清楚地表明,x被赋予了一个新值。

另一种方法是使用逗号运算符,如下所示:

if (x = advance (x), x != 0)
  …

但是,将赋值放在单独的语句中通常更清楚,除非赋值很短,因为它减少了嵌套。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章