C语言——字符串函数

一.前言

我们在日常写代码的过程中,经常会对字符串进行处理的过程。而在C语言中的<string.h>中,包含了众多字符串函数,我们可以借助这些字符串函数来对其进行各种操作。

二.strlen函数

strlen函数的作用是求出所传字符串的长度。该函数的参数待求长度的字符串的地址,返回值为size_t类型的整数,因为一个字符串的长度不可能是负数,所以可以用无符号整型数据接收。该函数的逻辑是:从所传字符串首地址开始向后寻找,直到遇到'\0'结束,后返回'\0'到首地址之间的字符个数。

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

int main()
{
	int arr[] = "abcdef";
	size_t len = strlen(arr);
	printf("%zd\n", len);
	return 0;
}

使用strlen函数的时候得包含对应头文件<string.h>。有上述代码可知,len的长度为6。注意:打印size_t类型的数据时,用%zd来打印。

2.1strlen函数的模拟实现

我们已经知道了strlen函数的使用方法,现在我们来模拟它的实现。要想实现它,我们得知道它的运行逻辑:我们先给它传了一个字符串的首地址,它就沿着该地址向后寻找,直到遇到'\0'停止,然后返回首地址与'\0'之间的字符个数。它的实现逻辑并不复杂,现在我们来模拟实现。

1.计数器法

我们可以创建一个变量用来统计字符的个数,然后通过while循环遍历该字符串的每一个字符,遇到'\0',则表示字符串遍历完毕,返回计数器。注意:当我们要使用该字符串时,应先判断该字符串是否为空。所以我们可以使用assert断言,使用assert得包含头文件<assert.h>

//1.计数器法

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* arr)
{
    assert(arr);//判断传入的地址是否为空
	size_t count = 0;
	while (*arr != '\0')
	{
		count++;
		arr++;
	}
	return count;
}

int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%zd\n", len);
	return 0;
}

2.不创建临时变量法(递归法)

不能创建临时变量,)意味着不能再使用计数器的方式来统计。这时,我们可以利用递归法来实现。这里我们在使用待求字符串前依旧得进行断言,判断地址是否有效。

//2.不能创建临时变量,递归法

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

size_t my_strlen(const char* arr)
{
	assert(arr);//判断所传字符串是否为NULL
	if (*arr != '\0')
	{
		return 1 + my_strlen(arr + 1);
	}
	else
	{
		return 0;
	}
}

int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%zd\n", len);
	return 0;
}

3.指针-指针

我们知道,两指针可以进行相减的操作,得出的结果的绝对值表示的是两指针之间的元素个数。所以我们可以将字符串首地址储存起来,在进行遍历,找到字符串的末尾,两者相减就是该字符串的元素个数。

//3.指针-指针

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

size_t my_strlen(const char* arr)
{
	assert(arr);//判断所传地址是否为NULL

	char* p1 =(char*) arr;//记录首地址
	char* p2 = NULL;//记录末尾

	while (*arr++ != '\0')
	{
		;
	}
	p2 = (char*)arr - 1;//当退出循环后,arr指向的'\0'的位置,所以字符串的末尾应该是arr-1
	return p2 - p1;
}

int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%zd\n", len);
	return 0;
}

三.strcpy函数

strcpy函数的参数为两个char*的指针,返回值也是char*类型的。该函数的作用为:将第二个指针所对应的字符串复制到第一个指针所指向的内容里。但复制的前提是目标地址的内存空间足够大。

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

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

打印结果为:

哪有的人就会问了,将第二个字符串复制给第一个指针是否会将第二个字符串的'\0'也复制过去?我们可以调试来看一下。

我们先将目标地址的字符串全部设置成x,并且x的数量是多于来源字符串的。如果我们在监视窗口看到前6个x被复制成abcdef,而第七个x被复制成'\0',那么就能说明strcpy复制的时候会将来源字符串的全部字符包括结尾的'\0'也复制给目标字符串。

这时我们可以清楚的看到,当我们执行了strcpy函数之后,它会将来源字符串的全部字符包括结尾的‘\0’全部复制给目标字符串。

注意:对于strcpy函数来说有几个注意点:

1.它会将来源字符串的全部包括'\0'都复制给目标

2.来源字符串必须包含'\0'

3.目标地址必须够大,以确保能够存放的下来源字符串

4.目标空间必须可以修改

3.1.strcpy函数的模拟实现

为了实现strcpy函数,我们就得清楚其复制的逻辑:strcpy是将第二个来源字符串的全部字符包括'\0'也复制给目标字符串。所以我们可以从两个指针的首地址开始,将来源字符串的内容依次赋给目标字符串。既然是模拟实现strcpy函数,所以我们在函数返回值和参数上应保持一致,目标地址为char*类型,而来源地址为const char*类型。因为我们只希望将第二个指针的内容复制给一指针,并不希望二指针的内容被修改,于是用const修饰。

//strcpy函数的模拟实现

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

char* my_strcpy(char* destination, const char* source)
{
	assert(destination && source);//判断传来的地址是否为空
	
	char* ret = destination;//记录目标字符串的首地址

	while (*destination++ = *source++)//将来源字符串的每个字符赋给目标字符串
	{
		;
	}
	return ret;
}

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

在使用两个指针之前,我们需要断言,判断两指针是否为NULL。在此之后,我们先创建一个指针用来存储目标地址的首地址。后利用循环,对目标地址进行赋值,随后指针++,两指针走到下一个位置,后继续进行赋值,直到将来源地址的'\0'也赋值给目标地址后,循环判断条件为假,退出循环,复制结束,后将之前存放来目标地址的首地址的指针返回。

四.strcat函数

strcat函数的返回值为char*类型,参数分别为char*类型的目标字符串和const char* 类型的源字符串。它的功能是将目标字符串和源字符串连接起来(将源字符串复制在目标字符串的后面)。

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

int main()
{
	char arr1[20] = "abcdef";//目标字符串
	char arr2[] = "ghi";//来源字符串

	char* ret = strcat(arr1, arr2);
	
	printf("%s\n", ret);
	return 0;
}

运行结果为:

那是否可以利用该函数对字符串本身进行追加呢?

我们看到当我们在给字符串追加自己时,在调试的时候就会报错。说发生了访问冲突,其实就是数组越界了。这是因为你在给自己追加的时候,字符串在同时的发生变化。

我们看到,当指针指向arr数组的第一个元素时,会将它追加在目标字符串的位置,而目标字符串和源字符串是同一个,所以两者都发生了改变。随后指针向后走,指向源字符串的第二个元素,将其追加到目标字符串的后面,两者又同时发生改变。我们这时发现,字符串中已经没有了\0,故追加不会停止,直到追加到大于目标字符串可以承受的最大元素,导致越界。所以我们得出结论,不可以给字符串追加自身。

我们在使用strcat函数时,有几个注意点:

1.源字符串必须以\0结束;

2.目标字符串也必须包含\0,否则不知道从什么位置开始追加;

3.目标地址的空间必须足够大,能够容纳源字符串;

4.目标空间必须得可修改;

5.最好不要给自身追加,否则可能导致越界访问

4.1.strcat函数的模拟实现

模拟实现之前,我们得知道strcat函数到底是怎么个逻辑来实现的追加字符串。我们上面说到源字符串必须以\0结尾,所以说明当对源字符串解引用的时候等于\0就说明已经将源字符串全部追加到了目标字符串;上面还提到目标字符串也必须含有\0,否则不知道从哪里开始追加。所以要实现该函数,我们就得找到目标字符串的第一个\0位置,以此开始进行追加,直到将源字符串的所有字符都追加到目标字符串,就表明追加完成。

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

char* my_strcat(char* destination, const char* source)
{
	assert(destination && source);//判断所传两指针是否为NULL

	char* ret = destination;//将目标字符串的首地址储存起来

	//找到目标字符串中第一次出现\0的位置
	while (*destination)
	{
		destination++;
	}

	//从该位置开始,将源字符串追加到目标字符串
	while (*destination++ = *source++)
	{
		;
	}

	return ret;
}

int main()
{
	char arr1[30] = "abcdef";//目标字符串
	char arr2[] = "ghijk";//源字符串

	char * ret = my_strcat(arr1, arr2);

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

运行结果:

我们在使用指针的时候最好先进行判断,以防该指针为NULL。

五.strcmp函数

strcmp也是C语言中字符串函数之一,它的参数为两个 const char*类型的指针,返回值为int。该函数的作用为:比较两字符串的大小。返回值的大小由比较结果来定。如果:str1 > str2,则返回大于0的数字;str1 = str2,则返回0;str2<str2,则返回小于0的数字。

那strcmp是如何比较两个字符串呢?

strcmp函数进行两字符串间的比较是同位之间进行比较,比较的是字符所对应的ASCII码值,ASCII码值大的该字符就大。

需要注意的是,字符串的大小与其长度没有关系,只跟相同位置上的字符所对应的ASCII码值有关。

5.1.strcmp函数的模拟实现

我们在介绍strcmp函数的时候就已经解释它的比较逻辑,我们只需要根据其比较逻辑写出代码即可:

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

int my_strcmp(const char* p1, const char* p2)
{
	//使用指针前先对其进行断言,判断其是否可用
	assert(p1 != NULL);
	assert(p2 != NULL);

	while (*p1 == *p2)
	{
		//如果出现\0,说明两个字符串完全相同
		if (*p1 == '\0')
		{
			return 0;
		}

		//使指针后移,指针下一个元素
		p1++;
		p2++;
	}

	//字符减法相当于用ASCII码值进行减法,如果p1>p2,则返回一个大于0的数,反之返回一个小于0的数
	return *p1 - *p2;
}

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

	int ret = my_strcmp(arr1, arr2);

	printf("%d\n", ret);
	return 0;
}

六.strncpy函数

从函数名可以看出来,它与strcpy只有一个n的区别,那区别究竟在哪里呢?

我们发现,两者的区别就在于参数的不同,strncpy比strcpy多了一个参数 size_t 类型的num。这个参数其实就是n。

该函数的作用与strcpy类似,是复制源字符串的前n个元素到目标地址。

那如果源字符串没有num个元素,为怎么样呢?

我们看到,arr2只有五个元素,如果num=5的话,刚好把arr1中前五个x改变,后面不改变;但当num =6的时候,arr2不够6个元素,此时进行复制的时候,会在第五个元素后面补0,直到够num个元素。

而对于字符数组来说,0就相当于\0,所以arr1中第六个元素的位置就会被复制成\0,导致不能打印后面的x。

注意:在使用strncpy函数的时候同样得保证目标地址的内存空间足够大。

七.strncat函数

我们看到,两者的区别也在于一个size_t类型的num参数。该函数的功能就是追加源字符串中的前num个字符到目标地址。

与strncpy相同,如果num大于源字符串的长度,就会在后面补0:

注意:目标地址的内存大小必须足够大。

八.strncmp函数

该函数中的size_t num,与上面两者有点不同,num的作用是比较str1 和 str2 的前num个字符。

如图所示,如果比较的是前四个字符,则arr1和arr2相同,返回0;如果比较前5个字符,则arr1 < arr2,返回小于0的值。

 九.strstr函数

strstr函数的功能是在str1中查找str2,如果找到了,就返回str2在str1中第一个字符出现的地址;如果找不到则返回NULL。

9.1strstr函数的模拟实现


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

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);

    //如果str2为空字符串,则直接返回str1
	if (!*str2)
	{
		return (char*)str1;
	}

	char* ch = (char*)str1;
	char* s1;
	char* s2;

	while (*ch)
	{
		s1 = ch;
		s2 = (char*)str2;

		//s1、s2都不是\0,并且两者相等
		while (*s1 && *s2 && !(*s1 - *s2))
		{
			//相等之后,指针向后走继续比较
			s1++;
			s2++;
		}

		//在此判断*s2是否为\0,若为\0则说明已经在str1中找到了str2
		if (!*s2)
		{
			return ch;
		}

		ch++;
	}

	//如果ch遇到\0,说明已经将str1查找完了,也没有找到str2,这时返回NULL
	return NULL;
}

int main()
{
	char arr1[] = "This is a apple";
	char arr2[] = "is";

	char* ret = my_strstr(arr1, arr2);

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

十.strtok函数

strtok函数的功能为将str分解成一系列字符串,以其中的分隔符来进行分离。delimiters指的是所有分隔符所存在的字符串。下面给出一个例子:

值得注意的是,在使用完一次strtok函数之后,它会记住第一次分隔符出现的地方,下次调用的时候第一个参数传一个空指针,第二个参数依旧时分隔符字符串,该函数会从第一次分隔符的下一个位置开始扫描。

但是如果我们想要分开一个字符串就得写多个这样的调用和输出语句,非常麻烦,我们可以利用循环来实现多次调用及打印:

法一:

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

int main()
{
	char arr[] = "xia-si-cheng.12138.com";
	char deli[] = "-.";

	char* str = NULL;
	for (str = strtok(arr,deli); str != NULL;str = strtok(NULL,deli))
	{
		printf("%s\n", str);
	}
	return 0;
}

法二:


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

int main()
{
	char arr[] = "xia-si-cheng.12138.com";
	char deli[] = "-.";

	char* ret = strtok(arr, deli);
	
	while (ret)
	{
		printf("%s\n", ret);

		ret = strtok(NULL, deli);
	}
	return 0;
}

注意:使用strtok函数后str1的内容会改变,所有分隔符都会被修改成\0。

十一.strerror函数

该函数的功能是返回错误信息,通过错误数字代码,返回一个错误信息指针,指针所指向的内容就是错误信息。该错误信息取决于开发平台和编译器。

总结:

字符串函数的内容就到此结束,如果文章出错还请大家帮我揪出问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

spring-boot之shiro安全框架配置使用

shiro架构&#xff08;外部&#xff09; shiro架构(内部) 具体API操作 获取当前的用户对象 Subject currentUser SecurityUtils.getSubject();通过当前用户拿到session Session session currentUser.getSession(); session.setAttribute("someKey", "aValu…

Android 自定义坐标曲线图(二)

Android 自定义坐标曲线图_android 自定义曲线图-CSDN博客 继上一篇文章&#xff0c;点击折线图上的点&#xff0c;显示提示信息进行修改&#xff0c;之前通过回调&#xff0c;调用外部方法&#xff0c;使用popupwindow或dialog来显示&#xff0c;但是这种方法对于弹框显示的位…

SpringCloud实用篇(二)——搭建eureka服务

搭建eureka服务 搭建EurekaServer 注册eureka自己本身 1.创建项目&#xff0c;引入spring-cloud-starter-neflix-eureka-server的依赖 <!--eureka服务端--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cl…

Windows 远程访问 Ubuntu Desktop - 虚拟网络控制台 (Virtual Network Console,VNC)

Windows 远程访问 Ubuntu Desktop - 虚拟网络控制台 [Virtual Network Console&#xff0c;VNC] References 1. Desktop Sharing 2. Desktop Sharing Preferences 勾选 允许其他人查看您的桌面 勾选 要求远程用户输入此密码 取消勾选 必须为对本机器的每次访问进行确定 3. 虚拟…

Qt 富文本处理 (字体颜色大小加粗等)

Qt中支持HTML的控件有textEdit 、label 、textBrowser 。 接口&#xff1a;setHtml("Qt"); toHtml(). 文本样式设置 : 可分字设置 &#xff0c;主要使用QTextCharFormat类进行文本样式设置。 示例&#xff1a; QTextCharFormat fmt; //粗体 fmt.setFontWeight…

Linux中常用命令(文件、目录和文件压缩)及功能示例

一、Linux关于文件与目录的常用命令及其功能示例 命令: ls 全名: List (列表) 常用选项: -l: 详细列表格式&#xff0c;显示详细信息。-a: 显示所有文件&#xff0c;包括隐藏文件。 功能: 列出目录内容。 示例: ls -la /home 此命令以详细格式列出/home目录中的所有文件&#x…

openLooKeng开发环境搭建

文章目录 搭建OpenLooKeng开发环境要求 以下是搭建OpenLooKeng开发环境的基本步骤&#xff1a;1、从OpenLooKeng的GitHub仓库克隆代码&#xff1a;2、 构建OpenLooKeng生成IntelliJ IDEA项目文件 airbase构建项目过程中出现的问题checkstyle错误版本冲突问题hetu-heuristic-ind…

java将文件转成流文件返回给前端

环境&#xff1a;jdk1.8&#xff0c;springboot2.5.3,项目端口号&#xff1a;9100 1.待转换的文件 一、路径 二、文件内容 2.controller中代码 package com.example.pdf.controller;import com.example.pdf.service.GetFileStreamService; import org.springframework.web.b…

linux离线安装jdk

一、下载jdk 地址: Java Downloads | Oracle 中国 具体下载什么版本要根据安装的linux系统架构来决定&#xff0c;是ARM64还是X64&#xff0c;linux命令行输入如下命令 uname -m 可以看到linux系统是x64 架构(x86是32位&#xff0c;x86_64是64位&#xff0c;由于x86已经淘汰&…

正弦实时数据库(SinRTDB)的使用(8)-过滤查询

前文已经将正弦实时数据库的使用进行了介绍&#xff0c;需要了解的可以先看下面的博客&#xff1a; 正弦实时数据库(SinRTDB)的安装 正弦实时数据库(SinRTDB)的使用(1)-使用数据发生器写入数据 正弦实时数据库(SinRTDB)的使用(2)-接入OPC DA的数据 正弦实时数据库(SinRTDB)…

腾讯 tendis 替代 redis linux安装使用

下载地址 Tendis存储版 点击下载 linux 解压 tar -zxvf 安装包.tgz cd 解压安装包/scripts 启动 ./start.sh 停止 ./stop.sh 详细配置 修改 /scripts tendisplus.conf # tendisplus configuration for testing # 绑定本机IIP bind 192.168.31.112 port 51002 #设…

C++ :STL中deque的原理

deque的结构类似于哈希表&#xff0c;使用一个指针数组存储固定大小的数组首地址&#xff0c;当数据分布不均匀时将指针数组内的数据进行偏移&#xff0c;桶不够用的时候会像vector一样扩容然后将之前数组中存储的指针拷贝过来&#xff0c;从原理可以看出deque的性能是非常高的…

2024年腾讯云4核8G服务器性能怎么样?价格有点便宜

腾讯云4核8G服务器价格&#xff1a;轻量4核8G12M优惠价格646元15个月、CVM S5服务器4核8G配置1437元买1年送3个月。腾讯云4核8G服务器支持多少人同时在线&#xff1f;支持30个并发数&#xff0c;可容纳日均1万IP人数访问。腾讯云百科txybk.com整理4核8G服务器支持多少人同时在线…

话题通信的python实现

一、发布者Publisher的python实现 step1&#xff1a;在scripts文件夹中创建py节点 step2&#xff1a;第一行是为了指定解释器&#xff0c;Ubuntu20.04是python3&#xff0c;比他低的版本是python。第二行是为了指定编码方式。第五行中&#xff0c;引用index.ros.org中数据类型…

E5063A是德科技E5063A网络分析仪

181/2461/8938产品概述&#xff1a; Keysight E5063A 是一款低成本网络分析仪&#xff0c;可为测试天线、电缆、滤波器和 PCB 等简单无源元件提供优化的性能和功能。Keysight E5063A 为您的企业提供价格和性能之间的最佳平衡&#xff0c;以满足您的业务和技术要求。它利用行业…

R60ABD1 呼吸心跳雷达睡眠监测模块

R60ABD1 呼吸心跳雷达睡眠监测模块 简介特征参数电气参数通讯协议说明使用步骤总结 简介 R60ABD1 雷达模块基于一发三收天线形式&#xff1a;宽波束雷达模块主要适用于置顶安装模式&#xff0c;通过算法控制一定角度范围&#xff0c;精准扫描人体全身的动作层析&#xff1b;实…

Kubernetes篇(一)— kubernetes介绍

目录 前言一、应用部署方式演变二、kubernetes简介三、kubernetes组件四、kubernetes概念 前言 本章节主要介绍应用程序在服务器上部署方式演变以及kubernetes的概念、组件和工作原理。 一、应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff…

HTLM 之 vscode 插件推荐

文章目录 vscode 插件live Serverprettiersetting 保存这个文档的更改Material Theme / Material Theme icon vscode 插件 live Server prettier setting 搜索 format default 保存这个文档的更改 cmds // mac ctrls // win Material Theme / Material Theme icon 来更换…

使用Flink实现Kafka到MySQL的数据流转换:一个基于Flink的实践指南

使用Flink实现Kafka到MySQL的数据流转换 在现代数据处理架构中&#xff0c;Kafka和MySQL是两种非常流行的技术。Kafka作为一个高吞吐量的分布式消息系统&#xff0c;常用于构建实时数据流管道。而MySQL则是广泛使用的关系型数据库&#xff0c;适用于存储和查询数据。在某些场景…

算法学习——LeetCode力扣动态规划篇3

算法学习——LeetCode力扣动态规划篇3 494. 目标和 494. 目标和 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 …