#c
Вопрос:
Я наткнулся на это, решая упражнение:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = 0;
while (s[i ])
f(i, s i);
}
Почему приращение post в while не работает, если я делаю это:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = -1;
while (s[ i])
f(i, s i);
}
Это работает?
Я новичок и все еще очень смущен всей концепцией указателя, есть ли здесь какой-либо нюанс, о котором я не знаю?
Комментарии:
1. Подумайте о том, какое значение
i
имеет значение приf
вызове.2. Это вопрос не столько об указателях , сколько о значении , возвращаемом
i
versusi
.3. @paddy: Значения, которые
i
иi
вычисляют (не возвращают), не являются проблемой, потому что инициализацияi
настраивается для компенсации. Проблема заключается в значении, которое они оставляютi
.4. То, что вы хотите сделать здесь, на английском языке, «Вызовите
f
для каждой позиции символа в строкеs
, отличной от нуля, то есть до, но не включая завершающий нулевой символ». Таким образом, ясный и очевидный способ сделать это был быfor(i = 0; s[i] != ''; i ) { f(i, s i); }
. Или, немного сокращенно,for(i = 0; s[i]; i ) { f(i, s i); }
. (Для этихfor
циклов не имеет значения, используете ли выi
илиi
; они эквивалентны.) Из двух опубликованных вами фрагментов один — более запутанный способ, который работает, а другой — более запутанный способ, который не работает.5. Они удалили и — из языка Swift. Я понимаю, почему 🙂
Ответ №1:
Проблема заключается в совпадении между сравнением и вызовом функции.
Рассмотрим первую итерацию. В первом фрагменте это будет:
if(!s[0]) break;
f(1, s 1);
Во втором фрагменте это было бы:
if(!s[0]) break;
f(0, s 0);
Ответ №2:
Если вы выполните простую отладку, вы увидите, в чем проблема.
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
i = 0;
while (s[i ])
{
printf("i = %dn", i);
if(f) f(i, s i);
}
}
void ft_striteri1(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
i = -1;
while (s[ i])
{
printf("i = %dn", i);
if(f) f(i, s i);
}
}
int main()
{
ft_striteri("Hello", NULL);
printf("n");
ft_striteri1("Hello", NULL);
}
https://godbolt.org/z/cqb1aMGje
Результат:
i = 1
i = 2
i = 3
i = 4
i = 5
i = 0
i = 1
i = 2
i = 3
i = 4
Функция с postincrement выполняет итерацию от индекса от 1 до 5 вместо от 0 до 4.
Но обе ваши функции не используют правильный тип для индексов. Это должно быть size_t
вместо int
.
Я бы лично написал другой способ, проверив «положительный» тест, если параметры в порядке и имеют только одну точку возврата:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
size_t i = 0;
if(s amp;amp; f)
{
while (s[i])
{
f(i, s i);
i ; // or i; - it makes no difference
}
}
}
Ответ №3:
Операторы приращения в C указывают, что в дополнение к значению, вычисляемому выражением, возникает побочный эффект.
Вы можете подумать, что это как-то особенное или свойственное этим операторам, но на самом деле это не так. Операторы присваивания также работают таким образом.
Например, (EXPR)
эквивалентно (EXPR) = (EXPR) 1
, за исключением того, что EXPR
вычисляется только один раз.
Оба этих выражения вычисляют результат (EXPR) 1
, а также имеют побочный эффект сохранения этого значения обратно (EXPR)
.
Побочный эффект возникает в любое время во время вычисления полного выражения, которое содержит это подвыражение.
Учитывая:
while (s[i ])
f(i, s i);
Здесь у нас есть два полных выражения: управляющее выражение while
цикла, s[i ]
, является полным выражением, как и f(i, s i)
вызов функции.
Побочные эффекты, требуемые каждым полным выражением, устраняются до вычисления следующего полного выражения. Предыдущее полное выражение видит предыдущее значение, а последующее полное выражение видит новое значение.
Другими словами, i
здесь означает:
- Вычислите значение
i
, а также поместитеi 1
вi
. - Убедитесь, что это
i
обновление выполняется когда-нибудь во время вычисления полного выраженияs[i ]
.
Следовательно, выражение f(i, s i)
будет учитывать новое значение i
, а не предыдущее значение i
, которое использовалось для вычисления s[i]
. Вызову функции будет присвоен не символ, который был проверен как ненулевой, а следующий символ после него.
Важным фактом здесь является то, что побочные эффекты упорядочиваются на уровне отдельных полных выражений, а не операторов. Здесь i
не означает «увеличивать i
после каждой итерации всего while
цикла, i
продолжая ссылаться на старое значение».Если бы это работало таким образом, код работал бы; но это не так.
Таким образом, ваше пересмотренное утверждение исправило согласованность:
while (s[ i])
f(i, s i);
потому что здесь i
означает:
- Вычислите значение
i 1
, а также поместите это значение вi
. - То же (2), что и выше.
Здесь управляющее выражение while
тестов s[i]
where i
является новым увеличенным значением i
; и вызов функции f(i, s i)
ссылается на то же i
самое . Управляющее выражение и вызов функции согласованы: они работают с одним и тем же символом строки.
Вы должны были компенсировать предварительное увеличение, инициализируя i
значение -1.
Если вы хотите увеличивать переменную после каждой итерации цикла и делать это в верхней части цикла, то for
конструкция предназначена именно для этого:
// misconception: // similar idea, correct:
i = 0;
while (s[i ]) for (i = 0; s[i]; i )
f(i, s i); f(i, s i);
for
Цикл позволяет нам иметь своего рода «постинкремент» на уровне всего оператора: в синтаксисе head есть место, где мы можем указать выражения приращения, которые будут вычисляться после всего тела.
Кстати, поскольку i
is unsigned int
(который также может быть указан как unsigned
, без int
), этот тип фактически не имеет значения -1 в своем диапазоне. Когда мы делаем это:
unsigned int x = -1; // initialize or assign a -1 value to unsigned
отрицательное значение уменьшается до наименьшего положительного остатка по модулю UINT_MAX 1
, а результирующее значение — это то, что фактически присвоено.
Значение -1 переходит в UINT_MAX
. Итак, вы действительно делаете это:
i = UINT_MAX; // same meaning as i = -1.
это работает, потому что, если i
есть unsigned
и содержит максимальное значение UINT_MAX
, когда мы затем увеличиваем i
, оно стремится к нулю. Эта арифметика по модулю или «перенос» является частью определения unsigned
типа; это указано в стандарте языка. В другом направлении unsigned
аналогично происходит уменьшение нулевого значения UINT_MAX
.
Кроме того, из соображений стиля, при обращении к массивам не смешивайте обозначения ptr index
и ptr[index]
. Это лучше:
// while the character isn't null, pass a pointer to that
// same character to f:
while (s[ i])
f(i, amp;s[i]); // address of the character
Это amp;s[i]
означает amp;*(s i)
, что amp;*
комбинация операторов («адрес разыменованного указателя») «алгебраически отменяет» оставление s i
; это не менее эффективно.
Эта рекомендация особенно актуальна , если функция f
работает с этим одним символом s[i]
, а не со всей s i
подстрокой s
. amp;array[index]
Обозначение обычно используется (как правило), когда акцент делается на конкретном элементе массива.
Как читатель C, вы, конечно, не можете доверять этому: amp;array[index]
в чужой программе может быть использовано для вычисления значения, которое функция затем использует для доступа к другим элементам массива, а не только к этому. Тем не менее, как автор C, вы можете сделать свой код «похожим на то, что он делает», чтобы было меньше подводных камней для кого-то другого.
Ответ №4:
Я не знаю о нюансах, но вот эквивалент вашего первого:
void first(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = 0;
while (s[i]) {
f(i, s i 1);
s = s 1;
}
}
и второе:
void second(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = -1;
while (s[i 1]) {
f(i, s i 1);
i = i 1;
}
}
На самом деле, ни один из них не выглядит правильным для меня; Я бы подумал, что вы хотели бы:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = 0
while (s[i]) {
f(i, s i);
i
}
}
что в идиоматическом стиле может быть:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
int c;
if (!s || !f)
return ;
for (; *s; s )
f(i, s);
}