while(*s = *t ) ; в C

#c

#c

Вопрос:

В языке программирования C от K amp; R я продолжаю видеть коды, подобные
while(*s = *t ) ; это всего лишь один пример из главы 5 «Указатели». Это полностью сбивает меня с толку, потому что я думал, что цикл while был следующим:

 while (something is true)
    do what your assigned to do
  

Это какой-то особый случай с C или я что-то пропустил? Будучи начинающим программистом на C, это просто не укладывалось у меня в голове. Любое объяснение того, как while(*s = *t ) ; работает, было бы отличным.

Меня не смущает, что указывает выражение (*s = *t ) . Мне просто интересно, почему в теле цикла while ничего нет.

Комментарии:

1. В каком контексте находится код? Это пример того, как выполнять итерацию по строке? Если это из учебника, то, может быть, они учат вас, как, прежде чем что-то с этим делать?

2. I'm just wondering why there is nothing in the body of the while loop. Потому что этот код предназначен только для компактного копирования строки в другую в несколько строк.

3. Никакой другой работы не требуется. Условие тоже является оператором, оно может иметь побочные эффекты. Единственное, что требуется, это то, что он должен вычисляться до bool .

4. Вам не следует читать K amp; R, если вы не разбираетесь в археологии программного обеспечения. Он соответствует устаревшей версии стандарта C, который с тех пор пересматривался в 1995, 1999 и 2011 годах. Помимо этого, это просто страница за страницей проповеди опасной практики, усеянной множеством ошибок .

5. Я не думаю, что вы найдете лучшую книгу для изучения C, чем K amp; R. C — старый язык, и неплохо бы немного изучить историю. Кроме того, вы видите код, подобный приведенному в вопросе, в каждом проекте. Вы не можете сказать, что знаете C, если вам не известны «плохие практики».

Ответ №1:

Вы можете представить себе код примерно так, если разбить его на простейшие команды:

 while (1) {
    char c = *t;
    *s = c;
    s  ;
    t  ;
    if (!c) {
        break;
    }
}
  

Чтобы объяснить, почему это работает, вы присваиваете *t значение *s , а затем оцениваете результат ( a=b вычисляется до присваиваемого значения). Если оно ненулевое, оно продолжается. Если оно равно нулю, цикл завершается.

Комментарии:

1. может быть, изменить !c на c == ? Я думаю, что часть путаницы заключается в том, как цикл выходит из

2. Я хотел сохранить там исходное выражение, но да, это сделало бы исходный код более понятным.

3. может быть подкачка t и s , т. е. копии из t в s ?

4. Верно, я просто предположил, что s = source, t = target и написал код, не перепроверяя оригинал.

Ответ №2:

s и t наиболее вероятны char * , что означает, что они завершаются нулевым значением. Поэтому, как только вы выполните итерацию по ним, они вернут 0. 0 равно false в C

Комментарии:

1. Скорее всего, это не так == .

2. Вы имеете в виду, что это должно быть == вместо = ? Это неверно, предполагается, что это присваивание.

3. да, изменил это, не увидел ; в конце

Ответ №3:

Здесь происходит несколько вещей:

  • Оператор присваивания x = y на самом деле является выражением (равным только что присвоенному значению), которое само по себе может использоваться как часть более сложного выражения. Это полезно для множественных назначений ( x = y = z ) и условий цикла, подобных while ((c = getchar()) != EOF) .
  • В отличие, скажем, от Pascal или Java, C не является строгим в отношении логических значений. Любое ненулевое значение является истинным (т. Е. вызывает выполнение тела инструкции if , while for или 0 ), а нуль (будь то 0.0 , '' NULL или, ,,) является ложным.
  • Строки представляются в виде массива символов (или указателя на такой массив), заканчивающегося символом NUL (ноль). Итак, если p это указатель на строку, *p == 0 это означает, что он находится в конце строки.
  • Постфиксный оператор увеличивает свою переменную, но возвращает старое значение.
  • while Цикл может иметь пустое тело.

Если собрать все это вместе, while(*s = *t ); это допустимый оператор C.

Перемещение приращений в тело цикла дает эквивалентный оператор:

 while (*s = *t)
{
   s  ;
   t  ;
}
  

И перемещение присваивания в тело цикла дает:

 while (1)
{
   if (!(*s = *t))
   {
      break;
   }

   s  ;
   t  ;
}
  

Или, что эквивалентно,

 while (1)
{
   // Copy one char from string t to string s.
   *s = *t;

   // If it was the string terminator, exit the loop. 
   if (*s == '')
   {
      break;
   }

   // Move both pointers to the next character.
   s  ;
   t  ;
}
  

Исходное выражение while(*s = *t ); является просто дополненной кодом версией приведенного выше.

Комментарии:

1. Превосходное пошаговое объяснение!!

Ответ №4:

В C каждая строка завершается '' символом, код ASCII которого является 0 .

Следовательно, когда вся строка в t копируется в s . Цикл завершится, потому что последний скопированный символ был, 0 который false находится в C.

ВНИМАНИЕ: при выполнении таких действий следует позаботиться о точках последовательности.

Ответ №5:

Выражение (*s = *t ) читает

  1. прочитайте символ, на который указывает t
  2. сохраните этот символ в месте, указанном s
  3. продвинуть t указатель на 1 позицию
  4. продвинуть s указатель на 1 позицию
  5. возвращает значение (символ), скопированное

и while(); инструкция является

  • пока значение не равно нулю (т. Е. мы еще не скопировали нулевой символ, завершающий строку), ничего не делайте

где ‘ничего не делать’ — это пустая инструкция, представленная единственной точкой с запятой в конце.

Ответ №6:

Другие уже ответили на этот вопрос. Хотя *s = *t , к сожалению, это довольно часто встречающаяся строка в C, я просто хочу указать на следующее:

  • Использование вместе с другими операторами в одном и том же выражении является опасной практикой. Всегда есть вероятность получить код, который зависит от порядка вычисления, что является неопределенным поведением. И есть также вероятность вызова неопределенного поведения в случае, если вы намеренно или случайно дважды измените одну и ту же переменную без промежуточных точек последовательности.

    Кроме того, как мы можем видеть из этого примера, объединение с другими операторами делает код излишне трудным для чтения и заставляет программиста и читателя быть на 100% уверенными во всех правилах приоритета операторов в C.

    Оптимальный код написан таким образом, что каждое выражение имеет только один побочный эффект. Следовательно, оптимальный код не смешивает с другими операндами в том же выражении. (Смотрите правила 12.2 12.4 12.13 MISRA-C: 2004, для некоторых примеров того, где может вызывать ошибки.)

  • Использование присваивания внутри выражений управляющих операторов рассматривается как плохая практика. Это затрудняет чтение кода и может привести к путанице = с == . Хорошие компиляторы предостерегают от использования управляющих операторов = inside . (См. MISRA-C:2004 13.1)

Итак, что вам следует сделать, так это переписать такой опасный, нечитаемый код во что-то более понятное и безопасное. Например:

 *s = *t

while(*t != '')
{
  s  ,
  t  ;
  *s = *t;
} 
  

Комментарии:

1. не будет ли цикл do ..while лучше в вашем примере?

2. @mch Да, do-while так же хорош. Лично у меня нет проблем с do-while, но по какой-то причине есть много людей, которым это трудно читать. Поэтому я специально создал цикл while.

Ответ №7:

Из стандартов C99

   An assignment expression has the value of the left operand after the assignment
  

Когда значение, указанное t , равно NULL . он будет назначен в ячейку памяти, указанную s . И общее значение этого выражения становится NULL . Это приведет к разрыву цикла while.

Комментарии:

1. Переменная, t вероятно, никогда не получает NULL значения, и код никогда не проверяет его. Кроме того, даже если t равно NULL, оно НЕ будет присвоено s .

2. Variable t probably never gets NULL value, and the code never tests it. Согласен с этим. Это условие должно быть проверено. even if t is NULL it will NOT be assigned to s . Не согласен. Он всегда будет присваиваться s (предполагается, что s и n являются указателями).

3. Нет. Указанные символы копируются, указатели — нет. Они просто увеличиваются «параллельно», но никогда одно не копируется в другое.

4. Да, верно, тогда никогда не копируется в другой. Потому что мы откладываем указатель, используя * . Обновите ответ, чтобы он был более понятным. Спасибо!