在C语言开发中,字符串操作是非常常见的需求,而string.h头文件作为C标准库的核心组件,提供了一系列专门处理字符串和内存的函数,能帮我们快速实现字符串复制、合并、比较、查找等功能。本文将系统梳理string.h中最常用的两类函数(str开头、mem开头),每个函数都配了完整的示例代码、运行结果和避坑要点,不管是新手入门,还是开发者日常查阅,都能轻松看懂,彻底搞懂string.h的使用逻辑。

温馨提示:只要用到string.h里的任何函数,必须在代码开头写一行#include <string.h>,否则程序会报错;另外,函数参数里常出现的size_t,大家可以简单理解为“无符号整数”,只能表示正数,这样能避免因传入负数导致的内存操作出错。

一、str开头函数(字符串专属操作)

str开头的函数,专门用来操作“以'\0'结尾的C风格字符串”(简单说就是字符串最后会自动加一个隐藏的'\0',表示字符串结束)。这类函数用起来很方便,但一定要注意这个隐藏的结束符,不然很容易出现内存乱码、程序崩溃等问题。下面按功能分类,讲解15个最常用的函数,每个函数都包含「原型+功能+参数解析+示例代码+运行结果+避坑要点」,新手跟着学就能会。

1. 字符串复制函数:strcpy()、strncpy()

(1)strcpy():完整复制字符串

原型:char * strcpy(char *dest, const char *src);

功能:将src指向的字符串(包含结束符'\0')完整复制到dest指向的内存空间,覆盖dest原有内容。

参数解析:dest是“目标字符串指针”,简单说就是我们要把内容复制到哪里,这个目标必须提前分配足够的内存(比如定义一个足够大的字符数组);src是“源字符串指针”,就是我们要复制的内容,可以是字符串常量(比如"hello")。

返回值:返回dest指针,这个功能主要是为了方便“链式调用”(比如一次性写多个函数),新手暂时不用深究,知道它会返回目标字符串地址即可。

示例代码

#include<stdio.h>
#include<string.h>
int main(void)
{
    char dest[20];  // 分配足够内存,避免溢出
    char* source = "hello world!";
    strcpy(dest, source);
    printf("源字符串  :%s\n", source);
    printf("目标字符串:%s\n", dest);
    return 0;
}

运行结果

源字符串 :hello world!
目标字符串:hello world!

避坑要点:一定要给dest分配足够大的内存!如果要复制的src长度,比dest的内存容量大,就会导致“缓冲区溢出”,轻则程序乱码,重则程序崩溃,日常开发中建议优先用更安全的strncpy()替代strcpy()。
常见错误案例

#include<stdio.h>
#include<string.h>
int main(void)
{
    char dest[5];  // 内存不足(仅能容纳4个有效字符+1个'\0')
    char* source = "hello world!";  // 长度12,远超dest容量
    strcpy(dest, source);  // 错误:缓冲区溢出,程序可能崩溃或出现乱码
    printf("目标字符串:%s\n", dest);
    return 0;
}

错误分析:dest只分配了5个字节,最多只能装4个有效字符+1个隐藏的'\0',但source的长度有12个字符,strcpy()会一直复制,直到找到'\0'才停止,这样就会超出dest的内存范围,破坏程序其他内存的数据,导致程序出现不可预料的错误(比如崩溃、乱码)。

(2)strncpy():指定长度复制字符串

原型:char * strncpy(char *dest, const char *src, size_t maxsize);

功能:和strcpy()类似,也是复制字符串,但可以指定最大复制长度maxsize。如果src的长度比maxsize小,剩下的空间会自动用'\0'填充;如果src的长度比maxsize大,就只复制前maxsize个字符,'\0' 不会自动加(这是重点,也是容易踩坑的地方)。

参数解析:maxsize为最大复制字符数(无符号整数),其余参数同strcpy()。

返回值:返回dest指针。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * src = "hello,world!";
    char dest[20];
    strncpy(dest, src, 20);  // 复制长度大于src,剩余空间填'\0'
    printf("源字符串  :%s\n", src);
    printf("目标字符串:%s\n", dest);
    system("pause");
    return(0);
}

运行结果

源字符串 :hello,world!
目标字符串:hello,world!

避坑要点:如果maxsize比src的长度小,复制后dest的末尾不会自动加'\0',此时dest就不是一个合法的字符串了,打印的时候会出现乱码。解决方法很简单:复制完成后,手动在dest的第maxsize个位置(下标从0开始,所以是dest[maxsize-1])加一个'\0'
常见错误案例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * src = "hello,world!";  // 长度12
    char dest[10];
    strncpy(dest, src, 9);  // 复制前9个字符,不自动加'\0'
    printf("目标字符串:%s\n", dest);  // 错误:dest无'\0',输出乱码
    system("pause");
    return(0);
}

正确写法:复制后手动添加'\0',也就是在strncpy()之后加一行dest[9] = '\0',这样就能确保dest是合法的字符串,避免打印乱码。

2. 字符串合并函数:strcat()、strncat()

(1)strcat():完整合并字符串

原型:char *strcat(char *dest, const char *src);

功能:把src指向的字符串,追加到dest字符串的末尾。比如dest是“hello,”,src是“world”,合并后就是“hello,world”。它会自动覆盖dest原来的'\0',并在合并后的字符串末尾,重新加一个'\0'

参数解析:dest是目标字符串,必须提前分配足够大的内存,要能装下它自己原来的内容,加上src的内容;src是要追加的字符串,比如“world”。

返回值:返回dest指针。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * src = "world";
    char dest[50] = "hello,";  // 分配足够内存,避免合并后溢出
    strcat(dest, src);
    printf("合并后的字符串:%s\n", dest);
    system("pause");
    return(0);
}

运行结果:合并后的字符串:hello,world

避坑要点:和strcpy()一样,dest的内存必须足够大,否则会出现缓冲区溢出;另外,src和dest不能指向同一段内存(比如都是同一个字符串常量),不然会导致内容被覆盖,出现异常。

(2)strncat():指定长度合并字符串

原型:char *strncat(char *dest, const char *src, size_t maxsize);

功能:和strcat()类似,也是追加字符串,但可以指定最大追加长度maxsize。只追加src的前maxsize个字符,并且会自动在合并后的字符串末尾加'\0';如果src的长度比maxsize小,就把src的全部内容(包括'\0')都追加进去。

参数解析:maxsize为最大追加字符数,其余参数同strcat()。

返回值:返回dest指针。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * src = "world";
    char dest[50] = "hello,";
    strncat(dest, src, 3);  // 仅追加src前3个字符
    printf("合并后的字符串:%s\n", dest);
    system("pause");
    return(0);
}

运行结果:合并后的字符串:hello,wor

避坑要点:比strcat()更安全,但还是要确保dest的内存足够大;另外,src必须是以'\0'结尾的合法字符串,否则可能会追加一些无关的垃圾数据。

3. 字符串比较函数:strcmp()、strncmp()

(1)strcmp():完整比较字符串

原型:int strcmp(const char *str1, const char *str2);

功能:按字符的ASCII码值,逐字符比较str1和str2,比如先比第一个字符,一样就比第二个,直到遇到不一样的字符,或者遇到字符串末尾的'\0'才停止。

返回值:非常好记,不用死记硬背:

  • 若str1 < str2,返回值小于0(大多数编译器返回-1);
  • 若str1 和 str2 完全一样,返回值等于0;
  • 若str1 > str2,返回值大于0(大多数编译器返回1)。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str1 = "hello,world!";
    char * str2 = "hello,human!";
    int res = strcmp(str1, str2);
    if(res < 0)
        printf("str1小于str2");
    else if(res == 0)
        printf("str1等于str2");
    else
        printf("str1大于str2");
    printf("\n");
    system("pause");
    return(0);
}

运行结果:str1大于str2

避坑要点:新手最容易踩的坑!不能用“”直接比较两个字符串是否一样!“”比较的是两个字符串的“内存地址”,不是字符串的内容;要比较字符串内容是否相同,必须用strcmp()。

(2)strncmp():指定长度比较字符串

原型:int strncmp(const char *str1, const char *str2, size_t maxsize);

功能:和strcmp()的比较规则一样,但只比较前maxsize个字符。只要前maxsize个字符完全相同,不管后面的字符有多少差异,都会返回0。

参数解析:maxsize为最大比较字符数,其余参数同strcmp()。

返回值:与strcmp()一致。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str1 = "hello,world!";
    char * str2 = "hello,human!";
    int res = strncmp(str1, str2, 5);  // 仅比较前5个字符
    if(res < 0)
        printf("str1小于str2\n");
    else if(res == 0)
        printf("str1等于str2\n");
    else
        printf("str1大于str2\n");
    printf("\n");
    system("pause");
    return(0);
}

运行结果:str1等于str2

关键说明:示例中str1和str2的前5个字符都是“hello”,所以strncmp()返回0。这就是它和strcmp()的核心区别——只关注我们指定的前几个字符,不关心后面的内容。

4. 字符串查找函数(6个核心)

查找类函数,主要用来找“字符”或“子串”在目标字符串中的位置。它们的返回值大多是“指针”:找到目标,就返回目标所在的位置指针;找不到,就返回NULL(空指针)。新手一定要记得,拿到返回值后先判断是不是NULL,避免出现“野指针”(无效指针)导致程序崩溃。

(1)strchr():查找字符第一次出现的位置

原型:char *strchr(const char *src, int c);

功能:在src指向的字符串中,找字符c(比如‘o’)第一次出现的位置,连字符串末尾的'\0'也会找(不过日常用不到找'\0')。

返回值:找到则返回该位置的指针,找不到则返回NULL。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str = "hello,world!";
    char * pos;
    pos = strchr(str, 'o');  // 查找字符'o'
    if(pos != NULL)
        printf("字符第一次出现的位置为第%d个(从0开始),内存地址为:0x%x\n", pos-str, pos);
    else
        printf("未找到指定字符\n");
    system("pause");
    return(0);
}

运行结果:字符第一次出现的位置为第4个(从0开始计数,新手要注意:C语言中字符串下标从0开始),内存地址为:0x7ffdxxxx(地址会因电脑环境不同而变化,不用在意)

(2)strrchr():查找字符最后一次出现的位置

原型:char * strrchr(const char *src, int c);

功能:和strchr()正好相反,找字符c在src字符串中“最后一次”出现的位置,同样会包含字符串末尾的'\0'

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str = "hello,world!";
    char * pos;
    pos = strrchr(str, 'o');  // 查找字符'o'最后一次出现的位置
    if(pos != NULL)
        printf("字符最后一次出现的位置为第%d个(从0开始),内存地址为:0x%x\n", pos-str, pos);
    else
        printf("未找到指定字符\n");
    system("pause");
    return(0);
}

运行结果:字符最后一次出现的位置为第7个(从0开始计数),内存地址为:0x7ffdxxxx(地址因环境而异,不用纠结)

(3)strcspn():查找“包含指定字符”的前缀长度

原型:size_t strcspn(const char *str1, const char *str2);

功能:这个函数有点绕,新手可以这么记:计算str1中“不包含任何str2中字符”的前缀长度。简单说,就是从str1的开头开始,找第一个“在str2中出现过的字符”,这个字符的下标,就是函数的返回值;如果str1中没有任何字符在str2中出现,就返回str1的总长度。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str1 = "world!";
    char * str2 = "jbgar";  // str2包含字符'r'
    size_t len = strcspn(str1, str2);  // world中'r'的下标为2
    printf("str1中第一个在str2中出现的字符,下标为:%zu\n", len);
    system("pause");
    return(0);
}

运行结果:str1中第一个在str2中出现的字符,下标为:2(str1是“world!”,str2包含‘r’,‘r’在str1中的下标是2)

(4)strspn():查找“不包含指定字符”的前缀长度

原型:size_t strspn(const char *str1, const char *str2);

功能:和strcspn()正好相反,新手别搞混!计算str1中“全部包含str2中字符”的前缀长度。简单说,就是从str1的开头开始,找第一个“不在str2中出现的字符”,这个字符的下标,就是函数的返回值。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str1 = "world!";
    char * str2 = "dewor";  // str2不包含字符'l'
    size_t len = strspn(str1, str2);  // world中'l'的下标为3
    printf("str1中第一个不在str2中出现的字符,下标为:%zu\n", len);
    system("pause");
    return(0);
}

运行结果:str1中第一个不在str2中出现的字符,下标为:3(str1是“world!”,str2不包含‘l’,‘l’在str1中的下标是3)

(5)strpbrk():查找指定字符集第一次出现的位置

原型:char * strpbrk(const char *str1, const char *str2);

功能:比strchr()更灵活,strchr()只能找单个字符,而strpbrk()可以找“一组字符”。只要str2中的任意一个字符,在str1中出现了,就返回这个字符第一次出现的位置指针;找不到就返回NULL。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str1 = "world!";
    char * str2 = "glar";  // str2包含'g','l','a','r'
    char * pos = strpbrk(str1, str2);  // 第一个出现的是'r'(下标2)
    if(pos != NULL)
        printf("str2的字符在str1中第一个出现的是%c,下标为:%zu\n", *pos, pos-str1);
    else
        printf("未找到任何匹配字符\n");
    system("pause");
    return(0);
}

运行结果:str2的字符在str1中第一个出现的是r,下标为:2(str2中的‘g’‘l’‘a’‘r’,第一个在str1中出现的是‘r’,下标为2)

(6)strstr():查找子串第一次出现的位置

原型:char * strstr(const char *str1, const char *str2);

功能:在str1(主串)中,找str2(子串)第一次出现的位置。比如主串是“wohello,world!”,子串是“wor”,就找“wor”第一次出现的地方;如果str2是空串(比如""),就返回str1的指针;找不到子串,就返回NULL。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char * str1 = "wohello,world!";
    char * str2 = "wor";  // 子串
    char * pos = strstr(str1, str2);
    if(pos != NULL)
        printf("\"wor\"在\"%s\"中第一次出现的位置下标为:%zu\n", str1, pos-str1);
    else
        printf("未找到子串\n");
    system("pause");
    return(0);
}

运行结果:"wor"在"wohello,world!"中第一次出现的位置下标为:8(从0开始计数,第8个位置开始是“wor”)

5. 字符串长度计算:strlen()

原型:size_t strlen(const char *str);

功能:计算str指向字符串的长度,只统计从字符串开头,到隐藏的'\0'之前的字符个数,不包含'\0'。比如“hello”的长度是5,因为它的末尾有一个隐藏的'\0',不算在长度里。

返回值:返回字符串的长度,类型是size_t(无符号整数,只能是正数)。

示例代码

#include <stdio.h> #include <string.h> #include <stdlib.h> int main() { char * str1 = "hello,world!"; size_t len = strlen(str1); printf("str1的长度:%zu\n", len); // 注意:size_t类型要用%zu格式输出,别用%d system("pause"); return(0); }

运行结果:str1的长度:12

避坑要点:两个常见坑,新手一定要记牢:1. 如果字符串没有以'\0'结尾,strlen()会一直往后找,直到找到内存中随机的'\0',返回一个随机的长度(内存越界);2. strlen()返回的是无符号整数,不能和负数比较,比如strlen(a) - strlen(b)可能是负数,但电脑会把它当成一个很大的正数,导致判断出错。
常见错误案例

#include <stdio.h>
#include <string.h>
int main()
{
    // 错误1:字符串无'\0'结尾
    char str1[] = {'h','e','l','l','o'};  // 未手动添加'\0'
    printf("str1长度:%zu\n", strlen(str1));  // 结果随机(内存越界)
    
    // 错误2:无符号整数与负数比较
    char str2[] = "abc";
    char str3[] = "abcd";
    if(strlen(str2) - strlen(str3) < 0)  // 错误:无符号数相减结果仍为无符号,永远不小于0
        printf("str2比str3短\n");
    else
        printf("str2比str3长\n");  // 错误输出此句
    return(0);
}

错误分析:1. str1是用字符数组定义的,没有手动加'\0',strlen()找不到结束符,就会一直往内存后面找,返回一个随机的长度;2. strlen返回的是无符号整数,strlen(str2)-strlen(str3)的结果是-1,但电脑会把它当成无符号的最大值,所以判断条件“<0”永远不成立,会错误输出“str2比str3长”。

6. 错误码解析:strerror()

原型:char * strerror(int n);

功能:程序运行时可能会出现错误(比如打开文件失败),系统会返回一个“错误码”(比如1、2、3),这个函数能把错误码转换成我们能看懂的文字描述,方便我们调试程序。

返回值:返回错误描述字符串的指针。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    // 打印常见错误码对应的描述
    printf("错误码0:%s\n", strerror(0));
    printf("错误码1:%s\n", strerror(1));
    printf("错误码2:%s\n", strerror(2));
    printf("错误码3:%s\n", strerror(3));
    printf("错误码-1:%s\n", strerror(-1));  // 错误码为负数时,会映射为对应错误
    system("pause");
    return(0);
}

运行结果

错误码0:Success
错误码1:Operation not permitted
错误码2:No such file or directory
错误码3:No such process
错误码-1:Unknown error 4294967295

7. 字符串截取:strtok()

原型:char * strtok(char* str1, const char* str2);

功能:以str2中的字符为分隔符,拆分str1字符串,每次调用返回一个截取的子串,直到拆分完成返回NULL。

参数解析:str1为待拆分字符串(第一次调用需传入,后续调用传入NULL);str2为分隔符集合(多个分隔符用字符串表示)。

注意:strtok()会修改原字符串——它会把分隔符(比如@)换成'\0',所以str1不能是字符串常量(比如char *str = "hello@world"),字符串常量是只读的,修改会导致程序崩溃,必须用字符数组(比如char str[] = "hello@world")。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char str[] = "hello@world@coffee@tea!";  // 必须是字符数组,不能是字符串常量
    char *c = "@";  // 分隔符为'@'
    
    // 第一次调用传入str,后续传入NULL
    printf("第一个子串:%s\n", strtok(str, c));
    printf("第二个子串:%s\n", strtok(NULL, c));
    printf("第三个子串:%s\n", strtok(NULL, c));
    printf("第四个子串:%s\n", strtok(NULL, c));
    
    // 重新拆分(需重新传入原字符串)
    printf("重新拆分第一个子串:%s\n", strtok(str, c));
    system("pause");
    return(0);
}

运行结果

第一个子串:hello
第二个子串:world
第三个子串:coffee
第四个子串:tea!
重新拆分第一个子串:hello

避坑要点:两个重点坑:1. strtok()是“线程不安全”的(多线程程序中使用会出问题),多线程开发建议用strtok_r()(strtok()的安全版本);2. 拆分时会破坏原字符串,如果你还需要用到原字符串,一定要提前复制一份。
常见错误案例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    // 错误1:拆分字符串常量(只读内存,修改会崩溃)
    char *str = "hello@world@coffee";  // 字符串常量,不可修改
    char *c = "@";
    printf("第一个子串:%s\n", strtok(str, c));  // 错误:修改只读内存,程序崩溃
    
    // 错误2:未保留原字符串,拆分后原字符串被破坏
    char str2[] = "hello@world@coffee";
    strtok(str2, c);
    printf("原字符串:%s\n", str2);  // 输出"hello",原字符串已被破坏
    system("pause");
    return(0);
}

正确写法:1. 拆分前,把字符串复制到一个可修改的字符数组中;2. 多线程程序中,用strtok_r()替代strtok(),避免出现线程安全问题。

二、mem开头函数(内存级操作)

mem开头的函数,和str开头的不一样,它们是“内存级操作函数”——不依赖字符串的隐藏结束符'\0',直接按“字节”操作内存。不管是字符、整数、结构体,它都能处理,效率通常比str开头的函数高,但需要我们手动指定操作的字节数,灵活性更强。下面讲解5个核心函数,按功能分类,新手也能轻松看懂。

1. 内存复制:memcpy()

原型:void * memcpy(void *dest, const void *src, size_t n);

功能:从src指向的内存空间,复制n个字节的内容,到dest指向的内存空间,不管里面存的是什么数据(字符、整数都可以),只按字节复制。

参数解析:dest是目标内存的指针(要复制到哪里),src是源内存的指针(要复制的内容),n是要复制的“字节数”;void是一种通用指针,可以接收任意类型的指针(比如char、int*),使用时偶尔需要强制转换(后面示例会体现)。

返回值:返回dest指针。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char str1[13];
    char *str2 = "hello,world!";
    // 复制13个字节(包含'\0')
    memcpy(str1, str2, 13);
    printf("复制后str1中的内容:%s\n", str1);
    system("pause");
    return(0);
}

运行结果:复制后str1中的内容:hello,world!

避坑要点:两个关键坑:1. dest和src指向的内存空间不能“重叠”(比如src是arr,dest是arr+5,相当于复制到自己的后面),否则复制结果会混乱;2. 要确保dest有足够的内存,能装下n个字节的内容,不然会溢出。
常见错误案例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char arr[20] = "hello,world!";
    // 错误:内存地址重叠(src = arr,dest = arr+5)
    memcpy(arr+5, arr, 7);  // 复制结果异常,预期"hellohello,world!",实际结果混乱
    printf("地址重叠后:%s\n", arr);
    system("pause");
    return(0);
}

错误分析:memcpy()不会处理内存重叠的情况,复制时会覆盖还没来得及读取的src内容,导致复制结果混乱。这种情况,只要把memcpy()换成memmove(),就能正常复制,memmove()专门处理内存重叠的场景。

2. 内存比较:memcmp()

原型:int memcmp(const void *str1, const void *str2, size_t n);

功能:逐字节比较str1和str2指向的内存空间,只比较前n个字节,比较规则是按“无符号字符”的ASCII码值来比(新手不用深究无符号字符,知道和strcmp()的比较逻辑类似即可)。

返回值:与strcmp()一致(小于0、等于0、大于0)。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char *str1 = "hello,world!";
    char *str2 = "helle";
    int res = memcmp(str1, str2, 5);  // 比较前5个字节
    char *resstr = NULL;
    if(res < 0)
        resstr = "小于";
    else if(res == 0)
        resstr = "等于";
    else
        resstr = "大于";
    printf("str1的前5个字节%sstr2的前5个字节\n", resstr);
    system("pause");
    return(0);
}

运行结果:str1的前5个字节大于str2的前5个字节

关键说明:memcmp()和strcmp()的核心区别,新手一定要分清:1. memcmp()按字节比较,不关心'\0',能处理任意类型的数据(比如整数、结构体);2. strcmp()只针对字符串,遇到'\0'就停止比较。

3. 内存查找:memchr()

原型:void * memchr(const void *str, int c, size_t n);

功能:在str指向的内存空间中,找字符c(ASCII码对应的值)第一次出现的位置,只找前n个字节,不依赖'\0',不管里面存的是什么数据。

返回值:找到就返回目标位置的指针,需要强制转换成对应的数据类型(比如char*);找不到就返回NULL。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char *str = "hello,world!";
    char c = ',';
    char *pos = (char*)memchr(str, c, 10);  // 在前10个字节中查找','
    if(pos != NULL)
        printf("\",\"在\"%s\"中的下标位置为:%zu\n", str, pos-str);
    else
        printf("未找到指定字符\n");
    system("pause");
    return(0);
}

运行结果:","在"hello,world!"中的下标位置为:5

4. 内存初始化/替换:memset()

原型:void * memset(void *ptr, int value, size_t n);

功能:把ptr指向的内存空间,前n个字节全部设置为value(value是ASCII码对应的值),常用在内存初始化(比如把数组全部置0,或者全部置某个字符)。

参数解析:value为要设置的值(通常为0或特定字符),n为要设置的字节数。

返回值:返回ptr指针。

示例1:字符数组初始化

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char str[] = "hello,world!";
    char c = 'w';
    memset(str, c, 5);  // 将前5个字节设置为'w'
    printf("memset修改后:%s\n", str);
    system("pause");
    return(0);
}

运行结果:memset修改后:wwwww,world!

示例2:结构体初始化

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义结构体
typedef struct manager {
    int iArr[12];
    float fArr[5];
    char ch;  // 用int类型的value赋值,需确保value在char的范围(-128~127)
} Manager;

// 打印结构体内容
void printManager(Manager manager)
{
    int i = 0;
    puts("iArr值:");
    for(; i < 12; i++)
        printf("第%d个: %d\n", i+1, manager.iArr[i]);
    puts("\nfArr值:");
    for(i = 0; i < 5; i++)
        printf("第%d个: %f\n", i+1, manager.fArr[i]);
    puts("\nch值:");
    printf("%c\n", manager.ch);
}

void memsetTest()
{
    Manager manager;
    // 用65(ASCII码对应'A')初始化整个结构体
    printf("使用65('A')初始化结构体:\n");
    memset(&manager, 65, sizeof(manager));
    printManager(manager);
    
    // 用0初始化整个结构体(最常用场景)
    printf("\n\n使用0初始化结构体:\n");
    memset(&manager, 0, sizeof(manager));
    printManager(manager);
}

int main()
{
    memsetTest();
    getchar();
    return 0;
}

运行结果

使用65(‘A’)初始化结构体:
iArr值:(12个65)
fArr值:(5个乱码,因float按字节赋值65,非有效浮点数)
ch值:A

使用0初始化结构体:
iArr值:(12个0)
fArr值:(5个0.000000)
ch值:(空字符)

避坑要点:新手最容易踩的坑!memset()是“按字节赋值”的,不能用来初始化非字符类型的数组(比如int数组)。比如想把int数组全部置1,用memset()的话,会把每个字节都设为1,最终int的值会是16843009,而不是1;初始化结构体时,要用sizeof(结构体名)获取结构体的总字节数,避免赋值不完整。
常见错误案例

#include <stdio.h>
#include <string.h>
int main()
{
    // 错误:用memset初始化int数组为1
    int arr[5];
    memset(arr, 1, sizeof(arr));  // 错误:每个字节设为1,int值为0x00000001(即1)?实际是0x01010101=16843009
    for(int i=0; i<5; i++)
        printf("arr[%d] = %d\n", i, arr[i]);  // 输出16843009,而非1
    
    // 正确:初始化int数组为0(memset唯一适合的非字符初始化场景)
    int arr2[5];
    memset(arr2, 0, sizeof(arr2));
    for(int i=0; i<5; i++)
        printf("arr2[%d] = %d\n", i, arr2[i]);  // 输出0
    return(0);
}

错误分析:memset()按字节赋值,32位系统中,int类型占4个字节。把每个字节都设为1,组合起来就是0x01010101,对应十进制的16843009,不是我们预期的1;只有初始化0的时候,每个字节都是0,组合起来int值就是0,符合预期,这也是memset()唯一适合初始化非字符数组的场景。

5. 安全内存复制:memmove()

原型:void * memmove(void *dest, const void *src, size_t n);

功能:和memcpy()的功能完全一样,都是从src复制n个字节到dest,但memmove()支持“内存地址重叠”的场景,相当于memcpy()的升级版本,更安全。

核心区别:memcpy()不处理内存重叠,一旦dest和src的内存重叠,复制结果就会混乱;memmove()会先把src的内容临时保存起来,再复制到dest,不会出现重叠问题,但效率比memcpy()略低一点。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    char *src = "hello,world!";
    char dest[20];
    // 地址不重叠,与memcpy()效果一致
    memmove(dest, src, 20);
    printf("源字符串  :%s\n", src);
    printf("目标字符串:%s\n", dest);
    
    // 测试地址重叠场景(memmove()正常,memcpy()异常)
    char arr[20] = "hello,world!";
    memmove(arr+5, arr, 7);  // 将arr前7个字节复制到arr+5位置
    printf("地址重叠后:%s\n", arr);
    system("pause");
    return(0);
}

运行结果

源字符串 :hello,world!
目标字符串:hello,world!
地址重叠后:hellohello,world!

避坑要点:不同系统下,memcpy()的表现不一样:Linux环境下,memcpy()完全不处理内存重叠,建议优先用memmove();Windows环境下,部分编译器对memcpy()做了优化,能处理简单的重叠,但为了让程序在不同系统下都能正常运行(跨平台),还是建议用memmove()。

三、核心总结与避坑指南

1. str与mem开头函数的核心区别

  • str开头:专门处理“以'\0'结尾的C风格字符串”,用起来方便,不用手动指定字节数,但必须依赖结束符,不然容易出现内存越界。

  • mem开头:处理任意类型的内存,不依赖'\0',按字节操作,需要手动指定字节数,能处理各种数据类型,效率高,memmove()还能处理内存重叠。

2. 高频避坑要点(重中之重)

  • 所有str开头的字符串函数,必须确保字符串以'\0'结尾,否则会出现乱码、内存越界,程序崩溃。

  • strcpy()、strcat()容易出现缓冲区溢出,日常开发优先用strncpy()、strncat(),并且要手动添加'\0'

  • memset()按字节赋值,不能用来初始化int、float等非字符类型的数组(除了置0)。

  • strtok()会修改原字符串,多线程用strtok_r();不能拆分字符串常量,否则程序会崩溃。

  • 内存地址重叠时,用memmove()替代memcpy(),避免复制结果混乱。

  • size_t是无符号整数,只能表示正数,不能和负数比较,printf输出时要用%zu格式,别用%d。

3. 适用场景推荐

  • 字符串操作(复制、合并、比较、查找):优先用str开头的函数,简单方便;需要安全控制(避免溢出),就用strncpy()、strncat()。

  • 非字符串操作(整数、结构体、数组等):用mem开头的函数,灵活又高效。

  • 内存地址重叠的复制场景:必须用memmove(),不能用memcpy()。

  • 内存初始化(比如数组置0):用memset(),简单高效。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐