Как работает этот код C? - PullRequest
11 голосов
/ 01 сентября 2009

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

Обновление : поэтому нет логических типов в c правильно? Условный оператор оценивается как «false», если значение равно 0, и «true» в противном случае?

Ответы [ 10 ]

38 голосов
/ 01 сентября 2009

Строка 2 проверяет, является ли текущий символ нулевым завершителем строки - поскольку строки C заканчиваются нулем, а нулевой символ считается ложным значением, он начнет развертывать рекурсию, когда достигнет конца строки (вместо попытки вызвать StrReverse4 для символа после нулевого терминатора, что будет за пределами допустимых данных).

Также обратите внимание, что указатель имеет значение char, поэтому приращение указателя увеличивается только на 1 байт (поскольку char является однобайтовым типом).

Пример:

 0  1  2  3
+--+--+--+--+
|f |o |o |\0|
+--+--+--+--+
  1. Когда str = 0, тогда *str равно 'f', поэтому рекурсивный вызов выполняется для str + 1 = 1.
  2. Когда str = 1, тогда *str равно 'o', поэтому рекурсивный вызов выполняется для str + 1 = 2.
  3. Когда str = 2, тогда *str равно 'o', поэтому рекурсивный вызов выполняется для str + 1 = 3.
  4. Когда str = 3, тогда *str равно '\0', а \0 является ложным значением, поэтому if(*str) оценивается как ложное, поэтому рекурсивный вызов не производится, поэтому, возвращаясь к рекурсии, мы получить ...
  5. За самой последней рекурсией последовал `putchar ('o'), затем после этого
  6. За следующей самой последней рекурсией последовал `putchar ('o'), затем после этого
  7. За последней рекурсией последовал `putchar ('f'), и мы закончили.
3 голосов
/ 01 сентября 2009

Тип строки C - не что иное, как указатель на char. Соглашение состоит в том, что указатель указывает на массив символов, завершается нулевым байтом .

*str, таким образом, является первым символом строки, на которую указывает str.

Использование *str в условном выражении дает false, если str указывает на завершающий нулевой байт в (пустой) строке.

2 голосов
/ 01 сентября 2009

В конце строки обычно находится 0 байт - строка if (*str) проверяет наличие этого байта и останавливается, когда он доходит до него.

1 голос
/ 01 сентября 2009

В строке 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

Вы можете отдать любую вещь в if, а условия в C.

1 голос
/ 01 сентября 2009

В конце строки есть 0 - поэтому у вас есть "test" => [0]'t' [1]'e' [2]'s' [3]'t' [4]0

и if(0) -> false

так будет работать.

0 голосов
/ 21 октября 2009

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

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

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

0 голосов
/ 06 сентября 2009

Попробуйте этот код, который так же прост, как и тот, который вы используете:

int rev(int lower,int upper,char*string)
{
  if(lower>upper)
          return 0;
   else
          return rev(lower-1,upper-1,string);
}
0 голосов
/ 01 сентября 2009

C не имеет понятия булевых значений: в C каждый скалярный тип (то есть арифметический тип и тип указателя) может использоваться в логических контекстах, где 0 означает false и ненулевое true.

Поскольку строки заканчиваются нулем, терминатор будет интерпретироваться как false, тогда как все остальные символы (с ненулевым значением!) Будут true. Это означает, что существует простой способ перебора символов строки:

for(;*str; ++str) { /* so something with *str */ }

StrReverse4() делает то же самое, но рекурсией, а не итерацией.

0 голосов
/ 01 сентября 2009

1.

str - указатель на символ. Увеличение str сделает указатель указателем на второй символ строки (так как это массив символов). ПРИМЕЧАНИЕ. Инкрементные указатели будут увеличиваться в зависимости от типа данных, на который указывает указатель.

Например:

int *p_int;
p_int++;     /* Increments by 4 */

double *p_dbl;
p_dbl++;      /* Increments by 8 */

2

if(expression)
{
   statements;
}

Выражение вычисляется, и если полученное значение равно нулю (NULL, \0, 0), операторы не выполняются. Поскольку каждая строка заканчивается на \0, рекурсия должна закончиться через некоторое время.

0 голосов
/ 01 сентября 2009

условные операторы (if, for, while и т. Д.) Ожидают логическое выражение. Если вы предоставите целочисленное значение, оценка сводится к 0 == false или non-0 == true. Как уже упоминалось, завершающим символом c-строки является нулевой байт (целое значение 0). Таким образом, if завершится ошибкой в ​​конце строки (или в первом нулевом байте в строке).

Кроме того, если вы делаете *str для NULL-указателя, вы вызываете неопределенное поведение; Вы всегда должны проверять, что указатель действителен перед разыменованием.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...