Мой вопрос: что делает этот код (из http://www.joelonsoftware.com/articles/CollegeAdvice.html):
while (*s++ = *t++);
веб-сайт говорит, что приведенный выше код копирует строку, но я не понимаю, почему...
это связано с указателями?
Мой вопрос: что делает этот код (из http://www.joelonsoftware.com/articles/CollegeAdvice.html):
while (*s++ = *t++);
веб-сайт говорит, что приведенный выше код копирует строку, но я не понимаю, почему...
это связано с указателями?
Это эквивалентно этому:
while (*t) {
*s = *t;
s++;
t++;
}
*s = *t;
Когда char, что t
указывает на '\0'
, цикл while будет завершен. До тех пор он скопирует char, который t
указывает на char, на который указывает s
, затем увеличивайте s
и t
, чтобы указать на следующий char в своих массивах.
Это так много происходит под обложками:
while (*s++ = *t++);
Переменные s
и t
- это указатели (почти наверняка символы), s
- предназначение. Следующие шаги иллюстрируют, что происходит:
*t
) копируется в s (*s
), один символ.s
и t
оба увеличиваются (++
).while
).while
продолжается до тех пор, пока этот символ не будет равен нулю (конец строки в C
).Фактически, это:
while (*t != 0) {
*s = *t;
s++;
t++;
}
*s = *t;
s++;
t++;
но выписано гораздо более компактным способом.
Предположим, что s
и t
являются char *
, которые указывают на строки (и предположим, что s
не менее, чем t
). В C все строки заканчиваются на 0
(ASCII "NUL" ), правильно? Итак, что это делает:
*s++ = *t++;
Сначала он выполняет *s = *t
, копируя значение в *t
до *s
. Затем оно s++
, поэтому s
теперь указывает на следующий символ. И затем он делает t++
, поэтому t
указывает на следующий символ. Это связано с приоритетом оператора и префиксом против приращения/уменьшения постфикса.
Приоритет оператора - это порядок, в котором операторы разрешены. Для простого примера посмотрите:
4 + 2 * 3
Это 4 + (2 * 3)
или (4 + 2) * 3
? Ну, мы знаем, что это первый из-за приоритета - двоичный *
(оператор умножения) имеет более высокий приоритет, чем двоичный +
(оператор сложения) и сначала разрешен.
В *s++
мы имеем унарный *
(оператор разыменования указателя) и унарный ++
(оператор приращения postfix). В этом случае ++
имеет более высокий приоритет (также называемый "привязывать более жестким" ), чем *
. Если бы мы сказали ++*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);
Но вы уже это знали, или вы скоро зададите вопрос о каждом любимом веб-сайте об этом.;)
* Собственно, они имеют одинаковый приоритет, но это разрешено разными правилами. В этой ситуации они имеют более низкий приоритет.
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);
больше похож на него
EDIT: Исправлена ошибка компиляции. Переменная tmp
должна быть объявлена вне цикла.
Аспект, который таинственен в этом, - это порядок операций. Если вы посмотрите на спецификацию языка 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
".
Он работает, копируя символы из строки, на которую указывает "t
", в строку, на которую указывает "s
". Для каждой копии символов оба указателя увеличиваются. Цикл завершается, когда он находит символ NUL
(равный нулю, следовательно, выход).
НАМЕКИ:
Совет:
он копирует строку, потому что массивы всегда передаются по ссылке, а строка - это просто массив char. В основном, что происходит (если я правильно помню термин), арифметика указателя. Здесь немного больше информации из wikipedia на c массивах.
Вы сохраняете значение, которое было разыменовано из t в s, а затем переместилось к следующему индексу через ++.
Скажите, что у вас есть что-то вроде этого:
char *someString = "Hello, World!";
someString указывает на первый символ в строке - в этом случае "H".
Теперь, если вы увеличиваете указатель на единицу:
someString++
someString теперь укажет на "e".
while ( *someString++ );
будет зацикливаться до тех пор, пока все точки someString at не станут NULL, что является сигналом конца строки ( "NULL Terminated" ).
И код:
while (*s++ = *t++);
равно:
while ( *t != NULL ) { // While whatever t points to isn't NULL
*s = *t; // copy whatever t points to into s
s++;
t++;
}
Язык программирования C (K & R) Брайана У. Кернигана и Денниса М. Ричи дает подробное объяснение этого.
Второе издание, стр. 104:
5.5 Указатели и функции символов
Строковая константа, написанная как
"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
- это указатель, инициализированный для указания на константу строки; указатель может быть впоследствии изменен, чтобы указать в другом месте, но результат undefined, если вы попытаетесь изменить содержимое строки.+---+ +--------------------+ 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
не будет записываться, как мы показали выше. Опытные программисты на C предпочли бы/* 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++); }
Хотя это может показаться загадочным как первый взгляд, нотационное удобство значимо, и идиома должна быть освоена, потому что вы увидите, часто ли это в программах на C.
strcpy
в стандартной библиотеке (<string.h>
) возвращает целевую строку в качестве ее значения функции.
Это конец соответствующих частей этого раздела.
PS: Если вам понравилось это читать, подумайте о покупке копии K & R - это не дорого.
Да, это связано с указателями.
Способ чтения кода таков: "значение, на которое указывает указатель" s "(который получает приращение после этой операции), получает значение, на которое указывает указатель" t "(который получает приращение после этой операции все значение этой операции оценивается значением символа, скопированным, итерации по этой операции до тех пор, пока это значение не станет равным нулю". Поскольку значение нулевого терминатора строки является символьным значением нуля ('/0'), цикл будет повторяться до тех пор, пока строка не будет скопирована из местоположения, на которое указывает t, до местоположения, на которое указывает s.
Да, это использует указатели, а также выполняет всю работу при оценке условия while. C позволяет условным выражениям иметь побочные эффекты.
Указатели указателя "*" указателя дерева s и t.
Оператор приращения ( "++" ) увеличивает указатели s и t после присваивания.
Цикл завершается при условии нулевого символа, который оценивается как false в C.
Один дополнительный комментарий... это не безопасный код, так как он ничего не делает, чтобы обеспечить достаточную выделенную память.
запускает цикл while....
* s = * t идет первым, это присваивает тому, что t указывает на то, что s точек at. т.е. копирует символ из строки t в строку s.
то, что назначается, передается условию while... любое ненулевое значение является "истинным", поэтому оно будет продолжаться, а 0 будет ложным, оно остановится... и просто произойдет конец строки также ноль.
s ++ и t ++ они увеличивают указатели
и все это снова начинается
поэтому он продолжает назначать цикл, перемещая указатели, пока не достигнет 0, это конец строки
Вопрос, который я задал, следующий ответ был закрыт как дубликат этого вопроса, поэтому я копирую соответствующую часть ответа здесь.
Фактическое семантическое объяснение цикла while будет примерно таким:
for (;;) {
char *olds = s; // original s in olds
char *oldt = t; // original t in oldt
char c = *oldt; // original *t in c
s += 1; // complete post increment of s
t += 1; // complete post increment of t
*olds = c; // copy character c into *olds
if (c) continue; // continue if c is not 0
break; // otherwise loop ends
}
Порядок сохранения s
и t
, а порядок, который s
и t
увеличивается, может быть изменен. Сохранение *oldt
до c
может произойти в любое время после сохранения oldt
и до c
. Назначение c
- *olds
может происходить в любое время после c
и olds
. На обратной стороне моего конверта это работает по меньшей мере на 40 различных интерпретаций.
Ну, это справедливо только в случае char, если нет \0, и это целочисленный массив, который программа выйдет из строя, потому что будет адрес, элементы которого не являются частью массива или указатель, если система имеет память, которая была выделена с помощью malloc, тогда система будет сохранять память