在C语言中复制字符串的一部分

6

这似乎应该很简单,但由于某些原因,我无法使其工作。我有一个名为seq的字符串,它看起来像这样:

ala
ile
val

我想将字符串的前三个字符复制到另一个字符串中。我使用的命令是:

memcpy(fileName, seq, 3 * sizeof(char));

那应该让fileName = "ala",对吧?但出于某种原因,我得到了fileName = "ala9"。目前我通过说fileName[4] = '\0'来解决这个问题,但想知道为什么会出现这个9。

注意:更改seq后

ala
ile
val
ser

重新运行相同的代码后,fileName 变成了"alaK"。不再是数字9,但仍然是一个错误的字符。


7
如果源字符不构成完整的 C 字符串,手动设置最后复制字符后面的字符是正确的做法。为什么您认为这是一种“解决方法”? - CB Bailey
我想我误以为在将字符串移动过去后,memcpy会自动为我终止字符串... 哦,好吧。感谢您的帮助。 - wolfPack88
2
@wolfPack88:为什么memcpy会终止字符串?它不知道你正在复制一个字符串(而不是结构体、数组、整数或其他任何东西)。Memcpy只是按照你的指示执行了操作,没有做更多的事情。如果你想要一个“知道”字符串的函数,可以使用strcpy和其他strXXX相关的函数。 - abelenky
@wolfPack88:memcpy 对以 null 结尾的字符串一无所知,它只了解字节范围。 - nategoose
5
@wolfPack88:您应该将 fileName[3] 改为 '\0',因为 [0][1][2] 分别指代 ala - dreamlax
@dreamlax。同意!在C语言中,字符串是从0开始计数的,因此应该是[3]而不是[4]。 - Syd
10个回答

18

C使用空终止符来表示字符串的结尾。memcpy不知道您正在复制字符串(它只复制字节),因此不会考虑放置空终止符。您现有的解决方法实际上是正确的答案。

编辑:wolfPack88有一个很好的观点。 您确实需要更改filename [3]。 另外,下面的评论提出了一些关于strncpy的很好的观点,这也是值得学习的选择。


很抱歉不同意,但是那个解决方法并不正确。它只是一个权宜之计。正解是使用Svisstack发布的strncpy。 - Bruno Brant
4
@Bruno:不是的。事实上,strncpy 是正确答案的情况非常少,通常只用在避免使用哪些函数的问题中。 - Jerry Coffin
@Jerry:嗯...为什么呢? - Bruno Brant
2
@Bruno:因为strncpy被指定为执行很少有用的操作。如果源字符串比指定的长度短,则填充结果以达到指定大小。如果源字符串比指定的长度长,则结果中不包括NUL终止符。它可以很好地完成其最初的目的(将字符串转换为UNIX文件名),但仅限于此。 - Jerry Coffin
1
@Bruno:关于strncpy()的定义和用法,Stack Overflow上有很多好的解释。我在这里提供了其中之一:https://dev59.com/_HM_5IYBdhLWcg3wt1nD#1258577。如果你能找到AndreyT的解释,那就更加全面了(我只是无法想象出搜索技巧来找到他的解释)。 - caf
显示剩余3条评论

11

sprintf函数可以帮助你从一个字符串中提取字符,并将它们放入一个带有空字符结尾的字符缓冲区中。

sprintf(fileName, "%.3s", seq);
或。
sprintf(fileName, "%.*s", 3, seq);

甚至更多

snprintf(fileName, sizeof(fileName), "%.*s", len, seq);

会给你想要的结果。使用 * 的版本允许可变长度,而使用 snprintf 则更安全,可避免缓冲区溢出。


5

你需要设置

fileName[3] = 0;

请确保fileName有足够的空间来存储字符串结尾的NUL字节。


1
这不是一个很好的解决方案,如果@wolfPack88想在项目的其他部分使用此代码,那么必须计算长度并键入常量吗? - Svisstack
在所问的情境下,这是正确的答案。当然,在实际生活中,应该定义一个常量并用它来推导数组的大小、memcpy 中字符的数量以及 \0 放在哪里。 - JeremyP

5
你应该使用filename[3]='\0';。为什么这是必要的呢?因为没有其他东西为字符串设置了NUL终止符,所以你必须自己设置。
编辑:当然,在实际使用中,你不会像我上面展示的那样使用常量。通常你会使用类似以下代码:
char *substring(char *out, char const *in, size_t len) { 
    memcpy(out, in, len);
    out[len] = '\0';
    return out;
}

请注意,你使用 memcpy 的想法基本上是正确的。例如,strncpy 并不是用于这个(或几乎任何其他)目的的正确方法。在应避免使用的标准库函数列表中,strncpy 排名第二,仅次于 gets(尽管公平地说,我必须指出,strtok 是一个接近第三的选项)。
此外,请注意(与大多数标准库函数一样),它不会尝试验证您传递的参数——例如,如果您告诉它从长度为 10 的字符串中复制 99 个字符到长度为 5 的缓冲区中,它将尝试复制 99 个字符,从而产生未定义的行为。
编辑2:另一种选择是使用 sprintf

我想我误以为在将字符串移动过去后,memcpy会自动为我终止字符串... 哦,好吧。感谢您的帮助。 - wolfPack88
1
@wolfpack88:不是的——memcpy用于一般的内存复制,对数据不做任何假设,因此它只是按照您告诉它的内容逐字复制。 - Jerry Coffin
除了内存重叠的部分,其他内容都可以返回已被翻译的文本。 - dreamlax
@dramlax:是的,如果存在重叠的可能性,你应该使用memmove - Jerry Coffin

5

如果您想使用memcpy复制字符串,您必须在字符串的最后一个字符之后手动设置字符'\0'。如果您不想手动处理'\0',请改用strcpy或strncpy。


4
注意:如果源字符串过长,strncpy() 无法保证对目标字符串的空字符终止。 - Jonathan Leffler
关于strncpy函数,只要指定从源字符串中复制的最大字符数的参数大于源字符串长度,它就保证目标字符串以null结尾。这种行为是有道理的,因为在C语言中分配给字符串的内存必须足够处理字符串字符和null字符。请查看cplusplus.com上的strncpy定义(http://cplusplus.com/reference/clibrary/cstring/strncpy/)。 - gclello
是的,但在这种情况下,提问者正在从一个超过3个字符的字符串中复制3个字符。在这种情况下,strncpy 的用途有限。更快的方法是使用 memcpy 复制3个字符,并始终将第4个字符设置为 '\0' - tomlogic
@tomlogic:没错。我只是指出strncpy的原因,解释为什么使用memcpy需要用户手动设置'\0'字符。 - gclello

4
C语言的标准库没有专门用于复制字符串部分内容的函数。正确的方法是使用memcpy(正如您已经做过的那样),并显式地在结果后添加空字符。您忘记了终止结果,这就是为什么在字符串的复制部分之后看到奇怪的额外字符。
请注意,只有在事先知道源字符串长度时,即您知道所复制的字符串部分完全位于源字符串内时,memcpy才能起作用。如果有可能复制的源部分包含终止空字符(即源字符串在复制部分中间结束),则必须编写自己的复制函数或使用非标准但广泛可用的strlcpy
有时,您可能会遇到尝试使用strncpy函数进行此操作的代码示例。虽然它在某些情况下似乎可以“工作”,但考虑到它不是用于此目的的,因此使用strncpy完全没有意义。

3

C语言中的字符串以nul字符结尾,这意味着你需要在字符串结尾处添加nul字符。看起来你很幸运,下一个字符正好是nul字符,所以你只多了一个垃圾字符。但实际上,你也可能会得到成千上万个垃圾字符...


2
意外的字符是因为没有正确地给fileName加上空终止符造成的。
在这种情况下,fileName必须是一个长度至少为4char缓冲区(三个字符ala和一个终止空字符)。要设置空字符,您可以使用以下方法:
fileName[3] = '\0';

memcpy之后。

2
除了在字符串末尾加上null终止符之外,
fileName[3] = '\0';

你可能还想考虑使用 strncpy 代替 memcpy。另外,sizeof(char) 总是等于1,所以多余了。
祝好运!

1
@lhf:C标准中没有提到“NUL”,它被称为空字符 - dreamlax
@Syd - 谢谢 @lhf - 我谷歌了NUL vs NULL,它们都可以,但NULL似乎更常见。 - Parappa
2
@Parappa:NUL是空字符的ASCII代号。NULL通常指空指针常量。这两者本质上是不同的,但它们共享相同的名称。NUL只有一个L的拼写方式是因为所有的ASCII控制字符都有2或3个字符的缩写。C标准不使用NUL这个术语来表示空字符,因为该名称是特定于ASCII及其超集或变体的实现细节。 - dreamlax
1
“NULL” 不应该用来指代 C 字符串的终止字符。当以这种大写形式键入时,它指的是定义 C 空指针的宏。你应该使用“null”(小写)或“空字符”,或者在使用 ASCII 字符集或其超集(例如 UTF-8)的任何实现上,NUL 也可以。 - JeremyP

2
原因是您从seq中复制了三个字符字节,但没有终止的空字符。因此,您的解决方法不是解决方法,而是正确的解决方案。
C字符串应该以空字符结尾。如果没有这样做,那么字符串的“用户”将读取直到无法再读取为止,这会导致未定义的行为。
顺便问一下,为什么不使用strncpy?

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