目录

strlen 函数

strlen 函数的参数和返回值

模拟实现 strlen 函数

strcpy 函数

strcpy 函数的参数和返回值

模拟实现 strcpy 函数

strcat 函数

strcat 函数的参数和返回值

模拟实现 strcat 函数

strcmp 函数

strcmp 函数的参数和返回值

模拟实现 strcmp 函数

strstr 函数

strstr 函数的参数和返回值

模拟实现 strstr 函数

strtok 函数

strtok 函数的作用

strtok 函数的使用

memcpy 函数

memcpy 函数的参数和返回值

模拟实现 memcpy 函数


strlen 函数

strlen 函数的参数和返回值

size_t strlen(const char* str);

首先明确函数的 “输入” 与 “输出” 设计,完全贴合标准strlen的规范:

  • 参数const char* str

    • char* str:表示接收一个 “指向字符串首字符的指针”——C 语言中字符串本质是 “以'\0'结尾的字符数组”,传递字符串时实际是传递其首字符地址,因此用char*接收;
    • const修饰:表明函数不会修改字符串的内容(仅读取字符判断是否为'\0'),这是一种安全设计 —— 避免误修改传入的字符串,同时也符合strlen“只读字符串” 的功能定位。
  • 返回值size_tsize_t是 C 语言专门用于表示 “无符号长度” 的类型(本质是unsigned intunsigned long),因为字符串长度不可能为负数,用size_tint更贴合语义,也与标准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 个字节—— 因为strchar*类型,指针移动的 “步长” 是 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) 可先复制src1dest,再拼接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需要通过destinationsource指针访问内存,若任一指针为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的核心功能是 “拼接”:

  1. 先在目标字符串中找到原有的'\0'(结束符),从这个位置开始写入;
  2. 将源字符串的所有字符(从首字符到'\0')逐个复制到目标字符串的'\0'位置及之后;
  3. 最终目标字符串会以源字符串的'\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 码值”,类似字典中单词的排序规则:

  1. 从两个字符串的首字符开始,逐个比较对应位置的字符(str1[0] 与 str2[0]str1[1] 与 str2[1]……);
  2. 若遇到第一个不相同的字符,直接根据该字符的 ASCII 码差值返回结果(正 / 负),停止比较;
  3. 若所有字符都相同,则比较到两个字符串的 '\0' 为止,返回 0;
  4. 若一个字符串是另一个的前缀(如 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确保传入的两个字符串指针str1str2均非空(NULL)。若任一指针为空,后续对*str1*str2的解引用会导致 “空指针访问错误”,assert能在运行时直接暴露这类问题,避免程序崩溃。

2. 核心比较循环:while (*str1 == *str2)—— 逐字符匹配相等的情况

循环的作用是遍历两个字符串中字符相同的部分,直到遇到不相等的字符或字符串结束符'\0'

  • 循环条件*str1 == *str2:比较当前指针指向的字符(str1str2初始指向两个字符串的首字符),若相等则进入循环体;
  • 循环体内if (*str1 == '\0') return 0;:若当前字符是'\0'(说明两个字符串同时遍历到结尾,且所有字符都相等),直接返回 0(表示两字符串相等);
  • str1++; str2++;:若字符相等且非'\0',两个指针同时后移 1 字节(指向各自的下一个字符),继续比较下一组字符。

3. 循环退出后的判断:确定大小关系

当循环退出时,说明找到了第一个不相等的字符*str1 != *str2),此时通过简单判断确定返回值:

  • *str1 > *str2str1当前字符的 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;

定义s1s2作为临时指针,用于在每次尝试匹配时遍历字符,而不修改原指针str1str2的位置。这一设计的核心作用是:每次从主串的新位置开始尝试匹配时,能快速将 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" 四个子串。其工作机制的关键特点是:

  1. 首次调用:需传入两个参数 —— 待分割的字符串(如上述例子中的完整字符串)和分隔符集合(如 ",;|"),函数会从字符串开头开始查找第一个分隔符,将分隔符替换为 '\0'(使其成为一个独立子串的结束符),并返回该子串的首地址。
  2. 后续调用:第一个参数需传入 NULL,函数会从上次分割的位置继续向后查找下一个分隔符,重复分割过程,直到字符串结束。
  3. 状态保持:函数内部会记录上次分割的位置(通过静态变量),因此后续调用无需再次传入原始字符串,即可延续分割过程。

注意事项:

  • 修改原字符串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 个字节到目标内存块的起始位置”,其特点是:

  • 不依赖数据类型:无论是 intfloat、结构体,还是自定义类型,只要提供正确的内存地址和字节数,都能完整复制(例如复制一个包含 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 字节”,能精确访问内存中的每个字节(无论原数据类型是intdouble还是结构体,本质都是连续的字节序列)。

  • 循环复制:

    • 正向复制(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的设计一致,支持 “链式操作”(如将复制结果直接作为其他函数的参数)。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐