C语言【第九篇】 ——— 常用的字符串处理函数和内存操作函数的实现原理与使用规范
文章摘要: 本文详细解析了C语言中常用的字符串处理函数(strlen、strcpy、strcat、strcmp、strstr、strtok)和内存操作函数(memcpy)的实现原理与使用规范。重点包括:1)各函数的参数设计(如const修饰符的安全意义)和返回值语义;2)通过模拟实现代码(如my_strlen的遍历计数、my_memcpy的内存重叠处理)揭示核心逻辑;3)关键特性说明(如strto
目录
strlen 函数
strlen 函数的参数和返回值
size_t strlen(const char* str);
首先明确函数的 “输入” 与 “输出” 设计,完全贴合标准strlen的规范:
-
参数
const char* str:char* str:表示接收一个 “指向字符串首字符的指针”——C 语言中字符串本质是 “以'\0'结尾的字符数组”,传递字符串时实际是传递其首字符地址,因此用char*接收;const修饰:表明函数不会修改字符串的内容(仅读取字符判断是否为'\0'),这是一种安全设计 —— 避免误修改传入的字符串,同时也符合strlen“只读字符串” 的功能定位。
-
返回值
size_t:size_t是 C 语言专门用于表示 “无符号长度” 的类型(本质是unsigned int或unsigned long),因为字符串长度不可能为负数,用size_t比int更贴合语义,也与标准strlen的返回类型完全一致。
模拟实现 strlen 函数
代码演示:
size_t my_strlen(const char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
1. 核心逻辑:while (*str != '\0') { count++; str++; }—— 遍历计数,找到结束符即停止
这部分是模拟strlen的核心,依赖 C 语言字符串 “以'\0'作为结束标志” 的本质特性,分三步理解:
(1)初始化计数变量:int count = 0
count用于累计字符串的有效字符个数,初始化为 0—— 从 “0 个有效字符” 开始计数,后续每遇到一个非'\0'的字符,计数就加 1。
(2)循环条件:while (*str != '\0')—— 判断当前字符是否为结束符
*str:对指针str进行解引用,获取str当前指向的字符(比如初始时str指向字符串首字符,*str就是第一个字符);- 条件
*str != '\0':判断当前字符是否不是字符串结束符'\0'——C 语言规定,任何字符串的末尾都会隐含一个'\0'(如字符串"abc"实际存储为'a','b','c','\0'),'\0'是 “非有效字符”,一旦遇到'\0',就说明已经遍历完所有有效字符,循环停止。
(3)循环体:count++; str++;—— 计数 + 指针后移
count++:若当前字符不是'\0',说明是有效字符,将计数加 1(比如第一次循环遇到'a',count从 0 变成 1);str++:让指针str向后移动 1 个字节—— 因为str是char*类型,指针移动的 “步长” 是 1 字节(刚好等于 1 个char的大小),移动后str指向 “下一个字符”,为下一次循环判断做准备(比如从'a'指向'b')。
2. 返回结果:return count—— 返回有效字符总数
当循环因*str == '\0'停止时,count中存储的就是 “从字符串首字符到'\0'前的有效字符个数”,这正是strlen函数需要返回的结果。
例如:对字符串"hello"(存储为'h','e','l','l','o','\0')调用my_strlen,循环会依次判断'h'、'e'、'l'、'l'、'o'(均非'\0',count累加到 5),直到str指向'\0'时循环停止,最终返回 5,与标准strlen("hello")的结果完全一致。
strcpy 函数
strcpy 函数的参数和返回值
char* strcpy(char* destination, const char* source);
一、参数的作用:明确复制的 “目标” 与 “来源”
1. char* destination:目标字符串的地址(复制的接收端)
- 本质是一个指向可修改内存空间的指针,该空间用于存储复制后的字符串。
- 要求:调用者必须提前为
destination分配足够大的内存(需能容纳源字符串的所有字符,包括结尾的'\0'),否则会导致 “缓冲区溢出”(写入超出目标空间的内存,引发程序崩溃或数据错乱)。 - 无
const修饰的原因:函数需要向destination指向的空间写入数据(复制源字符串的字符),因此允许修改该空间的内容。
2. const char* source:源字符串的地址(复制的发送端)
- 本质是一个指向原始字符串的指针,该字符串是被复制的对象。
- 核心特性:源字符串必须以
'\0'(字符串结束符)结尾 ——strcpy的复制逻辑是 “从source指向的首字符开始,逐个复制字符,直到遇到'\0'为止('\0'也会被复制到目标空间,确保目标字符串完整)”。 const修饰的意义:表明source指向的字符串是 “只读” 的,函数不会(也不应该)修改源字符串的内容。这既是对源数据的保护(避免误操作修改),也向调用者明确了 “此参数仅作为输入” 的语义,提升了代码的安全性和可读性。
二、返回值的作用:char*—— 返回目标字符串的起始地址
返回值为destination(即目标字符串的首地址),这一设计的核心价值是支持 “链式操作”:
- 允许将
strcpy的返回值直接作为其他函数的参数,无需额外变量暂存目标地址,简化代码。 - 例如:
printf("%s", strcpy(dest, src));可在复制完成后直接打印目标字符串;strcat(strcpy(dest, src1), src2)可先复制src1到dest,再拼接src2,实现连续操作。
模拟实现 strcpy 函数
代码演示:
char* my_strcpy(char* destination, const char* source)
{
assert(destination != NULL);
assert(source != NULL);
if (strlen(destination) < strlen(source))
return NULL;
char* start = destination;
while (*source != '\0')
{
*destination = *source;
destination++;
source++;
}
*destination = *source;
return start;
}
1. 安全检查:assert(destination != NULL); assert(source != NULL);
这两句是空指针防护,是模拟strcpy的基础安全保障:strcpy需要通过destination和source指针访问内存,若任一指针为NULL(空指针),后续解引用(如*destination)会触发 “空指针访问错误”,导致程序崩溃。assert会在运行时检查指针是否非空,若为空则直接终止程序并提示,提前暴露非法参数问题,避免隐蔽的内存错误。
2. 地址暂存:char* start = destination;
这是为了保留目标字符串的初始地址:后续循环中,destination指针会不断后移(用于逐个接收源字符),若不暂存初始地址,最终返回时会指向目标字符串的末尾(而非开头),不符合strcpy“返回目标字符串首地址” 的标准行为。start存储destination的初始值,确保后续能正确返回目标字符串的起始位置,支持链式操作(如printf("%s", my_strcpy(dest, src)))。
3. 复制循环:while (*source != '\0') { *destination = *source; destination++; source++; }
这是字符串内容复制的核心逻辑,负责将源字符串的有效字符(非'\0'部分)逐个复制到目标空间:
- 循环条件
*source != '\0':以源字符串的结束符'\0'为终止标志,确保只复制 “有效字符”(不包含'\0',留到后续单独处理); *destination = *source:解引用两个指针,将source当前指向的源字符(如'a')赋值给destination当前指向的目标空间;destination++、source++:两个指针均向后移动 1 字节(因是char*类型,步长恰好等于 1 个字符的大小),为下一次复制 “下一个字符” 做准备,实现 “从首字符到倒数第二个有效字符” 的完整遍历复制。
4. 结束符补充:*destination = *source;
这是保证目标字符串完整性的关键,负责复制源字符串的'\0':循环只复制了源字符串的有效字符(到'\0'前为止),而 C 语言字符串必须以'\0'结尾才合法。此时source已指向源字符串的'\0',*source即为'\0',将其赋值给当前destination(目标字符串有效字符的末尾位置),确保目标字符串也以'\0'结尾,避免后续使用(如printf)时出现 “字符串越界访问”(读取到无关内存数据)。
5. 返回值:return start;
返回之前暂存的start(目标字符串初始地址),完全贴合标准strcpy的设计:目的是支持 “链式操作”—— 可将my_strcpy的返回值直接作为其他函数的参数(如strcat(my_strcpy(dest, src1), src2)),无需额外变量暂存目标地址,提升代码灵活性。
strcat 函数
strcat 函数的参数和返回值
char* strcat(char* destination, const char* source);
一、函数的参数
1. char* destination:目标字符串的地址(被追加的对象)
- 指向一个已存在的字符串(需以
'\0'结尾),该字符串会被修改 —— 源字符串的内容将被追加到它的末尾,原有的'\0'会被覆盖。 - 关键要求:
destination指向的内存空间必须足够大,需能容纳自身原有内容 + 源字符串的全部内容(包括源字符串的'\0'),否则会发生 “缓冲区溢出”(写入超出目标空间的内存,导致程序崩溃或数据错乱)。 - 无
const修饰:因为函数需要修改目标字符串的内容(追加字符并更新结束符),所以允许对其指向的空间进行写入操作。
2. const char* source:源字符串的地址(要追加的内容)
- 指向需要被追加的字符串,该字符串必须以
'\0'结尾 ——strcat的拼接逻辑是 “从源字符串的首字符开始,逐个复制字符,直到遇到'\0'为止('\0'也会被复制到目标字符串的末尾,作为新的结束符)”。 const修饰的意义:表明源字符串是 “只读” 的,函数不会修改其内容,既保护了源数据(避免误操作),也向调用者明确了 “此参数仅作为输入” 的语义,增强代码安全性。
二、函数的返回值:char*—— 返回目标字符串的起始地址
返回destination(目标字符串的首地址),这一设计与strcpy一致,核心价值是支持链式操作:
- 例如,可直接将
strcat的返回值作为其他函数的参数,简化代码:printf("%s", strcat(dest, "world"));(拼接后直接打印结果) - 或连续进行多次拼接:
strcat(strcat(dest, "hello"), "!");(先拼接 "hello",再拼接 "!")
三、核心作用:将源字符串追加到目标字符串末尾
strcat的核心功能是 “拼接”:
- 先在目标字符串中找到原有的
'\0'(结束符),从这个位置开始写入; - 将源字符串的所有字符(从首字符到
'\0')逐个复制到目标字符串的'\0'位置及之后; - 最终目标字符串会以源字符串的
'\0'作为新的结束符,形成一个完整的拼接后字符串。
例如:若destination是 "abc"(存储为'a','b','c','\0'),source是 "def"(存储为'd','e','f','\0'),调用strcat后,destination会变成 "abcdef"(存储为'a','b','c','d','e','f','\0')。
模拟实现 strcat 函数
代码演示:
char* my_strcat(char* destination, const char* source)
{
assert(destination != NULL);
assert(source != NULL);
if (destination == source)
{
char* start = destination;
char* ret = destination;
size_t len = strlen(destination);
while (*destination != '\0')
destination++;
while (len--)
{
*destination = *start;
destination++;
start++;
}
*destination = '\0';
return ret;
}
else
{
char* start = destination;
while (*destination != '\0')
destination++;
while (*source != '\0')
{
*destination = *source;
destination++;
source++;
}
*destination = *source;
return start;
}
}
1. 基础安全检查:assert(destination != NULL); assert(source != NULL);
这是所有字符串操作的前置保障:通过assert确保传入的两个指针均非空(NULL)。若任一指针为空,后续对指针的解引用(如*destination)会导致 “空指针访问错误”,assert能在运行时直接暴露这类问题,避免程序崩溃或隐蔽的内存错误。
2. 特殊情况处理:if (destination == source)—— 应对 “自拼接”(内存重叠)
当目标字符串与源字符串地址相同时(即 “自己拼接自己”,如my_strcat(str, str)),直接按常规逻辑拼接会出问题:复制过程中可能覆盖源字符串尚未复制的部分(因为目标和源共享同一块内存)。因此代码单独设计了处理逻辑:
-
步骤 1:保存起始地址与计算长度
char* start = destination; char* ret = destination;保存目标字符串的初始地址(用于最终返回);size_t len = strlen(destination);计算原字符串的长度(确定需要复制的字符总数)。 -
步骤 2:定位到目标字符串的结尾
while (*destination != '\0') destination++;让destination指针移动到原字符串的'\0'位置(即拼接的起始点)。 -
步骤 3:复制原字符串内容到结尾
while (len--) { *destination = *start; destination++; start++; }循环len次(原字符串长度),将start(原字符串起始位置)指向的字符逐个复制到destination(结尾位置),同时移动两个指针。这一过程避免了 “覆盖未复制内容”—— 因为start始终指向原字符串的未复制部分,destination指向拼接的目标位置,二者不会重叠冲突。 -
步骤 4:补充结束符
*destination = '\0';拼接完成后,手动添加'\0'作为新的结束符,确保字符串完整。
3. 常规情况处理:else分支 —— 目标与源字符串地址不同
当目标和源是不同的字符串(无内存重叠)时,按strcat的标准逻辑拼接:
-
步骤 1:保存目标字符串起始地址
char* start = destination;保存初始地址,确保最终能返回目标字符串的开头(支持链式操作)。 -
步骤 2:定位到目标字符串的结尾
while (*destination != '\0') destination++;让destination指针移动到原字符串的'\0'位置(拼接的起始点),跳过原有内容。 -
步骤 3:追加源字符串内容
while (*source != '\0') { *destination = *source; destination++; source++; }循环复制源字符串的字符:从源字符串的首字符开始,逐个复制到目标字符串的结尾位置,同时移动两个指针,直到遇到源字符串的'\0'。 -
步骤 4:补充源字符串的结束符
*destination = *source;此时source指向自身的'\0',将其复制到目标字符串的当前位置,作为拼接后字符串的新结束符,确保目标字符串完整。
4. 最终返回:return start;(或return ret;)
两种情况均返回目标字符串的初始地址,与标准strcat的设计一致,目的是支持 “链式操作”(如printf("%s", my_strcat(dest, src))),提升代码灵活性。
strcmp 函数
strcmp 函数的参数和返回值
int strcmp ( const char * str1, const char * str2 );
一、参数的作用
两个参数均为 const char* 类型,分别指向需要比较的两个字符串:
const char* str1:指向第一个待比较的字符串,const修饰表明函数不会修改该字符串的内容(仅读取字符用于比较);const char* str2:指向第二个待比较的字符串,同样被const修饰,确保源字符串只读。
核心要求:两个字符串都必须以 '\0'(字符串结束符)结尾 ——strcmp 的比较逻辑依赖于 '\0' 来判断 “是否已比较到字符串末尾”,若字符串无 '\0',会导致函数越界访问内存(未定义行为)。
二、返回值的作用:int 类型 —— 通过返回值正负表示比较结果
返回值是一个整数,其符号(正 / 负 / 零)直接反映两个字符串的大小关系,具体规则:
- 返回值 > 0:表示
str1大于str2—— 即两个字符串中第一个不相同的字符,str1中字符的 ASCII 码值大于str2中对应位置的字符; - 返回值 = 0:表示
str1等于str2—— 两个字符串的所有字符完全相同,且同时以'\0'结尾; - 返回值 < 0:表示
str1小于str2—— 即两个字符串中第一个不相同的字符,str1中字符的 ASCII 码值小于str2中对应位置的字符。
注意:返回值的具体数值通常是 “第一个不同字符的 ASCII 码差值”(如 str1 中字符为 'd'(ASCII 100),str2 中对应字符为 'b'(ASCII 98),则返回 2),但标准未规定具体数值,仅要求通过符号判断大小,因此实际使用时不应依赖具体数值(如只判断 >0 而非 ==1)。
三、核心作用:按 “字典序” 比较两个字符串的大小
strcmp 的核心逻辑是 “逐字符比较 ASCII 码值”,类似字典中单词的排序规则:
- 从两个字符串的首字符开始,逐个比较对应位置的字符(
str1[0]与str2[0],str1[1]与str2[1]……); - 若遇到第一个不相同的字符,直接根据该字符的 ASCII 码差值返回结果(正 / 负),停止比较;
- 若所有字符都相同,则比较到两个字符串的
'\0'为止,返回 0; - 若一个字符串是另一个的前缀(如
str1="app",str2="apple"),则较短的字符串更小(因为str1先遇到'\0',而str2对应位置仍有字符,'\0'的 ASCII 码为 0,小于任何可见字符)。
模拟实现 strcmp 函数
代码演示:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
1. 安全检查:assert(str1 && str2);
这是基础防护措施:通过assert确保传入的两个字符串指针str1和str2均非空(NULL)。若任一指针为空,后续对*str1或*str2的解引用会导致 “空指针访问错误”,assert能在运行时直接暴露这类问题,避免程序崩溃。
2. 核心比较循环:while (*str1 == *str2)—— 逐字符匹配相等的情况
循环的作用是遍历两个字符串中字符相同的部分,直到遇到不相等的字符或字符串结束符'\0':
- 循环条件
*str1 == *str2:比较当前指针指向的字符(str1和str2初始指向两个字符串的首字符),若相等则进入循环体; - 循环体内
if (*str1 == '\0') return 0;:若当前字符是'\0'(说明两个字符串同时遍历到结尾,且所有字符都相等),直接返回 0(表示两字符串相等); str1++; str2++;:若字符相等且非'\0',两个指针同时后移 1 字节(指向各自的下一个字符),继续比较下一组字符。
3. 循环退出后的判断:确定大小关系
当循环退出时,说明找到了第一个不相等的字符(*str1 != *str2),此时通过简单判断确定返回值:
- 若
*str1 > *str2(str1当前字符的 ASCII 码值更大),返回 1(表示str1大于str2); - 否则返回 - 1(表示
str1小于str2)。
strstr 函数
strstr 函数的参数和返回值
const char * strstr ( const char * str1, const char * str2 );
一、参数的作用
两个参数均为 const char* 类型,分别对应 “被搜索的主字符串” 和 “要查找的子字符串”:
const char* str1:指向主字符串(即被搜索的目标字符串),const修饰表明函数不会修改主字符串的内容(仅读取用于匹配);const char* str2:指向子字符串(即需要查找的目标序列),同样被const修饰,确保子字符串在查找过程中只读。
核心要求:
- 两个字符串都必须以
'\0'结尾(这是 C 语言字符串的基本规范); - 若
str2是 “空字符串”(仅包含'\0'),标准规定此时strstr会直接返回str1(因为空字符串是任何字符串的子串)。
二、返回值的作用:const char* 类型 —— 指向子字符串首次出现的位置
返回值是一个指针,其含义根据查找结果分为两种情况:
- 找到子字符串:返回
str1中子字符串str2首次完整出现的起始位置的指针。例如,若str1是 "abcdeabc",str2是 "abc",则返回str1中第一个 'a' 的地址(即str1本身); - 未找到子字符串:返回
NULL(空指针)。例如,str1是 "hello",str2是 "xyz" 时,返回NULL。
三、核心作用:在主字符串中查找子字符串的首次出现
strstr 的核心功能是 “子串匹配”:从 str1 的首字符开始,逐个位置尝试匹配 str2 的完整字符序列(直到 str2 的 '\0'),一旦找到完全匹配的连续字符序列,就返回其起始位置;若遍历完 str1 仍未找到,则返回 NULL。
匹配规则:
- 子字符串必须是主字符串中连续的字符序列(非分散匹配)。例如,
str1是 "aabbcc",str2是 "abc" 时,因字符不连续,返回NULL; - 匹配需完全一致(区分大小写,依赖 ASCII 码值)。例如,
str1是 "Abc",str2是 "abc" 时,因 'A' 与 'a' 的 ASCII 码不同,返回NULL; - 匹配到
str2的'\0'即视为完整匹配(无需考虑str1后续是否还有字符)。
模拟实现 strstr 函数
代码演示:
const char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0')
return str1;
const char* s1 = str1;
const char* s2 = str2;
while (*str1 != '\0')
{
s2 = str2;
s1 = str1;
while (*s1 == *s2 && *s2 != '\0' && *s1 != '\0')
{
s1++;
s2++;
}
if (*s2 == '\0')
return str1;
str1++;
}
return NULL;
}
1. 安全检查与边界处理:assert(str1 && str2); 和 if (*str2 == '\0') return str1;
- 空指针防护:
assert(str1 && str2);确保传入的两个字符串指针均非空(NULL),避免后续解引用空指针导致的程序崩溃。 - 子串为空的特殊处理:
if (*str2 == '\0') return str1;是关键边界条件 —— 根据 C 语言标准,空字符串(仅含'\0')是任何字符串的子串,因此直接返回主字符串str1的起始地址。
2. 临时指针定义:const char* s1 = str1; const char* s2 = str2;
定义s1和s2作为临时指针,用于在每次尝试匹配时遍历字符,而不修改原指针str1和str2的位置。这一设计的核心作用是:每次从主串的新位置开始尝试匹配时,能快速将 s2重置为子串str2的起始位置,s1重置为当前主串的尝试起始位置,避免原指针被 “污染”。
3. 主循环:while (*str1 != '\0')—— 遍历主串的每个可能起始位置
主循环的作用是逐个尝试主串中所有可能的匹配起始点:只要主串未遍历到结尾(*str1 != '\0'),就持续尝试从当前str1指向的位置开始匹配子串。
4. 内部匹配循环:while (*s1 == *s2 && *s2 != '\0' && *s1 != '\0')—— 检查连续字符是否匹配
这是核心的 “子串匹配” 逻辑,用于判断从当前str1位置开始,是否能完整匹配子串str2:
- 循环条件:
*s1 == *s2(当前字符匹配)、*s2 != '\0'(子串未结束)、*s1 != '\0'(主串未结束),三者同时满足时继续匹配; - 循环体:
s1++; s2++;两个临时指针同时后移 1 字节,比较下一组字符,实现 “连续匹配” 的检查。
5. 匹配成功判断:if (*s2 == '\0') return str1;
当内部循环退出时,若*s2 == '\0',说明子串str2已被完全匹配(s2遍历到子串的结束符),此时str1指向的正是子串在主串中首次出现的起始位置,因此返回str1。
6. 未匹配则继续尝试:str1++; 与最终返回NULL
若内部循环退出后未满足 “子串完全匹配”(即*s2 != '\0'),说明当前str1位置无法匹配子串,主串指针str1后移 1 字节,尝试下一个起始位置。
当主串遍历结束(*str1 == '\0')仍未找到匹配的子串,最终返回NULL,表示查找失败。
strtok 函数
strtok 函数的作用
strtok 函数是 C 语言标准库中用于字符串分割的工具,核心用途是将一个字符串按照指定的 “分隔符集合” 拆分成多个独立的子字符串(通常称为 “令牌”,token)。
具体功能与工作方式:
例如,对于字符串 "apple,banana;orange|grape",若指定分隔符为 ",;|"(逗号、分号、竖线),strtok 可将其拆分为 "apple"、"banana"、"orange"、"grape" 四个子串。其工作机制的关键特点是:
- 首次调用:需传入两个参数 —— 待分割的字符串(如上述例子中的完整字符串)和分隔符集合(如
",;|"),函数会从字符串开头开始查找第一个分隔符,将分隔符替换为'\0'(使其成为一个独立子串的结束符),并返回该子串的首地址。 - 后续调用:第一个参数需传入
NULL,函数会从上次分割的位置继续向后查找下一个分隔符,重复分割过程,直到字符串结束。 - 状态保持:函数内部会记录上次分割的位置(通过静态变量),因此后续调用无需再次传入原始字符串,即可延续分割过程。
注意事项:
- 修改原字符串:
strtok会直接修改原字符串(将分隔符替换为'\0'),因此若需保留原始字符串,需先复制一份再进行分割。 - 分隔符是 “集合”:分隔符参数是一个字符串,其中的每个字符都是有效的分割标志(例如分隔符
",."表示遇到逗号或句号都会分割)。 - 连续分隔符处理:若原字符串中存在连续的分隔符(如
"a,,b;;c"),strtok会将其视为一个分隔符处理,不会生成空的子串。
strtok 函数的使用
代码演示:
char arr[] = "asofndw@soncsd$aondcs#ascasaz";
char sep[] = "@$#";
for (char* ret = strtok(arr, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n", ret);
}
1. strtok的首次调用:strtok(arr, sep)(循环初始化部分)
for循环的初始化语句char* ret = strtok(arr, sep)是strtok的首次调用,作用是:
- 传入待分割的主字符串
arr和分隔符集合sep,函数会从arr的起始位置开始查找第一个分隔符(这里是@); - 找到分隔符后,
strtok会将该分隔符替换为字符串结束符'\0'(因此原字符串arr会被修改),使@之前的部分("asofndw")成为一个独立的子串; - 返回这个子串的首地址(即
arr的起始地址),并在内部记录 “上次分割的结束位置”(即@被替换为'\0'后的下一个字符位置),为后续分割做准备。
2. strtok的后续调用:strtok(NULL, sep)(循环更新部分)
循环的更新语句ret = strtok(NULL, sep)是strtok的后续调用,关键逻辑是:
- 第一个参数传入
NULL,表示 “从上次分割的结束位置继续分割”(依赖strtok内部保存的状态); - 函数会从上次记录的位置向后查找下一个分隔符(这里第一个后续调用会找到
$),同样将其替换为'\0',得到子串"soncsd",返回其首地址; - 再次调用时,继续从
$的下一个位置查找,找到#并替换为'\0',得到子串"aondcs"; - 最后一次调用时,从
#的下一个位置查找,直到字符串末尾没有更多分隔符,得到最后一个子串"ascasaz"。
3. 循环终止条件:ret != NULL
当strtok遍历完主字符串的所有字符,且没有更多子串可分割时,会返回NULL,此时循环终止,所有子串都已被打印。
memcpy 函数
memcpy 函数的参数和返回值
void* memcpy ( void* destination, const void* source, size_t num );
一、参数的作用
三个参数共同定义了 “复制的目标、来源和规模”,核心是 “以字节为单位” 进行操作:
void* destination:指向目标内存块的起始地址,该内存块用于接收复制的数据。void*类型使其能接收任意类型的内存地址(如int*、float*、结构体指针等),无需限制数据类型。const void* source:指向源内存块的起始地址,该内存块提供要复制的数据。const修饰确保源内存块不会被修改,void*同样支持任意类型的源地址。size_t num:指定要复制的字节数(必须是无符号整数),这是 memcpy 最关键的参数 —— 它决定了复制的范围,与数据类型无关(例如复制一个int数组的 3 个元素,需传入3 * sizeof(int))。
二、返回值的作用:void*—— 返回目标内存块的起始地址
返回值为 destination(目标内存块的首地址),设计目的与 strcpy 类似:支持链式操作。例如,可将 memcpy 的返回值直接作为其他函数的参数(如 printf("%d", *(int*)memcpy(dest, src, 4))),无需额外变量暂存目标地址,提升代码灵活性。
三、核心作用:按字节复制任意内存块
memcpy 的核心功能是 “从源内存块的起始位置开始,连续复制 num 个字节到目标内存块的起始位置”,其特点是:
- 不依赖数据类型:无论是
int、float、结构体,还是自定义类型,只要提供正确的内存地址和字节数,都能完整复制(例如复制一个包含 3 个double元素的数组,num需设为3 * sizeof(double))。 - 不处理特殊结束符:与字符串函数(如
strcpy依赖'\0')不同,memcpy 严格按num字节复制,即使遇到'\0'也会继续复制,直到满足字节数要求。 - 内存块独立:源和目标是两个独立的内存块,复制后目标内存块的内容与源内存块的前
num字节完全一致(二进制层面的相同)。
模拟实现 memcpy 函数
代码演示:
void* my_memcpy(void* destination, const void* source, size_t num)
{
assert(destination && source);
if (destination > source)
{
for (int i = (int)num - 1; i >= 0; i--)
{
*((char*)destination + i) = *((char*)source + i);
}
}
else
{
for (size_t i = 0; i < num; i++)
{
*((char*)destination + i) = *((char*)source + i);
}
}
return destination;
}
1. 安全检查:assert(destination && source);
通过assert确保目标内存块指针destination和源内存块指针source均非空(NULL)。内存操作依赖指针访问具体字节,空指针会导致 “非法内存访问”,assert能提前暴露这类错误,避免程序崩溃。
2. 核心判断:if (destination > source)—— 处理内存重叠的关键
这是代码最核心的设计:通过比较目标和源内存块的起始地址,决定复制的方向(正向 / 反向),以避免 “内存重叠” 时的数据覆盖问题。
-
内存重叠场景:当目标内存块(
destination)的地址范围与源内存块(source)重叠(例如dest = src + 2,且复制字节数较大),若盲目从前往后复制,可能会覆盖源内存中 “尚未被复制的部分”,导致复制结果错误。 -
判断逻辑:
- 若
destination > source(目标内存块在源内存块的 “后方” 且可能重叠):采用从后往前复制(从最后一个字节开始,依次复制到第一个字节),避免覆盖源内存中未复制的字节; - 否则(目标在源前方,或无重叠):采用从前往后复制(从第一个字节开始,依次复制到最后一个字节),逻辑更简洁。
- 若
3. 字节级复制:*((char*)destination + i) = *((char*)source + i)
这是实现 “任意类型内存复制” 的核心操作,通过char*指针的特性完成字节级控制:
-
(char*)destination和(char*)source:将void*指针强制转换为char*——char*的 “步长为 1 字节”,能精确访问内存中的每个字节(无论原数据类型是int、double还是结构体,本质都是连续的字节序列)。 -
循环复制:
- 正向复制(
else分支):for (size_t i = 0; i < num; i++)从第 0 个字节到第num-1个字节,依次将源内存的每个字节复制到目标内存的对应位置; - 反向复制(
if分支):for (int i = (int)num - 1; i >= 0; i--)从第num-1个字节到第 0 个字节,反向复制,避免重叠时的覆盖问题。
- 正向复制(
4. 返回值:return destination;
返回目标内存块的起始地址,与标准memcpy的设计一致,支持 “链式操作”(如将复制结果直接作为其他函数的参数)。
更多推荐



所有评论(0)