C语言中字符串库函数

news2024/12/29 7:35:23

目录

  • 1.求字符串长度
    • strlen
      • 模拟实现strlen
  • 2.长度不受限制的字符串函数
    • strcpy
      • 模拟实现strcpy
    • strcat
      • 模拟实现strcat
    • strcmp
      • 模拟实现strcmp
  • 3.长度受限制的字符串函数介绍
    • strncpy
      • 模拟实现strncpy
    • strncat
      • 模拟实现strncat
    • strncmp
      • 模拟实现strncmp
  • 4.字符串查找
    • strstr
      • 模拟实现strstr
    • strtok
  • 5.错误信息报告
    • strerror

C语言中,本身没有字符串类型的,字符串通常以字符数组和常量字符串的形式出现。

而有一些库函数可以对字符串进行操作,使我们对字符串的处理可以简单许多,但是注意的是:这些库函数不可以修改常量字符串


1.求字符串长度

strlen

size_t strlen ( const char * str );
  • strlen函数计算的是字符串中'\0'前面出现的字符个数,不包含'\0'
  • 参数指向的字符串一定要以'\0'结尾,否则会计算出随机值
  • strlen是求字符串长度的,求出的长度是不可能为负数,所以返回size_t类型的值,size_t其实就是无符号的整形unsigned int

由于strlen返回无符号整形,所以这里是一个易错点,以接下来的代码为例


int main()
{
	char str1[] = "abcdefg";
	char str2[] = "abcdefghijk";
	
	if (strlen(str1) - strlen(str2) < 0)
		printf("str1长度小于str2长度");
	else
		printf("str1长度大于str2长度");

	return 0;
}

str1长度为7,str2长度为11,7-11 = -3,但是这里的7和11是无符号整形,他们俩相减会得到一个非常大的数,而不是-3,所以这个程序会输出str1长度大于str2长度


模拟实现strlen

这里有三种方法进行模拟实现

第一种:常规方法:

int my_strlen1(const char* str)
{
	assert(str != NULL);
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

第二种:递归:

int my_strlen2(const char* str)
{
	assert(str != NULL);
	if (*str != '\0')
	{
		return 1 + my_strlen2(str+1);
	}
	else
	{
		return 0;
	}
}

第三种:指针相减:

int my_strlen3(const char* str)
{
	assert(str != NULL);
	const char* start = str;
	while (*str != '/0')
	{
		str++;
	}
	return str - start;
}

两个指向同一块空间的两个指针,两个指针相减得到这两个指针间的元素的个数


2.长度不受限制的字符串函数

strcpy

char* strcpy(char * destination, const char * source );
  • 这个函数的作用是将source中的字符串拷贝到空间destination
  • destination是目标空间,函数将复制的字符串放到这个目标空间中
  • source是源字符串,函数会复制源字符串,因为只是对源字符串进行复制,并不会改变它,所以可以将源字符串写成const
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。
  • 源字符串必须以 ‘\0’ 结束,因为源字符串读到\0就停止拷贝。
  • 函数会将源字符串中的 ‘\0’ 拷贝到目标空间。

strcpy的使用:

int main()
{
	char arr1[20] = {0};
	char arr2[] = "abcdef";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

如果在源字符串中提前放一个\0,那么函数只会拷贝到\0

int main()
{
	char arr1[20] ="xxxxxxx";
	char arr2[] = "ab\0cdef";
	strcpy(arr1, arr2);
	return 0;
}

这里可以看到,在arr2ab后面放了一个\0,则在函数拷贝时只会拷贝ab\0

我们可以调试函数看一下:拷贝前的arr1
在这里插入图片描述
拷贝后的arr1

可以看到,只拷贝了ab\0


模拟实现strcpy

最常规写法:

void my_strcpy(char* destination, const char* source)
{

	assert(destination != NULL && source != NULL);
	while (*source!='\0')
	{
		*destination =  *source;
		destination++;
		source++;
	}
	*destination = *source;//拷贝\0
}

这么要注意的一点就是,strcpy拷贝过程中会源字符串中最后的\0,但是如果以while (*source!='\0')为循环条件的话,最后的\0就不会被拷贝
所以要在循环外部额外再拷贝一次*destination = *source;

简化一点:

void my_strcpy(char* destination, char* source)
{
	while (*src != '\0')
	{
		*destination++ = *source++;
	}
	*destination = *source;//拷贝\0
}

再简化:

前面的两种写法都需要在循环外部额外再对\0进行拷贝,而下面的写法直接将\0的拷贝也放到了循环中进行

void my_strcpy(char* destination,const char* source)
{
	assert(destination != NULL && source != NULL);
	while (*destination++ = *source++)
	{
		;
	}
}

strcat

char * strcat ( char * destination, const char * source );
  • 这个函数的作用是将source中的字符串追加到destination的后面
  • 目标空间要有\0结尾,因为要直到从哪开始追加
  • 源字符串要有\0结尾,因为要知道何时结束追加
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。

strcat的使用:

int main()
{
	char arr1[20] = "hello";
	char arr2[] = " world";
	strcat(arr1, " world");
	printf("%s", arr1);
	return 0;

}

当一个字符串,自己对自己追加的时候会有问题:
以字符串abcdef追加自己为例

在这里插入图片描述
从\0开始追加,a追加到f,本来f后面是\0,到了\0就停止追加,但是前面追加时已经就将\0覆盖了,所以不会停止
在这里插入图片描述

所以当一个字符串自己追加自己,会发生死循环


模拟实现strcat

void my_strcat(char* des, char* src)
{
	assert(des && src);
	while (*des != '\0')
	{
		des++;
	}
	while (*des++ = *src++)
	{
		;
	}
}

strcmp

int strcmp ( const char * str1, const char * str2 );
  • 标准规定(在非VS环境中):

    • 第一个字符串大于第二个字符串,则返回大于0的数字
    • 第一个字符串等于第二个字符串,则返回0
    • 第一个字符串小于第二个字符串,则返回小于0的数字
  • 而在VS环境中:

    • 第一个字符串大于第二个字符串,则返回1
    • 第一个字符串等于第二个字符串,则返回0
    • 第一个字符串小于第二个字符串,则返回-1

对于两个字符串的大小,比较的并不是字符串的长度,而是一个一个比较组成字符串的字符,字符对应ASCII码值大的字符大。例如:'z'大于'a'


模拟实现strcmp

int my_strcmp(char* str1,char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	//VS环境下:
	if (*str1 > *str2)
		return 1;
	else
		return -1;

	//非VS环境下:
	//return *str1-*str2;
}

3.长度受限制的字符串函数介绍

上面介绍的都是没有长度限制字符函数,它们都是进行到\0就停止,而接下来的三个函数有长度限制

strncpy

char * strncpy ( char * destination, const char * source, size_t num );
  • strncpystrcpy的区别就是:strcpy会将源字符串一直拷贝到\0,而strncpy会拷贝前num个字符
  • strncpy是不会在拷贝后自动加\0
//验证strncpy是不会在拷贝后自动加'\0'的
int main()
{
	char arr1[20] = "xxxxxxx";
	char arr2[] = "abcdef";
	strncpy(arr1, arr2,2);
	return 0;
}

arr2前2个字符拷贝到arr1中,在监视中看到:
在这里插入图片描述
所以可知:strncpy不会自动添加\0

  • num比源字符串要长,多出来的部分用\0来补
//验证num比源字符串要长,多出来的部分用\0来补
int main()
{
	char arr1[20] = "xxxxxxxxxxxxx";
	char arr2[] = "abc";
	strncpy(arr1, arr2,6);
	return 0;
}

在这里插入图片描述


模拟实现strncpy

void my_strncpy(char* str1, char* str2, int n)
{
	assert(str1 && str2);
	if (n < 0) return;
	while (n>0)
	{
		if (*str2 == '\0')
		{
			*str1 = '\0';
			str1++;
			n--;
		}
		else
		{
			*str1 = *str2;
			str1++;
			str2++;
			n--;
		}
	}
}

这里模拟实现时,要注意当num大于源字符串长度时需要补\0这点,其余地方很简单


strncat

char * strncat ( char * destination, const char * source, size_t num );
  • source字符串中的前num个字符追加到destination后面
  • 追加后,函数会在追加后的destination后面自动加\0
  • 如果num大于source的大小,则多余的部分补\0

模拟实现strncat

void my_strncat(char* str1, char* str2, int n)
{
	assert(str1 && str2);
	if (n < 0) return;
	while (*str1 != '\0')
	{
		str1++;
	}
	while (n > 0)
	{
		if (*str2 == '\0')
		{
			*str1 = '\0';
			str1++;
			n--;
		}
		else
		{
			*str1 = *str2;
			str1++;
			str2++;
			n--;
		}
		
	}
	*str1 = '\0';
}

strncmp

int strncmp ( const char * str1, const char * str2, size_t num );
  • 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
  • 返回值的情况与strcmp相同

模拟实现strncmp

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

4.字符串查找

strstr

char * strstr ( const char *str1, const char * str2);
  • str1字符串中查找str2字符串
  • 返回str2str1中出现的一个位置的地址

模拟实现strstr

void* my_strstr(char* str1, char* str2)
{
	assert(str1 && str2);
	if (*str2 == '0')
	{
		return (char*)str1;
	}
	const char* s1 = str1;
	const char* s2 = str2;
	const char* cp = str1;
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '0')
		{
			return (char*)cp;
		}
		cp++;
	}
	return NULL;
}

strtok

char * strtok ( char * str, const char * sep );
  • sep参数是个字符串,定义了用作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
int main()
{
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	strtok(arr1,arr2);
}

有一个字符串abc#defa|gh,可以看出它被字符#|分隔开,strtok的作用是取出被字符#|分隔开的每块字符串

  • strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
int main()
{
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	char arr3[20] = { 0 };
	strcpy(arr3, arr1);//防止strtok直接修改被操作的字符串,所以需要拷贝一份
	char* ret = strtok(arr3, arr2);
	printf("%s", ret);
	return 0;
}

取出第一块被分隔的字符串abc并且将#改为\0,此时的arr3abc\0defa|ghretabc的地址,输出ret为:
在这里插入图片描述

  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。
int main()
{
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	char arr3[20] = { 0 };
	strcpy(arr3, arr1);//防止strtok直接修改被操作的字符串,所以需要拷贝一份
	char* ret = strtok(arr3, arr2);
	printf("%s\n", ret);
	ret = strtok(NULL, arr2);//为了取出剩下部分的字符串,第一个参数需要改为NULL
	printf("%s\n", ret);
	ret = strtok(NULL, arr2);
	printf("%s\n", ret);
	return 0;
}

如果一个字符串被某些字符分隔为很多部分,如果我们一个一个地取出就会使代码冗长,这里我们可以使用循环解决

int main()
{
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	char arr3[20] = { 0 };
	strcpy(arr3, arr1);
	char* ret = NULL;
	for (ret = strtok(arr3, arr2); ret != NULL; ret = strtok(NULL, arr2))
	{
		printf("%s\n", ret);
	}
	return 0;
}

输出结果:
在这里插入图片描述


5.错误信息报告

strerror

char * strerror ( int errnum );
  • C语言的库函数在运行的时候,如果发生一些错误,就会将错误码放到一个变量中:erron
  • 错误码是一些数字:0 1 2 3 4 5……
  • strerror就是返回这些错误码对应的字符串的首字符地址

我们可以看一下这些错误码对应的错误信息

int main()
{
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	printf("%s\n", strerror(5));
	return 0;
}

在这里插入图片描述
当然,平常使用时并不需要我们将具体数字传入函数中

我们通常将变量errno传入函数中strerror(errno)
而这个errno变量在头文件errno.h中,使用前必须添加这个头文件。

另外还有一个perror函数,使用这个函数,可以直接将错误信息打印出来,并且如果perror中传了字符串,这个函数会先将传过去的字符串打印出来,再打印一个冒号,最后打印错误信息


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

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

相关文章

POE交换机全方位解读(下)

POE供电在安防系统中的应用 安防行业应用PoE组网主要有简化布线、节能灵活、安全方便等优势。众所周知&#xff0c;一般的网络摄像机除了需要通过网线来传输视频信号外&#xff0c;还必须全天候为其提供足够的电力。而在现实施工中&#xff0c;经常会出现因为无法提供稳定的电源…

[SSL]微信实机测试 request:fail -2:netLERR_FAILED

request:fail -2:netLERR_FAILED 微信小程序开发过程中&#xff0c;实机测试调用服务器链接报错。 SSL证书已安装&#xff0c;通过下面工具检查&#xff0c;中间证书安装有问题。 下面是检查证书链和补全证书链的工具网站&#xff0c;亲测有效。 w​​​​​​​​​​​​​…

[数仓]埋点数据接入

第40个视频的1&#xff1a;03&#xff1a;31一、采集flume日志服务器&#xff1a;将日志采集到本地&#xff0c;共有两个日志服务器&#xff0c;因此要安装两台flume&#xff0c;每个flume采集其所在服务器上的日志source:taildir source可以实时的读取文件中的数据&#xff0c…

详解Linux中网络文件系统

目录 前言 一、samba服务简介 1、windos如何共享文件 2、在linux中访问共享文件 二、samba基本信息 三、samba的安装与启用 1.服务端 2.客户端 3.服务启用 四、建立samba服务共享目录 五、samba用户的建立 六、 samba用户访问加目录 七、samba的访问控制 八、 sa…

Web(十一)

Request 1. request对象和response对象的原理 1. request和response对象是由服务器创建的。我们来使用它们 2. request对象是来获取请求消息&#xff0c;response对象是来设置响应消息 2. request对象继承体系结构&#xff1a; ServletRequest -- 接…

Kafka知识概况

Kafka知识概况Kafka简介Kafka 生产者Kafka BrokerKafka 消费者Kafka-Eagle 监控Kafka-Kraft 模式集成 SpringBootKafka简介 消息队列简介&#xff1a; 目 前企业中比较常见的消息队列产 品主 要有 Kafka、ActiveMQ 、RabbitMQ 、RocketMQ 等。在大数据场景主要采用 Kafka 作为…

【ElasticSearch8.X】学习笔记(一)

【ElasticSearch8.X】学习笔记一、8.x与7.x的对比二、安装elk8.x2.1、下载2.2、集群规划2.3、安装2.4、配置环境2.5、修改配置文件2.6、启动2.5、安装其他结点三、Kibana 安装3.1、下载3.2、配置环境3.2、修改配置文件3.4、启动一、8.x与7.x的对比 减少内存堆使用&#xff0c;…

JavaScript 进阶--charater2

系列文章目录 提示&#xff1a; JavaScript进阶笔记 &#xff0c;希望各位看官可以高抬小手一键三连 上一章测试题 答案在最后给出 文章目录系列文章目录前言一、深入对象1.1创建对象三种方式1. 利用对象字面量创建对象2.利用new Object 创建对象3. 利用构造函数创建对象1.2 构…

设计师百度百科词条创建怎么做?

设计分为平面设计、空间设计、工业设计、珠宝设计、游戏设计、家具设计、建筑设计、室内设计、景观设计、服装设计、网页设计、系统设计、剧场设计、动漫设计、品牌设计、造型设计、三维设计师、杂志封面设计师、包装设计师、形象设计师等领域。 设计师是一个提供创意的工作&a…

Hive(8):Hive内、外部表

关键字&#xff1a;EXTERNAL 1 什么是内部表 内部表&#xff08;Internal table&#xff09;也称为被Hive拥有和管理的托管表&#xff08;Managed table&#xff09;。 默认情况下创建的表就是内部表&#xff0c;Hive拥有该表的结构和文件。换句话说&#xff0c;Hive完全管理…

Hue(2):Hue 的安装

1 上传解压安装包 Hue 的安装支持多种方式&#xff0c;包括 rpm 包的方式进行安装、tar.gz 包的方式进行安装以及 cloudera manager 的方式来进行安装等&#xff0c;我们这里使用 tar.gz 包的方式来进行安装。 Hue 的压缩包的下载地址&#xff1a; http://archive.cloudera.…

SSH远程登录RaspberryPi命令行响应缓慢问题

SSH远程登录RaspberryPi命令行响应缓慢问题1. 问题2. 分析3. 解决3.1 去掉PAM部分鉴权模块3.2 去掉sshd的DNS设置3.3 无线WiFi信号优化方法一&#xff1a;ifconfig操作方法二&#xff1a;内核自动检测4. 结果5. 补充资料5.1 [排除wifi网卡功率自管理问题](https://raspberrypi.…

LeetCode刷题---21.合并两个有序链表(双指针)

文章目录一、编程题&#xff1a;19. 删除链表的倒数第 N 个结点&#xff08;双指针-快慢指针&#xff09;1.题目描述2.示例1&#xff1a;3.示例2&#xff1a;4.示例3&#xff1a;5.提示&#xff1a;二、解题思路1.思路2.复杂度分析&#xff1a;3.算法图解三、代码实现总结一、编…

【Linux】调试器 gdb 及 ‘\r‘ 的使用

目录 前言 gdb 断点 打断点 查看、删除断点 断点使能 调试 显示数据 其他指令 ‘\r’的使用 行缓冲区 小程序 前言 &#x1f951;在 Linux 下我们可以通过 gcc 进行编译&#xff0c;但与 vs 相比若想对代码进行调试&#xff0c;我们还需要学会使用调试器 gdb 。 &am…

Elastic Job学习笔记

目标&#xff1a; 第一章&#xff1a;概述 1、理解任务调度的概念 2、理解分布式任务调度的概念 3、能够说出Elastic-Job是什么 第二章&#xff1a;Elastic-Job快速入门 1、能够搭建Elastic-Job快速入门工程环境 2、能够编写Elastic-Job快速入门的程序 3、理解Elastic-Job整体架…

Studio 3T怎么用mysql语句执行查询

目录说明说明 mongo图形界面 Studio 3T怎么执行mongo的原生语句进行查询 就先说到这\color{#008B8B}{ 就先说到这}就先说到这 在下Apollo\color{#008B8B}{在下Apollo}在下Apollo 一个爱分享Java、生活的小人物&#xff0c;\color{#008B8B}{一个爱分享Java、生活的小人物&…

【前端】Vue项目:旅游App-(20)home:点击跳转至带参数的动态路由

文章目录目标过程与代码详情页detailhome中设置点击跳转效果总代码修改或添加的文件router/indexdetailhome-content参考本项目博客总结&#xff1a;【前端】Vue项目&#xff1a;旅游App-博客总结 目标 点击热门精选的item跳转至对应详情页&#xff1a; 详情页&#xff1a; 路…

什么是HTTPDNS?HTTPDNS有哪些作用?

近几年来&#xff0c;HTTPDNS技术大火&#xff0c;很多大的网站都开始部署自己的HTTDNS服务器&#xff0c;那么什么是HTTPDNS&#xff0c;HTTPDNS和传统的DNS技术相比有哪些区别&#xff1f;HTTPDNS又有哪些作用呢&#xff1f;针对这些问题&#xff0c;本文中科三方做下简单介绍…

【学习】Linux 系统 文件权限表示

学习内容描述&#xff1a;Linux 系统 文件权限格式 重点知识&#xff1a; Linux 系统 文件权限格式是10位&#xff0c;格式例如&#xff1a;-rw-rw-rw-&#xff0c;表示文件所有者、所属组、其他用户都具有读和写的权限 。 &#xff08;1&#xff09;第0位确定文件类型 其中: …

计算机组成原理 第三章笔记

视频网址 仅仅是笔记记录&#xff0c;若有错误请指出。 零碎的 存储器的分类 磁表面存储器:磁盘,磁带磁芯存储器半导体存储器 RAM ROM光存储器 看下面这个思维导图 存储器的性能指标 存储容量&#xff1a;存储字数字长单位成本: 每位价格总成本/总容量存储速度&#xff1a;数…