如何原地反转以 '\0' 结尾的 C 字符串?

6

我有一些关于反转以空字符结尾的C字符串的概念性问题,以及关于指针性质的澄清问题。

输入可能是

char arr[] = "opal";

代码如下:

void reverse(char *str) {  /* does *str = opal or does *str = o since the pointer str is type char? */

    char* end  = str; /* what is the difference between char* end and char *end? and is *end pointing to opal now? */

    char tmp;

    if (str) {  /* if str isn't null? */
        while (*end) 
            ++end; 
    }
    --end; /* end pointer points to l now */

    while (str < end) {     /* why not *str < *end? is this asking while o < l? */
        tmp = *str; /* tmp = o */

        *str++ = *end; /* what is the difference between *str++ and ++str? does *str++ = l? */
        *end-- = tmp; /* *end points to o */
        }
    }
}

2
如果你字面上传递了“opal”,那么这将是未定义的行为,因为你不允许修改一个字符串字面值。 - Shafik Yaghmour
一个字符数组,char arr[] = "opal";. - Shafik Yaghmour
要理解C指针,我不会将char视为字符串,而是将其视为内存地址。但大多数情况下,char指针将指向包含以NULL结尾的字符串的内存地址。 - TwilightSun
2
测试 if (str) { 没有提供安全性。如果 strNULL,那么 end 也是 NULL,下一个 LOC 是 --end --> seg fault。最好的方法是 if (str == NULL) return; - chux - Reinstate Monica
@chux 说得对,我更新了我的答案以反映这一点,并在阅读您的评论后想到了一些相关的想法。 - Floris
显示剩余2条评论
5个回答

9

有很多问题...试图回答每一个:

/* str 是 char 类型的指针,*str = opal, 还是 *str = o? */

*str 指向第一个字符,因此它是 'o'

/* char* end 和 char *end 有什么区别? *end 现在指向 opal 吗? */

char *endchar* end 没有区别。当你写成这种形式时会更加复杂:

char* a, b;

由于这相当于

char *a, b;

不是像您可能想象的那样


char *a, *b;

这就是为什么写char *end;更加规范。
现在,end指向opal - *end'o'

if (str) { /* 如果str不是空的? */

是的-检测你是否没有传递一个NULL指针。
要测试是否没有传递长度为0的字符串,您需要测试*str(在测试str不为NULL之后,否则您将获得"大胆地查看* NULL"的分段错误)

while (str < end) { /* 为什么不是 *str < *end? 这是在询问 o < l 吗? */

测试指针-one向末尾移动,另一个向后移动。当你在中间遇到时停止;否则,您需要两次交换,而没有净效应...
 *str++ = *end; /* what is the difference between *str++ and ++str? does *str++ = l? */
你需要先将*end的值复制到*str,然后再增加str指针。如果你使用++str,则会先增加,然后再使用它。这意味着你会将l放在o的位置而不是p的位置。
编辑一下你的代码(超出你所问的问题,并回应@chux的评论):当你测试if(str){}时,你真的需要一个else return;语句,因为你实际上确实做了end--;,然后使用*end。很确定0xFFFFFFFFFFFFFFFF几乎总是无效的地址...
如果你实际上正在测试if(*str!='\0'),那么你仍然只需要返回(空字符串是“不可逆转”的——或者说,它不需要任何东西被认为已经反转)。
顺便说一句,我更喜欢使条件明确(就像我刚才做的那样);不仅可以更清楚地显示你的意图,而且编译器可能会抱怨如果你做if(str!=' \ 0 ')if(*str != NULL),因为你比较的类型是不兼容的。这意味着你将拥有更健壮、更可读和更可能实现你预期目标的代码。

1
如果可以的话,我会再次为你在最后一段关于显式测试的评论点赞。 - Jonathan Leffler
@JonathanLeffler - 很可能是在过去的一年中通过阅读你的答案/评论之一,我学到了这种方法的重要性。是的,我大多数时间都在这里学习,尽管我是通过尝试回答问题来学习的。感谢你的赞美! - Floris

2

如果指针str是char类型,那么*str = opal或*str = o?

str是指向opal第一个元素的指针,也就是指向字母o的指针。所以,*s表示你正在解除引用s(第一个地址),因此它是'o'

char* end和char *end之间有什么区别?*end现在指向opal了吗?

它们没有区别。不是。它是指向opal的第一个元素的指针end

如果str不是null呢?

是的。

end指针现在指向l了吗?

是的。它现在指向这个单词的最后一个字母。

*str++和++str之间有什么区别?*str++ = l吗?

*str++表示在解除引用str指向的值后,str将被递增。++str只是对str进行预增量操作。
*str++ =在递增str之前将值分配给解除引用的变量。


我没有点踩,一定是别人点的。我只是编辑了问题以通过 char arr[] = "opal"; - Opal
1
好的回答,Haccks教授。+1 - Fiddling Bits

2

如果指针str的类型是char,那么*str = opal或*str = o吗?

*str对指针进行解引用,它的类型是char*,所以你得到的类型是char。这个char将是由str指向的值,即'o'

char* end和char *end之间有什么区别?

没有区别。

*end现在指向opal了吗?

是的,嗯,几乎是的。end指向与str完全相同的地址,即字符字符串的开头。*end是一个字符类型,而不是指针。它的值将是'o'

如果str不是null呢?

正确,对指针进行布尔测试是标准测试。如果指针不为null,则评估为“true”值,否则为“false”。请注意,这与零值不同。C标准允许任何值表示空地址。

为什么不是*str < *end?这是否在询问o < l?

不,它比较实际的内存地址。它表示在str指向字符串的较早部分时循环。你会注意到,在循环期间,str增加,而end减少。因此,最终它们将相互越过或在同一个字符处相遇(即字符串的中间)。

*str++和++str之间有什么区别?*str++ = l吗?

首先应用str++,它递增str并返回其先前的值,然后*一元运算符进行解引用,以得到那个旧位置上的字符。是的,在第一次循环时,end中的'l'将被分配给字符串的开头(在str递增之前)。tmp用于携带旧字符并将其分配给end。这是一个标准的“交换”操作。


2
假设像这样调用reverse
char str[] = "opal";
reverse(str);

以下是涉及地址的内容:
1. str + 0 等于 &str[0] 等于 100 2. str + 1 等于 &str[1] 等于 101 3. str + 2 等于 &str[2] 等于 102 4. str + 3 等于 &str[3] 等于 103 5. str + 4 等于 &str[4] 等于 104
以下是涉及值的内容:
1. *(str + 0) 等于 str[0] 等于 'o' 2. *(str + 1) 等于 str[1] 等于 'p' 3. *(str + 2) 等于 str[2] 等于 'a' 4. *(str + 3) 等于 str[3] 等于 'l' 5. *(str + 4) 等于 str[4] 等于 '\0'
关于 NULL,当指针未初始化时,即未指向有效内存时,应将其赋值为 NULL 的值,在大多数情况下,NULL 的地址为 0。请考虑以下内容:
char *str = NULL;
reverse(str);

今日免费次数已满, 请开通会员/明日再来
if(str)

它将被评估为FALSE。 在reverse内部,应该(你需要修复它)立即return,因为使用具有NULL地址的指针会导致不确定的行为,例如分段错误。

str的地址为NULL时,将str的地址赋给end,然后将其递减:

--end;

会导致不确定的行为。

1
这并不涵盖问题中的所有问题。 - Jonathan Leffler
@JonathanLeffler 你说得对,但如果你能理解这个,其他问题的答案就会变得明显。 - Fiddling Bits
@Awwww。不是的。但同意Jonathan Leffler的观点:D - haccks
2
@haacks,我扩展了我的答案。但你是对的。这个问题中有足够的问题,可以写一整章来涵盖它们。:-D - Fiddling Bits

1

如果指针str是char类型,那么 *str = opal 和 *str = 'o' 两种写法都可以使用。这可能有点令人困惑,但事实上在C语言中,指针可以有两种解释方式 - 直接作为指向单个项目(此处为char)的指针,或者作为指向相同类型项目序列开头的指针。在后一种情况下,需要定义序列的结束位置。有三种方法可以知道序列何时结束 - 明确知道长度、知道末尾指针,或者在序列末尾放置“终止符”项目。对于C字符串,使用特殊字符'\0'(空字符)作为“终止符”来终止序列。

char* end和char *end之间有什么区别? *end现在指向opal吗?

它们没有任何区别 - 它们都指向同一个位置。星号的放置位置并不重要。

如果str不是null,怎么办?

正确

end指针现在指向'l'

完全正确!

为什么不是*str < *end?这是在问while o < l吗?

你正在比较内存中的位置,而不是它们所指向的字符。这是要求指针不要“交叉”,因为两个指针都从字符串的两端向中心移动。

tmp = o

在第一次迭代中,是这样的。在第二次迭代中,它指向'p'

*str++++str之间有什么区别?*str++等于'l'吗?

++str 表达式会将指针的值加 1,并返回增加后的 指针 值;*str++ 表达式会将指针的值加 1,并返回增加前该指针所指向的 字符 值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接