【C语言】字符函数和字符串函数(一)—>库函数的介绍与模拟实现

news2024/11/23 22:29:20

目录

前言:

一、函数介绍:

(一)求字符串长度

(1)strlen

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

(2)strcpy

(3)strcat

(4)strcmp

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

(5)strncpy

(6)strncat

(7)strncmp

(四)字符串查找

(8)strstr

(9)strtok

(五)错误信息报告

(10)strerror

(六)字符操作

(11)字符分类函数

(12)字符转换

(七)内存操作函数

(13)memcpy

(14)memmove

(15)memcmp

(16)memset

二、模拟实现

(一)strlen

(1)计数器

(2)递归

(3)指针-指针

(二)strcpy

(三)strcat

(四)strcmp

(五)strstr

(六)memcpy

(七)memmove


前言:

本篇重点围绕字符和字符串的部分库函数展开讲解,并进行模拟实现,下一篇内容会运用本篇所学进行左旋右旋字符串的判断及实现(三步翻转法)。

一、函数介绍:

(一)求字符串长度

(1)strlen

size_t  strlen( const char* str );

该函数的作用是统计字符串中'\0'之前的字符个数(不含'\0')。

  • 参数指向的字符串必须以'\0'结束
  • 函数的返回值为无符号整型size_t。

利用strlen函数进行字符串长度比较时,要注意得到的结果是无符号整型,所以不能用相减为正负数为依据进行比较。

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

长度不受限制的意思是指本分类下的库函数没有设置长度限制,他们都是根据'\0'的位置来自行判断是否停止的,所以'\0'对于此类目下的库函数非常重要,所以你可以发现此类目下的库函数都要求源字符串必须以'\0'结束。

(2)strcpy

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

该函数会将source指针指向的字符串拷贝到destination指针指向的字符串中,注意包含'\0'。 

  • 源字符串必须以'\0'结束。
  • 目标空间必须有足够大,确保可以容纳源字符串内容。
  • 目标空间必须可修改。
  • 返回值为目标空间的首元素地址(初始状态下destination的地址)

(3)strcat

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

该函数会将source指针指向的字符串追加到destination指针指向的字符串后面,注意包含'\0'。

  •  源字符串必须以'\0'结束。
  • 目标空间必须有足够大,确保可以容纳源字符串内容。
  • 目标空间必须可修改。

问:该函数是否可以运用到字符串自己给自己追加的情况?

当destination指针指向了'\0'时,就做好了追加的准备,下一步就是将'\0'替换为source指向的元素,并依次向后进行,直到检测到source指向'\0'时结束,但当两指针指向同一块区域时,明显source要寻找的'\0'已经被destination修改了,就导致越界出现错误。 

问:那么如何利用库函数使字符串自己给自己追加呢?

我们可以利用另一个库函数strncat就可以解决这一问题,因为strncat是长度受限制的字符串函数,他停止的依据取决于参数num,而不是'\0'的位置

(4)strcmp

int  strcmp( const char* str1, const char* str2 );

 该函数会将两个字符串进行比较,比较的依据是两者第一次出现不同字符的ASCII码值,如果前者大于后者返回一个正数,反之返回一个负数,全部相等返回0。

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

与长度不受限制的字符串函数的区别在于,该种函数需要设置需要操作的字符个数,也就是参数num,执行结束的依据取决于num,而不取决于'\0'的位置,长度受限制的字符串函数相较于长度不受限制的字符串函数相对更安全

(5)strncpy

char* strncpy( char* destination, const char* source, size_t num );

 该函数的作用是拷贝num个字符从源字符串到目标空间。

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

(6)strncat

char* strncat( char* destination, const char* source, size_t num );

 该函数将源字符串num个字符追加到目标字符串后,并加入结束标志'\0'。

  • strncat可以用于字符串自己给自己追加,具体解释见strcat。

(7)strncmp

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

该函数的作用是比较到字符不一样或者一个字符串结束或者num个字符全部比较完,前者大于后者返回正数,反之返回负数,全部相等返回0。 

(四)字符串查找

(8)strstr

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

该函数的作用是返回str2指向的字符串在str1指向的字符串中第一次出现的位置。 

  • 如果str2指向的字符串不是str1指向的字符串的一部分,那么返回NULL。

(9)strtok

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

该函数用作分割字符串,简单的说就是根据分隔符的字符集合sep分割该字符串。

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

下面给出一段代码助理解:

假设我们想要得到一个邮箱mrfanf@csdn.tech中的去掉符号后的字符串,即mrfanf、csdn、tech,我们就可以利用strtok函数,首先声明分隔符集合“@.”。

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

int main()
{
	char* p = "mrfanf@csdn.tech";
	const char* sep = "@.";
	char arr[30];
	char* str = NULL;
	strcpy(arr, p);//将数据拷贝一份,处理arr数组的内容
	for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
	{
		printf("%s\n", str);
	}
}

(五)错误信息报告

(10)strerror

char* strerror( int errnum );

该函数的作用是返回错误码errnum所对应的错误信息字符串的地址。

  • 库函数在执行时如果发生错误,会将一个错误码存放在errno这个变量中,errno是C语言提供的一个全局变量。
  • 使用该库函数需要额外引用头文件errno.h。

 应用:

#include<errno.h>

int main()
{
	//C语言中可以操作文件
	//操作文件的步骤
	//1. 打开文件
	//2. 读/写
	//3. 关闭文件

	FILE* pf = fopen("data.txt", "r");//在当前路径下,打开data.txt文件并读取
	if (pf == NULL)//打开或读取失败
	{
		printf("fopen: %s\n", strerror(errno));
		perror("fopen");
		//fopen: xxxxxx
		return 1;
	}
	//读文件
	//...

	//关闭文件
	fclose(pf);

	return 0;
}

perror("fopen");

printf("fopen: %s",strerror(errno)); 

以上代码效果相同(%s前有一个空格字符),perror的作用就是直接打印错误信息,""内为自定义信息,因为错误是默认已经存储的,不需要手动输入,perror自动接收。

(六)字符操作

(11)字符分类函数

函数如果他的参数符合下列条件就返回真
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任何可打印字符,包括图形字符和空白字符

不可打印字符:ASCII码值为0~31的字符都是不可打印字符。

#include <ctype.h>
int main()
{
	printf("%d\n", isupper('a'));
	printf("%d\n", isdigit('2'));
	return 0;
}

 注:有关字符分类函数和字符转换函数都需要引用头文件ctype.h。

(12)字符转换

int tolower ( int c );转换为小写字符

int toupper ( int c );转换为大写字符

 应用:

#include <ctype.h>
int main()
{
	char arr[20] = { 0 };
	gets(arr);//遇到空格继续读

	char* p = arr;
	while (*p)
	{
		if (isupper(*p))// *p>='A' && *p<='Z'
		{
			*p = tolower(*p);//*p = *p+32;
		}
		p++;
	}
	printf("%s\n", arr);
	return 0;
}

(七)内存操作函数

(13)memcpy

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

该函数从source的位置开始向后复制num个字节的数据到destination的内存位置。

  • 该函数在遇到'\0'的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的,即memcpy是用来处理不重叠的内存拷贝的。

请参考模拟实现部分方便理解。 

(14)memmove

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

该函数的功能与memcpy相近,差别在memmove函数处理的源内存块和目标内存块是可以重叠的。

  • 如果源空间和目标空间出现重叠,就得使用memmove函数处理,当然不重叠拷贝也可以使用memmove,在未来应用时,不管两空间重不重叠都是用memmove即可。 

那么为什么memmove可以处理重叠内存的数据呢,请以下图参考模拟实现代码理解:

(15)memcmp

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

该函数作用为比较从ptr1和ptr2指针开始的num个字节,返回值同strncmp。 

(16)memset

void* memset( void* ptr, int value, size_t num);

该函数的作用为将ptr指针开始的num个字节的数据设置为value。 

二、模拟实现

(一)strlen

(1)计数器

//计数器方式
int my_strlen(const char* str)
{
	int count = 0;
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}

(2)递归

//不能创建临时变量计数器
int my_strlen(const char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}

(3)指针-指针

//指针-指针的方式
int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;
}

(二)strcpy

#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);//断言两个指针都为非空指针

	while (*dest++ = *src++)
	{
		;
	}

	return ret;
}

(三)strcat

#include<assert.h>
char* my_strcat(char*dest, const char *src)
{
	assert(dest && src);//断言两个指针都为非空指针

	char* ret = dest;
	//1. 找目标空间中的\0
	while (*dest)
	{
		dest++;
	}
    //2. 赋值
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

(四)strcmp

#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
    //断言两个指针为非空指针
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return (*str1 - *str2);
}

(五)strstr

char* my_strstr(char *str1, char* str2)
{
	char* cp = str1;
	char* s1 = cp;
	char* s2 = str2;

	if (*str2 == '\0')
		return str1;

	while (*cp)
	{
		//开始匹配
		s1 = cp;
		s2 = str2;
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return cp;
		cp++;
	}
	return NULL;
}

(六)memcpy

#include<assert.h>
void* memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dst && src);//断言两个指针为非空指针

	while (num--) 
	{
		*(char*)dest = *(char*)src;//强制转化为char*为一个字节方便逐个赋值
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

注意:

由于dest与src为void*类型,所以不能直接++或--,可以使用

dest = (char*)dest + 1;
src = (char*)src + 1;

而不是dest++;   src++

(七)memmove

#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);//断言两个指针为非空指针

	if (dest < src)
	{
		//前->后
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		//后->前
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

本篇内容就到这里,下一篇文章仍然是对字符串的讨论,我会引入旋转字符串的两种巧妙方法,关注博主不迷路🔥🔥🔥

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/745312.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是HTML5?HTML5的含义、元素和好处

HTML5是超文本标记语言(HTML)的第五版&#xff0c;网络浏览器使用它来可视化代码。它在网站功能、网页内容开发等方面有一些改进。 HTML的发展 在万维网的早期&#xff0c;主要的网络浏览器创造者&#xff08;例如微软Internet Explorer和Mosaic Netscape&#xff09;开发了特…

std::thread和std::mutex

std::thread std::thread用来创建一个线程&#xff1a; #include <thread>void threadFun(int temp) {int i 0; }int main() {std::thread t1(threadFun, 100);t1.join();//t1.detach();return 0; }创建了一个名为t1的线程&#xff0c;调用join方法阻塞等待线程退出&a…

【leetcode】61. 旋转链表 (python)

题目链接&#xff1a;61.旋转链表 写法一&#xff1a;暴力 class Solution(object):def rotateRight(self, head, k):""":type head: ListNode:type k: int:rtype: ListNode"""# 暴力&#xff1a;每次移动一个&#xff0c;时间复杂度 O(N^2)&am…

详解汽车电磁悬架(上)

摘要&#xff1a; 从汽车发明之日起&#xff0c;人们对于汽车平稳行驶的追求步伐就不曾停止过。充气轮胎的发明让车辆能够更加平稳舒适的行驶&#xff0c;承载式车身以及加厚的底盘则进一步提高了汽车的舒适性。随着科技的发展与生活水平的提高&#xff0c;人们对汽车的舒适性…

【C】字符串函数和内存函数的介绍

库函数&#xff08;这些函数都在头文件string.h中&#xff09; 字符串函数求字符串长度strlen 长度不受限的字符串函数strcpystrcmpstrcat 长度受限的字符串函数strncpystrncmpstrncat 字符串查找strstrstrtok 错误信息报告strerror 字符操作字符分类函数字符转换函数 内存函数…

测试必备的15个docker命令,你都掌握了吗

1、Docker容器信息 ##查看docker容器版本docker version##查看docker容器信息docker info##查看docker容器帮助docker --help 2、镜像操作 提示&#xff1a;对于镜像的操作可使用镜像名、镜像长ID和短ID。 2.1、镜像查看 ##列出本地imagesdocker images##含中间映像层docke…

iview-admin使用小结

首先在使用一个框架之前一定要完整的看一下相关文档&#xff0c;因为框架中会封装常用的功能&#xff0c;也会更加符合大众要求。在ui设计图上&#xff0c;可能实现某个功能设计图中给出的交互并不是很好&#xff0c;而在框架中有更好的组件可以实现&#xff0c;但因为没有看文…

物联网技术、测试要点和测试标准

目录 物联网定义 物联网中最常用的技术 物联网测试概述 测试人员在物联网中面临的挑战 测试物联网系统时使用了各种工具 &#x1f381;更多干货 完整版文档下载方式&#xff1a; 物联网定义 物联网&#xff1a;利用嵌入式电子设备、微芯片等连接车辆、家电、医疗设备&am…

广州华锐互动 | 鸡养殖VR教育实训系统

鸡养殖VR教育实训系统由广州华锐互动开发&#xff0c;是一种基于虚拟现实技术的培训工具&#xff0c;旨在为鸡养殖行业的从业人员提供更加直观、生动的学习体验。该系统通过虚拟现实技术&#xff0c;将鸡养殖过程中的各种场景、操作和技能呈现在学员面前&#xff0c;让学员可以…

关于 Linux 的常用命令

目录 前言什么是 Linux获得 Linux 环境为什么使用命令控制关于 XShell 下的复制粘贴 Linux 常用命令ls (罗列文件)cd (切换目录)pwd (显示当前路径)touch (创建一个空文件)cat (读取文件内容)man (查看用户手册)echo (回显)vim (文件编辑器)mkdir (创建目录)rm (删除目录/文件)…

数仓常见问题以及解决方案yyds

文章目录 01 数据仓库现状业务系统侧【上游】数仓内部数据应用测【下游】 02 如何解决问题业务系统侧【上游】数仓内部 03 体系化架构设计一致性维度一致性事实数据应用侧【下游】 建设可视化的取数平台 大家好&#xff0c;我是脚丫先生 (o^^o) 看到几篇不错的文章&#xff0c…

App Inventor 2 从.aia项目文件中提取.aix拓展包

从.aia项目文件中提取.aix拓展包的步骤 如果看到一个项目中&#xff0c;用到了拓展&#xff0c;但拓展的帮助中没有原链接&#xff0c;这时就可以考虑从项目文件中进行提取&#xff1a; 导出项目.aia文件&#xff1a; 在电脑的“下载”目录查看.aia项目文件&#xff1a; 将.a…

写在2023年年中

写在2023年年中 收拾旧山河再出发 很久没有写规划了&#xff0c;博客也很少写&#xff0c;着实是太懒了。想想自己年纪也不小了&#xff0c;加上程序员的环境越来越不好&#xff0c;总得想想自己的出路不是。 目标 提升系统设计能力保持算法刷题能力(一般面试需要)知识输出能…

RabbitMQ工作模式代码示例及用SpringBoot整合RabbitMQ

一&#xff0c;RabbitMQ的工作模式 RabbitMQ 的工作模式是指 RabbitMQ 中不同的消息传递方式&#xff0c;包括简单模式、工作队列模式、发布订阅模式、路由模式和主题模式 。这些工作模式适用于不同的应用场景。详细的文档可参照RabbitMQ的官网&#xff1a;RabbitMQ: easy to u…

基于JavaSwing+MySQL的员工工资管理系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88042468?spm1001.2014.3001.5503 JDK1.8 MySQL5.7

虚拟与现实的交融:VR与数字孪生的融合之道

VR即虚拟现实&#xff08;Virtual Reality&#xff09;和数字孪生&#xff08;Digital Twin&#xff09;是当今科技领域的两大热门概念&#xff0c;VR以其沉浸式的体验和逼真的虚拟环境&#xff0c;让用户身临其境&#xff0c;开启了全新的交互方式和感官体验&#xff1b;而数字…

副业做什么比较靠谱,不想只靠一个收入

科思创业汇 大家好&#xff0c;这里是科思创业汇&#xff0c;一个轻资产创业孵化平台。赚钱的方式有很多种&#xff0c;我希望在科思创业汇能够给你带来最快乐的那一种&#xff01; 如今做一些副业能不能有一点安全感&#xff0c;分享一些可靠的副业&#xff0c;坚持下去&…

Abandoning the Bayer-Filter to See in the Dark 论文阅读笔记

这是CVPR2022的一篇暗图增强的文章&#xff0c;TCL AI Lab与福州大学&#xff0c;韩国延世大学&#xff0c;安徽大学的合作论文网络以黑暗环境下拍摄的color raw为输入&#xff0c;用一个de-bayer-filter module恢复无拜尔滤波器的raw data&#xff08;文章认为拜尔滤波器使得光…

leetcode 987. 二叉树的垂序遍历(java)

二叉树的垂序遍历 leetcode 987. 二叉树的垂序遍历题目描述DFS 优先队列&#xff08;堆&#xff09;代码演示 二叉树专题 leetcode 987. 二叉树的垂序遍历 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/vertical-orde…