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

news2024/12/22 22:08:53

图片来源于网络

✨博客主页:小钱编程成长记
🎈博客专栏:进阶C语言
🎈相关博文:字符串函数(一)

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

  • 3.长度受限制的字符串函数
    • 3.1 strncpy(指定操作长度的拷贝字符串)
    • 3.2 strncat(指定操作长度的字符串追加)
    • 3.3 strncmp(指定操作长度的字符串比较)
    • 3.4 strstr(查找子字符串 / 在字符串中找字符串)
    • 3.5 strtok(字符串切割 / 分隔)
    • 3.6 strerror(翻译错误码)
  • 总结

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

要指定操作长度,思考的更多,相对更安全

3.1 strncpy(指定操作长度的拷贝字符串)

具体介绍链接

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

将源字符串的第一个字符拷贝到目标字符串。如果在拷贝完 num 个字符之前找到源 C
字符串的末尾(由’\0’表示),则目标将填充0,直到总共写入 num 个字符为止。

注:
  • 从源字符串拷贝num个字符到目标空间。

  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。在这里插入图片描述

  • 源字符串(字符数组) 不需要一定以’\0’结束。(因为操作数不同,新的目标字符串末尾会自动补充’\0’)。

  • 目标空间必须足够大,以确保能存放源字符串。(否则会报错)

  • 目标空间必须可变。(因为要把另一个字符串拷贝到这里)

  • 返回值是目标空间的起始地址,然后通过%s来打印,%s是从给的地址开始 *解引用打印,遇到’\0’结束

  • 学会模拟实现。

模拟实现strncpy :

//模拟strncpy
#include <stdio.h>
#include <assert.h>

char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* start = dest;
	int i = 0;
	for (i = 0; i < num; i++)
	{
		if (*src != '\0')
		{
			*dest++ = *src++;
		}
		else
		{
			*dest++ = '\0';//写0 或 '\0'都一样,因为在内存中'\0'(ASCII码)就是0
		}
	}

	//num <= 源字符串长度(不包含'\0') 时,新目标字符串中没有'\0',要再加个'\0'
	if (*(dest-1) != '\0')//-1是因为循环结束时dest指向的是第num+1个数
	{
		*dest = '\0';//在num个数后面加个结束标志'\0'
	}
	return start;
}

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

在这里插入图片描述

3.2 strncat(指定操作长度的字符串追加)

具体介绍链接

char * strncat ( char * destination, const char * source, size_t num );
注 :
  • 从目标空间的第一个 ‘\0’ 开始('\0’被覆盖),追加规定的字符个数,追加完后还会在后面补充一个 ‘\0’ ,这样才构成了一个完整的字符串。在这里插入图片描述

  • 如果指定追加的字符个数 > 源字符串的字符个数,则在实际追加时只追加现有源字符串 ‘\0’ 之前的全部内容。
    在这里插入图片描述

  • 源字符串(字符数组) 不需要一定以’\0’结束。(因为操作数不同,新的目标字符串末尾会自动补充’\0’)。
    在这里插入图片描述

  • 目标空间必须要有’\0’,保证能找到目标空间的末尾,进行追加。(编译器认为从左到右第一个’\0’是字符串的末尾)

  • 目标空间必须有足够的大,能容纳下源字符串的内容。

  • 目标空间必须可修改。

  • strncat返回的是目标空间的起始地址。

  • 学会模拟实现。

模拟实现strncpy :

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

char* my_strncat(char* dest, const char* src, size_t num)
{
	assert(dest && src);//断言
	char* start = dest;

	//找目标函数的末尾'\0'
	while (*dest)
	{
		dest++;
	}
	//数据追加
	int i = 0;
	for (i = 0; i < num; i++)
	{
		if (*src != '\0')
		{
			*dest++ = *src++;
		}
		else//*src == '\0'时跳出循环,在新目标字符串末尾补充'\0'
		{
			break;
		}
	}
	*dest = '\0';//在末尾补充'\0'

	return start;
}

int main()
{
	char arr1[20] = "abc\0xxxxxxxxxxxx";
	char arr2[] = "def";
	my_strncat(arr1, arr2, 1);
	printf("%s\n", arr1);
	return 0;
}

3.3 strncmp(指定操作长度的字符串比较)

具体介绍链接

int strncmp ( const char * str1, const char * str2, size_t num );
介绍:
  1. 此函数开始比较每个字符串的第一个字符,如果它们相等,则继续向下比较,直到字符不同达到终止空字符(‘\0’)拷贝完num个字符 停止。
  2. 比较的不是长度,而是对应位置上字符的大小ASCII码,因为字符在内存中是以ASCII码的形式存储的)
C语言标准规定:
返回值(整型)解释
大于 0第一个字符串大于第二个字符串
0第一个字符串等于第二个字符串
小于 0第一个字符串小于第二个字符串
不同的编译器具体返回的值不同

3.4 strstr(查找子字符串 / 在字符串中找字符串)

具体介绍链接

const char * strstr(const char *str1, const char *str2);
介绍:

在str1中查找str2,strstr会返回str1中str2第一次出现的位置的第一个字符的地址;如果str1中没str2,则返回NULL(空指针)。

在这里插入图片描述

若在arr1上用str1遍历,如图第二次遍历时没找到子字符串,那就需要从第3个字符处再遍历,但是唯一的一个指针被我们用来遍历了,我们现在连首字符都不知道在哪了,所以我们要提前保留初始指针,创建一个专门用来对比遍历的指针,arr2也是一样。在arr1中也可以再创建个指针用来指向每次遍历的第一个字符,将初始指针保存起来,在遇到复杂问题时会更加方便,更加得心应手。

因此:
当出现需要多个指针、指针需要移动等比较复杂的情况时,原始的指针最好不要移动或改变。

第一次遍历时,a与b不相同,第二次遍历从a后面一个字符再开始遍历。
在这里插入图片描述
b和c不同,
开始第三次遍历:
在这里插入图片描述
当s2指向’\0’时,说明在arr1中能找到arr2,查找结束。

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

const char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);//断言,防止为空指针
	
	//写不写都行,因为这种情况,后面的代码已经考虑到了
	if (*str2 == '\0')//如果要找的字符串为空,则说明在arr1中直接找完了 / (没有要找的字符串,指针没有移动),直接返回arr1的首字符地址
	{
		return str1;
	}
	
	//cp,s1,s2指向的数据都不需要改动,可以用const保护起来
	const char* cp = str1;//cp用来记录开始遍历(匹配)的位置
	const char* s1 = cp;//s1用来遍历str1指向的字符串
	const char* s2 = str2;//s2用来遍历str2指向的字符串
	while (*cp)//如果arr1中第一个字符就是'0',说明是个空字符串,一定找不到arr2,直接返回NULL
	{
		s1 = cp;
		s2 = str2;
		while (*s1 && *s2 && *s1 == *s2)//三个条件要同时为真。
			//若*s1为假--*s1=='\0' 说明指针已经指向了arr1的末尾,此时还没找到arr2,说明arr1中没有arr2,跳出循环,返回NULL
			//若*s2为假--*s2=='\0' 说明arr1中找到了arr2,跳出循环,函数返回str1中str2第一次出现的位置的第一个字符地址
			//若*s1 == *s2说明两个字符相等,继续遍历
		{
			s1++;
			s2++;
		}
		
		if (*s2 == '\0')//说明arr1中找到了arr2,函数返回str1中str2第一次出现的位置的第一个字符地址
		{
			return cp;
		}
		else if (*s1 == '\0')//说明指针已经指向了arr1的末尾,此时还没找到arr2,说明arr1中没有arr2,返回NULL
		{
			return NULL;
		}
		cp++;
	}
	return NULL;//*cp为假--*cp=='\0'  说明指针已经指向了arr1的末尾,此时还没找到arr2,说明arr1中没有arr2,返回NULL
}

int main()
{
	char arr1[] = "abbcdef";
	char arr2[] = "bbc";

	//函数返回的指针是受保护的const char*类型的,赋给不受保护的char*类型指针,属于类型放大了(容易丢失精度不太安全),最好赋给const char*类型指针
	char* ret = my_strstr(arr1, arr2);
	printf("%s\n", ret);//%s是从给的地址开始 *解引用打印,遇到'\0'结束
	return 0;
}

这种模拟实现是一种暴力求解,算法不够高效,后期会用KMP算法实现,更高效。

3.5 strtok(字符串切割 / 分隔)

具体介绍链接

char * strtok(char *str, const char *sep);
介绍:

strtok是个有点奇怪的函数,它和之前见过的函数都不一样。 是用来拆分字符串的,比如:
在这里插入图片描述

  • 参数sep是个字符串,定义了用作分隔符的字符集合。分隔符在字符串中的顺序无所谓。

  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或多个分隔符分割的标记。

  • strtok找到str中的下一个标记,并将其用 \0 结尾(替换掉原来的分隔符),返回一个指向这个标记首字符的指针。若下一个标记的结尾是\0,则也返回这个标记的首字符地址。
    ( 注:strtok函数会改变被操作的字符串,所以使用strtok函数切分的字符串一般都是临时拷贝的并且可修改的。)
    在这里插入图片描述

  • 若strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置 或者说是保存这个标记末尾 \0 的位置。

  • 若strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。(有记忆功能(static))

  • 如果字符串中不存在更多的标记或查找到了 \0,则返回 NULL 指针。

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

int main()
{
	char arr[] = "0111222333@qq.com";

	char buf[200] = { 0 };
	strcpy(buf, arr);//buf的内容是临时拷贝的并且是可修改的

	char* p = "@.";//和char p[] = "@." 几乎相同,p都是字符串的首字符地址,内存中也都存储了字符串
	char* s = strtok(buf, p);//strtok函数的第一个参数不为NULL,找buf中的第一个标记,并且找到后用\0替换标记符来结尾
	printf("%s\n", s);//找到了一个记录,返回这个记录的首字符地址

	s = strtok(NULL, p);//strtok函数的第一个参数为NULL,从同一个字符串中被保存的上一个标记的末尾\0开始,查找下一个标记
	printf("%s\n", s);

	s = strtok(NULL, p);
	printf("%s\n", p);

	return 0;
}

在未来真正使用这个函数时,并不是这样使用的,应该是这样:

//strtok函数的正确使用方式1:
#include <stdio.h>
#include <string.h>

int main()
{
	char arr[] = "0111222333@qq.com";
	char buf[200] = { 0 };
	strcpy(buf, arr);
	
	char p[] = "@.";// == char *p = "@.";//分隔符在字符串中的顺序无所谓
	char* s = 0;//strtok的返回值
	//当字符串中不存在更多标记时,返回NULL空指针,循环的条件判断部分为假,跳出循环。
	for (s = strtok(buf, p); s != '\0'; s = strtok(NULL, p))
	{
		printf("%s\n", s);
	}
	return 0;
}


//使用方式2:
#include <stdio.h>
#include <string.h>

int main()
{
	char arr[] = "0111222333@!qq.com";
	char buf[200] = { 0 };
	strcpy(buf, arr);

	char p[] = "@.!";// == char *p = "@.!";
	char* s = 0;//strtok的返回值
	
	s = strtok(buf, p);//strtok函数的第一个参数不为NULL,找buf中的第一个标记,找到后用\0替换标记符来结尾
	while (s != NULL)
	{
		printf("%s\n", s);
		s = strtok(NULL, p);//strtok函数的第一个参数为NULL,从同一个字符串中被保存的上一个标记的末尾\0开始,查找下一个标记
	}
	return 0;
}

若出现两个分隔符连在一起了,则第二个分隔符直接被跳过,如图:
在这里插入图片描述

3.6 strerror(翻译错误码)

具体介绍链接

char * strerror ( int errnum );
介绍:

将错误码翻译成错误信息,返回错误信息的字符串的起始地址。
(只能将C语言标准库中的错误码翻译成错误信息)

注:

C语言中使用库函数的时候,如果发生错误,就会将错误码放在errno的变量中;errno是个全局的变量,可直接使用,需要头文件errno.h

》打开文件的例子:
fopen以读的形式打开文件(头文件stdio.h),
如果文件存在,则打开成功;
如果文件不存在,则打开失败;

//strerror
#include <stdio.h>//main,printf,fopen,perror的头文件
#include <string.h>//strerror的头文件
#include <errno.h>//errno的头文件

int main()
{
	FILE* pfile = fopen("add.txt", "r");//若打开成功,则返回FILE*类型的指针
	if (pfile == NULL)//若打开失败,则返回NULL空指针
	{
		printf("打开文件失败,原因是:%s\n", strerror(errno));
	}
	else
	{
		printf("打开文件成功\n");
	}
	return 0;
}

在这里插入图片描述

若在此源文件所在的文件夹中有add.txt这个文件,则打开文件成功,否则失败。
在这里插入图片描述

补充小知识:

perror可直接打印错误码所对应的错误信息。
perror == printf + strerror
在这里插入图片描述

总结

我们一起学习了长度受限制的字符串函数。
感谢大家的阅读,大家一起进步!

点赞收藏加关注,C语言学习不迷路!
图片来源于网络

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

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

相关文章

594.最长和谐子序列(滑动窗口)

目录 一、题目 二、代码 一、题目 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 二、代码 class Solution { public:int findLHS(vector<int>& nums) {sort(nums.begin(), nums.end());int left 0, right 0;int MaxLength 0;while…

【网络安全-SQL注入】SQL注入----一篇文章教你access数据库SQL注入以及注入点利用。SQL注入【3】

前言&#xff1a; 本篇文章以凡诺企业网站管理系统为例&#xff0c;讲解了access数据库是如何进行SQL注入的&#xff0c;以及注入点如何利用&#xff0c;如何判断查询字段个数&#xff0c;如果用联合查询爆出数据库数据等&#xff1b; 之前有两篇文章详细介绍了MySQL数据库的…

Qt Creator 预览界面 快捷键

一般来说&#xff0c;我们运行Qt程序所花费的时间是比较长的&#xff0c;那有时我们只改变了界面&#xff0c;那么此时花费如此长的时间去运行程序来观察界面改动的效果是非常浪费时间的行为。 此时我们可以选择预览界面来观察界面改动后的效果&#xff1a;

九、GC收集日志

JVM由浅入深系列一、关于Java性能的误解二、Java性能概述三、了解JVM概述四、探索JVM架构五、垃圾收集基础六、HotSpot中的垃圾收集七、垃圾收集中级八、垃圾收集高级👋GC收集日志 ⚽️1. 认识GC收集日志 垃圾收集日志是一个重要的信息来源,对于与性能相关的一些悬而未决的…

基本的五大排序算法

目录&#xff1a; 一&#xff0c;直接插入算法 二&#xff0c;希尔排序算法 三&#xff0c;选择排序 四&#xff0c;堆排序 五&#xff0c;冒泡排序算法 简介&#xff1a; 排序算法目前是我们最常用的算法之一&#xff0c;据研究表明&#xff0c;目前排序占用计算机CPU的时…

1003 我要通过!

一.问题&#xff1a; “答案正确”是自动判题系统给出的最令人欢喜的回复。本题属于 PAT 的“答案正确”大派送 —— 只要读入的字符串满足下列条件&#xff0c;系统就输出“答案正确”&#xff0c;否则输出“答案错误”。得到“答案正确”的条件是&#xff1a; 字符串中必须仅…

数组和切⽚ - Go语言从入门到实战

数组和切⽚ - Go语言从入门到实战 数组的声明 package main import "fmt" func main() { var a [3]int //声明并初始化为默认零值 a[0] 1 fmt.Println("a:", a) // 输出: a: [1 0 0] b : [3]int{1, 2, 3} //声明同时初始化 fmt.Println("b:…

番外6:下载+安装+配置Linux

#########配置Linux---后续 step08: 点击编辑虚拟机设置&#xff0c;选择下载好的映像文件.iso进行挂载&#xff1b; step09: 点击编辑虚拟机选项&#xff0c;选择UEFI启动模式并点击确定&#xff1b; step10: 点击开启虚拟机&#xff0c;选择Install rhel &#xff1b; 备注&…

架构的未来:微前端与微服务的融合

文章目录 微服务架构简介微前端架构简介微前端与微服务的融合1. 共享服务2. 基于事件的通信3. 统一的身份和认证4. 交付管道的集成 示例&#xff1a;使用微服务和微前端的电子商务平台微服务架构微前端架构融合微服务和微前端 结论 &#x1f389;欢迎来到架构设计专栏~架构的未…

【Linux系统编程】僵尸进程与孤儿进程

文章目录 1. 僵尸进程2. 僵尸进程的危害3. 孤儿进程 1. 僵尸进程 上一篇文章进程的状态中最后我们提出了僵尸状态&#xff1a; 为了方便子进程退出后父进程或操作系统获取该进程的退出结果&#xff0c;Linux进程退出时&#xff0c;进程一般不会立即死亡&#xff0c;而是要维持…

【Spring底层原理】BeanFactory的实现

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 容器实现 一、BeanFactory实现的特点1.1 Be…

2023年中国半导体IP行业发展概况及趋势分析:半导体IP的市场空间广阔[图]

半导体指IP指芯片设计中预先没计、验证好的功能模块&#xff0c;处于半导体产业链最上游&#xff0c;为芯片设计厂商提供设计模块。半导体IP按交付方式可分为软核、硬核和固核&#xff1b;按产品类型可分为处理器IP、接口IP、其他物理IP及其他数字IP。 半导体IP分类 资料来源&…

K-Means(上):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

ubuntu 18.04 LTS安装opencv 3.4.16 + opencv_contrib 3.4.16

1.下载 opencv 3.4.16 opencv_contrib 3.4.16 其中&#xff0c;opencv_contrib解压后的多个文件夹复制到opencv内、合并 2.安装 参考博文&#xff1a; https://zhuanlan.zhihu.com/p/650792342 https://zhuanlan.zhihu.com/p/87197806 其中 &#xff08;1&#xff09;cmake前…

【设计模式】五、原型模式

文章目录 概述示例传统的方式的优缺点原型模式原理结构图-uml 类图 原型模式解决克隆羊问题的应用实例Sheep类实现clone()运行原型模式在 Spring 框架中源码分析 深入讨论-浅拷贝和深拷贝浅拷贝的介绍 小结 概述 示例 克隆羊问题 现在有一只羊 tom&#xff0c;姓名为: tom, 年…

nginx隐藏版本号和标识

1.隐藏版本号:nginx-服务器banner泄漏风险_banner信息泄露_javachen__的博客-CSDN博客 2.隐藏nginx标识 cd /usr/local/nginx-1.24.0/src/corevi nginx.h在第14行 cd /usr/local/nginx-1.24.0/src/httpvi ngx_http_special_response.c在第22,29,36行 cd /usr/local/nginx-1.2…

踩坑日记 uniapp 底部 tabber遮挡住购物车结算

tabbar 被购物车结算遮挡 在小程序上tabbar没有将固定栏遮挡&#xff0c;如果直接调高&#xff0c;浏览器H5页面是对了&#xff0c;但在小程序上面离底部的定位就太高了 原代码 // 底部结算样式.shop-foot {border-top: 2rpx solid #F7F7F7;background-color: #FFF;position: …

法国心理健康平台【Teale】完成1000万欧元A轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;位于法国巴黎的心理健康平台【Teale】今日宣布已完成1000万欧元的A轮融资。 本轮融资由Alter Equity和Bpifrance的Digital Venture基金领投&#xff0c;目前的支持者ISAI和Evolem也参与其中。 该公…

ChatGPT的截图识别功能测评:开启图像中的文字与信息的新纪元

文章目录 根据截图&#xff0c;识别菜品根据截图&#xff0c;识别数学公式根据截图生成前端UI代码可视化图像复现案例一案例二 更多可以使用的方向 制作人&#xff1a;川川 辛苦测评&#xff0c;如果对你有帮助支持一下书籍&#xff1a;https://item.jd.com/14049708.html 根据…

spring boot整合常用redis客户端(Jedis、Lettuce、RedisTemplate、Redisson)常见场景解决方案

Java操作redis有三种客户端供选择&#xff1a;Jedis、Lettuce、Redisson。 在实际项目中运用最多的客户端还是Redisson、RedisTemplate&#xff1b;其中RedisTemplate并非是一个新的redis客户端实现&#xff0c;RedisTemplate是Spring Data Redis中提供的封装好的redis操作模板…