2012-04-16 2 views
4

Может кто-то, пожалуйста, помогите мне понять эти строки кода в программе ниже этой программы, согласно писателю, она пишет строку мира привет, тогда в ней есть функция, которая также меняет строку на мировой привет , мой квест - это то, что делает этот код?Работа со строками в c

char * p_divs = divs; //what does divs do 
    char tmp; 
    while(tmp = *p_divs++) 
     if (tmp == c) return 1 

;

также этот код в функции ничтожной

*dest = '\0';//what does this pointer do? 
    int source_len = strlen(source); //what is source 
    if (source_len == 0) return; 
    char * p_source = source + source_len - 1; 
    char * p_dest = dest; 
    while(p_source >= source){ 
     while((p_source >= source) && (inDiv(*p_source, divs))) p_source--; 

это основная программа

#include <stdio.h> 
#include <string.h> 

int inDiv(char c, char * divs){ 
    char * p_divs = divs; 
    char tmp; 
    while(tmp = *p_divs++) 
     if (tmp == c) return 1; 
    return 0; 
} 

void reverse(char * source, char * dest, char * divs){ 
    *dest = '\0'; 
    int source_len = strlen(source); 
    if (source_len == 0) return; 
    char * p_source = source + source_len - 1; 
    char * p_dest = dest; 
    while(p_source >= source){ 
     while((p_source >= source) && (inDiv(*p_source, divs))) p_source--; 
     if (p_source < source) break; 
     char * w_end = p_source; 
     while((p_source >= source) && (!inDiv(*p_source, divs))) p_source--; 
     char * w_beg = p_source + 1; 
     for(char * p = w_beg; p <= w_end; p++) *p_dest++ = *p; 
     *p_dest++ = ' '; 
    } 
    *p_dest = '\0'; 
} 

#define MAS_SIZE 100 

int main(){ 
    char source[MAS_SIZE], dest[MAS_SIZE], divs[MAS_SIZE]; 
    printf("String   : "); gets(source); 
    printf("Dividers  : "); gets(divs); 
    reverse(source, dest, divs); 
    printf("Reversed string : %s", dest); 
    return 0; 
} 
+9

«Мой квест - это то, что делает этот код?» Учитывая, что он использует 'gets', ответ таков: он ставит под угрозу вашу систему по самому ее существованию. По крайней мере, ИМО, попросив кого-то проанализировать этот код, это как дать кому-то 5-галлоновую ведро сточных вод и спросить, откуда все это. Код - уродливый беспорядок. Если вы хотите знать, как выполнять задачи, которые он якобы делает, вы должны спросить об этом напрямую, поэтому кто-то может объяснить, как это сделать. Этот код следует игнорировать, кроме (возможно) в качестве примера того, чего следует избегать. –

+3

@JerryCoffin: Ого, это довольно грубо. Я видел намного худший код. –

+0

@MichaelBurr: Я тоже видел хуже, но, чтобы читать слова и печатать их в обратном порядке, это все еще * ерунда * отправная точка. Легко понять задачу, начиная с чистого листа. –

ответ

4

Здесь ИНДИВ можно назвать поиск символа c в строке divs, например:

inDiv('x', "is there an x character in here somewhere?') will return 1 
inDiv('x', "ahhh... not this time') will return 0 

Работа через него:

int inDiv(char c, char * divs) 
{ 
    char * p_divs = divs; // remember which character we're considering 
    char tmp; 
    while(tmp = *p_divs++) // copy that character into tmp, and move p_divs to the next character 
          // but if tmp is then 0/false, break out of the while loop 
     if (tmp == c) return 1; // if tmp is the character we're searching for, return "1" meaning found 
    return 0; // must be here because tmp == 0 indicating end-of-string - return "0" meaning not-found 
} 

Мы можем сделать вывод, что о reverse глядя на месте вызова:

int main() 
{ 
    char source[MAS_SIZE], dest[MAS_SIZE], divs[MAS_SIZE]; 
    printf("String   : "); 
    gets(source); 
    printf("Dividers  : "); 
    gets(divs); 
    reverse(source, dest, divs); 
    printf("Reversed string : %s", dest); 

Мы можем видеть, что gets() вызывается для чтения со стандартного ввода в массивы символов source и divs -> эти входы затем предоставляются на номер reverse(). Путь dest напечатан, это явно предназначено для назначения для разворота строки в source. На этом этапе нет понимания значимости divs.

Давайте посмотрим на источник ...

void reverse(char * source, char * dest, char * divs) 
{ 
    *dest = '\0'; //what does this pointer do? 
    int source_len = strlen(source); //what is source 
    if (source_len == 0) return; 
    char* p_source = source + source_len - 1; 
    char* p_dest = dest; 
    while(p_source >= source) 
    { 
     while((p_source >= source) && (inDiv(*p_source, divs))) p_source--; 

Здесь *dest = '\0' записывает символ NUL в массив символов dest - это нормальное значение сторожевого кодирующая конца-строки позиции - положить его в на первый символ *dest подразумевает, что мы хотим, чтобы пункт назначения был очищен. Мы знаем, что source - это текстовый ввод, который мы будем реверсировать - strlen() установит source_len на количество символов в нем. Если символов нет, то return, так как нет работы, и вывод уже завершен с помощью NUL. В противном случае создается новый указатель p_source до source + source_len - 1 ->, что означает, что он указывает на последний символ не-NUL в источнике. p_dest указывает на символ NUL в начале буфера назначения.

Тогда петля говорит: while (p_source >= source) - для этого, чтобы сделать что-нибудь p_source должен быть изначально >= source - это имеет смысл, как p_source точек на последнем символе и source является первым адресом символа в буфере; сравнение подразумевает, что мы будем перемещать один или оба друг к другу, пока они не переберутся, - делая какую-то работу каждый раз. Что приводит нас к:

while((p_source >= source) && (inDiv(*p_source, divs))) p_source--; 

Это тот же самый тест, который мы только что видели, - но на этот раз мы только перемещение p_source назад к началу строки, а inDiv(*p_source, divs) также верно ... это означает, что символ в *p_source является одним из символов в строке divs. Это означает, что в основном: перемещение назад, пока вы не прошли старт строки (хотя этот тест имеет неопределенное поведение, как отмечает Майкл Берр в комментариях, и действительно может не работать, если строка будет выделена по адресу 0 - даже если относительно некоторого определенного сегмента данных, поскольку указатель мог бы перейти от 0 к чему-то вроде FFFFFFFF hex без когда-либо кажущегося меньше 0) или до тех пор, пока вы не найдете символ, который не является одним из символов «разделителя».

Здесь мы получаем некоторое представление о том, что делает код ... разделяя входные слова на «слова», разделенные любым набором символов в входе divs, а затем записывая их в обратном порядке с разделителями пробелов в место назначения буфер. Это становится впереди себя немного - но давайте отслеживать его через:

Следующая строка ...

if (p_source < source) break; 

... что означает, если петля возбужденных Поддержав мимо переднюю часть исходной строки , затем вырваться из всех циклов (смотря вперед, мы видим, что код просто добавляет новый NUL в конец уже сгенерированного вывода и возвращает - но это то, что мы ожидаем?), если бы мы продержались через «hello» в «hello world», тогда мы бы нажали на начало строки и закончили цикл, не копируя это последнее «привет» слово на выход! На выходе всегда будут все слова на входе - кроме первого слово - обратное - это не поведение, описанное автором).

В противном случае:

char* w_end = p_source; // remember where the non-divider character "word" ends 

// move backwards until there are no more characters (p_source < source) or you find a non-divider character 
while((p_source >= source) && (!inDiv(*p_source, divs))) p_source--; 

// either way that loop exited, the "word" begins at p_source + 1 
char * w_beg = p_source + 1; 

// append the word between w_beg and w_end to the destination buffer 
for(char* p = w_beg; p <= w_end; p++) *p_dest++ = *p; 

// also add a space... 
*p_dest++ = ' '; 

Это постоянно происходит для каждого «слова» на входе, то последняя строка добавляет терминатор NUL к месту назначения.

*p_dest = '\0'; 

Теперь, вы сказали:

согласно [к] писатель пишет нитка привет мир, то существует функция в нем, что также изменяет строку мира привет

Ну, учитывая входы «hello world» и разделительные символы, включая пробел (но ни один из других символов на входе), тогда выход будет «hello world» (обратите внимание на пространство в конце).

Для чего это стоит - этот код не так уж плох ... это довольно нормально для обработки C буферов ASCIIZ, хотя предположения о длительности ввода опасны, и в нем отсутствует первое слово ....

** Как исправить неопределенное поведение **

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

+0

Итак, если в зависимости от неопределенного поведения ('if (p_source

+0

Этот конкретный бит абсолютно плох - не вопрос ... Я просто имею в виду, что - если бы это было исправлено - код в целом был достаточно читабельным, сжатым, не будучи откровенно загадочным, поддерживаемым и эффективным. –

3
char * p_divs = divs; //what does divs do 
char tmp; 
while(tmp = *p_divs++) 
    if (tmp == c) return 1 

divs является указателем на массив символов (конечно, строка). p_divs просто указывает на одну и ту же строку, и внутри цикла while один символ добавляется и записывается в tmp, а затем указатель увеличивается, что означает, что следующий символ будет выделен на следующем итераторе. Если tmp соответствует c, функция возвращается.

Редактировать: Вы должны узнать больше о указателях, взгляните на Pointer Arithmetic.

1

Как я уже отмечал в комментариях, я не думаю, что C действительно является идеальным инструментом для этой задачи (учитывая выбор, d используйте C++ без второй мысли).

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

Единственная часть, которая может быть открыта для реального аргумента, - это элегантность, но думаю, что этого достаточно проще и понятнее, поскольку в этом отношении мало реальности. Это явно более красноречиво - с использованием примерно того же соглашения о форматировании, что и оригинал, мои rev_words - 14 строк, а не 17. Поскольку большинство людей отформатировали их, мои 17 строк, а его 21.

Для исполнения, я 'd ожидать, что два будут около эквивалент при большинстве обстоятельств. Шахта избегает запускать начало массива, что экономит немного времени. В оригинале есть ранний выход, который сохранит крошечный бит времени для изменения пустой строки. Однако я бы считал оба незначительными.

Я думаю, что еще один момент гораздо важнее: я достаточно уверен, что мой не использует/invoke/не зависит от неопределенного поведения, как это делает оригинал. Я полагаю, что некоторые люди могли бы считать, что это оправдано, если бы оно предоставило огромное преимущество в другой области, но, учитывая, что оно грубо связано или хуже в других областях, я не могу себе представить, кто бы это ни считал (даже близким к), оправданным в Это дело.

#include <string.h> 
#include <stdlib.h> 

#include <stdio.h> 

int contains(char const *input, char val) { 
    while (*input != val && *input != '\0') 
     ++input; 
    return *input == val; 
} 

void rev_words(char *dest, size_t max_len, char const *input, char const *delims) { 
    char const *end = input + strlen(input); 
    char const *start; 
    char const *pos; 

    do { 
     for (; end>input && contains(delims, end[-1]); --end); 
     for (start=end; start>input && !contains(delims,start[-1]); --start); 
     for (pos=start; pos<end && max_len>1; --max_len) 
      *dest++=*pos++; 
     if (max_len > 1) { --max_len; *dest++ = ' '; } 
     end=start; 
    } while (max_len > 1 && start > input); 
    *dest++ = '\0'; 
} 

int main(){ 
    char reversed[100]; 

    rev_words(reversed, sizeof(reversed), "This is an\tinput\nstring with\tseveral words in\n  it.", " \t\n."); 
    printf("%s\n", reversed); 
    return 0; 
} 

Edit::

if (max_len > 1) { --max_len; *dest++ = ' '; } 

должны быть действительно:

if (max_len > 1 && end-start > 0) { --max_len; *dest++ = ' '; } 

Если вы хотите, чтобы позволить max_len < 1, вы можете изменить:

*dest++ = '\0'; 

до:

Если длина буфера может быть задана посредством ввода с помощью (возможно, враждебного) пользователя, это, вероятно, было бы полезно. Для многих целей достаточно просто потребовать положительный размер буфера.

+0

Надеюсь, что ваше обсуждение UB в оригинальном коде, «оправдано», не было сделано с учетом моих комментариев. Мои комментарии не были предназначены для того, чтобы сказать, что UB в оригинале был в порядке, как есть, - просто потому, что он не сделал код как опубликованный, заслуживающий описания «ведра сточных вод». Поведение UB в оригинале может быть исправлено с незначительными изменениями до нескольких строк без серьезной реструктуризации. Я просто считал, что суровое описание размещенного кода; Я не сказал, что код был моделью, которая не нуждалась в улучшении. –

+0

Они были в основном в ответ на утверждение Тони, что исходный код был «не так уж плох». Хотя код можно взломать, чтобы предотвратить UB, он, вероятно, еще больше снизит читаемость кода, который не слишком велик для начала. Лучше исправить проблему с корнем. –

+0

@Jerry: вы знаете - с моей точки зрения ваше решение очень похоже на оригинал, и если что-то подтверждает общий стиль и подход :-). Дьявол в деталях. В результате разворота появляются два конечных пробела, когда ввод начинается с символа «delims» и имеет хотя бы один токен. Вы пишете NUL, когда max_len равно 0! ;-) Но вернемся к C++ и назовите это перемирием :-). (Отдельно у меня возникнет соблазн предложить один проход делимсов, заполняющий массив, индексированный символьным кодом, что позволяет определять ограничитель O (1), но это менее масштабируемое до> 8-битных символов). +1 в любом случае! –

Смежные вопросы