详谈C语言中strcpy,strncpy,strcat,strncat,strcmp,strncmp,strlen,memcpy,memset,strchr常用函数

1 引言

我发现在开发过程中或者面试过程中经常会遇到这些函数(strcpy,strncpy,strcat,strncat,strcmp,strncmp,strlen,strchrmemset,memcpy),从过往经验来看大部分面试都是不能调用库函数,需要实现函数原型。可见这些函数的底层原理其重要性和作用就不多说了。经过对这些函数的不断研究和验证,下面就一 一详谈这些函数的功能和特性以及函数实现。

2 函数功能及特性

1)strcpy函数

函数原型:char *strcpy(char *dest, const char *src);
函数功能:把从src地址开始且含有NULL结束符的字符串复制到dest开始的地址空间,并且返回指向dest的指针。
函数特性:
(1)、strcpy只能复制字符串,strcpy不需要指定长度,它遇到被复制字符串结束符’\0’才结束。很容易造成缓冲区溢出。
(2)、src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src所指向的字符串。
(3)、src所指向源字符串参数用const修饰,防止修改源字符串。

2)strncpy函数

函数原型:char *strncpy(char *dest, const char *src, size_t n);
函数功能:把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest。
函数特性:
(1)、strncpy只能复制字符串,strncpy需要指定长度,它遇到被复制字符串结束符’\0’才结束。
(2)、如果n<src的长度,只是将src的前n个字符复制到dest的前n个字符,不自动添加’\0’,也就是结果dest不包括’\0’,需要再手动添加一个’\0’。如果n>src的长度,则以NULL填充dest直到复制完n个字节。
(3)、src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符长度+’\0’。(4)、src所指向源字符串参数用const修饰,也是防止修改源字符串。

3)strcat函数

函数原型:char *strcat(char *dest, const char *src);
函数功能:把src所指向的字符串复制到dest所指向的字符串后面。要保证dest所指向字符串空间足够长,以容纳被复制进来的src所指向的字符串。src所指向字符串中原有的字符不变。并且返回指向dest的指针。
函数特性:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src所指向的字符串。

4)strncat函数

函数原型:char *strncat(char *dest, const char *src, size_t n);
函数功能:把src所指字符串的前n个字符添加到dest所指字符串的结尾处,并覆盖dest所指字符串结尾的’\0’。
函数特性:src和dest所指内存区域不可以重叠,并且dest必须有足够的空间来容纳src的字符串。

5)strcmp函数

函数原型:int strcmp(const char *dest, const char *src);
函数功能:比较src和dest所指向的两个字符串并根据比较结果返回整数。
函数特性:
(1)、当dest<src时,返回为负数;当dest=src时,返回值= 0;当dest>src时,返回正数。
(2)、比较规则是两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。
(3)、这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。

6)strncmp函数

函数原型:int strncmp(const char *dest, const char *src, size_t n);
函数功能:把src所指向的前n个字节的字符串与dest所指向的字符串进行比较,若dest与src的前n个字符相同,则返回0;若dest大于src,则返回大于0的值;若dest小于src,则返回小于0的值。
函数特性:
(1)、函数strncmp与函数strcmp极为类似,但功能不完全相同。
(2)、两者不同之处是,strncmp函数是指定比较n个字符,strcmp函数比较整个字符,直到出现不同的字符或遇’\0’为止。

7)strlen函数

函数原型:size_t strlen(const char *src);
函数功能:计算src所指向字符串的(unsigned int型)长度,不包括’\0’在内,并且返回src所指向字符串的长度,不包括结束符NULL。
函数特性:
(1)、size_t代表的是unsigned int型。
(2)、该函数从内存的某个位置可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域开始扫描,直到遇到第一个字符串结束符’\0’为止。

8)strchr函数

函数原型:char *strchr(const char *src, int c);
函数功能:在str所指向的字符串中搜索第一次出现字符c的位置,并且返回一个指向该字符串中第一次出现的字符的指针,如果字符串中不包含该字符则返回NULL空指针。
函数特性:注意strchr函数是区分大小写的。

9)memset函数

函数原型:void* memset(void *src, int c, size_t n);
函数功能:将src中当前位置后面的n个字节用c替换并返回src。
函数特性:
(1)、该函数是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。
(2)、memset函数参数中c实际范围应该在0~255,因为该函数只能取c的后八位赋值给你所输入的范围的每个字节。

10)memcpy函数

函数原型:void* memcpy(void *dest, const void *src, size_t n);
函数功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中,src和dest所指内存区域不能重叠,函数返回指向一个目标存储区dest的指针。
函数特性:
(1)、memcpy可以复制任意内容,例如字符数组、整型、结构体、类等而strcpy只能复制字符串。
(2)、需要复制其他类型数据时则一般用memcpy函数,而复制字符串时则一般用strcpy函数。
(3)、strcpy不需要指定长度,它遇到被复制字符的串结束符’\0’才结束,所以容易溢出。memcpy则是根据第3个参数决定复制的长度。
(4)、src和dest所指的内存区域可能重叠,如果src和dest所指的内存区域重叠,那么memcpy函数并不能够确保src所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向dest的指针。(memcpy函数在下面的功能实现中是考虑了内存覆盖情况的)

3 函数功能实现

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

typedef struct _OP
{
	char* (*p_strcpy)(char*, const char*);
	char* (*p_strncpy)(char*, const char*, size_t);
	char* (*p_strcat)(char*, const char*);
	char* (*p_strncat)(char*, const char*, size_t);
	int   (*p_strcmp)(const char*, const char*);
	int   (*p_strncmp)(const char*, const char*, size_t);
	size_t (*p_strlen)(const char *);
	char* (*p_strchr)(const char *, int);
	void* (*p_memset)(void *, int, size_t);
	void* (*p_memcpy)(void *, const void *, size_t);
}OP;

char *my_strcpy(char *dest, const char *src)
{
	assert((dest != NULL) && (src != NULL));

	char *temp = dest;
	while((*dest++ = *src++) != '\0');

	return temp;
}

char *my_strncpy(char *dest, const char *src, size_t n)
{
	assert((dest != NULL) && (src != NULL));
	
	/*不考虑内存重叠情况*/
	char *pdest = dest;
	while(*src != '\0' && n > 0 )
	{
		*dest++ = *src++;
		--n;
	}

	if (n > 0)
	{
		*pdest = '\0';
	}

	return pdest;
}

char *my_strcat(char *dest, const char *src)
{
	assert((dest != NULL) && (src != NULL));

	char *pdest = dest;
	while(*dest != '\0')
	{
		++dest;
	}

	while(*src != '\0')
	{
		*dest++ = *src++;
	}

	return pdest;
}

char *my_strncat(char *dest, const char *src, size_t n)
{
	assert((dest != NULL) && (src != NULL));

	char *pdest = dest;

	while(*dest != '\0')
	{
		++dest;
	}

	while(*src != '\0' && n--)
	{
		*dest++ = *src++;
	}

	*dest = '\0';

	return pdest;
}

int my_strcmp(const char *dest, const char *src)
{
	assert((dest != NULL) && (src != NULL));
	while(*dest && *src && (*dest++ == *src++));

	return (*dest - *src);
}

int my_strncmp(const char *dest, const char *src, size_t n)
{
	assert((dest != NULL) && (src != NULL));

	if (!n)
	{
		return 0;
	}

	while(n--)
	{
		if (*dest == '\0' || *dest != *src)
		{
			return *dest - *src;
		}

		dest++;
		src++;
	}

	return 0;
}

size_t my_strlen(const char *src)
{
	#if 1
	/* 非递归 */
	int len = 0;

	assert(src != NULL);
	while(*src++ != '\0')
	{
		++len;
	}

	return len;
	#else
	/* 递归 */
	if (src == NULL || *src == '\0')
	{
		return 0;
	}

	return my_strlen(src + 1) + 1;
	#endif
}

char *my_strchr(const char *src, int c)
{
	assert(src != NULL);

	while(*src != '\0' && *src != c)
	{
		++src;
	}

	if (*src == c)
	{
		return (char *)src;
	}
	else
	{
		return NULL;
	}
}

void* my_memset(void *src, int c, size_t n)
{
	assert(src != NULL);

	char *psrc = (char *)src;

	while((n--) > 0)
	{
		*psrc++ = n;
	}

	return psrc;
}

void* my_memcpy(void *dest, const void *src, size_t n)
{
	assert(dest != NULL && src != NULL);

	#if 0
	/* 不考虑内存重叠情况 */

	char *pdest = dest;
	const char *psrc = src;
	
	if (0 == n)
	{
		return dest;
	}

	while((n--) > 0)
	{
		*pdest++ = *psrc++;
	}

	*(pdest + n) = '\0';

	return dest;
	#else
	/* 考虑内存重叠情况 */
	#if 0
	char *pdest;
	const char *psrc;

	if (0 == n)
	{
		return dest;
	}

	if (dest >= (src+n) || dest <= src)
	{
		pdest = dest;
		psrc = src;

		while(n--)
		{
			*pdest++ = *psrc++;
		}
	}
	else
	{
		pdest = (char *)(dest + n - 1);
		psrc = (char *)(src + n - 1);

		while(n--)
		{
			*pdest-- = *psrc--;
		}
	}

	return dest;
	#else
	char *pdest;
	const char *psrc;

	if (0 == n)
	{
		return dest;
	}

	if (dest > src)
	{
		pdest = (char *)(dest+n-1);
		psrc = (char *)(src+n-1);

		while(n >= 4)
		{
			*pdest-- = *psrc--;
			*pdest-- = *psrc--;
			*pdest-- = *psrc--;
			*pdest-- = *psrc--;

			n -= 4;
		}

		while(n--)
		{
			*pdest-- = *psrc--;
		}
	}
	else if (dest <= src)
	{
		pdest = (char *)dest;
		psrc = (char *)src;

		while(n >= 4)
		{
			*pdest++ = *psrc++;
			*pdest++ = *psrc++;
			*pdest++ = *psrc++;
			*pdest++ = *psrc++;

			n -= 4;
		}

		while(n--)
		{
			*pdest++ = *psrc++;
		}
	}

	return dest;
	#endif
	#endif
}

void init_op(OP *op)
{
	op->p_strcpy = my_strcpy;
	op->p_strncpy = my_strncpy;
	op->p_strcat = my_strcat;
	op->p_strncat = my_strncat;
	op->p_strcmp = my_strcmp;
	op->p_strncmp = my_strncmp;
	op->p_strlen = my_strlen;
	op->p_strchr = my_strchr;
	op->p_memset = my_memset;
	op->p_memcpy = my_memcpy;
}

void my_test(void)
{
	char *src = "9876543210";
	char pdest[11] = {0};

	char dest[7] = {"abcdef"};
	char qdest[10] = {0};

	char *cat = "asdfg";
	char pcat[20] = {"10010110"};

	char *ncat = "00111100";
	char pncat[25] = {"abcdefg"};

	char *str1 = "ABCDEF G";
	char *str2 = "ABCDHK L";

	const char *str = "This is a sample string!";
	char mset[] = {"01011100 ABCDEF"};
	char mmcpy[] = {"hello,world!"};

	OP *op = (OP *)malloc(sizeof(OP));
	init_op(op);

	(char *)op->p_memcpy(mmcpy+1, mmcpy, my_strlen(mmcpy)+1);

	printf("my_strcpy:%s\t\nmy_strncpy:%s\t\n \
		    \rmy_strcat:%s\t\nmy_strncat:%s\t\n \
		    \rmy_strcmp:%d\t\nmy_strncmp:%d\t\n \
		    \rmy_strlen:%ld\t\nmy_strchr:%s\t\n \
		    \rmy_memset:%s\t\nmy_memcpy:%s\t\n", 
						op->p_strcpy(pdest, src),
						op->p_strncpy(qdest, dest, 3),
						op->p_strcat(pcat, cat),
						op->p_strncat(pncat, ncat, 6),
						op->p_strcmp(str1, str2),
						op->p_strncmp(str1, str2, 5),
						op->p_strlen(str1),
						op->p_strchr(str, 'a'),
						(char *)op->p_memset(mset, 0, 3),
						mmcpy
						);

	free(op);
	op = NULL;
}

int main(int argc, char const *argv[])
{
	my_test();

	return 0;
}

4 编译

最后在Ubuntu上进行编译,编译结果如下所示:
在这里插入图片描述

5 总结

其实这些比较常见常用的函数原理需要我们逐个去理解,多思考,多写程序,多实验,才能在以后的工作或者学习中运用自如,达到事倍功半的效果,如果有需要的朋友可以进行参考。有不足之处还望指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值