c# 你能在C语言的printf语句中改变变量的值吗?

gorkyyrv  于 2023-04-27  发布在  C#
关注(0)|答案(4)|浏览(199)

下面两个我遇到麻烦的问题来自K.N. King的“C Programming a Modern Approach”中的第5章练习3。

1) i = 7; j = 8; k = 9;
   printf("%d ",(i = j) || (j = k));                           
   printf("%d %d %d\n", i, j, k);

    // answer I think it is 1 9 9 9
    // actual answer after running in C: 1 8 8 9

2) i = 1; j = 1; k = 1;
   printf("%d ", ++i || ++j && ++k);
   printf("%d %d %d\n", i, j, k);

   // answer I think it is 1 2 2 2
   // actual answer after running in C: 1 2 1 1

我的问题如下:
1.为什么问题1中的i和j的值在第一个printf语句中赋值后没有改变?因为赋值运算符表达式(i = j)和(j = k)在括号中,它们从右到左首先被求值。此时它们的值应该是9和9。我很困惑为什么第二个printf语句输出8和8的i和j?
1.问题2中的j和k的值在第一个printf语句中递增后为什么没有改变?由于前缀递增具有更高的优先级,因此它们首先从右到左计算i,j和k。此时它们的值应该是2,2和2。像问题1一样,为什么第二个printf语句输出1和1 j和k?

lskq00tm

lskq00tm1#

当你有

expr1 || expr2

expr1将首先被评估。
如果expr1的结果为真(也称为非零),则expr2永远不会被计算,expr1 || expr2的结果将为1。
如果expr1的结果为假(也称为零),则将计算expr2。如果expr2的结果为非零,则expr1 || expr2的结果将为1。否则,它将为零。
所以这行:

printf("%d ",(i = j) || (j = k));

可以重写为:

int tmp;
i = j;       // This assignment will always happen
if (i != 0)
{
    tmp = 1;
}
else
{
    j = k;  // This assignment may happen (depends on result of first assignment)
    if (i != 0)
    {
         tmp = 1;
    }
    else
    {
        tmp = 0;
    }
}
printf("%d ", tmp);

printf("%d ", ++i || ++j && ++k);使用同样的想法,您将看到为什么只有i发生了变化。

pinkon5k

pinkon5k2#

首先,运算符优先级 * 仅 * 控制运算符与操作数的分组,它 * 不 * 影响求值顺序。
其次,请记住,||&&运算符都是从左到右计算和短路的。首先计算左操作数,并应用任何副作用。根据结果(&&0||为非零)右操作数根本不会被计算 *,并且不会应用右手操作数中的任何副作用。
在声明中

printf("%d ", (i = j) || (j = k));

首先计算表达式(i = j),因此i现在具有j的值,即8。由于结果为非零,因此根本不计算表达式(j = k);表达式的结果是1,并且j不被更新(它保持为8)。
在声明中

printf("%d ", ++i || ++j && ++k);

首先计算表达式++i(因此i现在的值为2);因为结果是非零的,所以++j && ++k根本不被计算(运算符优先意味着表达式被解析为++i || (++j && ++k)),并且jk都不被更新,所以它们都保持为1

bwleehnv

bwleehnv3#

让我们考虑第一个代码片段

1) i = 7; j = 8; k = 9;
   printf("%d ",(i = j) || (j = k));                           
   printf("%d %d %d\n", i, j, k);

printf的第一次调用中

printf("%d ",(i = j) || (j = k));

使用具有逻辑OR运算符的表达式。
来自C标准(6.5.14逻辑或运算符)
3The||如果运算符的任一个操作数比较不等于0,则运算符将产生1;否则,结果为0。结果的类型为int。
4不像按位|操作员||操作符保证从左到右求值;如果第二个操作数被求值,则在第一个操作数和第二个操作数的求值之间存在一个序列点。如果第一个操作数比较不等于0,则不对第二个操作数求值。
由于表达式的左操作数(i = j)不等于0,因此不计算右操作数(j = k)
因此,在赋值后(i = j)i等于j,即等于8。根据C标准引用的表达式的结果等于1
因此,第一次调用printf时,输出的逻辑OR运算符表达式的结果等于1,下一次调用printf时,输出的变量ijk等于889
现在让我们考虑第二个代码片段

2) i = 1; j = 1; k = 1;
   printf("%d ", ++i || ++j && ++k);
   printf("%d %d %d\n", i, j, k);

printf的第一次调用中,还使用了一个带有逻辑OR和逻辑AND运算符的表达式。

printf("%d ", ( ++i ) || ( ++j && ++k ));

同样,由于逻辑OR运算符的操作数++i不等于0(它的值在递增i之后等于2),则逻辑OR运算符( ++j && ++k )的右操作数不被求值,并且printf的调用相应地输出1211 .
如果要按以下方式交换逻辑OR运算符的操作数,那么考虑第一个调用或printf是很有趣的

printf("%d ", ++j && ++k || ++i );

使用的表达式等效于

printf("%d ", ( ++j && ++k ) || ( ++i ) );

具有逻辑OR运算符的表达式的第一个操作数是具有逻辑AND运算符的表达式。
根据C标准(6.5.13逻辑与运算符)
3**如果&&运算符的两个操作数比较都不等于0,则&&运算符将产生1;**否则,结果为0。结果的类型为int。
4与按位二元&运算符不同,&&运算符保证从左到右求值;如果第二个操作数被求值,则在第一个操作数和第二个操作数的求值之间存在序列点。如果第一个操作数比较等于0,则第二个操作数不被求值。
在这种情况下,由于逻辑AND运算符的第一操作数++j不等于0(在递增j后表达式的值等于2),则第二个操作数++k也将被计算,并且其值也不等于0(在递增k之后,它等于2)。因此,由于此时逻辑OR运算符的左操作数( ++j && ++k )不等于0,则右操作数++i将不被求值,结果输出将是1, 1, 2, 2
另外,请考虑以下代码片段

int i = 1;

printf( "%d\n", i++ || i++ || i++ );
printf( "%d\n", i );

第一次调用printf将输出1,而第二次调用printf将输出2
然后呢

int i = 1;

printf( "%d\n", i++ && i++ && i++ );
printf( "%d\n", i );

在这种情况下,第一次调用printf也将输出1(逻辑OR和逻辑AND运算符的结果是10,根据C标准提供的报价)。第二个调用或printf将输出4,因为将计算具有逻辑AND运算符的表达式的所有操作数。

nhhxz33t

nhhxz33t4#

  • printf("%d ",(i = j) || (j = k));是混淆的垃圾,将替换为:
i = j;
if(i > 0)
{
  printf("%d ", i > 0);
}
else // i == 0
{
  j=k;
  printf("%d ", j > 0);
}
  • printf("%d ", ++i || ++j && ++k);是混淆的垃圾,将替换为:
i++;
if(i > 0)
{
  printf("%d ", i > 0);
}
else // i == 0
{
  j++;
  if(j > 0)
  {
    k++;
    printf("%d ", k > 0);
  }
  else
  {
    printf("%d ", j > 0);
  }
}

这本书的作者想让你学到什么:

我们实际学到的:

  • 有很多糟糕的C书。总是使用KISS Principle。或者正如Brian Kernighan所说:

调试的难度是编写代码的两倍,因此,如果你尽可能聪明地编写代码,那么你就没有聪明到可以调试它。

  • 不要在同一个表达式中不必要地混合使用不同的运算符。
  • 当运算符优先级和求值顺序不明显时,请始终使用括号。例如,++i || (++j && ++k),因为每个人可能都不清楚&&的优先级高于||。但这并不重要。

在这个练习之后我们必须忘掉危险的废话:

  • 在同一个表达式中将++操作符与其他操作符混合使用是危险的,容易出错,应该避免。这种脆弱的垃圾代码能够按预期工作的唯一原因是因为||&&具有定义良好的从左到右求值,并且操作数之间有一个 * 序列点 *。这通常不是C操作符的情况。

例如,(i = j) | (j = k)(按位OR而不是逻辑)是未定义的行为,是完全破坏的代码。
Why are these constructs using pre and post-increment undefined behavior?
或者以更紧凑的形式:Why can't we mix increment operators like i++ with other operators?

相关问题