Для цикла с указателем в C
Я не понимаю, что делает указатель в цикле for
. Что делает *p
в следующем цикле?
char str[128] = "Some Text";
char *p;
for (p = str; *p /*what does this mean?*/; p++)
{
// Code
}
Я понимаю все остальное, но почему не *p
как p > 3
или что-то в этом роде?
Почему это одно?
Почему это написано именно так?
Ответы
Ответ 1
В булевом контексте, таком как условие цикла for
, каждое выражение в C имеет значение true (отличное от нуля) или false (ноль).
Вы хотите, чтобы цикл for
завершился, когда он достигнет конца строки.
В C каждая строка заканчивается символом '\0'
, который практически равен 0
. Итак, когда цикл for
достигает конца строки, *p
оценивается как '\0'
, который равен 0
, который вычисляется как false, что завершает цикл for
.
Ответ 2
Цикл for завершится, если все, что находится между двумя ;
в инструкции, равно нулю (false). *p
dereferences p и возвращает теги char
, p
. Согласно Деннис Ричи "C обрабатывает строки как массивы символов, условно заканчивающихся маркером". Этот маркер является нулевым символом с нулевым значением (ASCII). Итак, это для цикла:
for (p = str; *p; p++)
эквивалентно этим
for (p = str; *p != '\0'; p++)
for (p = str; *p != 0; p++)
for (p = str; p[0] != '\0'; p++)
Другое имя для нуль-завершающего символа является дозорным или согласно Дональду Кнуту "фиктивная ценность" (Art of Computer Programming, Volume 1). Ниже приведена диаграмма строки str
, индексы (смещения от начала) каждого символа и значения в каждом индексе:
![введите описание изображения здесь]()
Для полноты и после запроса в комментариях здесь находится то, что отладчик видит в блоке памяти, который занимает str
:
0x00007fffffffe6a0:
0x53 0x6f 0x6d 0x65 0x20 0x54 0x65 0x78 0x74 0x00 0x00 0x00 0x00 0x00 0x00 0x00
S o m e T e x t
- Шестнадцатеричное значение в первой строке - это адрес (64 бит) этого блока памяти. То, что
p
указывает на начало цикла for.
- Во второй строке вы видите шестнадцатеричные значения букв в строке. Вы можете увидеть таблицу ASCII здесь. Последний char в вашей строке
t
с шестнадцатеричным значением 0x74
. После этого вы получите нулевой символ строки 0x00
. Затем вы видите еще несколько нулевых символов, потому что я построил в режиме отладки и инициализировал нулевой инициализатор компилятора. Обычно вы видите мусор (по-видимому, случайные значения)
- На третьей строке я добавил символы вашей строки для справки
Я понимаю, что на данный момент вы находитесь на стремительной кривой обучения с указателями на C, но в конце концов вы сможете сказать "I C the point"
Ответ 3
Это можно было бы переписать так:
for (p = str; *p != '\0'; p++)
{
// Code
}
В C строка всегда должна быть завершена нулевым символом, который является таким же, как "\ 0" или 0
.
Ответ 4
Давайте проанализируем его сухим, но глухим способом!
Или как D. Ritchie сказал бы: пусть это сделает с силой языка ассемблера и удобством... ассемблера.
Я попытаюсь объяснить все необходимые аспекты, ссылаясь на стандарт ISO/IEC: 9899 (основное внимание) - C99.
(Стиль почты мотивирован фразой Дональда Кнута "Наука - это то, что мы хорошо понимаем, чтобы объяснить компьютеру. Искусство - это все, что мы делаем".)
Прежде всего, давайте посмотрим, что именно должен делать for
-loop!
Ссылаясь на ISO/IEC: 9899 6.8.5 "Итерационные утверждения"
Семантика
4 Оператор итерации приводит к тому, что оператор, который повторяется тело цикла, будет выполняться повторно , пока контрольное выражение не сравнится с 0.
Пока ничего нового я не догадываюсь, так что давайте его получим:
6.8.5.3 Оператор for
1 Утверждение for ( clause-1 ; expression-2 ; expression-3 ) statement
ведет себя следующим образом: выражение выражение-2 является управляющим выражением, которое оценивается перед каждым выполнением тела цикла....
Итак, теперь мы знаем, что тело (в вашем случае // Code
) будет выполняться так долго, как предварительно оцененное значение вашего *p
не равно нулю.
... Выражение выражение-3 оценивается как выражение void после каждого выполнения тела цикла. [...]
Итак, теперь мы знаем (я полагаю, что определение p++
не требуется?!), что для каждой итерации p
увеличивается, поэтому может быть изменение в *p
.
Следующий пункт не связан, но я добавляю его, так как это делает семантическую часть for
полной и ее хорошо знать, так как причина, почему for(;;)
является inf-loop.
2 (---) Оба предложения-1 и выражение-3 могут быть опущены. Пропущенное выражение-2 заменяется ненулевой константой.
Хорошо, что сухая, но обогащенная информацией часть того, что делает цикл for
в вашем случае.
Теперь перейдем к арифметике указателя:
6.5.6 Аддитивные операторы
Ограничения
2 Для добавления оба операнда должны иметь арифметический тип, или один операнд должен быть указателем на тип объекта, а другой должен иметь целочисленный тип. (Приращение эквивалентно добавлению 1.)
Итак, в вашем случае вы добавляете 1 (целое число) к типу "указатель на объект".
Что эквивалентно увеличению адреса по размеру его указательного типа, как показано на этом рисунке tomislav kostic:
![CC BY-SA 3.0 от tomislav kostic]()
Теперь посмотрим, что на самом деле делает *p
.
6.5.3.2 Операторы адреса и косвенности
Ограничения
[...]
2 Операнд унарного * оператора должен иметь тип указателя.
Семантика
[...]
4 Оператор унарного * обозначает косвенность. Если операнд указывает на функцию, результат будет обозначать функцию; если указывает на объект, результатом будет lvalue, обозначающий объект. Если операнд имеет тип '' указатель на тип, результат имеет тип ''. Если для указателя присвоено недопустимое значение, поведение унарного * оператора undefined.
Это немного сухое снова 1 но для лучшего понимания это может быть обратное проектирование:
6.5.2.1 Подстрока массива
[...]
Семантика
2 Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным обозначением элемента объекта массива. Определение индексного оператора [] состоит в том, что E1 [E2] идентичен (* ((E1) + (E2))).
Итак, *((p)+(0))
то, что (поскольку p+0
совпадает с p
... очевидным), равно p[0]
, ничего не делает для оценки объекта p
.
И поскольку мы знаем, что expression-2
цикла for прерывает итерацию, если она оценивает 0
, мы можем сказать, что она такая же, как p[0] != 0
.
Теперь последний шаг
Позволяет просто взглянуть на друга C-Coder; JSSCA... Нет, подождите... наш друг был вызван... ASCII Теперь, когда это выяснено, мы можем выяснить, что 0
представляет.
Это NULL-токен, который в C обозначает конец строки.
Итак, окончательный:
Все, что делает это:
Итерирование тела этого for
-loop, пока p
фактически не укажет на адрес, где объект оценивает "конец строки" -token.
Или:
Пусть p
проходит строку до конца.
А теперь просто для того, чтобы ссылаться на себя; Что-то, чего вы никогда не должны забывать:
(внимание мое.....)
Переменная объявляется через декларатор (спецификатор типа), который предшествует идентификатору, который называет объект lvalue, который может быть оценен с его значением
Это ни больше, ни меньше!
1 То есть, что я обещал!;)
Ответ 5
Прежде чем погрузиться, я хотел бы указать простое правило в C относительно выражения
Когда C требует булевское значение выражения, значение false
выводится, когда выражение сравнивается с ноль и значением true
в противном случае. То есть, всякий раз, когда вы пишете
if(expr)
где expr
- любое выражение вообще, компилятор по существу действует так, как если бы он был написан как
if((expr) != 0)
Теперь на ваш вопрос:
Что делает *p
в следующем цикле?
В C строки заканчиваются нулевым символом '\0'
.
![введите описание изображения здесь]()
Каждый символ имеет десятичный эквивалент. Этот '\0'
является ASCII escape-символом. Десятичный эквивалент '\0'
равен 0
.
Итак, выражение *p
в цикле просто проверяет, что десятичный эквивалент символа в адресе памяти, указанном p
, является либо нулевым, либо ненулевым. Когда p
достигает конца строки и находит первый символ '\0'
, выражение *p
возвращает 1 нулевое значение. Нуль означает false
в C. Это эквивалентно тестированию *p != '\0'
или *p != 0
, как указано выше.
Вот как это работает:
![введите описание изображения здесь]()
1 Когда *p
оценивается, тогда значение *p
извлекается из памяти. Это значение является значением выражения *p
.
Ответ 6
The * p Haiku
Поэтично я попытался представить борьбу в * p в цикле:
Храбрый C * p (rogrammers)
В петле безвкусности
NUL остановит их
Это стихотворение хайку, оно состоит из трех строк, причем первая и последняя строки имеют 5 слогов, а средняя строка имеет 7. Другой пример @Samidamaru (Поэма Учителя Хайку, см. комментарий ниже): First p равно str, Затем p увеличивается, до * p - NUL.
Немного поп
![введите описание изображения здесь]()
Час посла Кода, Джессика Альба
Что делает * p в цикле?
Следуя мнимому совету Джессики (который цитирует Д. Кнута (1)),
мы попытаемся увидеть значение * p в цикле for:
for (p = str; *p; p++)
Для этой цели мы сначала рассмотрим, как работает унарный оператор "*" в C:
"Унарный оператор * является оператором косвенности или отсрочки, и когда применяется к указателю, он обращается к объекту, на который указывает указатель". (Б. Керниган и Д. Ричи (2))
So * p - это просто значение, обозначенное p:
![введите описание изображения здесь]()
1.1 Более пристальный взгляд на цикл for
Цикл for состоит из трех команд:
В 1. мы назначаем указатель на массив str на p. В C следующие назначения имеют тот же эффект:
p = &str[0];
p = str;
"По определению значение переменной или выражения массива типа является адресом элемента нуль массива" (K и R (2)).
Кроме того, мы имеем: "При оценке a [i] C немедленно преобразует его в * (a + i)...... следует, что & a [i] и a + я идентичны" (K и R (2)), Если положить я = 0, мы получим указанные выше задания.
Теперь мы можем указать, что в начале цикла for p указывает на первый элемент str.
1.2 Ядро вопроса
Перейдем к точке 2., суть вашего вопроса. Второе выражение цикла управляет условием выхода: вычисляется команда "* p", а если ложь - выход цикла. Это означает, что "* p" эквивалентно "* p!= 0" или в словах: когда значение, указанное p, равно нулю, выйдите.
Теперь, чтобы понять, когда * p равно нулю, напомним, что массив str был инициализирован следующим образом:
char str[128] = "Some Text";
и: "все строковые константы содержат символ нулевой остановки (\ 0) в качестве последнего символа" (gnu-manual). Таким образом, строка, фактически сохраненная в памяти, имеет \0 в конце: "Some Text\0".
В третьей инструкции p ++ указатель p переводится в следующий элемент массива str, поэтому на 9-й итерации * p становится 0 (или\0, NULL, NUL, см. ответ от @Joe), и петля завершается.
1.3 Посмотрите, чтобы верить
Изображение стоит тысячи слов, вот графическое представление цикла:
![введите описание изображения здесь]()
1.4 Еще один пример: такое же использование * p в другом примере
В следующем фрагменте * p используется так же, но в цикле while:
#include <stdio.h>
int main() {
char str[128] = "We all scream for ice cream!";
char *p = str;
// here we see again the loop exit condition *p == '\0'
while(*p) {
printf("%c", *p);
p++;
}
printf("\n");
}
Пусть for (; * C;) e будет с вами!
Ссылки
(1) Vol. I, Фундаментальные алгоритмы, раздел 1.1 (1968)
(2) Язык программирования C Pg 94-99
Ответ 7
Он использует тот факт, что терминатор для строки (в конечном итоге найденный для этого цикла) будет ASCII NUL
, который равен нулю, что также происходит для вычисления false, что завершает цикл for.
Стоит отметить разницу и сходство между 0, ложными, NULL и ASCII NUL. См. Этот вопрос: В чем разница между NULL, '\ 0' и 0
Ответ 8
Давным-давно, в PDP далеко, далеко не хватало ресурсов, имена были короткими: i
для индекса, p
для указателя были бы ранними программистами Jedi.
Неявные тесты сказали правду в пространстве условий for
. Единственное *
было все, что они набрали, доверяя p
и нажав его до конца строк.
По сей день они используют for(e = s;*e;e++)
самый знакомый и элегантный цикл, чтобы бросить вызов империи С++ и ее когортам ctors, dtors и мерзким итераторам. Голые биты и байты против шаблонов, исключений и неясных типов, только храбрый все еще дерзает за C, чтобы сражаться, и отключил void *
.
Ответ 9
Я пытался удовлетворить пожелания лауреатов премии, которые упоминались в разное время. Чтобы это было просто, я ограничил свой ответ тремя разделами по три строки каждый, и потому что (как "Беллман" сказал в своем "Правиле из трех" ) То, что я вам говорю три раза, верно "(тема этого ответа).
Технический
Истина вашего цикла for
завершает его, когда выражение *p
оценивается как 0
, и эта оценка выполняется перед каждой итерацией цикла, обратите внимание, что в C 0
ложно и что-то еще истинно - это очень экспансивное определение в других мирах!
Переменная указателя p
инициализируется один раз, указывая на начало массива с p = str
, а p
увеличивается в конце каждой итерации, поэтому *p
обращается к последовательным элементам массива на каждой итерации.
Таким образом, выражение *p
будет оцениваться как 0
(false), когда элемент массива, который читается *p
, является терминатором 0
или '\0'
, который сигнализирует конец строки C ", но вы не может видеть этот нуль в инициализации str
, потому что он предоставляется компилятором автоматически.
Лирическая
Выражения истины
Не поняты молодежью
Прочитайте Ричи и Кнут
Причудливая
Джессика Альба - прекрасная актриса, которая очень хорошо осведомлена, взяв на борт истины наблюдение за развитием компьютерных технологий, поскольку эти цитаты показывают:
"Каждые пять лет я чувствую, что я совершенно другой человек".
"Все о вашем продукте и его действиях. Либо он работает, либо это не так."
Ответ 10
Хайку:
WHY for (p=str; *p; p++)
IS for (p=str; p[0] != 0; p++)
THINK for (i=0; str[i]; ++i)
EDITED
Вот несколько дополнительных деталей:
Вторая строка кода "хайку" эквивалентна первой строке. В исходном сообщении задается вопрос "что это значит" в комментарии к коду. Вторая строка демонстрирует ответ на эквивалентность. * p означает p [0]. Второе предложение в цикле для заботится о том, эквивалентно ли p [0] нулю.
Третья строка кода "хайку" - это строка кода, которая может быть использована концептуально: вы можете думать о работе исходной строки как о том, что она очень похожа на третью строку.
Ответ 11
![String in str]()
Как видно из рисунка, цикл for
начинается с *p
, где p
указывает str
. На этом этапе *p
имеет S
.
При непрерывном цикле for
он, наконец, достигает str[9]
, который имеет '\0'
, что означает NULL
.
В этот момент оператор условия *p
в for (p = str; *p; p++)
равен NULL
, поэтому код будет прерываться из цикла for
.
Ответ 12
Это условие является частью цикла.
Если это условие не выполняется, цикл больше не выполняется.
*p
разделяет указатель p
и возвращает символ, указанный в строке str
.
Строка стиля C str
заканчивается значением \0
.
Цикл выполняет итерацию по каждому символу (используя p
), пока условие не будет выполнено.
В C значение 0
или \0
похоже на значение false
, то есть условие не выполняется.
Любое другое значение похоже на значение true
, то есть условие выполнено.
Короче говоря, p
выполняет итерацию по каждому символу в str
и останавливается, как только он нажимает символ окончания строки \0
.
Почему бы не использовать p
вместо *p
?
Потому что p
является указателем и содержит адрес. Иногда бывает сложно или даже невозможно использовать адресную арифметику. Это не хорошая практика и делает код трудным для чтения.
*p
- это разыменованный указатель и содержит значение, на которое указывает p
. В этом случае легко использовать значения, на которые указывает p
, потому что вы знаете, что строка завершается символом \0
. В качестве условия (if
, while
и т.д.) *p
эквивалентно *p != '\0'
.
Ответ 13
Во-первых, вам нужно понять концепцию указателя, так как имя говорит, что они указывают на что-то.
Указатель содержит адрес переменной.
int var=0;
int *p;
int p=&var;
в этом коде p
является указателем, а printf("%d",p);
печатает адрес переменной var
и printf("%d",*p);
печатает значение переменной var
, которое в этом примере равно 0.
Во-вторых, вы должны понимать, как работают массивы. Привязки представляют собой структуру данных, которая может хранить коллекцию SEQUENTIAL фиксированного размера одного и того же типа.
int array[3]={9,8,7};
printf("%d",array[0]); //prints what is on 1st position,9
printf("%d",array[1]); //prints what is on 2nd position,8
printf("%d",array[2]); //prints what is on 3rd position,7
operator []
- это просто удобная работа с массивами.
Последние три строки кода могут быть заменены следующими строками (и они будут делать то же самое):
printf("%d",*(array+0)); //prints what is on 1st position,9
printf("%d",*(array+1)); //prints what is on 2nd position,8
printf("%d",*(array+2)); //prints what is on 3rd position,7
array
является указателем на первый элемент массива (содержит адрес первого элемента в массиве), поэтому разыменовывая его, мы получаем значение первого элемента, например. *array
.
Мы знаем, что массивы seqential, что означает, что array+1
указывает на второй элемент массива, поэтому разыменовывая это, вы получаете значение второго элемента, например. *(array+1)
и т.д.
![сегмент памяти массива]()
То же самое также подходит для строк, потому что они представляют собой массив из char, за исключением того, что строка имеет "\ 0" (нулевой символ) в конце строк.
char str[128] = "Some Text";
char *p;
for (p = str; *p; p++)
{
printf("%c",*p);
}
Эта программа печатает строку str
.
p = str
//присваиваем адрес первого символа строки str
p
, мы не потеряем трек первого char в строке, поэтому используем p
not str
для итерации
*p
//это выражение означает *p!=0
, поэтому это верно, пока вы не дойдете до конца строки, помните, что "0" в ascii имеет целочисленное значение 48
p++
//в конце для блока вы добавляете +1 к p
, чтобы получить адрес следующего char
Ответ 14
Это можно объяснить следующим образом:
for( initialization ; Conditional Expression ; expression3)
{
Code here will execute while 2nd Expression(Conditional Expression) is true
true means non-zero value
'\0' is equivelant to 0,so when *p equal '\0' : loop will terminate
}