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

63

我有一个字符串:

char * someString;
如果我想要这个字符串的前五个字母并将其设置为"otherString",我该如何做?

4
someString 不是一个字符串,它是指向字符串的指针。而且,字符并不一定是字母。在移动字母之前,您需要知道什么是字符串。 - user14554
根据您的需要,最好声明一个大小为6的char数组用于otherstring(5个字节加上一个'\0')。这样,如果您在使用后忘记释放otherstring,就不必担心内存泄漏的问题。 - HeretoLearn
我投票关闭此问题,因为它教导新用户提问的错误方式,甚至投票也应该被禁用。 - Wolf
警告:这个旧问题带来了一大堆非常糟糕的答案,教授危险或不正确/有缺陷的做法。其中一些甚至无法编译。我建议读者避免阅读此帖子,或者向下滚动到我刚刚发布的答案。我还强烈建议打倒所有可怕的答案,特别是那些明显存在错误和编译器错误的答案。 - Lundin
13个回答

70
#include <string.h>
...
char otherString[6]; // note 6, not 5, there's one there for the null terminator
...
strncpy(otherString, someString, 5);
otherString[5] = '\0'; // place the null terminator

4
或者,如果你想更严谨一些,可以使用otherString[5] = (char)0;。Char是整型类型,因此编译器不应该会对将原始整数赋值给它进行抱怨(或不应该)。 - pib
2
谢谢您提醒我在单引号中放置终止字符而不是双引号。 - Seagull
2
@pib 对于评论不太清楚。在C语言中,otherString[5] = '\0';otherString[5] = 0;都将一个值为0的int赋给了一个char。那么(char)otherString[5] = (char)0;中有什么作用呢? - chux - Reinstate Monica
2
这是错误的建议。strncpy 是一个危险的函数,永远不应该使用,因为太多人无法理解它的工作原理。在这种情况下,strncpy 可能会或可能不会自行终止字符串,这取决于长度。它是反复无常、不可靠的,并且最初并不打算与以空字符结尾的字符串一起使用。请参见 Is strcpy dangerous and what should be used instead? - Lundin

10
char* someString = "abcdedgh";
char* otherString = 0;

otherString = (char*)malloc(5+1);
memcpy(otherString,someString,5);
otherString[5] = 0;

更新:
提示:理解定义的好方法是使用右左法则(一些链接在末尾):

从标识符开始阅读,大声说出 => "someString 是..."
现在去到 someString 右边(语句以分号结束,无需说什么)。
现在去到标识符左边(遇到了 *)=> 所以说 "...指向...".
现在去到 "*" 的左边(找到了关键字 char)=> 说 "..char".
完成!

因此,char* someString; => "someString 是 char 类型的指针"。

由于指针只是指向某个内存地址,它也可以用作字符“数组”的“起点”。

这适用于任何东西..试一试:

char* s[2]; //=> s is an array of two pointers to char
char** someThing; //=> someThing is a pointer to a pointer to char.
//Note: We look in the brackets first, and then move outward
char (* s)[2]; //=> s is a pointer to an array of two char

以下是一些链接: 如何解读复杂的C/C++声明如何阅读C语言声明


1
我认为你应该尝试编译char *[] someThing;char []* someThing;。你需要分别使用char *someThing[];char (*someThing)[];。这会破坏你理解定义的算法。 - Alok Singhal
//谢谢,你说得对,语法有问题..已经修复了代码。然而,算法仍然有效,请查看更新。 - Liao
别忘了在 malloc 后释放内存。 - ShihabSoft
如果somestring的字符数少于4个,memcpy(someString, otherString, 5)可能会产生未定义的行为。它在你的例子中可以工作是因为源字符串更长,但对于小的源字符串,行为并没有完全定义。 - chqrlie

10

通用:

char* subString (const char* input, int offset, int len, char* dest)
{
  int input_len = strlen (input);

  if (offset + len > input_len)
  {
     return NULL;
  }

  strncpy (dest, input + offset, len);
  return dest;
}

char dest[80];
const char* source = "hello world";

if (subString (source, 0, 5, dest))
{
  printf ("%s\n", dest);
}

2
在目标字符串的末尾加上'\0',这样不是更好吗? - JoseLinares
1
这里有一个微妙的错误;你需要将if(offset + len)更改为if(offset),否则字符串的最后几个字符将永远不会被读取;例如:http://cpp.sh/955ib - Blaskovicz

4

您可以使用snprintf函数来精确地获取char数组的子串:

#include <stdio.h>

int main()
{
    const char source[] = "This is a string array";
    char dest[17];

    // get first 16 characters using precision
    snprintf(dest, sizeof(dest), "%.16s", source);

    // print substring
    puts(dest);
} // end main

输出:

这是一个字符串

注意:

更多信息请参见printf手册页面。


不确定这个的成本是多少,但仅使用一个标准库来执行此操作的事实确实非常值得注意。 - PYK

4

你需要为新字符串 otherString 分配内存。通常情况下,对于长度为 n 的子字符串,以下代码可能适用于你(不要忘记进行边界检查...)

char *subString(char *someString, int n) 
{
   char *new = malloc(sizeof(char)*(n+1));
   strncpy(new, someString, n);
   new[n] = '\0';
   return new;
}

这将返回someString的前n个字符的子字符串。使用free()释放内存时,请确保您完成了它。


1
请检查malloc返回值。 - pm100
......或者是全新的东西...... char *new=new char[n+1] :-) - dolphin
1
sizeof(char)*n+1 的概念是错误的。可以使用 sizeof(char)*(n+1) 或者简单地使用 n+1,因为 sizeof(char)==1 - chux - Reinstate Monica

1
直译为:我直到现在才看到这篇帖子,目前的回答集合形成了一堆错误建议和编译器错误的狂欢,只有少数推荐使用 memcpy 的是正确的。基本上问题的答案是:
意译为:直到现在我才看到这个帖子,目前所有的回答都是错误的建议和编译器错误的混合体,只有少数人推荐使用 memcpy 是正确的。总的来说,这个问题的答案就是:
someString = allocated_memory; // statically or dynamically
memcpy(someString, otherString, 5);
someString[5] = '\0';

假设我们知道 otherString 至少有 5 个字符,那么这就是正确答案,没有争议。memcpystrncpy 更快、更安全,而且不会让人困惑是否要在字符串中加入空终止符 - 它不需要加入,所以我们必须手动添加空终止符。
这里的主要问题是,strncpy 是一个非常危险的函数,不应该用于任何目的。该函数从未旨在用于空结尾字符串,它出现在 C 标准中是一个错误。请参见Is strcpy dangerous and what should be used instead?,我将为方便引用该帖子的一些相关部分:
在微软将`strcpy`标记为过时和危险的那个时期,有另一个误导人的谣言开始传播。这个恶意谣言声称,应该使用`strncpy`作为更安全的`strcpy`版本。因为它将大小作为参数,并且已经是C标准库的一部分,所以它是可移植的。这似乎非常方便-传播这个消息,忘记非标准的`strcpy_s`,让我们使用`strncpy`!不,这不是一个好主意...
看看`strncpy`的历史,它可以追溯到Unix最早的日子,当时存在几种字符串格式。存在一种称为“固定宽度字符串”的东西-它们没有以空字符结尾,但与字符串一起存储了固定大小。在创建C语言时,Dennis Ritchie(C语言的发明者)希望避免将大小与数组一起存储[《C语言的发展》Dennis M. Ritchie]。很可能出于同样的精神, "固定宽度字符串"随着时间的推移逐渐淘汰,而改用以空字符结尾的字符串。
用于复制这些旧的固定宽度字符串的函数名为`strncpy`。这就是它被创建的唯一目的。它与`strcpy`没有任何关系。特别是它从来没有被设计为更安全的版本-当这些函数被创造时,计算机程序安全甚至还没有被发明。
不知怎么的,`strncpy`仍然进入了1989年的第一个C标准。整个高度可疑的函数都有-原因总是向后兼容性。我们还可以在C99 rationale 7.21.2.4中阅读关于`strncpy`的故事:
Codidact的链接中还包含一些示例,展示了strncpy无法终止复制字符串的情况。

1
虽然我同意你对这个问题发布的答案的评估,但是你推荐使用memcpy也不完美:如果somestring少于4个字符,则memcpy(someString,otherString,5);可能会有未定义的行为。 对于这个问题有一个简单的一行代码解决方案:sprintf(someString, "%.5s", otherString),但感觉就像用谢尔曼坦克去买菜。 - chqrlie

1
您可以将C字符串视为指针。因此,当您声明时:
char str[10];

str可以用作指针。因此,如果您只想复制字符串的一部分,可以使用以下方法:

char str1[24] = "This is a simple string.";
char str2[6];
strncpy(str1 + 10, str2,6);

这将从 str1 数组中复制6个字符到 str2,从第11个元素开始。

还需要添加空终止符,对吧? - IAbstract
1
你可以这样做,但如果你想精确地复制哪些字节,这种方法是可行的。它不是动态的,但它能够实现。 - calvinjarrod
OP明确写道:“我想要前五个字母”:为什么你复制了6个字符并省略了设置空终止符? - chqrlie

0

我认为这是一种简单的方法...但我不知道如何直接传递结果变量,所以我创建了一个本地字符数组作为临时变量并返回它。

char* substr(char *buff, uint8_t start,uint8_t len, char* substr)
{
    strncpy(substr, buff+start, len);
    substr[len] = 0;
    return substr;
}

start and len should have type size_t, not uint8_t - chqrlie

-1
strncpy(otherString, someString, 5);

别忘了为 otherString 分配内存。


3
请注意,这可能会导致字符串未终止(如果“someString”包含五个或更多字符)。 - strager

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

int main ()
{
        char someString[]="abcdedgh";
        char otherString[]="00000";
        memcpy (otherString, someString, 5);
        printf ("someString: %s\notherString: %s\n", someString, otherString);
        return 0;
}

如果您不使用printf语句,那么就不需要stdio.h了。在除最小程序之外的所有程序中都放置常量是不好的做法,应该避免。

你还需要设置otherString[5] = '\0'。 - Bill Forster
复制后的otherstring不是有效的C字符串,因为它没有以空字符结尾。在memcpy之后,您需要添加otherstring [5] = '\0'; - HeretoLearn
在使用之前,您可以对其进行memset(otherstring,'\0',sizeof(otherstring));。 - HeretoLearn
这是真的,同时也揭示了一个很好的问题。代码灵活性不应以简单性为代价。它可以像 char otherString[]="00000"; 这样简单,因此空终止字符就不再是问题。我之所以会在第一时间回复是因为使用 '0' 而不是 '\0',结果自己还忘记了…… - gavaletz
这是明显错误的,因为您没有给字符串加上空终止符,也没有为其分配空间。 - Lundin

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