Как работает этот код?

Я смотрел следующий код, который мне попался, для печати строки в обратном порядке в 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 реализован в ассемблере, и рекурсия добавляет довольно накладные расходы.

Я абсолютно уверен, что эталонный показатель является представительным, если я ошибаюсь, пожалуйста, исправьте меня.

В любом случае, я думал, что должен поделиться этим с вами.