0%

字符串复制相关 (strcpy, strncpy, memcpy, sprintf, snprintf, sizeof, strlen)

字符数组与字符串

  • 字符串是最后一个字符为 NULL \0 字符的字符数组。字符串一定是字符数组
  • 字符数组,即字符类型的数组。字符数组不一定是字符串
  • 字符数组的长度是固定的,其中的任何一个字符都可以为 NULL 字符
  • 字符串只能以 NULL 结尾,其后的字符便不属于该字符串
  • strlen() 等字符串函数对字符串完全适用,对不是字符串的字符数组不适用,容易出现问题
//这是字符数组赋初值的方法
char cArr[] = {'Q','U','A','N','X','U','E'};
//这是字符串赋初值的方法
char sArr[] = "quanxue";

//用sizeof()求长度
printf("cArr的长度=%d\n", sizeof(cArr));   //长度为7
printf("sArr的长度=%d\n", sizeof(sArr));   //长度为8,最后一位是NULL

//用printf的%s打印内容
printf("cArr的内容=%s\n", cArr);   //不能正确显示
printf("sArr的内容=%s\n", sArr);   //可以正确显示

//用strlen()求长度
printf("cArr的长度=%d\n", strlen(cArr));   //不正确的结果
printf("sArr的长度=%d\n", strlen(sArr));   //NULL不在计算范围

从上面例子看来,还要注意以下几点:

  • char sArr[] = “quanxue”;这种方式,编译时会自动在末尾增加一个NULL字符
  • sizeof()运算符求的是字符数组的长度,而不是字符串长度
  • strlen()函数求的是字符串长度,而不是字符数组。它不适用于字符串以外的类型
  • char sArr[] = “quanxue”;也可以写成char sArr[8] = “quanxue”;(注意:是8而不是7)

字符数组中插入一个NULL字符,NULL字符前面(包括NULL字符)就成了字符串,一般NULL字符插在有效字符的最后

//因为最后有NULL,所以这就变成了字符串
char cArr[] = {'Q', 'U', 'A', 'N', 'X', 'U', 'E', '\0'};
//因为少定义了一位,最后无NULL,所以这就变成了字符数组
char sArr[7] = "quanxue";
//最后一个元素未赋值
char tArr[16] = "www.quanxue.cn";

sizeof

sizeof(...)是运算符,在头文件中 typedefunsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。
它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。
由于在编译时计算,因此 sizeof 不能用来返回动态分配的内存空间的大小。实际上,sizeof 来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系
具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:

  • 数组 编译时分配的数组空间大小
  • 指针 存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4
  • 类型 该类型所占的空间大小
  • 对象 对象的实际占用空间大小
  • 函数 函数的返回类型所占的空间大小。函数的返回类型不能是void

strlen

strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。
它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL

strcpy

strcpy提供了字符串的复制,只用于字符串复制,会复制字符串的结束符。

char* strcpy(char* dest, const char* src);

strcpy不需要指定长度,它遇到被复制字符的串结束符\0才结束,所以容易溢出

memcpy

memcpy提供了一般内存的复制,对于需要复制的内容没有限制。例如字符数组、整型、结构体、类等。

void *memcpy( void *dest, const void *src, size_t count );

memcpy是根据其第3个参数决定复制的长度

strncpy

char *strncpy(char *dest, char *src, int n);

功能:把src所指由NULL结束的字符串的前n个字节复制到dest所指的数组中。

  • 如果src的前n个字节不含 \0 字符,则结果不会以 \0 字符结束
  • 如果src的长度小于n个字节,则以 \0 填充dest直到复制完n个字节
  • src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串

如果对src发生了截取,返回的dest不是想要的字符串,因为没有\0,如果对dest进行字符串类操作,会发生错误!!
使用 snprintf 替换这种可能有隐含错误的用法

snprintf

snprintfsprintf 的安全版本,防止溢出

int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
snprintf(char *dest, size_t size, "%s", char *src);

dest 会输出 size-1 字符和 \0 结尾的字符串

sprintf 与 snprintf

代码如下:

char tlist_1[1024] = {0},tlist_2[1024]={0};
char fname[7][8] = {"a1","b1","c1","d1","e1","f1","g1"};
int i = 0, len_1,len_2 = 0;

len_1 = snprintf(tlist_1,1024,"%s;",fname[0]);
len_2 = snprintf(tlist_2,1024,"%s;",fname[0]);

for(i=1;i<7;i++)
{
    len_1 = snprintf(tlist_1,1024,"%s%s;",tlist_1,fname[i]);
    len_2 = sprintf(tlist_2,"%s%s;",tlist_2,fname[i]);
}

printf("tlist_1: %s\n",tlist_1);
printf("tlist_2: %s\n",tlist_2);

>> tlist_1: g1;
>> tlist_2: a1;b1;c1;d1;e1;f1;g1;

上述代码表明, snprintf 会清除缓冲区内容, sprintf 不会清除缓冲区内容,针对使用场景分别使用

snprintf 调用 _IO_vsnprintf 实现:

int
_IO_vsnprintf (string, maxlen, format, args)
    char *string;
    _IO_size_t maxlen;
    const char *format;
    _IO_va_list args;
{
    ...
        string[0] = '\0';
    ...
}

sprintf 调用 _IO_vsprintf 实现中,没有相关的操作。