常用操作符函数的模拟实现详解(strlen,strcmp,strcpy,strncpy,strcat,strncat)

本文介绍了如何模拟实现C语言中的strncat、strcat、strlen、strcmp等字符串操作函数,帮助初学者深入理解这些函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

试着去模拟实现一些库函数,可以帮助我们更好的理解编程思想,巩固我们的知识。有不少朋友可能和我一样,刚开始尝试,会遇到很多问题,但是没关系,迈过这一步,你的能力就会有质的提升。

推荐一个网站查库函数的使用,里面是简洁的中文介绍,附带例子,对英语不友好的朋友佷方便。

C 标准库 - <stdio.h>

strncat函数介绍

strncat 是我最开始尝试实现的一个字符操作函数,其实并不难,就是用到了一些指针的知识,完全理解后就可以触类旁通的去实现其他函数了。

描述

把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。

声明

下面是 strncat() 函数的声明。

char *strncat(char *dest, const char *src, size_t n)

参数

  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
  • src – 要追加的字符串。
  • n – 要追加的最大字符数。

返回值

该函数返回一个指向最终的目标字符串 dest 的指针。

strncat函数模拟实现

一个函数的实现主要看实现的功能、返回值、传递的参数,库函数已经把这些内容都描述的很清楚了,我就不再赘述。

函数返回一个指向目标的指针,需要传递的有要追加的字符串地址、目标字符串地址、传递的字符数三个形式参数,我们不妨照猫画虎声明一个自己写的函数:

char *my_strncat(char *dest, const char *src, size_t n)

接下来就可以根据函数功能的描述去实现函数体了。要往目标字符串后添加内容,需要先对 ‘\0’ 有一个明确的认识。

  1. ’\0’ 是判定字符数组结束的标识,表示这串字符到结尾了;
  2. 在字符数组中 ’\0’ 是占一个位置的;
  3. ’\0’在数组中占有空间却不被我们看到;

有了这个理解,我们自然思路就清晰了,从目标数组的停止标志位 ’\0’ 开始,依次替换成需要添加的字符串,循环n次,并最终返回最初目标字符串的地址即可达成目的。需要注意的是,目标字符串的空间要足够大,以保证接受完添加的字符后不会越界。接下来用函数实现上述思路:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>

char* my_strncat(char* dest, const char* src, size_t n)
//src不涉及修改,可以用const修饰成常量
{
	assert(dest != NULL);
	assert(src != NULL);//先断言,确保两个字符串地址不是空指针
	char* start = dest; //先保存dest起始地址,因为后面地址会改变
	
	while (*dest)
	{
		dest++;
	}
	while (n--)
	{
		*dest++ = *src++;
	}
	return start;
}
int main()
{
	char arr1[10] = "hello ";
	char arr2[10] = "world";
	my_strncat(arr1, arr2, 3);//数组名就是首元素地址
	printf("%s\n", arr1);
	return 0;
}

输出结果;
在这里插入图片描述
有两个while循环虽然看起来很简单,但信息量极大,对于小白来说可能读起来略微吃力,我需要解释一下:

	while (*dest)
	{
		dest++;
	}

先理解一些小细节

  • *dest++和dest++两种操作都会使指针后移
  • *(dest+i)相当于dest[i],可以遍历字符串(数组)

这个while循环的意思就是遍历dest每个字符,遍历到’\0’时循环结束,此时dest指向’\0’。

	while (*dest++)
	{
		;
	}

可能会有人想到用这个方法,找到’ \0 ’ ,其实是错误的,涉及到后置++的用法(先使用,后+1),当dest=’ \0 '时循环确实停止了,但是dest还会+1指向下一个元素 ,因为while判断条件算做使用一次。这样做的后果就是dest的停止标志位还在,往后添加任何字符,最终都看不到。

	while (n--)
	{
		*dest++ = *src++;
	}

避免错误后,我们继续,到第二个循环时,dest指针指向 ‘\0’ ,接下来依次将src的字符传递给dest。
第一次循环时:
因为后置++是先使用再+1的操作,所以可以分解成以下几步

*dest = '\0';
*src = 'w';
*dest = *src = 'w';
*src = *src+1;
*dest = *dest + 1;

后面循环依次类推,共循环n次,也就是在dest后面添加了n个字符。因为返回的是一开始保存的dest数组的首元素地址,所以通过地址就可以找到修改后的字符串啦。

如果你能完全理解这个模拟函数的实现,那么后面几个函数对你来说已经不是什么难题了,你可以试着先查看库函数的使用,然后自己实现看看,如果不会可以接着往下看。

strcat函数介绍

描述

把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。

声明

下面是 strcat() 函数的声明。

char *strcat(char *dest, const char *src)

参数

  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
  • src – 指向要追加的字符串,该字符串不会覆盖目标字符串。

返回值

该函数返回一个指向最终的目标字符串 dest 的指针。

strcat函数模拟实现

strcat的实现和strncat差不多,只不过是往后添加时的停止判断条件改为遇到src的停止标志位 ’ \0 '时,添加字符结束。

函数声明

char* my_strcat(char* dest, const char* src)

函数实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>

char* my_strcat(char* dest, const char* src)
//src不涉及修改,可以用const修饰成常量
{
	assert(dest != NULL);
	assert(src != NULL);//先断言,确保两个字符串地址不是空指针
	char* start = dest; //先保存dest起始地址,因为后面地址会改变
	
	while (*dest)
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return start;
}
int main()
{
	char arr1[20] = "hello ";
	char arr2[10] = "world";
	my_strcat(arr1, arr2);//数组名就是首元素地址
	printf("%s\n", arr1);
	return 0;
}

输出结果:
在这里插入图片描述

strlen函数介绍

描述

计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。

声明

下面是 strlen() 函数的声明。

size_t strlen(const char *str)

参数

  • str – 要计算长度的字符串。

返回值

该函数返回字符串的长度。

strlen函数模拟实现

strlen的实现也很简单,这里给出两种方法,一种就是利用指针遍历技术,遇到’ \0 '时停止计数,返回最终的计数值即可。另一种方法是用数组的尾地址(指针指向 ’ \0 ’ 的地址)减去起始地址也可以得到字符串长度。能这么做的原因是字符型指针步长正好为1。

函数声明

size_t my_strlen1(char* arr)
size_t my_strlen2(char* arr)

函数实现:

size_t my_strlen1(char* arr)
{
	assert(arr != NULL);
	size_t len = 0;
	while (*arr++)
	{
		len++;
	}
	return len;
}
size_t my_strlen2(char* arr)
{
	assert(arr != NULL);
	char* start = arr;//首地址
	while (*arr)
	{
		arr++;//循环结束指针指向'\0'
	}
	return arr - start;
}
int main()
{
	char arr[10] = "hello";
	size_t len1 = my_strlen1(arr);
	size_t len2 = my_strlen2(arr);
	printf("len1 = %d\n", len1);
	printf("len2 = %d\n", len2);
	return 0;
}

输出结果
在这里插入图片描述

strcmp函数介绍

描述

把 str1 所指向的字符串和 str2 所指向的字符串逐个字符的ASCLL码进行比较,出现字符不相等情况,或者遇到’\0’时,立刻返回字符大小(ASCLL码)之差。

声明

下面是 strcmp() 函数的声明。

int strcmp(const char *str1, const char *str2)

参数

  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。

返回值

该函数返回值如下:

  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。

strcmp函数模拟实现

strcmp的实现其实也很简单,无非是先找到两个字符串不同的地方,然后进行相减运算,再返回结果就行。有了前面的铺垫,也很容易用代码实现。

函数声明

int my_strcmp(const char *str1, const char *str2)

函数实现:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	while (!(*(unsigned char*)str1 - *(unsigned char*)str2) && *str1)
	//只要str1和str2的字符相同并且str1没遇到'\0',就一直循环
	{
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[10] = "adcdA";
	char arr2[10] = "abcde";
	int ret = my_strcmp(arr1, arr2);
	if (ret < 0)
	{
		printf("str1<str2\n");
	}
	else if (ret>0)
	{
		printf("str1>str2\n");
	}	
	else
	{
		printf("str1=str2");
	}
}

输出结果:
arr1的第二个字符d的ascll码为100,arr2的第二个字符ascll码大小为98,100 - 98 = 2 > 0,所以结果如下
在这里插入图片描述

strncmp函数介绍

描述

把 str1 和 str2 进行比较,最多比较前 n 个字节。

声明

下面是 strncmp() 函数的声明。

int strncmp(const char *str1, const char *str2, size_t n)

参数

  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。
  • n – 要比较的最大字符数。

返回值

该函数返回值如下:

  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str2 小于 str1。
  • 如果返回值 = 0,则表示 str1 等于 str2。

strncmp函数模拟实现

和strcmp类似,只不过循环次数要有n的限定。

函数声明

int my_strncmp(const char* str1, const char* str2,size_t n)

函数实现

int my_strncmp(const char* str1, const char* str2,size_t n)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	
	while (!(*(unsigned char*)str1 - *(unsigned char*)str2) && *str1 && --n)
		//只要str1和str2的字符相同并且str1没遇到'\0',就一直循环
		//这里只能用--n,而不能用n--,留给读者思考为什么?
	{
		
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[10] = "abceA";
	char arr2[10] = "abcde";
	int ret = my_strncmp(arr1, arr2,3);
	if (ret < 0)
	{
		printf("str1<str2\n");
	}
	else if (ret>0)
	{
		printf("str1>str2\n");
	}
	else
	{
		printf("str1=str2\n");
	}
}

输出结果
在这里插入图片描述

strcpy函数介绍

描述

把 src 所指向的字符串复制到 dest。

需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。

声明

下面是 strcpy() 函数的声明。

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

参数

  • dest – 指向用于存储复制内容的目标数组。
  • src – 要复制的字符串。

返回值

该函数返回一个指向最终的目标字符串 dest 的指针。

strcpy函数模拟实现

把源字符串逐个遍历到目标字符串即可。

函数声明

char *my_strcpy(char *dest, const char *src)

函数实现

char* my_strcpy(char *dest, const char *src)
{
	assert(dest != NULL);
	assert(src != NULL);//先断言,确保两个字符串地址不是空指针
	char*start = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return start;
}
int main()
{
	char arr1[10] = "hellohe";
	char arr2[10] = "world";
	my_strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

输出结果:
在这里插入图片描述

strncpy函数介绍

描述

把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。

声明

下面是 strncpy() 函数的声明。

char *strncpy(char *dest, const char *src, size_t n)

参数

  • dest – 指向用于存储复制内容的目标数组。
  • src – 要复制的字符串。
  • n – 要从源中复制的字符数。

返回值

该函数返回最终复制的字符串。

strncpy函数模拟实现

函数声明

char *my_strncpy(char *dest, const char *src, size_t n)

函数实现

char* my_strncpy(char *dest, const char *src,size_t n)
{
	assert(dest != NULL);
	assert(src != NULL);//先断言,确保两个字符串地址不是空指针
	char*start = dest;
	while (n--)
	{
		*dest++ = *src++;
	}
	return start;
}
int main()
{
	char arr1[10] = "hellohe";
	char arr2[10] = "world";
	my_strncpy(arr1, arr2,5);
	printf("%s\n", arr1);
	return 0;
}

输出结果
在这里插入图片描述

### HAL_TIM_PeriodElapsedCallback 函数功能与用法 #### 1. 功能描述 `HAL_TIM_PeriodElapsedCallback` 是 STM32 HAL 库中的回调函数,用于处理定时器周期结束事件。当定时器的计数值达到设定的最大值并触发更新事件时,该回调函数会被调用[^1]。 此函数的主要作用是在中断服务程序中被自动调用,允许用户在不修改底层驱动的情况下实现自定义逻辑。它通常用来响应特定的时间间隔到达后的动作,例如刷新数据、切换状态或其他实时任务调度[^2]。 --- #### 2. 定义形式 以下是 `HAL_TIM_PeriodElapsedCallback` 的典型定义: ```c void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // 用户可以在此处编写自己的代码来处理定时器周期溢出事件 } ``` - **参数说明** - `TIM_HandleTypeDef *htim`: 这是一个指向定时器句柄结构体的指针,包含了配置和运行状态的信息。通过这个句柄,可以在回调函数内部访问当前定时器的相关属性或重新设置其行为。 --- #### 3. 使用方法 为了使能这一回调机制,需完成以下几个步骤: 1. 初始化定时器:利用 `HAL_TIM_Base_Init` 或其他初始化接口完成硬件资源分配以及基础参数配置(如预分频系数、计数器周期等)。 2. 启动带中断模式的定时器:调用 `HAL_TIM_Base_Start_IT(htim)` 来开启定时器及其关联的中断请求。这一步会启用相应的中断线,并注册默认的中断服务例程(ISR)[^1]。 3. 实现回调函数:根据实际需求重写 `HAL_TIM_PeriodElapsedCallback` 方法的内容。每当发生一次完整的计数循环后,即进入下一轮计数前,都会跳转到此处执行指定的操作[^3]。 4. 清除标志位/中断挂起比特 (可选): 如果需要手动管理某些特殊类型的干扰信号,则可能还需要借助宏指令如 __HAL_TIM_CLEAR_IT() 对应位置零操作。 --- #### 示例代码片段 下面展示了一个简单的应用案例——每秒钟点亮 LED 一次: ```c #include "stm32f4xx_hal.h" // 假设已正确设置了 GPIO 和 TIM 句柄 htim2 uint8_t led_state = 0; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ if(htim->Instance == TIM2){ // 判断是否来自 TIM2 中断 if(led_state == 0){ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 打开LED led_state = 1; } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 关闭LED led_state = 0; } } } int main(void){ /* MCU Initialization */ // 配置GPIO PA5作为输出端口 // 设置 TIM2 参数 TIM_HandleTypeDef timHandle; timHandle.Instance = TIM2; timHandle.Init.Prescaler = 8399; // 设定预分频值使得频率接近1KHz timHandle.Init.CounterMode = TIM_COUNTERMODE_UP; timHandle.Init.Period = 9999; // 计数至最大值约等于一秒 timHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if(HAL_TIM_Base_Init(&timHandle) != HAL_OK){ Error_Handler(); } // 开启 IT 模式的定时器 HAL_TIM_Base_Start_IT(&timHandle); while(1); } ``` 上述例子展示了如何结合外部设备控制形成规律性的脉冲序列。 ---
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几叶知期

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值