常见 字符串库函数 的使用与模拟实现 #strlen #strcpy #strcat #strcmp#strstr #strtok #strncpy #strncat #strncmp

文章目录

一、求字符串长度

strlen

1、使用:

2、模拟实现 strlen 

2.1 方法一:循环+计数器

2.2 方法二:指针 - 指针

2.3 方法三: 递归

二、长度不受限制的字符串函数

strcpy

1、使用

1.1 一定要确保目标空间可变

1.2 strcpy 遇到'\0' 便会停止拷贝,

2、模拟实现 strcpy 

strcat

1、使用:

1、1目标空间要足够大并且可变

2、模拟实现strcat 

3、注意:

         strcmp

1、使用

1.1 字符串的比较应该用 strcmp 

1.2  strcmp 比较的不是字符串的长度,比较的是其内容

2、模拟实现 strcmp 

三、长度受限制的字符串函数

strncpy

1、使用:

strncat

strncmp

四、字符串查找

strstr

strtok

五、错误信息报告

strerror

六、字符函数

字符分类函数

字符转换

tolower

toupper

总结


前言

路漫漫其修远兮,吾将上下而求索。


  • 在C语言之中,提供了字符类型,也有字符串的概念,但是却并没有字符串的类型。没有类型就不方便操作,于是乎就提供了一系列的字符串函数来支持对字符串的操作;

一、求字符串长度

  • strlen

  • 专门用来求字符串长度的函数
  • size_ t  strlen ( const void * str );
  • 由于字符串将'\0' 作为结束的标志,所以 strlen 计算字符串中字符个数的原理是 从传参的地址str 开始向后计算字符个数,直到遇到 '\0' 才会停止计算
  • 注意库函数strlen 的返回类型为size_t ,即为 unsigned int ;如果利用strlen 所得结果相减,其结果的类型也为 unsigned int ;注意unsigned int 无符号位!!

1、使用:

1.1 strlen 遇到 '\0' 才会停下来;如果一个字符串中没有 ’\0‘ ,strlen 便会越界去内存中找 '\0' ,此时 库函数 strlen 的返回值为随机值;

代码运行运行结果如下:

调试--> 内存 --> &ch1

1.2 库函数strlen 的返回类型为 size_t, 即 unsigned int 类型;

代码如下:

代码运行结果如下:

注:

  • 同时如果打印两strlen 之差的结果时,也要留意占位符;
  • %d 是以 signed int 的视角看待内存中的补码;
  • 而%p 则将内存中的补码看作地址,无原码、反码、补码的区分,直接将内存中的补码以十六进制的形式呈现出来
  • %u 则以 unsigned 的视角看待内存中的补码,即所有二进制位全为有效位;
  • %f 则会以浮点数的规则读取内存中的补码;(有关浮点数在内存中的存储形式可以戳此链接:http://t.csdnimg.cn/C4DVS

同理:用strlen 求了各个字符串的长度直接相减来判断其大小会因为其返回类型 unsigned int 而产生bug;代码如下:

代码运行结果如下:

2、模拟实现 strlen 

2.1 方法一:循环+计数器

代码如下:

//循环+计数器

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

size_t my_strlen(const char* str)
{
	assert(str);
	size_t count = 0;
	while (*str != '\0')
	{
		str++;
		count++;
	}
	return count;
}
int main()
{
	char ch[] = "i love china";
	size_t ret= my_strlen(ch);
	printf("%u\n", ret);

	return 0;
}

代码运行结果如下:

2.2 方法二:指针 - 指针

代码如下:

//指针-指针
#include<stdio.h>
#include<assert.h>

size_t my_strlen(const char* str)
{
	assert(str);
	char* tmp = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - tmp;
	//随着数组下标的增长其地址逐渐增大
}
int main()
{
	char ch[] = "i love china";
	size_t ret = my_strlen(ch);
	printf("%u\n", ret);
	return 0;
}

代码运行结果如下:

2.3 方法三: 递归

代码如下:

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

size_t my_strlen(const char* str)
{
	if (*str == '\0')
		return 0;
	else
		return (1 + my_strlen(str + 1));
}
int main()
{
	char ch[] = "i love china";
	size_t ret = my_strlen(ch);
	printf("%u\n", ret);
	return 0;
}

代码运行结果如下:

二、长度不受限制的字符串函数

  • strcpy

  • char * strcpy ( char* strDestination , const char* source);
  • strcpy--> 字符串拷贝
  • strcpy 的工作原理:strcpy 将源字符串的内容(从指针 source指向的位置开始拷贝,遇到'\0' 结束,但是会将'\0' 拷贝到目标空间中)拷贝到从strDestination指向的位置开始的目标空间中;
  • 源字符串必须以 '\0' 结束
  • strcpy 会将源字符串的 '\0' 拷贝到目标空间中;
  • 目标空间要可变并且要足够大,以确保能放下所要拷贝的字符串
  • strcpy 返回的是目标空间的起始地址

1、使用

1.1 一定要确保目标空间可变

常量字符串存储在常量区,而在常量区的数据只可以读取而不可以修改;所以指针变量p所指向的空间不可变,故而此处出现访问错误;

1.2 strcpy 遇到'\0' 便会停止拷贝,

存在以下两种思考:

question 1: 在源字符串中提前遇到 '\0'

strcpy 在源字符串中遇到'\0' ,便会停止拷贝并且还会将遇到的'\0' 拷贝到目标空间中;

调试结果如下:

question 2: 源字符串中没有'\0'

由于strcpy 没有遇到'\0' 便会一直拷贝下去直到遇到 '\0' ;

调试结果如下:

2、模拟实现 strcpy 

原理分析:

strcpy 遇到源字符串中的'\0' 便会停止拷贝,并且将此 '\0' 也会拷贝到目标空间中;strcpy 拷贝是从目标空间的起始地址开始存放;

代码如下:

//模拟实现strcpy 
#include<stdio.h>
#include<string.h>
#include<assert.h>

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* tmp = dest;
	while (*dest++ = *src++)//赋值表达式的返回值为其右操作数的值
	{
		;
	}
	return tmp;
}

int main()
{
	char ch1[20] = "i  love";
	char ch2[] = "chongqing";

	char* tmp= my_strcpy(ch1, ch2);

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

代码运行结果如下:

分析: *dest ++ = *src++; 后置++,先使用再自增;即先 *dest = *src,根据此表达式的返回值判断while 循环要不要继续进行下去; 然后再 desr++ ;src++; 且while 循环的判断条件是此赋值表达式;赋值表达式的返回值是其右操作数,即当*src = '\0' 时,已然完成了 *dest = *src,即将'\0' 拷贝放入了目标空间中,且循环停止,dest 与 src 不用自增;

  • strcat

  • char * strcat (char* strDestination , const char* source);
  • strcat --> 字符串追加,也可理解为拼接;
  • strcat 的工作原理:将 strDestination 指向空间'\0' 的地址作为所拼接字符串的起始位置,strcat 在处理源字符串时,遇到'\0' 结束拷贝,但是会将'\0' 拷贝到目标空间;
  • strcat 包含了 strcpy 拷贝的功能,只不过strcat 会找到目标空间中字符串的结尾'\0'才开始放数据,而strcpy 是直接将源代码放入目标空间中;
  • 源字符串必须以'\0' 结束
  • 目标空间可变并且足够大,以放下所要拼接的字符串内容
  • strcat 返回的是目标空间的起始地址

1、使用:

1、1目标空间要足够大并且可变

2、模拟实现strcat 

原理分析:

1、找到目标空间中字符串的结束位置'\0' ,从此位置开始

2、strcpy 的拷贝原理,在源字符串中遇到'\0' 停止拷贝,但是会将此'\0' 拷贝放入目标空间之中;

代码如下:

//模拟实现 strcat
#include<stdio.h>
#include<string.h>
#include<assert.h>

char* my_strcat (char* dest, const char* src)
{
	assert(dest && src);//因为要进行解引用操作,就要确保此指针不为空指针
	char* tmp = dest;//strcat 返回的是目标空间的起始地址
	//步骤一:找到目标空间中字符串的结束位置'\0'
	while (*dest != '\0')
	{
		dest++;
	}
	//步骤二:strcpy 的拷贝原理
	while (*dest++ = *src++)//赋值表达式的返回值为其右操作数的值
	{
		;
	}
	return tmp;
}

int main()
{
	char ch1[30] = "i  love ";
	char ch2[] = "chongqing";

	char* tmp = my_strcat(ch1, ch2);

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

代码运行结果如下:

3、注意:

尽量避免使用 strcat 而让字符串自己追加(拼接自己)

原因:strcat 的第一个步骤就是找到目标空间中字符串的结束位置'\0' ,并且从此位置开始放置元字符串的字符;第二个步骤为strcpy 原理的拷贝,遇到'\0'停止往后拷贝的操作,但是会将'\0' 拷贝放入目标空间;但让strcat 自己追加自己的时候,目标空间和源字符串都是自己,步骤一中的放置源字符串中的字符改变了该字符串中的'\0' ,即在拷贝时遇不到'\0',便会陷入死循环;

我们在VS编译器实际操作:

也确实陷入了死循环导致写入访问权限冲突--> 越界了;

  • strcmp

  • int strcmp ( const char* str1 ,const char* str2);
  • strcmp --> 比较两字符串的函数
  • strcmp 的工作原理:从指针str1 与指针str2 开始一对一对地比较其字符ASCII码值的大小,如若是相等便 str1++ ; str2++; 然后再比较;(strcmp 不会管你字符串里面放的是什么,它只负责将这两个字符串对应的内存中的值一个字节一个字节地拿出来比较;若是相等便比较下一对,对应位字节中的ASCII码值谁大,哪个字符串就更大)
  • strcmp 的返回值: str1> str2,返回值>0 的数字; str1 = str2 ,返回值= 0; str1 <str2 ,返回值<0 的数字 ;
  • strcmp 比较的不是字符串的长度,而是对应字节位的ASCII码值的大小(比较的是内容);

1、使用

1.1 字符串的比较应该用 strcmp 

ch1 与 ch2 是数组名代表着其首元素的地址,显然数组ch1 与 数组 ch2 创建的时候各自向栈区申请空间来存放数据,故而输出 != ;

1.2  strcmp 比较的不是字符串的长度,比较的是其内容

对应的字节位上,其ASCII码值相同便跳过去比较下一对;'a' 的ASCII码值 小于 'q' 的ASCII码值,故而输出 < ;

2、模拟实现 strcmp 

原理分析:遇到相同ASCII码值的字节位跳过到下一对;如果双方都同时跳过且比到了'\0',说明这两字符串相等;

代码如下:

//模拟实现strcmp 
#include<stdio.h>
#include<assert.h>

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);//由于要对str1,str2进行解引用操作,就要确保其不为空指针
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return ((*str1) - (*str2));

}

int main()
{
	char ch1[30] = "chonga";
	char ch2[] = "chongqing";

	int ret = my_strcmp(ch1, ch2);
		if (ret > 0)
			printf(">\n");
		else if (ret == 0)
			printf("=\n");
		else
			printf("<\n");

	return 0;
}

代码运行结果如下:

三、长度受限制的字符串函数

  • strncpy

  • char * strncpy ( char* strDestination ,const char * strSourse, size_t count);
  • strncpy --> 拷贝一定字节空间数据的 字符串函数
  • strncpy --> 将strSourse指向空间中 count 个字节的数据拷贝放到 strDestination所指向的空间之中;
  • 当count 大于strSource 中字节的数据时,多余的会用'\0'来补 ;
  • strncpy 结束拷贝有两个条件,一是count 个字节的数据拷贝完了;二是,在count 个数据之内遇到了 '\0'
  • strncpy 返回的是目标空间的起始地址;

1、使用:

1.1 常规使用

1.2 当count 大于strSource 中字节的数据时,多余的会拷贝 '\0' ;

即拷贝的长度实际大于所需拷贝的字符串长度时,会用'\0' 来补;

  • strncat

  • char * strncat ( char* strDestination, const char* strSource,size_t count);
  • strncat -->追加(拼接)一定字节数的 字符串函数
  • strncat 的工作原理:找到strDestination 所指向目标空间中字符串的结尾'\0',再将strSource所指向空间中 count 个字节的数据拷贝放入目标空间中;strncat 返回的是目标空间的地址;
  • strncat 在操作完之后还会在目标空间字符串的结尾添一个 '\0' 
  • strncat 最多是将源字符串中全部字符追加(拼接)标空间中字符的末尾,并不会因为count 的值大于源字符串中字符的个数而多追加;

1、使用

1.1 常规

1.2 strncat 不会因为 count 的参数为 7 ,大于源字符串的长度而越界多拷贝;

同样的,strncat 结束操作有两个标志,一是 遇到源字符串中的'\0' ;二是 追加完 源字符串中 count 个字节的数据 

  • strncmp

  • char * strncmp ( const char * str1, const char* str2, size_t count);
  • strncmp --> 比较一定个数的字符串比较函数
  • strncmp 的工作原理: 比较str1 和 str2 所指向空间中 count 个字节空间的字符;

四、字符串查找

  • strstr

  • char * strstr ( const char* str1, const char* str2);
  • strstr --> 查找一个子字符串的函数(在一个字符串中找另外一个字符串的函数)
  • strstr 的工作原理: 在 str1 所指向的字符串中查找str2 所指向的字符串;如果找到了,然后就返回在str1 中找到 str2 的起始位置;如果没有找到就返回空指针 NULL;

1、使用

2、模拟实现strstr 

原理分析:在str1 中的每个字符都有可能作为字符串 str2 的开头,需要一个一个地比对,当发现相同时,指向str1 、str2 的指针便都往后走,并且一一比对,如果从str2 开头到str2 的'\0' 之前的字符都相同,那么此时str1开头的指针便为strstr 的返回值;如果一一比对在str2 到达 '\0' 之前发现了不同,说明str1 开头的指针就不对,需要从此开头指针的下一个位置开始,指向str2 的指针回归,重新开始比对;

上述语段图解如下:

 

代码如下:

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

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);//因为对此指针进行解引用操作,所以要避免它们为空指针
	const char* s1 = str1;//工具指针1
	const char* s2 = str2;//工具指针2
	const char* p = str1;//在str1中定位str2开头的字符
	//当 *s1 == *s2 时,便 s1++; s2++; 当 *s2 =='\0' ;时,代表此时p 指向的就是strstr 的返回地址 
	//当*s1 != *s2 时; s2 = str2; (刷新) p++ ; s1 = p;
	while (*p)//当p指向 '\0' 代表着在str1 中找完了
	{
		s1 = p;
		s2 = str2;
		while ((*s1 == *s2) && (*s1 !='\0')&&(*s2 !='\0'))//确保在找的过程中str1 ,str2 都没有结束
		{
			s1++;
			s2++;
			if (*s2 == '\0')
				return (char*)p;

		}
		//不等--> *s1 != *s2 ; s2 刷新 ; p++;跳出while 循环就代表着不相等
		p++;
	}
	//在str1 中找完了都没有返回代表着,str2 在str1 中找不到
	return NULL;
	
}
int main()
{
	char ch1[30] = "i love china";
	char ch2[] = "china";

	char* tmp = my_strstr(ch1, ch2);
	if (tmp == NULL)
		printf("没有找到\n");
	else
		printf("找到了\n");

	return 0;
}

代码运行结果如下:

  • strtok

  • char * strtok ( char * str , const char * sep);
  • strtok --> 一个切割字符串的函数
  • strtok 的工作原理: 参数str 是被分割的字符串,其中包含了0个或者多个由sep 字符串中一个或者多个分隔符分割的标记;参数sep是字符串,为定义用作分割符的字符集合; 即在字符串str 中可以找到 sep ,在sep 之间的字符段将会被分割;
  • 库函数strtok 在str 中找到标记(标记意为所被分隔符分割得到的字符段),会将得到的这个字符段用 '\0' 结尾(将结尾的分隔符改为'\0'),并返回该标记(字符段)的地址;
  • strtok 会改变被操作的字符串str ,如果不想改变源数据可以将str 拷贝一份(可变的数组),然后再让strtok 对拷贝的这一份字符串进行操作; 
  • strtok 的第一个参数不为NULL,即库函数strtok 将在str 找到第一个标记(字符段),然后库函数strtok 会保存这个标记(字符段)在str 中的地址;
  • strtok 的第一个参数为 NULL时,库函数strtok从上一次操作完保存的str 中标记(字符段)的地址开始,查找下一个标记(字符段);

strtok 的返回值:

  • 在str 中找到标记,会将该标记变为 '\0' ,并返回得到字符段首字符的地址
  • 当查找的字符串str 中不存在标记(字符段),则返回NULL;

1、使用

代码如下:

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

int main()
{
	char ch[] = "qin.shaoyou.3296171307@qq.com";
	char sep[] = "@.";
	//拷贝
	char str[30] = { 0 };
	strcpy(str, ch);
	
	char* tmp = strtok(str, sep);//tmp中要么是所得字符段的地址;要么是空指针
	if (tmp != NULL)//如果是空指针便不打印
		printf("%s\n", tmp);

	tmp = strtok(NULL, sep);
	if (tmp != NULL)
		printf("%s\n", tmp);

	tmp = strtok(NULL, sep);
	if (tmp != NULL)
		printf("%s\n", tmp);

	tmp = strtok(NULL, sep);
	if (tmp != NULL)
		printf("%s\n", tmp);

	tmp = strtok(NULL, sep);
	if (tmp != NULL)
		printf("%s\n", tmp);

	return 0;
}

代码运行结果如下:

在以上代码中,我们在找第一个字符段的时候,库函数strtok 的第一个参数为所分割字符串的地址;动一下你聪明的小脑袋也显然清楚地看到了,我们之后调用了四次 ,库函数strtok 的第一个参数为NULL,这是因为strtok 具有记忆功能;在第一次调用结束之后,会保存分隔符的地址,下次在分割字符段的时候,便可以从上一次所存地址的开始...而在每次调用完strtok 之后,会将分隔符变为了'\0',库函数strtok 又返回该字符段首字符的地址,占位符%s 通过该地址打印到'\0' 结束,也就实现了上述字符段的呈现;

调试观察str 如下图:

但是上述的写法可以实现,但是必须根据有多少个字符段来写代码,显得十分死板;我们来分析一下:在第一次调用strtok时,第一个参数为所要分割字符串的地址-->判断所得到的地址是否为空-->  将strtok 返回值进行打印;后面 再调用strtok 其第一个参数为NULL--> 判断所得到的地址是否为空--> 将strtok 返回值进行打印……其中调用库函数strtok 的条件是:strtok 返回的地址为字符段的首字符地址--> 字符串str 还需要分割 ;而当字符串str 已经被分割完了,此时strtok 的返回值为NULL,因为没有找到下一个标记;--> 当 strtok 的返回值为NULL的时候,不再继续调用strtok ;

看了以上的分析,是不是感觉这种结构:先执行语句1--> 判断-->打印--> 判断--> 执行语句2--> 打印 --> 判断--> 执行语句2--> ……当判断的条件不成立时,便结束; 

聪明的你是不是觉着这种感觉特别像 for 循环呀?

代码如下:

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

int main()
{
	char ch[] = "qin.shaoyou.3296171307@qq.com";
	char sep[] = "@.";
	//拷贝
	char str[30] = { 0 };
	strcpy(str, ch);
	
	char* tmp = NULL;
	for (tmp = strtok(str, sep); tmp != NULL; tmp = strtok(NULL, sep))
	{
		printf("%s\n", tmp);
	}

	return 0;
}

运行结果如下:

太厉害啦~

五、错误信息报告

  • strerror

  • char* strerror ( int errnum);
  • strerror --> 返回错误码所对应的错误信息;
  • 什么是错误码? C语言在执行失败的时候会设置错误码,这个错误码可能会是1、2、3、4……,每个一数字代表着一个错误信息;
  • 显然,当我们知道错误码也不会知道它究竟代表着什么意思,于是乎就有了库函数strerror 来将错误码翻译为我们可读的错误信息;
  • strerror 的运行流程:将错误码传给strerror,strerror便会返回错误码对应的错误信息字符串的首字符地址;所以strerror 的返回类型为char *;

1、使用

在使用strerror 之前,我们先来了解一下C语言有关存放错误码的变量;

在不同系统的C语言标准库的实现中都规定了一些错误码,一般是放在 errno.h 这个头文件之中说明的,C语言程序在启动的时候就会使用一个全局变量errno 来记录程序当前的错误码,只不过程序在启动的时候errno为0,以表示没有错误,而当我们在调用标准库中的函数时发生了某些错误,就会将对应的错误码存放在errno中,而一个错误码都是有对应的错误信息,光凭借一个错误码难以知道对应的是什么错误信息,所以此处便会利用库函数strerror 来打印错误信息;

在打印错误信息的时候,将变量errno 传给strerror 即可;如若有多个错误,该错误码是会变的(覆盖);

实现代码如下:

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

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if(pf == NULL)
	{
		printf("%s\n", strerror(errno));//使用变量errno记得引头文件<errno.h>
		return 1;
	}
    else
    {
         //代码
     }

    fclose(pf);
    pf = NULL;
	
	return 0;
}

代码运行结果如下:

库函数:fopen --> 打开一个文件

此处我们还可以了解到另外一个函数 perro 

  • perro
  • void perror( const char *string );
  • perro 集合了 strerro接收错误码 + printf 打印错误信息的功能; 

代码如下:

有意思的是,perro 不仅会将错误信息打印出来,还会在你传递字符串的后面添上一个冒号和空格;

六、字符函数

头文件 <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任何可打印的字符,包括图形字符和空白字符

字符转换

  • tolower

  • int tolower( int c ) ; 
  • 将大写字母转换为小写字母

  • toupper

  • int toupper ( int c);
  • 将小写字母转换为大写字母

总结

小手点点目录~

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值