如何在C语言中复制字符数组?

108

在 C 语言中,我有两个字符数组:

char array1[18] = "abcdefg";
char array2[18];

如何将array1的值复制到array2中?我可以这样做吗:array2 = array1


3
strcpymemcpy - Alok Save
1
char array1[18] = {"abcdefg"}; 不是一个正确的字符数组。 - aragaer
1
虽然它可以作为字符数组工作(只是碰巧能工作),但应该声明为char array1[18] = "abcdefg";char array1[18] = {'a','b','c','d','e','f','g','\0'}; - aragaer
14
字符类型的数组可以用字符字符串字面值或UTF-8字符串字面值进行初始化,可选择加上大括号char array1[18] = {"abcdefg"}; 是不寻常的,但是完全正确的。 - Daniel Fischer
memcpy(..)是否处理内存(我的意思是其中的maloc和realoc)? - Coldsteel48
显示剩余4条评论
14个回答

106

直接使用 array2 = array1 是不行的,因为这种情况下你操作的是数组 (char *) 的地址而不是它们内部值 (char) 的地址。

从概念上讲,你想要做的是遍历源数组 (array1) 中的所有字符并将它们复制到目标数组 (array2)。有多种方式可以实现这一点。例如,你可以编写一个简单的 for 循环,或者使用 memcpy

话虽如此,对于字符串来说,推荐使用 strncpy。它可以防止常见的错误,比如缓冲区溢出(特别是当 array1 来自用户输入:键盘、网络等时,这种错误尤其危险)。使用方法如下:

// Will copy 18 characters from array1 to array2
strncpy(array2, array1, 18);

正如@Prof. Falken在评论中提到的,strncpy可能有害。确保您的目标缓冲区足够大,可以容纳源缓冲区(包括字符串末尾的\0)。


1
strncpy()也是有问题的,参见https://dev59.com/l3I95IYBdhLWcg3w3iDG - Prof. Falken
为什么我们不能只是做array2 = array1? - user2131316
2
@user2131316:这是因为数组名称在语义上转换为指针值的方式。我在我的答案中解释了这一点。 - jxh
让我困惑的是你提到array1=array2,这意味着array1会得到新值,但你的代码实际上却相反。以防其他人也会犯同样的错误。 - lucidbrot
strncat会在不发生缓冲区溢出的情况下始终提供以空字符结尾的结果。请参见此处 - Louis Go
谢谢,这正是我所需要的,特别是额外的1个字符缓冲区来容纳/0。 - JonoJames

46
如果您的数组不是字符串数组,请使用以下代码进行复制: memcpy(array2,array1,sizeof(array2));

sizeof() 不会返回指针的大小(即您计算机上的字长)吗? - Wecherowski
4
当你将数组传递给函数时,它会退化为一个指针。在函数内部,你不能使用sizeof()来确定数组的大小。否则,sizeof()可以很好地完成这个任务。 - user6214440

31
如果您想防止未终止的字符串引起各种问题,可以像这样复制您的字符串:
char array1[18] = {"abcdefg"};
char array2[18];

size_t destination_size = sizeof (array2);

strncpy(array2, array1, destination_size);
array2[destination_size - 1] = '\0';

最后一行非常重要,因为strncpy()并不总是会将字符串null terminate。(如果目标缓冲区太小而无法容纳整个源字符串,则sntrcpy()将不会对目标字符串进行空终止。)

甚至strncpy()的manpage中都声明了:"警告:如果src的前n个字节中没有null byte,则放入dest的字符串将不以null结尾。"

strncpy()表现出这种有点奇怪的方式的原因是,它实际上最初并不是用作安全复制字符串的方法。

另一种方法是使用snprintf()作为strcpy()的安全替代品:

snprintf(array2, destination_size, "%s", array1);

(感谢 jxh 的提示。)


3
请注意,snprintf() 没有 strncpy() 存在的问题。 - jxh
这会不会生成警告?strncpy接受const char作为源。 - kon psych
@konpsych 这不应该生成警告。 - Prof. Falken
1
@Rahav,使用 snprintf()strncpy() 更容易正确使用。 - Prof. Falken
1
@Rahav,没有测试很难说清楚,但是*a)在现代硬件上这些操作本来就很快。b)*对于非常小的字符串,也许strncpy()可能更快,对于较大的字符串,实际的内存复制将占据时间,并且我猜两者的速度应该差不多。今天的编译器和库也非常擅长优化,以前很难猜测的事情现在变得非常非常难猜测。如果你想确保,只需测量/分析即可。在实际使用中,我的最佳猜测是几乎永远不会有实质性的差异。 - Prof. Falken
显示剩余2条评论

12

正如其他人所指出的那样,使用strcpy()或其变种函数复制字符串。在某些情况下,您也可以使用snprintf()

只有作为结构体赋值的一部分,您才能按照您想要的方式来分配数组:

typedef struct { char a[18]; } array;
array array1 = { "abcdefg" };
array array2;

array2 = array1;

如果您的数组被传递到一个函数中,似乎允许您对它们进行赋值,但这只是语义上的偶然。在C中,数组将衰减为指向数组第一个成员的地址的指针类型,并且传递的是该指针。因此,您在函数中的数组参数实际上只是一个指针。赋值只是一个指针赋值:

void foo (char x[10], char y[10]) {
    x = y;    /* pointer assignment! */
    puts(x);
}

从函数返回后,数组本身保持不变。

数组的"退化为指针值"语义是导致赋值无法正常工作的原因。左值具有数组类型,但右值是退化的指针类型,因此赋值是不兼容的类型之间的操作。

char array1[18] = "abcdefg";
char array2[18];
array2 = array1; /* fails because array1 becomes a pointer type,
                    but array2 is still an array type */

为什么引入了“衰减为指针值”的语义,是为了实现与C的前身源代码兼容。您可以阅读The Development of the C Language获取详细信息。


唯一一个解释为什么 array2 = array1; 不起作用的答案。点赞。 - Miljen Mikic

8

您不能分配数组,这些名称是无法更改的常量。

您可以使用以下方式复制内容

strcpy(array2, array1);

假设源字符串是有效的,并且目标空间足够大,就像你的示例一样。

4
应该长这样:
void cstringcpy(char *src, char * dest)
{
    while (*src) {
        *(dest++) = *(src++);
    }
    *dest = '\0';
}
.....

char src[6] = "Hello";
char dest[6];
cstringcpy(src, dest);

1
你的 cstringcpy 函数没有在目标数组中添加空字符。 - chqrlie
没明白,请提供您的“修复”方案。 - Boris
请注意,这个 cstringcpy 函数和 strcpy 函数几乎在语义上完全等效,具有相同的缺陷,即没有检查缓冲区溢出。 - chqrlie

2

我建议使用memcpy()来复制数据。 此外,如果我们将一个缓冲区分配给另一个缓冲区,如array2 = array1,那么两个数组具有相同的内存,并且在arrary1中的任何更改也会反映在array2中。但是如果我们使用memcpy,则两个缓冲区具有不同的数组。我建议使用memcpy(),因为strcpy和相关函数不会复制NULL字符。


1
以下是仅适用于C语言的函数,如果要在C++中使用,您需要先创建字符数组,然后使用字符串复制功能,最后再使用字符串分词器函数。C++使得任何事情都变得更加困难。
#include <iostream>
#include <fstream>
#include <cstring>
#define TRUE 1
#define FALSE 0
typedef int Bool;
using namespace std;
Bool PalTrueFalse(char str[]);
int main(void)
{
char string[1000], ch;
int i = 0;
cout<<"Enter a message: ";

while((ch = getchar()) != '\n') //grab users input string untill 
{                               //Enter is pressed
    if (!isspace(ch) && !ispunct(ch)) //Cstring functions checking for
    {                                //spaces and punctuations of all kinds
        string[i] = tolower(ch); 
        i++;
    }
}
string[i] = '\0';  //hitting null deliminator once users input
cout<<"Your string: "<<string<<endl; 
if(PalTrueFalse(string)) //the string[i] user input is passed after
                        //being cleaned into the null function.
    cout<<"is a "<<"Palindrome\n"<<endl;
else
   cout<<"Not a palindrome\n"<<endl;
return 0;
}

Bool PalTrueFalse(char str[])
{
int left = 0;
int right = strlen(str)-1;
while (left<right)
{
   if(str[left] != str[right]) //comparing most outer values of string
       return FALSE;          //to inner values.
   left++;
   right--;
}
return TRUE;
}

1
array2 = array1;

c中不支持strcpy()这样的操作,你需要使用类似于其它函数来实现。


1
嗯,从技术上讲,你是可以的…
typedef struct { char xx[18]; } arr_wrap;

char array1[18] = "abcdefg";
char array2[18];

*((arr_wrap *) array2) = *((arr_wrap *) array1);

printf("%s\n", array2);     /* "abcdefg" */

但是它看起来不会很美观。

...除非您使用C预处理器...

#define CC_MEMCPY(DESTARR, SRCARR, ARRSIZE) \
    { struct _tmparrwrap_ { char xx[ARRSIZE]; }; *((struct _tmparrwrap_ *) DESTARR) = *((struct _tmparrwrap_ *) SRCARR); }

您可以这样做:

char array1[18] = "abcdefg";
char array2[18];

CC_MEMCPY(array2, array1, sizeof(array1));

printf("%s\n", array2);     /* "abcdefg" */

它可以与任何数据类型一起使用,不仅限于char

int numbers1[3] = { 1, 2, 3 };
int numbers2[3];

CC_MEMCPY(numbers2, numbers1, sizeof(numbers1));

printf("%d - %d - %d\n", numbers2[0], numbers2[1], numbers2[2]);     /* "abcdefg" */

是的,上述代码始终有效,且具有可移植性。


这个有文档记录吗? - Daniel

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