资讯专栏INFORMATION COLUMN

手撕C语言进阶---字符串和内存函数(详解+实现+原码)

vslam / 748人阅读

摘要:自己实现时返回值可根据实际情况而定源字符串必须以结束。语言中给了一些长度受限的字符串函数,而前面的函数是长度不受限的字符串函数。拷贝个字符从源字符串到目标空间。

目录

字符函数和字符串函数

函数介绍

strlen

strcpy

strcat

strcmp

strncpy

 strncat

strncmp

strstr

strtok

strerror

memcpy

memmove

memcmp


字符函数和字符串函数

本章重点

重点介绍处理字符和字符串的库函数的使用和注意事项
求字符串长度
  • strlen
长度不受限制的字符串函数
  • strcpy
  • strcat
  • strcmp
长度受限制的字符串函数介绍
  • strncpy
  • strncat
  • strncmp
字符串查找
  • strstr
  • strtok
错误信息报告
  • strerror
字符操作
内存操作函数
  • memcpy
  • memmove
  • memset
  • memcmp

 前言

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

函数介绍

strlen

size_t strlen ( const char * str );

  • 字符串已经 "/0" 作为结束标志,strlen函数返回的是在字符串中 "/0" 前面出现的字符个数(不包含 "/0" )。
  • 参数指向的字符串必须要以 "/0" 结束。
  • 注意函数的返回值为size_t,是无符号的( 易错 )
  • 学会strlen函数的模拟实现

 注:size_t 即无符号整型(unsigned int)

strlen使用:

#include#includeint main(){	char arr1[] = "abcdef";	char arr2[] = { "a","b","c","d","e","f","/0"};//计算字符数组长度时末尾必须加"/0",                                                  //否则计算出的长度为随机值.	printf("%d/n", strlen(arr1));	printf("%d/n", strlen(arr2));	return 0;}

 strlen模拟实现:

//方法一:使用计数器size_t my_strlen(char* str){	assert(str);//检查指针有效性	int count = 0;//计数器	//while (*str != "/0")	//{		//count++;		//str++;	//}		//简化	while(*str++)	{		count++;	}	return count;}//方法二:递归size_t my_strlen2(char* str){	assert(str);	if (!*str)	{		return 0;	}	else	{		return 1 + my_strlen(str + 1);	}}//方法三:指针减指针size_t my_strlen3(char* str){	assert(str);	char* cur = str;	while (*cur)	{		cur++;	}	return cur - str;//两指针相减,结果为他们之间的元素个数}

注:下面的代码结果如何?

#include #include int main(){    const char*str1 = "abcdef";    const char*str2 = "bbb";    if(strlen(str2)-strlen(str1)>0)//由于strlen函数的返回值为无符号整型,                                   //所以在计算时,会恒为正数。自己实现时返回值可根据实际情况而定    {        printf("str2>str1/n");    }    else    {        printf("srt1>str2/n");    }    return 0;}

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).
  • 源字符串必须以 "/0" 结束。
  • 会将源字符串中的 "/0" 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。
  • 学会模拟实现

 strcpy使用:

#include#includeint main(){	char arr1[10] = "xxxxxxxxx";	char arr2[] = "abcdef";	printf("%s/n", strcpy(arr1, arr2));//将arr2字符串中的内容拷贝到arr1中(包括"/0)	return 0;                          //需保证arr1的空间大于等于arr2的空间}

strcpy模拟实现:

#include//用assert函数需包含此头文件char* my_strcpy(char* dest, const char* src)//返回值为目标空间的起始地址,src中的字符串                                           //不需要改变,为避免被修改所以再它前面加上const{	assert(dest && src);//检查指针的有效性	char* ret = dest;//保存目标空间的起始地址,dest后面会移动	//while (*src!="/0")	//{	//	*dest = *src;	//	dest++;	//	src++;	//}	//简化	while (*dest++ = *src++)//src先将值赋给dest,然后dest和src才++	{		;	}	return ret;}

strcat

char * strcat ( char * destination, const char * source );

  • Appends a copy of the source string to the destination string. The terminating null character indestination is overwritten by the first character of source, and a null-character is included at the end ofthe new string formed by the concatenation of both in destination.
  • 源字符串必须以 "/0" 结束。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 字符串自己给自己追加,如何?

 strcat使用:

#include#includeint main(){	char arr1[10] = "abcd";	char arr2[] = "efgh";	printf("%s", strcat(arr1, arr2));//将arr2中的字符串追加到arr1中的字符串后面	return 0;                        //需保证arr1的空间容纳连接后的字符串}

strcat模拟实现:

#includechar* my_strcat(char* dest, const char* src){	assert(dest && src);	char* ret = dest;	while (*dest)//因为是将src中的字符串追加到dest的后面所以需先找到dest中"/0"的位置	{		dest++;	}	while (*dest++ = *src++)//同strcpy	{		;	}	return ret;}

其中大概步骤如图: 

 

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, itcontinues with the following pairs until the characters differ or until a terminating null-character isreached.

  • 标准规定:
  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字
  • 那么如何判断两个字符串?

strcmp使用 :

#include#includeint main(){	char arr1[] = "abcdef";	char arr2[] = "abcdxx";	int ret = strcmp(arr1, arr2);	if (ret > 0)	{		printf("arr1 > arr2/n");	}	else if (ret < 0)	{		printf("arr1 < arr2/n");	}	else	{		printf("arr1 = arr2/n");	}	return 0;}

strcmp模拟实现

#includeint my_strcmp(const char* str1, const char* str2)//str1和str2都不需要被改变{	assert(str1 && str2);	while (*str1 == *str2)//如果找到不相等的字符直接返回他们的ascll码值之差,                          //而不是将所有的字符全部比较完之后再返回.	{		if (*str1 == "/0")		{			return 0;		}		str1++;		str2++;	}	return *str1 - *str2;}

前面学习了这些字符串函数之后,大家是否有些许收获呢?

其实这些函数中有些是不安全的,比如:strcpy中如果目标空间的大小不能够容纳原空间的字符串,就会造成数组越界访问,strcat中也是同样的道理。还有如果字符串自己给自己追加,是不能使用strcat函数的;如果我们只想比较字符串中的部分字符串的大小,而不是全部,也不能使用strcmp。C语言中给了一些长度受限的字符串函数,而前面的函数是长度不受限的字符串函数

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 issignaled by a null-character) is found before num characters have been copied, destination is paddedwith zeros until a total of num characters have been written to it.
  • 拷贝num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

 strncpy使用:

#include#includeint main(){	char arr1[] = "xxxxxxxxxx";	char arr2[] = "xx";	char arr3[] = "abcdef";	printf("%s/n", strncpy(arr1, arr3, 8));//拷贝了8个字符,所以字符串中的"/0"也会被拷贝	printf("%s/n", strncpy(arr2, arr3, 1));//拷贝了1个字符,"/0"不会被拷贝	return 0;}

strncpy模拟实现:

#includechar* my_strncpy(char* dest, const char* src, int count)//参数count为需要拷贝的字节数{	assert(dest && src);	char* ret = dest;	while(count--)	{		*dest++ = *src++;	}	return ret;}

 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 nullcharacter is copied.

 strncat使用:

/* strncat example */#include #include int main (){    char str1[20];    char str2[20];    strcpy (str1,"To be ");    strcpy (str2,"or not to be");    strncat (str1, str2, 6);    puts (str1);    return 0;}

 strncat模拟实现:

char* my_strncat(char* dest, const char* src, int count){	assert(dest && src);	char* ret = dest;	while (*dest)//找到目标字符串中"/0"的位置	{		dest++;	}	while (count--)	{		*dest++ = *src++;//从"/0"处开始追加	}        *dest = "/0";//末尾需"/0"	return ret;}

strncmp

int strncmp ( const char * str1, const char * str2, size_t num );

  •  比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。

strncmp使用: 

/* strncmp example */#include #include int main (){    char str[][5] = { "R2D2" , "C3PO" , "R2A6" };    int n;    puts ("Looking for R2 astromech droids...");    for (n=0 ; n<3 ; n++)    if (strncmp (str[n],"R2xx",2) == 0)//比较两个字符串的前两个字符    {        printf ("found %s/n",str[n]);    }    return 0;}

strncmp模拟实现:

int my_strncmp(char* str1, char* str2, int count){	assert(str1 && str2);	while (*str1 == *str2)	{		if (count <= 0)		{			return 0;		}		str1++;		str2++;			}    return *str1 - *str2;}

strstr

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

  • 在str1中查找 是否存在str2字符串,如果有则返回str2第一次出现的位置的地址,否则返回NULL;

strstr使用:

/* strstr example */#include #include int main (){    char str[] ="This is a simple string";    char * pch;    pch = strstr (str,"simple");    strncpy (pch,"sample",6);    puts (str);    return 0;}

strstr模拟实现:

char* my_strstr(const char* str1, const char* str2){	assert(str1 && str2);	while (*str1)	{		char* cur1 = str1;		char* cur2 = str2;		while (*cur1 == *cur2)		{			cur1++;			cur2++;			if (*cur2 == "/0")			{				return str1;			}		}		str1++;	}	return NULL;}

 

 此情况下可直接判断str2是否是str1的子串,并且返回相应的值。

 

当比较到第三个字符时,两字符不相等,而str1并未结束,后续还需比较,所以cur2需回到起始地址,而cur1会回到第一个相等字符的下一个字符的地址处(即第二个b的地址) 。

 

strtok

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

  • sep参数是个字符串,定义了用作分隔符的字符集合。
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用 /0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改
  • 变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。

strtok使用:

/* strtok example */#include #include int main (){    char str[] ="- This, a sample string.";    char * pch;    printf ("Splitting string /"%s/" into tokens:/n",str);    pch = strtok (str," ,.-");    while (pch != NULL)    {        printf ("%s/n",pch);        pch = strtok (NULL, " ,.-");    }    return 0;}

strerror

char * strerror ( int errnum );

返回错误码,所对应的错误信息。

/* strerror example : error list */#include #include #include //必须包含的头文件int main (){    FILE * pFile;    pFile = fopen ("unexist.ent","r");    if (pFile == NULL)    printf ("Error opening file unexist.ent: %s/n",strerror(errno));    //出现错误时,会将错误码(一种错误对应一个错误码)放进errno中,strerror会将这个错误码对应的内容打印出来    //errno: Last error number    return 0;}

例如: 

#include#include#includeint main(){	printf("%s/n", strerror(0));	printf("%s/n", strerror(1));	printf("%s/n", strerror(2));	printf("%s/n", strerror(3));	return 0;}

对应错误码结果为:

memcpy

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

  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 这个函数在遇到 "/0" 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的。
  • 返回值为目标空间首地址。
     

memcpy使用:

#include#includeint main(){	int arr1[] = { 1,2,3,4,5,6,7,8,9 };	int arr2[10] = { 0 };	int* ret = memcpy(arr2, arr1, 16);	int i = 0;	for (i = 0; i < 10; i++)	{		printf("%d ", ret[i]);	}	return 0;}

memcpy模拟实现:

#includevoid* 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;}

memmove

void * memmove ( void * destination, const void * source, size_t num );

  • 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用memmove函数处理。

memmove使用:

/* memmove example */#include #include int main (){    char str[] = "memmove can be very useful......";    memmove (str+20,str+15,11);    puts (str);    return 0;}

memmove模拟实现:

情况一:不存在内存重叠,可直接将内容移动。 

情况二:存在内存重叠,且目标空间地址大于源空间地址, 如图将1移动到3的位置之后,3就会被覆盖,在移动3时实际移动的是1.

 解决方法:从后往前移动,先移动4,再移动3直到所有字节移动完。

 

情况三:存在内存重叠,且目标空间地址小于源空间地址,如果再从后向前移动也会导致前面的内容被覆盖。

解决方法:从前往后移动,先移动3,再移动4直到所有字节移动完。

 实现代码:

void* my_memmove(void* dest, const void* src, size_t count){	assert(dest && src);	void* ret = dest;	if (dest > src)//情况二	{		void* cur1 = (char*)src + count - 1;//从后往前移动,需先找到两个空间的末尾地址		void* cur2 = (char*)dest + count - 1;//减1是因为加count之后会直接跳到目标空间和源空间的尾地址的下一个字节的地址		while (count--)		{			*(char*)cur2 = *(char*)cur1;//void*内型指针不能直接加减或解引用操作,需强制类型转换			cur2 = (char*)cur2 - 1;//指针移动			cur1 = (char*)cur1 - 1;		}	}	else//情况1一,三	{		void* cur1 = (char*)src;		void* cur2 = (char*)dest;		while (count--)		{			*(char*)cur2 = *(char*)cur1;			cur2 = (char*)cur2 + 1;			cur1 = (char*)cur1 + 1;		}	}	return ret;}

memcmp

int memcmp ( const void * ptr1,
                       const void * ptr2,
                             size_t num );

  • 比较从ptr1和ptr2指针开始的num个字节
  • 返回值如下:
     

 memcmp使用:

/* memcmp example */#include #include int main (){    char buffer1[] = "DWgaOtP12df0";    char buffer2[] = "DWGAOTP12DF0";    int n;    n=memcmp ( buffer1, buffer2, sizeof(buffer1) );    if (n>0) printf (""%s" is greater than "%s"./n",buffer1,buffer2);    else if (n<0) printf (""%s" is less than "%s"./n",buffer1,buffer2);    else printf (""%s" is the same as "%s"./n",buffer1,buffer2);    return 0;}

memcmp模拟实现:

int my_memcmp(const void* str1, const void* str2, int count){	assert(str1 && str2);	while (*(char*)str1 == *(char*)str2)	{		count--;		if (count <= 0)		{			return 0;		}		str1 = (char*)str1 + 1;		str2 = (char*)str2 + 1;	}	return (char*)str1 - (char*)str2;}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/120819.html

相关文章

  • C语言进阶第一问:数据在内存中是如何存储的?(手把手带你深度剖析数据在内卒中的存储,超全解析,码住不

    摘要:在符号位中,表示正,表示负。我们知道对于整型来说,内存中存放的是该数的补码。在计算机系统中,数值一律用补码来表示和存储。表示有效数字,。规定对于位的浮点数,最高的位是 ...

    ghnor 评论0 收藏0
  • C语言进阶】☀️数据类型&amp;&amp;整型在内存中的存储

    目录 ​  一、数据类型介绍 二、类型的意义 三、类型的基本归类 整型家族 浮点数家族 构造类型(自定义类型) 指针类型 空类型 四、整形在内存中的存储 原码、反码、补码 大小端字节序 为什么有大端和小端? 一道经典笔试题  一、数据类型介绍 数据从大的方向分为两类: 内置类型自定义类型内置类型我们前面已经学习过,如下: char            //字符数据类型 short      ...

    Xufc 评论0 收藏0
  • C语言进阶学习】一、数据的存储 (深度剖析数据在内存中的存储)

    摘要:的理解和区别代表有符号,整数在内存中存储的二进制位的最高位为符号位,表示负数,表示正数。那接下来我们来学习数据在所开辟的内存空间时如何存储的。请看下面例子为什么内存中存储的是补码对于整数来说数据存放内存中其实存放的是补码。 ...

    AprilJ 评论0 收藏0
  • 详解c语言整形浮点数在内存中的存储

    摘要:目录数据在计算机的存储方式补码,反码,原码数据在计算机的存储方式补码,反码,原码整形在内存中的存储整形在内存中的存储整形类型整形类型大端字节序和小端字节序大端字节序和小端字节序浮点数在内存的储存浮点数在内 目录 数据在计算机的存储方式(补码,反码,原码) 整形在内存中的存储:    整形...

    不知名网友 评论0 收藏0
  • 玩转指针,手撕c语言——(指针进阶

    摘要:函数的返回值为指针就按照字面意思,指针函数的定义顾名思义,指针函数即返回指针的函数。 目录 前言指针与函数函数的返回值为指针作为函数参数的指针指针函数可以改变变量...

    genedna 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<