Как работает этот код?
Я смотрел следующий код, который мне попался, для печати строки в обратном порядке в C с использованием рекурсии:
void ReversePrint(char *str) { //line 1
if(*str) { //line 2
ReversePrint(str+1); //line 3
putchar(*str); //line 4
}
}
Я относительно новичок в C и запутался в строке 2. *str
из моего понимания разыменовывает указатель и должен возвращать значение строки в текущей позиции. Но как это используется в качестве аргумента условного оператора (который должен исключать логическое право?)? В строке 3 указатель всегда будет увеличиваться до следующего блока (4 байта с момента его int)... так что не мог ли этот код выйти из строя, если в конце блока остались данные в следующем блоке памяти?
Обновить: значит, нет правильных логических типов в c? Условное выражение оценивается как "false", если значение равно 0, а "true" в противном случае?
Ответы
Ответ 1
Строка 2 проверяет, является ли текущий символ нулевым терминатором строки, поскольку строки C имеют нулевой конец, а нулевой символ считается ложным значением, он начнет разворачивать рекурсию, когда она попадает в конец (вместо того, чтобы пытаться вызвать StrReverse4 на символ после нулевого терминатора, который выходит за пределы допустимых данных).
Также обратите внимание, что указатель имеет значение char
, поэтому приращение указателя увеличивается только на 1 байт (поскольку char
- однобайтовый тип).
Пример:
0 1 2 3
+--+--+--+--+
|f |o |o |\0|
+--+--+--+--+
- Когда
str
= 0
, тогда *str
равно 'f'
, поэтому рекурсивный вызов выполняется для str + 1 = 1.
- Когда
str
= 1
, тогда *str
есть 'o'
, поэтому рекурсивный вызов выполняется для str + 1 = 2.
- Когда
str
= 2
, тогда *str
есть 'o'
, поэтому рекурсивный вызов выполняется для str + 1 = 3.
- Когда
str
= 3
, то *str
есть '\0'
, а \0
- это ложное значение, поэтому if(*str)
вычисляется как false, поэтому рекурсивный вызов не выполняется, таким образом, возвращаясь к рекурсии, мы получить...
- За последней рекурсией последовал `putchar ('o'), затем после этого
- Следующая последняя рекурсия сопровождалась `putchar ('o'), затем после этого
- По крайней мере недавняя рекурсия сопровождалась `putchar ('f'), и мы закончили.
Ответ 2
Тип строки C - это не что иное, как указатель на char. Соглашение заключается в том, что указывает указатель на массив символов, заканчивающийся нулевым байтом.
*str
, таким образом, является первым символом строки, на которую указывает str
.
Использование *str
в условном выражении равно false
, если str
указывает на завершающий нулевой байт в (пустой) строке.
Ответ 3
В конце строки, как правило, 0 байт - строка if (*str)
проверяет существование этого байта и останавливается, когда он добирается до него.
Ответ 4
В конце строки есть 0
- поэтому у вас есть "test" => [0]'t' [1]'e' [2]'s' [3]'t' [4]0
и if(0) -> false
таким образом это сработает.
Ответ 5
В строке 3 указатель всегда будет увеличиваться до следующего блока (4 байта с момента его создания)...
Это неправильно, это char *, он будет увеличиваться только на 1. Поскольку char имеет длину 1 байт.
Но как это используется как аргумент условного оператора (который должен исключать логическое право?)?
Вы можете использовать любое значение в if ($$) в $$, и оно будет проверять, не отличается ли оно от нуля или нет, в основном bool, как простой 1 = true и только 0 = false.
На другом языке с более высоким уровнем строгого типа вы не можете использовать такие вещи в if, но в C все сводится к числам. И вы можете использовать что угодно.
if(1) // evaluates to true
if("string") // evaluates to true
if(0) // evaulates to false
Вы можете дать любую вещь, если, хотя условия в C.
Ответ 6
условные операторы (if
, for
, while
и т.д.) ожидают булевское выражение. Если вы указали целочисленное значение, оценка сводится к 0 == false
или non-0 == true
. Как уже упоминалось, конечным символом c-строки является нулевой байт (целочисленное значение 0). Таким образом, if
завершится с ошибкой в конце строки (или первого нулевого байта внутри строки).
В стороне, если вы выполняете *str
по указателю NULL, вы вызываете поведение undefined; вы всегда должны убедиться, что указатель действителен до разыменования.
Ответ 7
1.
str
является указателем на char. Приращение str
сделает указатель точкой второго символа строки (как массив char).
ПРИМЕЧАНИЕ. Инкрементные указатели будут увеличиваться по типу данных, на который указывает указатель.
Для ex:
int *p_int;
p_int++; /* Increments by 4 */
double *p_dbl;
p_dbl++; /* Increments by 8 */
2.
if(expression)
{
statements;
}
Выражение оценивается, и если результирующее значение равно нулю (NULL
, \0
, 0
), операторы не выполняются. Поскольку каждая строка заканчивается на \0
, рекурсия должна заканчиваться на некоторое время.
Ответ 8
C не имеет понятия булевых значений: в C каждый скалярный тип (то есть арифметические и типы указателей) может использоваться в булевых контекстах, где 0
означает false
и ненулевое true
.
Как строки заканчиваются на нуль, терминатор будет интерпретироваться как false
, тогда как каждый другой символ (с ненулевым значением!) будет true
. Это означает, что есть простой способ перебора символов строки:
for(;*str; ++str) { /* so something with *str */ }
StrReverse4()
делает то же самое, но рекурсивно вместо итерации.
Ответ 9
Попробуйте этот код, который так же прост, как тот, который вы используете:
int rev(int lower,int upper,char*string)
{
if(lower>upper)
return 0;
else
return rev(lower-1,upper-1,string);
}
Ответ 10
Это отчасти не по теме, но когда я увидел вопрос, я сразу подумал, что это на самом деле быстрее, чем просто выполнение strlen и повторение со спины.
Итак, я сделал небольшой тест.
#include <string.h>
void reverse1(const char* str)
{
int total = 0;
if (*str) {
reverse1(str+1);
total += *str;
}
}
void reverse2(const char* str)
{
int total = 0;
size_t t = strlen(str);
while (t > 0) {
total += str[--t];
}
}
int main()
{
const char* str = "here I put a very long string ...";
int i=99999;
while (--i > 0) reverseX(str);
}
Сначала я скомпилировал его с помощью X = 1 (используя функцию reverse1), а затем с X = 2. Оба раза с -O0.
Он последовательно возвращал примерно 6 секунд для рекурсивной версии и 1,8 секунды для версии strlen.
Я думаю, это потому, что strlen реализован в ассемблере, и рекурсия добавляет довольно накладные расходы.
Я абсолютно уверен, что эталонный показатель является представительным, если я ошибаюсь, пожалуйста, исправьте меня.
В любом случае, я думал, что должен поделиться этим с вами.