4

有人可以帮我理解这个程序下面程序中的这些代码行,根据作者它写一个hello world字符串然后里面有一个函数也可以将字符串反转为world hello,我的任务是这段代码做什么?

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

;

void函数中的这段代码

*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;  
}
4

3 回答 3

4

这里可以调用 inDiv 来搜索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_sourcesource

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

这是我们刚刚看到的相同测试 - 但这次我们只是p_source向后移动到字符串的开头,而inDiv(*p_source, divs)这也是正确的......这意味着字符 at*p_source是字符串中的字符之一divs。它的意思基本上是:向后移动,直到你超过了字符串的开头(尽管这个测试有未定义的行为,正如 Michael Burr 在评论中指出的那样,如果字符串恰好被分配在地址 0 上,真的可能不起作用 -即使相对于某些特定的数据段,因为指针可以从 0 变为类似于 FFFFFFFF 十六进制的值,而不会看起来小于 0),或者直到您找到一个不是“分隔符”字符之一的字符。

在这里,我们对代码的作用有了一些真正的了解......将输入分成由输入中的任何一组字符分隔的“单词” divs,然后将它们以相反的顺序用空格分隔符写入目标缓冲区。这有点超前了 - 但让我们跟踪一下:

下一行是...

if (p_source < source) break;

...这意味着如果循环退出时已超过源字符串的前面,则退出所有循环(向前看,我们看到代码只是在已经生成的输出的末尾放置了一个新的 NUL 并返回- 但这是我们所期望的吗? - 如果我们一直通过“hello world”中的“hello”支持,那么我们会点击字符串的开头并终止循环,而不会将最后一个“hello”单词复制到输出!输出将始终是输入中的所有单词 - 除了第一个单词 - 反转 - 这不是作者描述的行为)。

否则:

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';

现在,你说:

根据 [to] 作者,它写了一个 hello world 字符串,然后其中有一个函数,它也将字符串反转为 world hello

好吧,给定输入“hello world”和包含空格的分隔符(但输入中没有其他字符),那么输出将是“hello world”(注意末尾的空格)。

对于它的价值 - 这段代码还不错......对于 ASCIIZ 缓冲区的 C 处理来说是很正常的,尽管关于输入长度的假设是危险的并且它丢失了第一个单词......

** 如何修复未定义的行为 **

关于未定义的行为 - 对地址的最小更改是更改循环,以便它们在缓冲区开始时终止,并让下一行明确检查它终止的原因并确定需要什么行为。那会有点难看,但不是火箭科学……

于 2012-04-16T07:21:59.463 回答
3
char * p_divs = divs; //what does divs do
char tmp;
while(tmp = *p_divs++)
    if (tmp == c) return 1

divs是指向 char 数组(当然是字符串)的指针。p_divs只是指向同一个字符串,并且在 while 循环中提取单个字符并将其写入tmp,然后指针递增,这意味着下一个字符将在下一个迭代器中提取。如果tmp匹配c,则函数返回。

编辑:您应该了解有关指针的更多信息,请查看Pointer Arithmetic

于 2012-04-16T05:41:45.680 回答
1

正如我在评论中指出的那样,我不认为 C 真的是完成这项任务的理想工具(如果可以选择,我会毫不犹豫地使用 C++)。

但是,我想如果我要谈论代码有多糟糕,反驳意见确实是正确的:我应该发布更好的东西。然而,与有问题的评论相反,我认为这并不代表优雅、简洁或性能方面的妥协。

唯一可能对真正争论开放的部分是优雅,但认为这足够简单和直接,在这方面几乎没有真正的问题。它显然更简洁——使用与原始格式大致相同的格式约定,我的 rev_words 是 14 行而不是 17 行。大多数人会格式化它们,我的是 17 行,他是 21 行。

对于性能,我希望在大多数情况下两者大致相当。我的避免了从数组的开头跑掉,这节省了一点时间。原版包含一个提前退出,这将节省一点点反转空字符串的时间。不过,我认为两者都微不足道。

我认为还有一点更重要:我有理由确定我的不会像原来那样使用/调用/依赖未定义的行为。我想有些人可能会认为如果它在另一个领域提供了巨大的优势是合理的,但鉴于它在其他领域大致并列或逊色,我无法想象在这种情况下谁会认为它(甚至接近)是合理的.

#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;
}

编辑:该:

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

真的应该是:

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

如果你想允许 max_len < 1,你可以改变:

*dest++ = '\0';

至:

if (max_len > 0) *dest++ = '\0';

如果缓冲区长度可以通过来自(可能是敌对的)用户的输入以某种方式设置,那可能是值得的。对于许多目的,只需要一个正缓冲区大小就足够了。

于 2012-04-16T09:43:12.557 回答