Как "while (* s ++ = * t ++)" копировать строку? - PullRequest
52 голосов
/ 01 мая 2009

У меня вопрос, что делает этот код (с http://www.joelonsoftware.com/articles/CollegeAdvice.html):

while (*s++ = *t++);

на сайте написано, что код выше копирует строку, но я не понимаю, почему ...

это имеет отношение к указателям?

Ответы [ 16 ]

39 голосов
/ 01 мая 2009

Это эквивалентно этому:

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

Когда символ, на который указывает t, равен '\0', цикл while прерывается. До тех пор он будет копировать символ, на который t указывает на символ, на который указывает s, затем увеличивает s и t, чтобы указывать на следующий символ в их массивах.

29 голосов
/ 01 мая 2009

Так много всего происходит под одеялом:

while (*s++ = *t++);

Переменные s и t - это указатели (почти наверняка символы), s - пункт назначения. Следующие шаги иллюстрируют, что происходит:

  • содержимое t (*t) копируется в s (*s), один символ.
  • s и t оба увеличиваются (++).
  • присваивание (копия) возвращает скопированный символ (в while).
  • while продолжается до тех пор, пока этот символ не станет нулевым (конец строки в C).

По сути, это:

while (*t != 0) {
    *s = *t;
    s++;
    t++;
}
*s = *t;
s++;
t++;

но записано гораздо более компактно.

19 голосов
/ 01 мая 2009

Предположим, s и t - это char * с, которые указывают на строки (и предположим, что s по крайней мере равен t). В Си все строки заканчиваются на 0 (ASCII "NUL"), правильно? Так что же это делает:

*s++ = *t++;

Сначала выполняется *s = *t, копирование значения с *t на *s. Затем он делает s++, поэтому s теперь указывает на следующий символ. И тогда он делает t++, поэтому t указывает на следующий символ. Это связано с приоритетом оператора и префиксом против постфиксного увеличения / уменьшения .

Приоритет оператора - это порядок, в котором разрешаются операторы. Для простого примера посмотрите:

4 + 2 * 3

Это 4 + (2 * 3) или (4 + 2) * 3? Ну, мы знаем, что это первый из-за приоритет - двоичный файл * (оператор умножения) имеет более высокий приоритет, чем двоичный файл + (оператор сложения), и разрешается первым.

В *s++ мы имеем унарный * (оператор разыменования указателя) и унарный ++ (оператор приращения постфикса). В этом случае ++ имеет более высокий приоритет (также называемый «крепче связывать»), чем *. Если бы мы сказали ++*s, мы бы увеличили значение на *s вместо адреса , на который указывает s, поскольку префикс имеет меньшее значение приоритет * как разыменование, но мы использовали постфикс приращение, которое имеет более высокий приоритет. Если бы мы хотели использовать приращение префикса, мы могли бы сделать *(++s), так как круглые скобки переопределили бы все более низкие приоритеты и заставили бы ++s идти первым, но это привело бы к нежелательному побочному эффекту - оставить пустой символ начало строки.

Обратите внимание, что то, что оно имеет более высокий приоритет, не означает, что это произойдет первым Постфиксный прирост, в частности, происходит после значения, которое используется, поэтому его *s = *t происходит до s++.

Итак, теперь вы понимаете *s++ = *t++. Но они поместили это в цикл:

while(*s++ = *t++);

Этот цикл ничего не делает - действие находится в состоянии. Но проверьте это условие - оно возвращает «false», если *s равно 0, что означает, что *t было 0, что означает, что они были в конце строки (yay для ASCII «NUL»). Таким образом, этот цикл зацикливается, пока в t есть символы, и покорно копирует их в s, постепенно увеличивая s и t. Когда этот цикл завершается, s завершается NUL и является правильной строкой. Единственная проблема в том, что s указывает на конец. Держите под рукой еще один указатель, который указывает на начало s (т.е. s перед циклом while()) - , что будет вашей скопированной строкой:

char *s, *string = s;
while(*s++ = *t++);
printf("%s", string); // prints the string that was in *t

В качестве альтернативы, проверьте это:

size_t i = strlen(t);
while(*s++ = *t++);
s -= i + 1;
printf("%s\n", s); // prints the string that was in *t

Мы начали с получения длины, поэтому, когда мы закончили, мы сделали больше арифметики с указателями, чтобы вернуть s в начало, где оно началось.

Конечно, этот фрагмент кода (и все мои фрагменты кода) игнорируют проблемы с буфером для простоты. Лучшая версия такова:

size_t i = strlen(t);
char *c = malloc(i + 1);
while(*s++ = *t++);
s -= i + 1;
printf("%s\n", s); // prints the string that was in *t
free(c);

Но вы уже знали об этом или скоро зададите вопрос об этом на любимом веб-сайте каждого. ;)

* На самом деле, они имеют одинаковый приоритет, но это разрешается по другим правилам. Они эффективно имеют более низкий приоритет в этой ситуации.

16 голосов
/ 20 сентября 2011
while(*s++ = *t++);

Почему люди думают, что это эквивалентно:

while (*t) {
    *s = *t;
    s++;
    t++;
}
*s = *t; /* if *t was 0 at the beginning s and t are not incremented  */

когда это явно не так.

char tmp = 0;
do {
   tmp = *t;
   *s = tmp;
   s++;
   t++;
} while(tmp);

больше нравится

РЕДАКТИРОВАТЬ: Исправлена ​​ошибка компиляции. Переменная tmp должна быть объявлена ​​вне цикла.

3 голосов
/ 01 мая 2009

Таинственный аспект этого - порядок операций. Если вы посмотрите спецификацию языка C, она скажет, что в этом контексте порядок операций следующий:

1. * operator
2. = (assignment) operator
3. ++ operator

Таким образом, цикл while становится по-английски:

while (some condition):
  Take what is at address "t" and copy it over to location at address "s".
  Increment "s" by one address location.
  Increment "t" by one address location.

Теперь, что такое "какое-то условие"? Спецификация C lang также говорит, что значением выражения присваивания является само присвоенное значение, которое в данном случае равно *t.

Таким образом, «некоторое условие» - это «t указывает на что-то, что ненулевое», или, проще говоря, «тогда как данные в местоположении t не NULL».

1 голос
/ 20 сентября 2011

Язык программирования C (K & R) Брайана У. Кернигана и Денниса М. Ричи дает подробное объяснение этого.

Второе издание, стр. 104:

5.5. Символьные указатели и функции

A строковая константа , записанная как

"I am a string"

это массив символов. Во внутреннем представлении массив заканчивается нулевым символом '\0', чтобы программы могли найти его конец. Таким образом, длина в хранилище на один больше числа символов в двойных кавычках.

Возможно, самое распространенное вхождение строковых констант - это аргументы функций, как в

printf("hello, world\n");

Если в программе появляется такая символьная строка, доступ к ней осуществляется через символьный указатель; printf получает указатель на начало массива символов. То есть строковая константа доступна указателем на ее первый элемент.

Строковые константы не обязательно должны быть аргументами функций. Если pmessage объявлено как

char *pmessage;

тогда утверждение

pmessage = "now is the time";

присваивает pmessage указатель на массив символов. Это не строковая копия; участвуют только указатели. C не предоставляет никаких операторов для обработки всей строки символов как единого целого.

Существует важное различие между этими определениями:

char amessage[] = "now is the time"; /* an array */
char *pmessage = "now is the time"; /* a pointer */

amessage - это массив, достаточно большой, чтобы содержать последовательность символов и '\0', которая его инициализирует. Отдельные символы в массиве могут быть изменены на amessage, всегда ссылаясь на одно и то же хранилище. С другой стороны, pmessage - указатель, инициализированный для указания на строковую константу; впоследствии указатель может быть изменен, чтобы указывать в другом месте, но результат будет неопределенным, если вы попытаетесь изменить содержимое строки.

          +---+       +--------------------+
pmessage: | o-------->| now is the time \0 |
          +---+       +--------------------+

          +--------------------+
amessage: | now is the time \0 |
          +--------------------+

Мы проиллюстрируем больше аспектов указателей и массивов, изучая версии двух полезных функций, адаптированных из стандартной библиотеки. Первая функция - strcpy(s,t), которая копирует строку t в строку s. Было бы неплохо просто сказать s = t, но это копирует указатель, а не символы. Чтобы скопировать символы, нам нужен цикл. Версия массива первая:

/* strcpy: copy t to s; array subscript version */
void strcpy(char *s, char *t)
{
    int i;

    i = 0;
    while((s[i] = t[i]) != '\0')
        i ++;
}

Для контраста вот версия strcpy с указателями:

/* strcpy: copy t to s; pointer version 1 */
void strcpy(char *s, char *t)
{
    while((*s = *t) != '\0')
    {
        s ++;
        t ++;
    }
}

Поскольку аргументы передаются по значению, strcpy может использовать параметры s и t любым удобным для него способом. Здесь они представляют собой удобно инициализированные указатели, которые маршируют по массивам персонажа за раз, пока '\0', заканчивающийся t, не будет скопирован в s.

На практике strcpy не будет написано, как мы показали выше. Опытные программисты на Си предпочли бы

/* strcpy: copy t to s; pointer version 2 */
void strcpy(char *s, char *t)
{
    while((*s++ = *t++) != '\0')
        ;
}

Это перемещает приращение s и t в тестовую часть цикла. Значение *t++ - это символ, на который t указывал до увеличения t; постфикс ++ не изменяется t до тех пор, пока не будет выбран этот символ. Таким же образом, символ сохраняется в старой позиции s до увеличения s. Этот символ также является значением, которое сравнивается с '\0' для управления циклом. Чистый эффект заключается в том, что символы копируются с t до s, вплоть до завершающего '\0'.

В качестве последней аббревиатуры, обратите внимание, что сравнение с '\0' является излишним, поскольку вопрос заключается лишь в том, является ли выражение нулевым. Так что функция, скорее всего, будет записана как

/* strcpy: cope t to s; pointer version 3 */
void strcpy(char *s, char *t)
{
    while(*s++ = *t++);
}

Хотя это может показаться загадочным на первый взгляд, удобство обозначений значительно, и идиома должна быть освоена, потому что вы будете часто видеть это в программах на Си.

strcpy в стандартной библиотеке (<string.h>) возвращает целевую строку в качестве значения функции.

Это конец соответствующих частей этого раздела.

PS: Если вам понравилось это чтение, подумайте о покупке копии K & R - это не дорого.

1 голос
/ 01 мая 2009

НАМЕКАЕТ:

  • Что делает оператор '='?
  • Каково значение выражения "a = b"? Например: если вы делаете «c = a = b», какое значение получает c?
  • Что завершает строку C? Оценивает ли это истину или ложь?
  • В "* s ++" какой оператор имеет более высокий приоритет?

КОНСУЛЬТАЦИЯ:

  • Используйте взамен strncpy ().
1 голос
/ 01 мая 2009

Он работает путем копирования символов из строки, на которую указывает 't', в строку, на которую указывает 's'. Для каждого символа копируются оба указателя. Цикл завершается, когда он находит символ NUL (равный нулю, следовательно, выход).

0 голосов
/ 26 марта 2018

Многие приверженцы языка С убеждены, что "while (* s ++ = * t ++)" это подлинная милость.

В условное выражение цикла "while" вставляются три побочных эффекта (смещение одного указателя, смещение второго указателя, присваивание).

Тело цикла в результате оказалось пустым, поскольку все функции помещены в условное выражение.

0 голосов
/ 08 ноября 2014

Что ж, это верно только в случае символа char, если \ 0 отсутствует, и если это целочисленный массив, программа завершится сбоем, поскольку будет адрес, элементы которого не являются частью массива или указателя, если система имеет память, которая была выделена с помощью malloc, то система будет продолжать выделять память

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...