C语言拯救者(模拟实现字符串函数和内存函数--11)

目录

C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

1. 长度不受限制的字符串函数

1.1 strlen

1.2 三种方式模拟实现strlen库函数

1.3 strcpy 字符串拷贝

1.4 模拟实现strcpy

strcpy函数返回的是目标空间的起始地址

strcpy函数的返回类型的设置是为了实现链式访问

1.5 strcat 字符串追加

1.6 模拟实现strcat

1.7 strcmp 比较字符串

1.8 模拟实现strcmp

2  长度受限制的字符串函数

2.1 strncpy

2.2 strncat

2.3 strncmp

2.4 strstr  在一个字符串中找另一个字符串

2.5 模拟实现strstr

2.6 strtok

2.7 strerror  perror  返回错误码,所对应的错误信息

2.8 字符分类函数

3. 内存操作函数

3.1 memcpy 内存空间数据拷贝

3.2 模拟实现memcpy

 在同一块内存拷贝,目标和源头数据空间有交叉,我们应该用memmove

3.3 memmove  可以实现重叠内存拷贝

3.4  模拟实现memmove

3.5 memcpy  内存字节对应比较

3.6  memset  以字节为单位进行内存设置


C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

1. 长度不受限制的字符串函数

1.1 strlen

size_t strlen ( const char * str );//函数原型,注意函数的返回值为size_t

字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' )。

char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };//无法用strlen求字符串长度

char arr[10] = { 'a', 'b', 'c', 'd', 'e', 'f' };//限定长度,可以求

 下面结果是什么?

 if ((int)strlen("abc") - (int)strlen("qwerty") > 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<=\n");
	}

答案:>,两个无符号数相减结果是正数


1.2 三种方式模拟实现strlen库函数

通常写法

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

size_t my_strlen(const char* str)
{
	int count = 0;//统计字符的个数
    assert(str);
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
 
 
int main()
{
	char arr[] = "abcd";
	//char* str = arr;
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

递归

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

//my_strlen("abcdef")
//1+my_strlen("bcdef")
//1+1+my_strlen("cdef")
//1+1+1+ my_strlen("def")
//1+1+1+1+ my_strlen("ef")
//1 + 1 + 1 + 1 +1+my_strlen("f")
//1 + 1 + 1 + 1 + 1 + 1+ my_strlen("")
//1 + 1 + 1 + 1 + 1 + 1 + 0 = 6
 
size_t my_strlen(const char* str)
{
    assert(str);
	if (*str != '\0')
		return 1 + my_strlen(str+1);
	else
		return 0;
}
 
int main()
{
	char arr[] = "abcd";
	//char* str = arr;
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

指针-指针方法

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


size_t my_strlen(const char* arr,int sz)//指针-指针
{
    assert(arr);
    char* right = arr + sz - 1;
    return right - arr;
}
 
int main()
{
    char arr[] = "hello";
    int sz = sizeof(arr) / sizeof(arr[0]);
    int len=my_strlen(arr,sz);
    printf("%d", len);
    return 0;
}

1.3 strcpy 字符串拷贝

字符串拷贝,把源字符串拷贝放到目标空间字符串中,其中需要注意的事项

1.源字符串必须以 '\0' 结束。

char arr1[20] = {0};
char arr2[] = {'a','b','c'};//程序崩溃,没有\0

2.会将源字符串中的 '\0' 拷贝到目标空间。

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

int main()
{
	
	char arr1[] = "XXXXXXXXXXXX";//arr1指向的是常量字符串,常量是不可修改的
	char arr2[] = "abcdef";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

3.目标空间必须足够大,以确保能存放源字符串。

strcpy不管空间够不够,只要你敢写它就敢放,哪怕程序崩溃

char arr1[4] = "x";
char arr2[] = "abcdef";
strcpy(arr1, arr2);//程序崩溃

4.目标空间必须可变。

char* arr1 = "qwertyuiop";//arr1指向的是常量字符串,常量是不可修改的
char arr2[] = "abcdef";
strcpy(arr1, arr2);//程序崩溃

1.4 模拟实现strcpy

strcpy函数返回的是目标空间的起始地址

strcpy函数的返回类型的设置是为了实现链式访问

char* my_strcpy(char*dest, const char* src)
{
	assert(src && dest);
	char* ret = dest;
	while(*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char arr1[20] = { 0 };
	char* arr2 = "hello bit";

	printf("%s\n", my_strcpy(arr1, arr2));
	return 0;
}


1.5 strcat 字符串追加

int main()
{
	char arr1[10] =  "hello " ;
	char* arr2 = "bit";

	printf("%s\n", strcat(arr1, arr2));
	return 0;
}

注意事项:

1.源字符串必须以 '\0' 结束。 从\0位置开始追加

int main()
{
	char arr1[20] =  "hello\0XXXXX" ;
	char arr2[] = "bit";

	printf("%s\n", strcat(arr1, arr2));
	return 0;
}

2.目标空间必须有足够的大,能容纳下源字符串的内容。

3.目标空间必须可修改。

1.6 模拟实现strcat

char* my_strcat(char* dest, char* src)
{
	assert(dest && src);
	char* ret = dest;
	//找目标空间中的\0
	while (*dest)
	{
		dest++;
	}
	//拷贝
	while (*dest++ = *src++)
	{
		;
	}

	return ret;
}
int main()
{
	char arr1[20] = "hello";
	char arr2[] = " bit";

	printf("%s\n", my_strcat(arr1, arr2));
	
	return 0;
}

字符串自己给自己追加,如何?

我们所写的模拟函数无法完成自己追加自己,也不建议这么使用


1.7 strcmp 比较字符串

strcmp函数比较的不是字符串的长度

而是比较字符串中对应位置上的字符的大小(ASCII码值),如果相同,就比较下一对儿,直到不同或者都遇到\0   \0的ASCII码值是0

 标准规定:

第一个字符串大于第二个字符串,则返回大于0的数字

第一个字符串等于第二个字符串,则返回0

第一个字符串小于第二个字符串,则返回小于0的数字


1.8 模拟实现strcmp

int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
		{
			return 0;//相等
		}
		s1++;
		s2++;
	}
	//不相等
	return *s1 - *s2;
}

int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abdc";
	int ret = my_strcmp(arr1, arr2);
	if (ret >0)
	{
		printf(">\n");
	}
	else if (ret == 0)
	{
		printf("== \n");
	}
	else
	{
		printf("<\n");
	}
	printf("%d\n", ret);
	return 0;
}

2  长度受限制的字符串函数

2.1 strncpy

 拷贝num个字符从源字符串到目标空间。

如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

char *strncpy( char *strDest, const char *strSource, size_t count );
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "qwewwwwww";

	strncpy(arr1, arr2, 5);

	printf("%s\n", arr1);
	return 0;
}

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "qwe";

	strncpy(arr1, arr2, 5);//不够默认补\0

	printf("%s\n", arr1);
	return 0;
}


2.2 strncat

 追加num个字符从源字符串到目标空间。

int main()
{
	char arr1[20] = "abcdef\0XXXXX";
	char arr2[] = "qwe";

	strncat(arr1, arr2, 3);//追加三个,还会再把\0放进去

	printf("%s\n", arr1);
	return 0;
}


2.3 strncmp

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdq";
	int ret = strncmp(arr1, arr2, 4);//相等
	
	printf("%d\n", ret);

	return 0;
}


2.4 strstr  在一个字符串中找另一个字符串

char * strstr ( const char *str1, const char * str2);

 判断str2是不是str1的子串,如果str2在str1中出现,返回在str1中第一次出现的地址

如果没有出现,返回空指针

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "de";
	char * p=strstr(arr1, arr2);
	if (p == NULL)//strstr找不到返回NULL指针,我们需要判断
	{
		printf("找不到");
	}
	else
	{
		printf("%s ", p);
	}
	return 0;
}

2.5 模拟实现strstr

 思路:只需要找的子串如果复杂,我们需要三个指针协助

s1指针指向str1,s2指针指向str2。cur指针指向str1,作用是记录开始匹配的地址

如果两个字符串对应位置不相等,str1往后走

如果相等,开始匹配,我们应该记住开始匹配的str1位置,因为有可能相等,有可能不相等

如果一直到\0结束,str2就是str1子串

如果不相等,重新找到记录位置的地址,往后+1.往后走重新开始匹配,其中str2指针重新指向数组起始位置地址

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);

	const char* s1 = str1;
	const char* s2 = str2;

	const char* cur = str1;
	while (*cur)//cur不等于\0进来 
	{
		s1 = cur;//判断失败返回cur指向的位置
		s2 = str2;//判断失败回到起始位置

		while (*s1 && *s2 && (*s1 == *s2))//两个字符串都被查找完,没有数据了
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//字串找到,返回记录地址
		{
			return (char*)cur;
		}
		cur++;//匹配不成功,指向下一步
	}

	return NULL;//找不到
}

int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbc";
	char* ret = my_strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("找不到子串\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

2.6 strtok

作用:指定分隔符,让数组分段

char * strtok ( char * str, const char * sep );

 sep参数是个字符串,定义了用作分隔符的字符集合

第一个参数指定一个字符串,它包含了 0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。

strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)

 strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。

strtok函数的第一个参数为 NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。

如果字符串中不存在更多的标记,则返回 NULL 指针。

int main()
{
	char arr[] = "[email protected]";
  //char arr[] = "lanyangyang\0landawang\0cunba";  strtok函数会把数组变成这样
	char buf[50] = { 0 };
    const char* sep = "@. ";
	strcpy(buf, arr);
    
   //printf("%s\n", strtok(buf, sep));//只找第一个标记
   //printf("%s\n", strtok(NULL, sep));//是从保存的好的位置开始继续往后找
   //printf("%s\n", strtok(NULL, sep));//是从保存的好的位置开始继续往后找
优化
    char* str = NULL;
	for (str=strtok(buf, sep); str!=NULL; str=strtok(NULL, sep))
	{
		printf("%s\n", str);
	}

    return 0;
}


2.7 strerror  perror  返回错误码,所对应的错误信息

char * strerror ( int errnum );

 库函数在使用失败的时候,会留下错误码errno(全局变量),类似网站404错误码

strerror就是翻译错误信息

#include <string.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	int* p = (int*)malloc(INT_MAX);//想堆区申请内存的
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		perror("Malloc");//perror是打印错误信息,strerror是把错误码转换成错误信息
 //只需要写字符串,然后输出错误码对应得错误信息,更加方便
		return 1;
	}
	return 0;
}

返回错误码所对应错误信息字符串首字符地址

 


2.8 字符分类函数

函数 如果他的参数符合下列条件就返回真
iscntrl 任何控制字符
sspace 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
isdigit 十进制数字 0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母a~z或A~Z
isalnum 字母或者数字,a~z,A~Z,0~9
ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 任何图形字符
sprint 任何可打印字符,包括图形字符和空白字符

 

 举例:isdigit接收字符的ASCII码值,返回int类型(如果是数字字符返回非0,不是数组字符返回0)

#include <ctype.h>

int main()
{
int ret = isdigit('5');//5
int ret = isdigit('Q');//0
printf("%d\n", ret);

return 0;
}
char ch = 'A';

	if (ch >= 'a' && ch <= 'z')
	{

	}

这样写很麻烦,我们一个函数搞定

int ret = islower(ch);//判断是否小写,是小写字母返回非0,否则返回0,快速判断

字符转换:

int tolower ( int c );
int toupper ( int c );
	int main()
{
	char ch = 'A';
	putchar(toupper(ch));
	putchar(tolower(ch));

	return 0;
}


3. 内存操作函数

3.1 memcpy 内存空间数据拷贝

void * memcpy ( void * destination, const void * source, size_t count );

注意事项:

1.函数memcpy从source的位置开始向后复制count个字节的数据到destination的内存位置。

2.这个函数在遇到 '\0' 的时候并不会停下来。

3.如果source和destination有任何的重叠,复制的结果都是未定义的。

4.返回目的地的起始地址

int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[5] = { 0 };
	return 0;
    memcpy(arr2,arr1,20);//拷贝20个字节
}

3.2 模拟实现memcpy

思路:  

1. 作者实现memcpy函数的时候,作者不知道你要拷贝什么数据,类似qsort 

2.拷贝时应该根据数据类型强制类型转换,一个字节一个字节拷贝

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

//void* my_memcpy(void* dest, const void* src, size_t count)
//{
//	assert(src && dest);
//	while (count--)
//	{
//		*(char*)dest = *(char*)src;
//		dest = (char*)dest + 1;
//		src = (char*)src + 1;
//	}
// }

void* my_memcpy(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* ret = dest;
	while (count--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}

	return ret;
}


int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[5] = { 0 };
	my_memcpy(arr2, arr1, 20);



    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };   
    // 1 2 1 2 3 4 5 8 9 10 期望的结果
	my_memcpy(arr1+2, arr1, 20);

	return 0;
}

但是当我们想拷贝同一块空间的数据时,数据却发生错误

 原因在于数据覆盖了我们想要拷贝的空间

 在同一块内存拷贝,目标和源头数据空间有交叉,我们应该用memmove


3.3 memmove  可以实现重叠内存拷贝

void *memmove( void *dest, const void *src, size_t count );

#include <string.h>

int main()
{
		int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
		memmove(arr1 + 2, arr1, 20);
	return 0;
}

1

3.4  模拟实现memmove

思路:

把34567拷贝放到12345,正着放不会覆盖数据(dest<src时)

 而想把34567拷贝放到45678,正着放会覆盖数据,我们可以先把7放到8上,6放到7,5放到6...从后往前拷贝数据,这样数据就不会被覆盖

 总结:当需要拷贝的地址dest > src地址,从后向前拷贝 ;dest < src时,从前向后拷贝

当dest和src空间无交集,前后关系无所谓,我们这里默认从后向前拷贝(方便)

{
	if (dest > src)
	{
		;  //从后向前拷贝
	}
	else
	{
		;  //从前向后拷贝
	}
}

也可以换成另一种写法

if (dest > src && dest<((char*)src+count))
	{
		;//从后向前拷贝
	}
	else
	{
		;//从前向后拷贝
	}

代码思路:从前向后的代码就是memcpy模拟,从后向前,我们需要+20个字节来到dest和src末尾

void* my_memmove(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* ret = dest;
	if (dest > src)
	{
		while (count--)
		{
	*((char*)dest + count) =  *((char*)src + count);  //count=19,正好指向最后一个字节
		}
		//从后向前拷贝
	}
	else
	{
			void* ret = dest;
			while (count--)
			{
				*(char*)dest = *(char*)src;
				dest = (char*)dest + 1;
				src = (char*)src + 1;
			}
		 //从前向后拷贝
	}
	return ret;
}


	int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr1 + 2, arr1, 20);
	//my_memmove(arr1 , arr1+2, 20);
	return 0;
}

3.5 memcpy  内存字节对应比较

int memcmp ( const void * ptr1, 
 const void * ptr2, 
 size_t num );//比较的字节个数

 

 int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,0x11223305 };
	int ret = memcmp(arr1, arr2, 18);
	printf("%d\n", ret);

	return 0;
}


3.6  memset  以字节为单位进行内存设置

void *memset( void *dest, int c, size_t count );//目的空间,设置的字符,字符个数

 int main()
{
	int arr[] = { 0x11111111,0x22222222,3,4,5 };
	memset(arr, 6, 20);//memset是以字节为单位来初始化内存单元的
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_63543274/article/details/123986777
今日推荐