在c中使用字符串

时间:2012-04-16 05:36:31

标签: c

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

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

3 个答案:

答案 0 :(得分: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()从标准输入读取到字符数组sourcedivs - &gt;然后将这些输入提供给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 - &gt;这意味着它指向源中的 last 非NUL字符。 p_dest指向目标缓冲区开头的NUL字符。

然后循环说:while (p_source >= source) - 为了做这件事p_source最初必须是>= source - 这是有道理的,因为p_source指向最后一个字符source 1}}是缓冲区中的第一个字符地址;比较意味着我们将一个或两个移向另一个,直到它们交叉 - 每次做一些工作。这将我们带到:

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

这是我们刚刚看到的相同测试 - 但这次我们只向p_source向后移动到字符串的开头,而inDiv(*p_source, divs)也是如此......这意味着*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';

现在,你说:

  

根据作者写的一串hello world然后在其中有一个函数也将字符串反转为世界你好

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

对于它的价值 - 这段代码并不是那么糟糕......对于ASCIIZ缓冲区的C处理来说很正常,尽管关于输入长度的假设是危险的并且它缺少第一个字.... / p>

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

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

答案 1 :(得分: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

答案 2 :(得分: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&lt; 1,你可以改变:

*dest++ = '\0';

为:

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

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