字符串函数与内存操作函数详解及模拟实现

发布于:2022-12-13 ⋅ 阅读:(586) ⋅ 点赞:(0)

目录

求字符串长度 strlen 

长度不受限制的字符串函数 strcpy strcat strcmp

strcpy

strcat

strcmp

长度受限制的字符串函数介绍 strncpy strncat strncmp

strncpy

strncat

strncmp

字符串查找 strstr strtok

strstr

strtok

错误信息报告 strerror

字符分类和转换操作函数 

内存操作函数 memcpy memmove memset memcmp

memcpy

memmove

memset

memcmp


 在使用字符串函数时,一定要使用 #include <string.h> 头文件

求字符串长度 strlen 

功能:求字符串长度。从开始的第一个字符开始计数,直到遇到'\0'停止计数。如果你只定义了一个字符型数组但没有给它赋初值,这个结果是不定的,它会从首地址一直找下去,直到遇到'0'停止。

注意返回值是size_t。

模拟实现

int my_strlen(const char* str)
{
	if (*str == 0)
		return 0;
	char* end=str;
	do
	{
		end++;
	} while (*end);
	return end - str;	
}
int main()
{
	char arr[] = "abcdefgh";
	printf("%d", my_strlen(arr));
	return 0;
}

长度不受限制的字符串函数 strcpy strcat strcmp

strcpy

功能:将源头指向的 C 字符串复制到目标所指向的数组中,包括终止 null 字符(并在该点停止)。

返回值为目的地的地址,为了便于使用链式访问。 

 注意:为避免溢出,目标所指向的数组的大小应足够长,以包含与 source 相同的 C 字符串(包括终止的 null 字符)。否则会出现错误。

并且destination在内存中不应与 source 重叠。

 如图,拷贝到i时,源头的结束标志变成了正常字符,无法停下。

模拟实现:

char* my_strcpy(char* dest,const char* src)
{
	assert(dest && src);//包含头文件:#include <assert.h>
	char* r = dest;
	while (*dest++ = *src++);
	return r;
}
int main()
{
	char arr1[] = "**************";
	char arr2[] = "abcdefgh";
	printf("%s", my_strcpy(arr1,arr2));
	return 0;
}

strcat

功能:将源字符串的副本追加到目标字符串。目标中的终止空字符被源的第一个字符覆盖,并且遇到源头的'\0'字符结束,'\0'也被追加了。

 返回值为目的地的地址,为了便于使用链式访问

注意:为避免溢出,目标所指向的数组的大小应足够长,以包含与 source 相同的 C 字符串(包括终止的 null 字符)。否则会出现错误。

目的地和源不得重叠,即自己给自己追加。

如图,源头的'\0'被覆盖了,源头不能遇到'\0'而停下。 

模拟实现:


char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);//#include <assert.h>
	char* r = dest;
	while (*dest)
		dest++;
	while (*dest++ = *src++);
	return r;
}
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "xyz!";
	printf("%s", my_strcat(arr1, arr2));
	return 0;
}

strcmp

功能:将 C 字符串 str1 与 C 字符串 str2 进行比较。此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续执行以下对操作,直到字符不同或达到终止空字符。字符不同时,str1更大返回>0的数,反之返回<0的数,全部相等返回0.

 模拟实现:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);//#include <assert.h>
	while (*str1 && *str2)
	{
		if (*str1 != *str2)
			return *str1 - *str2;
		str1++, str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "abcdefghi";
	char arr2[] = "abcdefghz";
	printf("%d", my_strcmp(arr1, arr2));
	return 0;
}

长度受限制的字符串函数介绍 strncpy strncat strncmp

strncpy

功能:将源的前 num 个字符复制到目标。

  返回值为目的地的地址,为了便于使用链式访问

注意:如果在复制数字字符之前找到源 C 字符串(由空字符表示)的末尾,则目标将用零填充,直到总共写入数字字符为止。

如果没复制数字字符之前找到源 C 字符串(由空字符表示)的末尾,则只copy对应数目字符,不会加'\0'。

源头与目的地不应该重叠。如果源头与目的地有重叠会导致拷贝达不到要的效果。

如:

如果想正确拷贝可以调用memomove,利用从后往前拷贝解决。最后结果为:ststunt

模拟实现:

char* my_strncpy(char* dest, char* src,int num)
{
	char* ret = dest;
	int i = 0;
	while (i < num && *src != 0)
	{
		*dest = *src;
		dest++, src++;
		i++;
	}
	while (i < num)
	{
		*dest = *src;
		dest++;
		i++;
	}
	return ret;
}

int main()
{
	char a[] = "abcd";
	char b[] = "destination";
	char c[] = "jiaruxuanni";
	printf("%s\n", my_strncpy(b,a,6));
	printf("%s\n", my_strncpy(c,a,3));
	return 0;
}

strncat

功能:将源的前 num 个字符追加到目标,外加一个终止空字符。 

  返回值为目的地的地址,为了便于使用链式访问

注意:  如果源中 C 字符串的长度小于 num,则仅复制到终止空字符之前的内容。

模拟实现:

char* my_strncat(char* dest,const char* src,int num)
{
	char* ret = dest;
	while (*dest)
	{
		dest++;
	}
	int i = 0;
	while (i < num && *src)
	{
		*dest = *src;
		src++, dest++, i++;
	}
	*dest = 0;
	return ret;
}

int main()
{
	char a[30] = "zhangsan";
	char b[] = "--abcd";
	printf("%s\n", my_strncat(a, b, 4));
	char c[30] = "wangdawu";
	printf("%s", my_strncat(c, b, 7));
	return 0;
}

strncmp

功能:将 C 字符串 str1 的num个数字字符与 C 字符串 str2 的字符进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续执行以下对操作,直到字符不同,直到达到终止空字符,或者直到两个字符串中的数字字符不匹配,字符不同时,str1更大返回>0的数,反之返回<0的数,全部相等返回0.

 模拟实现:

int my_strncmp(const char* s1,const char* s2,int num)
{
	for (int i = 0; i < num; i++)
	{
		if (*(s1 + i) == *(s2 + i))
		{
			if (s1[i] == 0)
				return 0;
			else
				continue;
		}
		else
			return *(s1 + i) - *(s2 + i);
	}
	return 0;
}

int main()
{
	char s1[] = "abcdef";
	char s2[] = "abcde";
	char s3[] = "abcdek";
	printf("%d ", my_strncmp(s1, s2, 4));
	printf("%d ", my_strncmp(s1, s2, 6));
	printf("%d ", my_strncmp(s1, s3, 6));
	printf("%d ", strncmp(s1, s2, 4));
	printf("%d ", strncmp(s1, s2, 6));
	printf("%d ", strncmp(s1, s3, 6));

	return 0;
}


字符串查找 strstr strtok

strstr

功能: 返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回空指针。匹配过程不包括终止空字符,但它在那里停止。

 注意:

  1. 不论str1是不是空串,只要str2是空串,总是返回str1的首地址
  2. str1是空串,且str2不是空串时,返回NULL  

模拟实现:

int fun(const char* dest, const char* src)
{
	while (*dest && *src)
	{
		if (*dest != *src)
			return 0;
		dest++, src++;
	}
	if (*src == 0)
		return 1;
	return 0;
}
char* my_strstr(const char* dest, const char* src)
{
	assert(dest && src);
	while (*dest)
	{
		if (fun(dest, src))
			return dest;
		dest++;
	}
	return NULL;
	
}
int main()
{
	char arr1[20] = "abcdefxyz!";
	char arr2[] = "fxay";
	printf("%s", my_strstr(arr1, arr2));
	return 0;
}

KMP算法:看这里

strtok

功能:分解字符串为一组字符串。str为要分解的字符,delim为分隔符字符(如果传入字符串,则传入的字符串中每个字符均为分割符)。

注意:1.每次调用函数都会把查找到的第一个分割字符改成'\0',然后返回分割的两断字符串的前一个字符串的首地址,前一个字符串的末尾地址会被记录下来。 如图:分隔符为'-'。              2.   首次调用时,str指向要分解的字符串,之后再次调用要把s设成NULL,这样函数会使用前一次记录下来的末尾位置作为起始位置开始扫描。否则开始位置还是str开头。

3.   如果第一次分割查找不到delim中的分割字符时,返回当前strtok的字符串的指针。如果第二次 及以后str 已经查找到了终止空字符,则对此函数的所有后续调用(以空指针作为第一个参数)都将返回空指针。

4.   需要注意的是,使用该函数进行字符串分割时,会破坏被原来字符串,调用前和调用后的str已经不一样了,所以一般分割str的临时拷贝。而且不能分割常量字符串。

5.   如果查找过程中,第一到第n个字符都是分隔符,函数将跳过他们,从第一个不是分割字符的字符开始查找处理。

 

 模拟实现:

static char* end =NULL;//记录每次分割后的末尾位置,便于下次调用
static int flag =0;//是否遍历完成了的标志
char* my_strtok(char* str, char* d)
{
    if (flag == 1)
    {
        return NULL;
    }
    if (str == NULL)
    {
        str = end + 1;
    }

    int i = 0;
    char arr[2] = "";
    arr[0] = str[i];
    //找到第一个非分隔符字符
    while (strstr(d, arr) && str[i] != 0)
    {
        str++;
        i = 0;
        arr[0] = str[i];
    }
    //遍历完了没有出现非分隔符
    if (str[i] == 0)
    {
        flag = 1;
        return NULL;
    }
    //找到第一个分隔符
    while (!strstr(d,arr)&&str[i]!=0)
    {
        i++;
        arr[0] = str[i];
    }
    if (str[i] != 0)
    {
        str[i] = 0;
        end = str + i;
    }
    else
    {
        flag = 1;
    }
    return str;
}
int main()
{
    char str[] = "- This, a sample string.";
    char* pch;
    pch = my_strtok(str, " ,.-");
    while (pch != NULL)
    {
        printf("%s\n", pch);
        pch = my_strtok(NULL, " ,.-");
    }
    //char a[100] = "12nkdaoiakl";
    //char a1[100] = "*****";
    //printf("%s\n", strtok(a1,"*"));
    //printf("%s\n", strtok(NULL,"*"));
    return 0;
}

错误信息报告 strerror

功能:用来依参数errnum 的错误代码来查询其错误原因的描述字符串, 然后将该字符串指针返回.

返回的指针指向静态分配的字符串,程序不得修改该字符串。对此函数的进一步调用可能会覆盖其内容。strerror 产生的错误字符串可能特定于每个系统和库实现。

注意:使用方法,传入errno即可实现打印错误信息。

#include <errno.h>

int main ()
{
  FILE * pFile;
  pFile = fopen ("unexist.ent","r");
  if (pFile == NULL)
    printf (" %s\n",strerror(errno));
  return 0;
}
系统调用的错误都存储于 errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。使用必须引用头文件:#include <errno.h>

另外,perror函数与之相似,都可以实现打印错误信息。

#include<errno.h>
int main()
{
	FILE* pFile;
	pFile = fopen("unexist.ent", "r");
	if (pFile == NULL)
	{
		printf(" %s\n", strerror(errno));
		perror("The following error occurred");

	}
	return 0;
}

字符分类和转换操作函数 

使用前需要引用头文件ctype.h 

iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\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 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

如果 c 是大写字母并且具有小写等效字母,则将 c 转换为其小写等效字母。如果无法进行此类转换,则返回的值 c 保持不变。


如果 c 是小写字母并且具有大写等效字母,则将 c 转换为其大写等效字母。如果无法进行此类转换,则返回的值 c 保持不变。 

例子:(注意返回值是int需要赋值给对应字符,而不是直接改了值)

#include<ctype.h>
int main()
{
	char arr[] = "I like C VERY muCh!";
	for (int i = 0; i < strlen(arr); i++)
	{
		if (islower(arr[i]))
			arr[i]=toupper(arr[i]);
	}
	for (int i = 0; i < strlen(arr); i++)
	{
		arr[i] = toupper(arr[i]);
	}

		printf("%s  ", arr);

	return 0;
}

 

内存操作函数 memcpy memmove memset memcmp

memcpy

功能:将数字字节的值从源指向的位置直接复制到目标所指向的内存块。停止依据是字节数。 

注意:

1.为避免溢出,目标和源参数所指向的数组大小应至少为数字字节,否则会发生错误。

2.目的地和源头不应重叠,否则可能会与想要的效果不同。

想要的是ststunt     ,对于重叠的内存块,memmove 是一种更安全的方法,会考虑这个问题得到正确答案。

但其实很多编译器里的库函数memocpy进行了优化也可以达到效果,但是C语言的标准并不要求memocpy达到这样的效果。

模拟实现:(考虑到优化问题版本)

void* my_memcpy(void* dest, void* src,int num)
{
	void* ret = dest;
	char* r1 = dest;
	char* r2 = src;
	if (r1 > r2)
	{
		for (int i = num-1; i >= 0; i--)
		{
			*(r1 + i) = *(r2 + i);
		}
	}
	else
	{
		for (int i = 0; i < num; i++)
		{
			*(r1 + i) = *(r2 + i);
		}

	}
	
	return dest;
}
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 12);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	printf("\n");
	my_memcpy(arr1+6, arr1+4, 12);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

memmove

功能: 将数字字节的值从源指向的位置复制到目标所指向的内存块。复制就像使用了中间缓冲区一样进行,从而允许目标和源重叠

注意:

为避免溢出,目标和源参数所指向的数组大小应至少为数字字节。

模拟实现:


void* my_memmove(void* dest, void* src, int num)
{
	void* ret = dest;
	char* r1 = dest;
	char* r2 = src;
	if (r1 > r2)
	{
		for (int i = num - 1; i >= 0; i--)
		{
			*(r1 + i) = *(r2 + i);
		}
	}
	else
	{
		for (int i = 0; i < num; i++)
		{
			*(r1 + i) = *(r2 + i);
		}

	}

	return dest;
}

typedef struct s
{
	int a;
}ster,*p,s2;
int main()
{
	
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memmove(arr2, arr1, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	printf("\n");
	my_memmove(arr1 + 6, arr1 + 4, 16);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

memset

功能: 将 ptr 所指向的内存块的前 num 个字节设置为指定的值。

注意:指定的值类型为无符号字符。

模拟实现:

void* my_memset(void* buf, int set, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		*((char*)buf + i) = set;
	}
	return buf;
}
int main()
{
	int  buf[] = {1,2,3,4,5,6,7,8,9,10};
	int i = 0;
	my_memset(buf,0, 5);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", buf[i]);
	}

	
	return 0;
}

memcmp

功能:将 ptr1 所指向的内存块的前 num 个字节与 ptr2 所指向的前num个字节数进行比较,如果它们全部匹配,则返回零,或者返回一个不同于零的值,表示如果它们不匹配,则返回更大的数字字节。

 注意:与 strcmp 不同,该函数在找到空字符后不会停止比较。

int my_memcmp(const void* str1, const void* str2, size_t count) {
	assert(str1 && str2);

	const char* pstr1 = (const char*)str1;
	const char* pstr2 = (const char*)str2;
	while (count--) {
		if (*pstr1 && *pstr2 && (*pstr1 == *pstr2)) {
			continue;
		}
		else {
			break;
		}
	}
	return (*pstr1 - *pstr2);
}

int main()
{
    int a[] = { 1,2,3,4,5,6,7,8,9 };
    int r = my_memcmp(a, a + 4, 8);
    printf("%d\n", r);

    return 0;
}