字符串与内存函数的介绍+模拟实现

0.前言

C语言中对字符和字符串的处理很是繁琐,但是C语言本身是没有字符串类型的字符串通常存放在常量字符串或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数。

1.函数介绍

1.1 strlen

size_t strlen(const char* str);
  • 字符串以'\0'作为结束标志,strlen函数返回的是在字符串中'\0'前面出现的字符个数(不包含'\0'
  • 参数指向的字符串必须要以'\0'结束
  • 注意函数的返回类型为size_t,是无符号的(相减时易错)
//错误写法
#include <stdio.h>
#include <string.h>
int main()
{
	char a[] = "abc";
	char b[] = "abcd";
	if (strlen(a) - strlen(b) >= 0)
	{
		printf("a比b长\n");
	}
	else
	{
		printf("b比a长\n");
	}
	return 0;
}
//打印结果:
//a比b长
/*
这个打印结果显然是不对的,a的长度为3,b的长度为4.
导致结果出错的原因就是函数的返回类型,size_t是无符号的整型,这两相减的值也是无符号类型不可能为负数的,所以导致了结果错误。
*/

//正确写法
#include <stdio.h>
#include <string.h>
int main()
{
	char a[] = "abc";
	char b[] = "abcd";
	if ((int)strlen(a) - (int)strlen(b) >= 0)//2.strlen(a)>=strlen(b)
	{
		printf("a比b长\n");
	}
	else
	{
		printf("b比a长\n");
	}
	return 0;
}

1.2 strcpy

char* strcpy(char* destination,const char* source);
  • Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
  • 原字符串必须以Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.结束。
  • 会将源字符串中的'\0'拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放原字符串。
  • 目标函数必须可变。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "xxxxxxxxxxxx";
	char arr2[] = "hello world";
	strcpy(arr1,arr2);
	printf("%s\n",arr1);
	return 0;
}
//打印结果:hello world

1.3 strcat

char* strcat(char* destination,const char* source);
  • Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.
  • 原字符串必须以'\0'结束。
  • 目标空间必须足够大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 可以自己给自己追加。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	strcat(arr1,arr2);
	printf("%s\n",arr1);
	return 0;
}
//打印结果:hello world

1.4 strcmp

int strcmp(const char* str1,const char* str2);
  • This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.
  • 规定
    • 第一个字符串大于第二个字符串,返回大于0的数字
    • 第一个字符串等于第二个字符串,返回0.
    • 第一个字符串小于第二个字符串,返回小于0的数字。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abc";
	char arr3[] = "abc";
	int ret = strcmp(arr1,arr2);
	printf("arr1:arr2 %d\n",ret);
	ret = strcmp(arr2,arr1);
	printf("arr2:arr1 %d\n",ret);
	ret = strcmp(arr2,arr3);
	printf("arr2:arr3 %d\n",ret);
	return 0;
}
//打印结果
/*
arr1:arr2 1
arr2:arr1 -1
arr2:arr3 0
*/

1.5 strncpy

char* strncpy(char* destination,const char* source,size_t num);
  • Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
  • 拷贝num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,则拷贝源字符串之后,在目标的后面追加0,直到num个。
//代码1
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "xxxxxxxxxxxx";
	char arr2[] = "hello world";
	strncpy(arr1,arr2,5);
	printf("%s\n",arr1);
	return 0;
}
//打印结果:
//helloxxxxxxx

//代码2
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "xxxxxxxxxxxx";
	char arr2[] = "he";
	strncpy(arr1, arr2, 5);
	printf("%s\n", arr1);
	return 0;
}
//打印结果:
//he

1.6 strncat

char* strncat(char* destination,const char* source,size_t num);
  • Appends the first num characters of source to destination, plus a terminating null-character.
  • If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.
//代码1
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	strncat(arr1,arr2,3);
	printf("%s\n",arr1);
	return 0;
}
//打印结果:
//hello wor

//代码2
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	strncat(arr1,arr2,10);
	printf("%s\n",arr1);
	return 0;
}
//打印结果:
//hello world

1.7strncmp

int strncmp(const char* str1,const char* str2,size_t num);
  • Compares up to num characters of the C string str1 to those of the C string str2.
    This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ, until a terminating null-character is reached, or until num characters match in both strings, whichever happens first.
  • 比较到出现另一个字符不一样或者一个字符串结束或者num个字符串全部比较完。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abc";
	char arr3[] = "abc";
	int ret = strncmp(arr1,arr2,3);
	printf("arr1:arr2(3) %d\n",ret);
	return 0;
}
//打印结果:arr1:arr2(3) 0

1.8 strstr

char* strstr(const char* str1,const char* str2);
  • Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
  • 找不到会返回NULL
#include <stdio.h>
#include <string.h>
int main()
{
	char str[] = "This is a simple string";
	char* pch;
	pch = strstr(str,"simple");
	if(pch!=NULL)
		printf("%s\n",pch);
	else
		printf("找不到\n");
	pch = strstr(str,"hello");
	if(pch!=NULL)
		printf("%s\n",pch);
	else
		printf("找不到\n");
	return 0;
}
//打印结果:
/*
simple string
找不到
*/

1.9 strtok

char* strtok(char* str,const char* sep);
  • sep参数是一个字符串,定义了用来作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将以'\0'结尾,返回一个指针指向这个标记的指针(strtok函数会改变被操作的字符串,所以在适用strtok函数切分的字符串一个都是临时拷贝的内容并且可修改。)
  • strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个函数为NULL,将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回NULL指针。
#include <stdio.h>
#include <string.h>
int main()
{
	char str[] = "legacy.cplusplus.com/reference/cstring/strtok/?kw=strtok";
	char sep[] = "./";
	char cpy[60];
	strcpy(cpy,str);
	for(char* ret = strtok(cpy,sep);ret!=NULL;ret = strtok(NULL,sep))
	{
		printf("%s\n",ret);
	}
	return 0;
}
//打印结果:
/*
legacy
cplusplus
com
reference
cstring
strtok
?kw=strtok
*/

1.10 strerror

char* strerror(int errnum);
  • 返回错误码所对应的错误信息。

库函数在执行的时候,发生了错位会将一个错误码存放在errno这个变量中
errno是C语言提供的一个全局变量.

//打印0~10所对应的错误信息
#include <stdio.h>
#include <string.h>
int main()
{
	for(int i = 0;i<=10;++i)
	{
		printf("%d:%s\n",i,strerror(i));
	}
	return 0;
}
//打印结果
/*
0:No error
1:Operation not permitted
2:No such file or directory
3:No such process
4:Interrupted function call
5:Input/output error
6:No such device or address
7:Arg list too long
8:Exec format error
9:Bad file descriptor
10:No child processes
*/

字符分类函数

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

字符转换函数

函数 功能
tolower 将大写字母转换为小写字母
toupper 将小写字母转换为大写指针
//大写转小写
#include <stdio.h>
#include <ctype.h>
int main()
{
	char str[] = "I Am A Student";
	for(int i = 0;str[i];++i)
	{
		str[i] = tolower(str[i]);
	}
	return 0;
}
//打印结果:
//i am a student

1.11 memcpy

void* memcpy(void* destination,const void* source,size_t num);
  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 这个函数在遇到'\0'的时候不会停止。
  • 如果source和destination有任何重叠,复制的结果是未定义的。(C语言标准未定义,编译器可能会实现)
  • memcpy不仅可以复制字符型,还可以复制整型,浮点型,结构体型等等。
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[20] = {0};
	int arr2[] = {1,2,3,4,5,6};
	memcpy(arr1,arr2,sizeof(arr2));
	for(int i = 0;i<20;++i)
	{
		printf("%d ",arr1[i]);
	}
	return 0;
}
//打印结果:
//1 2 3 4 5 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0

1.12 memmove

void* memmove(void* destination,const void* source,size_t num);
  • 和memcpy的区别就在于memmove函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就需要所有memmove来处理。
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = {1,2,3,4,5,6,7,8,9,0};
	memmove(arr1+2,arr1,20);
	for(int i = 0;i<10;++i)
	{
		printf("%d ",arr1[i]);
	}
	return 0;
}
//打印结果:
//1 2 1 2 3 4 5 8 9 0

1.13 memcmp

int memcmp(const void* ptr1,const void* ptr2,size_t num);
  • 比较从ptr1和ptr2指针开始的num个字节。
  • 规则于strcmp类型
    • ptr1大于ptr2返回大于0的数。
    • prt1等于ptr2返回等于0的数。
    • ptr1小于ptr2返回小于0的数。
      在这里插入图片描述
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = {1,2,3,4,5,6,7,8,9,0};
	int arr2[] = {1,2,4,0};
	int ret = memcmp(arr1,arr2,12);
	printf("%d\n",ret);
	return 0;
}
//打印结果:
//-1
/*
解释:根据函数传递的参数得知要比较前12个字节
arr1的前12个字节:01 00 00 00 02 00 00 00 03 00 00 00
arr2的钱12个字节:01 00 00 00 02 00 00 00 04 00 00 00
在第9个字节时就已经分出大小了
04大于03所以返回-1.
*/

2.库函数的模拟实现

2.1 模拟实现strlen函数

计算字符串长度,要求:遇到'\0'就停止,不能将'\0'记录进长度
有三种方法。
方法1:

int my_strlen(const char* str)
{
	int count = 0;
	while(*str)
	{
		count+=1;
		str+=1;
	}
	return count;
}

方法2(利用指针减指针的运算)

int my_strlen(const char* str)
{
	char* p = str;
	while(*str)
	{
		str+=1;
	}
	return str-p;
}

方法3 利用递归实现
问题拆分:在求一个字符串长度时,以"hello"为例。我们求它的长度时,因为‘h’的长度肯定是1,所以我们是不是可以转化成求1+“ello”的长度。依次类推还可以写成1+1+“llo”的长度…
最后求到’\0’时,因为’\0’不能作为字符串长度所以+0。依照这个思路写成的代码就是这样。

int my_strlen(const char* str)
{
	if(str=='\0')
		return 0;
	return 1+my_strlen(str+1);
}

2.2 模拟实现strcpy

实现字符串的拷贝

char* my_strcpy(char* dest,char* src)
{
	char* ret = dest;//保存首元素地址方便后续返回复制后的字符串
	assert(dest&&src);//预防传递空指针
	while(*dest++=*src++)
	{
		;
	}
	return ret;
}

2.3 模拟实现strcat

功能:追加字符

char* my_strcat(char* dest,const char* src)
{
	assert(dest&&src);//防止空指针。
	char* ret = dest;//保存起始位置
	//因为是在目标函数的最后追加字符串,所以我们要先找到目标函数的'\0'的位置
	while(*dest)
	{
		dest+=1;
	}
	//找到后开始追加字符,类似于字符拷贝咯
	while(*dest++=*src++)
	{
		;
	}
	return ret;
}

2.4 模拟实现strstr

功能:找子串

char* my_strstr(const char* dest,const char* src)
{
	if(!*src)
		return dest;//如果src是空的,就直接返回dest就可以了
	//利用3指针,定义3个指针,两个指向dest,一个指向src
	//指向dest的指针其中一个为保存返回位置的起始地点的指针
	//另一个于指向src的指针进行比较。
	char* ret = dest;
	char* p1 = dest;
	char* p2 = src;
	while(*ret)
	{
		p1 = ret;//每次从ret位置向后于p2匹配
		p2 = src;//每次没找到就要从头开始于p1匹配
		while(*p1!='\0'&&*p2!='\0'&&*p1==*p2)//p1与p2相等才能向后走
		//除此之外还要判断是否走到字符串的末尾
		{
			p1+=1;
			p2+=1;
		}
		//当不满足条件出循环时,如果*p2 == '\0'就说明src字符串已经被全部找到,可以返回了
		if(*p2=='\0')
		{
			return ret;
		}
		ret+=1;
	}
	//走到这里就说明前面都没有找到,只能返回NULL了
	return NULL;
}

2.5 模拟实现strcmp

int my_strcmp(char* ptr1,char* ptr2)
{
	while(*ptr1&&*ptr2&&*ptr1==*ptr2)
	{
		ptr1+=1;
		ptr2+=1;
	}
	//出循环就说明不相等了或者有字符串走完了
	if(*ptr1-*ptr2>0)
		return 1;
	else if(*ptr1-*ptr2 < 0)
		return -1;
	else
		return 0;	
}

2.6 模拟实现memcpy

提问:为什么使用void*接收呢?
回答:Void指针 是无具体类型的指针。Void 类型的指针可以接任意类型的地址(这种类型的指针是不能直接解引用操作的,也不能直接进行指针运算的)。
所以用void*接收是没问题的。然后,这个函数不仅可以拷贝整型数组排序,还可以对字符数组,浮点型数组,甚至是结构体数组。这也就造成了不能使用特定类型指针来接收的情况,如果使用了特定的类型,那其它类型就不能被接收了,所以才会选择使用void*来接收。

void* my_memcpy(void* dest,const void* src,size_t num)
{
	char* p1 = (char*)dest;
	char* p2 = (char*)src;
	for(int i = 0;i<num;++i)
	{
		*p1 = *p2;
		p1+=1;
		p2+=1;
	}
	return dest;
}

C语言规定:memcpy只需要实现不重叠的拷贝就可以了
但是vs上memcpy函数实现了重叠拷贝,所以你在vs上让memcpy处理重叠的拷贝也是没问题的,只是不能保证所以的编译器都会这么设计。

2.7 模拟实现memmove

memmove和memcpy的功能其实是类似的。
memcpy不会处理重叠数据的,
memmove才会处理重叠数据.
src在dest前
在这里插入图片描述

如图,将src后的20字节(5个元素)拷贝到dest中,正常直接拷贝的话,会出现覆盖的情况如下图:
在这里插入图片描述

元素3和4就被覆盖了,程序继续进行下去,也只会在把1、2往后面拷贝,这可不是我们想要的结果。为什么不让没被使用的数据被覆盖,我们要怎么办呢?
其实很简单,我们从后往前拷贝。
在这里插入图片描述

这样从后往前拷贝就可以完美避免未使用的值在使用前被覆盖。

src在dest后
在这里插入图片描述

如果是这种情况,观察可以发现从前往后拷贝不会影响结果。反而如果是从后往前负值才会影响结果。

确定范围
如果dest大于等于src就从后向前覆盖
其他情况都从前向后覆盖。

void* my_memmove(void* dest, const void* src, size_t num)
{
	char* p1 = (char*)dest;
	char* p2 = (char*)src;
	if (dest >= src)
	{
		//从后往前覆盖
		p1 += num-1;//减1的目的是为了让指针停在一个类型的最后一个字节上
		p2 += num-1;
		for (int i = 0; i < num; ++i)
		{
			*p1 = *p2;
			p1 -= 1;
			p2 -= 1;
		}
	}
	else
	{
		//从前往后覆盖
		for (int i = 0; i < num; ++i)
		{
			*p1 = *p2;
			p1 += 1;
			p2 += 1;
		}
	}
	return dest;
}

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 04:22:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 04:22:02       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 04:22:02       58 阅读
  4. Python语言-面向对象

    2024-07-19 04:22:02       69 阅读

热门阅读

  1. LVS的DR模式

    2024-07-19 04:22:02       19 阅读
  2. 前端常用工具库

    2024-07-19 04:22:02       19 阅读
  3. 智能灯光的工作原理

    2024-07-19 04:22:02       19 阅读
  4. 安全防御:防火墙基本模块

    2024-07-19 04:22:02       21 阅读
  5. Qt区分鼠标按下时移动的是哪个多边形

    2024-07-19 04:22:02       19 阅读
  6. Unlink

    Unlink

    2024-07-19 04:22:02      20 阅读
  7. 扩展你的App:Xcode中App Extensions的深度指南

    2024-07-19 04:22:02       25 阅读
  8. 计算机算法思想

    2024-07-19 04:22:02       13 阅读
  9. ApplicationRunner applicationRunner 是什么?

    2024-07-19 04:22:02       19 阅读
  10. 介绍threadlocal

    2024-07-19 04:22:02       18 阅读
  11. cpu100%排查

    2024-07-19 04:22:02       19 阅读
  12. 黑龙江等保2.0新规

    2024-07-19 04:22:02       25 阅读