字符串函数和字符函数

news2024/11/22 20:41:42

文章目录

  • 前言
  • strlen()
    • 实现strlen()
  • strcpy()
    • 模拟实现
  • strcat()
    • strcat()模拟实现
  • strcmp()
    • 模拟实现strcmp()
  • strstr()
    • 模拟实现strstr()
  • strncpy(),strncmp(),strncat()
  • strtok()
  • memcpy()
    • memcpy()的模拟实现
  • memmove()
    • memmove()的模拟实现
  • memset()

前言

在C语言中,我们经常会遇到字符串的处理,我们要是每当用到字符串总要自己写函数去解决问题的时候,未免有些麻烦,所以在这里我们可以调用库中的 “string.h" 的头函数来解决问题;下面就对一些经常使用的字符函数和字符串函数进行使用讲解和函数解析;

strlen()

size_t strlen ( const char * str );
获取字符串的长度

对于一个字符串,如”abc“,会通过字符串的首个字符开始计算,直至遇到终止字符‘\0’开始结束计算,终止字符’\0’ 不会算进去,所以”abc"长度为3;
当我们要使用的时候,输入一个字符指针或者字符数组即可;

char arr1[]=“abc”;
char* arr2=“bcd”
strlen(arr1);
strlen(arr2);

先看下面例子:

#include <string.h>
#include<stdio.h>
int main()
{
	if (strlen("abc") - strlen("abcdef") > 0)
	{
		printf("大于\n");
	}
	else
	{
		printf("小于等于\n");
	}

	return 0;
}

大于

我们可以很明显看出,"abc"是比“abcdef”字符串长度短的,但程序的结果却是大于,这是因为对于strlen()函数来说,它的返回类型是size_t,这个类型相当于unsigned char,是一个无符号的数据类型;所以对于使用strlen()来比较字符串长短,要用大于小于,像程序中的操作,只会显示错误;
正确方式:

#include <string.h>
#include<stdio.h>
int main()
{
	if (strlen("abc") >strlen("abcdef") )
	{
		printf("大于\n");
	}
	else
	{
		printf("小于等于\n");
	}

	return 0;
}

实现strlen()

代码:

size_t my_strlen(const char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

对于一个字符串来说,我们用指针传参,那么传过去的是字符串的首个字符的地址,那么就可以通过指针的移动来统计字符串的长短;这里要注意:由于count类型和函数的类型不统一,一般以函数类型为主。

strcpy()

char * strcpy ( char * destination, const char * source );
复制字符串
将源指向的 C 字符串复制到目标指向的数组中,包括终止的 null 字符(并在该点停止)。
为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的 C 字符串(包括终止空字符),并且不应在内存中与源重叠。

所以strcpy函数一般用于字符数组;
使用例子:

int main()
{
	
	char arr1[11] = "";
	char arr2[6] = "abcdef";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);

	return 0;
}

abcdef

模拟实现

代码:

#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest != NULL);
	assert(src != NULL);

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

	return ret;
}

对于源字符串src是不可变的,所以用const来修饰;首先先用ret指针来记住arr1的首元素位置,然后对传参进行断言判断,如果为空那么将会在报出在哪行代码发生错误,可以快速的找到错误源头;这里的循环用到星号和++的优先级关系;这里是先解引用dest和src,然后将src赋值给dest,执行完之后dest和src才++,当src走到终止字符时,就会将终止字符赋给*dest当while循环条件判断时,将会终止该循环,最后返回ret指针

strcat()

char * strcat ( char * destination, const char * source );
连接字符串
将源字符串的副本追加到目标字符串。目标中的终止空字符被源的第一个字符覆盖,并且在目标中由两者串联形成的新字符串的末尾包含一个空字符。这里不用在意源字符串的大小
目的地和来源不得重叠。

对于字符串来说都有终止字符,那么说明可以在目标字符串终止字符位置上开始追加源字符串;
使用例子:

int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	strcat(arr1, arr2);

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

hello world

strcat()模拟实现

代码:

char* my_strcat(char*dest, const char *src)
{
	assert(dest && src);

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

首先先对dest和src进行断言;然后用ret记住dest的地址,然后对dest移动,当移动到终止符号时停下,然后开始src对dest赋值;最后返回ret。

这里记住不能这样写:

在这里插入图片描述
这是因为当我们指针指向终止字符’\0’时,本来是判断结束了,但由于后置加加,所以加速之后指针还会向后走一步;

strcmp()

int strcmp ( const char * str1, const char * str2 );
比较两个字符串
将 C 字符串 str1 与 C 字符串 str2 进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续以下对,直到字符不同或达到终止空字符.

这里的字符比较,将会根据字符对应的ACLII码字表对应的十进制数字进行比较大小,在VScode的gcc编译环境下,大于则返回1,小于则返回-1,等于则返回0;

int main()
{
    char* arr1="abc";
    char* arr2="ABC";
    printf("%d\n",strcmp(arr1,arr2));
    printf("%d\n",strcmp(arr2,arr1));
}

1
-1

模拟实现strcmp()

代码:

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);
}

先对str1,str2进行断言判断;当两个字符串的字符都相等时,则指针先后移动,移动到终止字符时就返回0,当两个字符串的字符不一样时,就返回它们的差值。

strstr()

const char * strstr ( const char * str1, const char * str2 );
char * strstr ( char * str1, const char * str2 );
查找子字符串
返回指向 str2 中第一次出现的 str1 的指针,如果 str2 不是 str1 的一部分,则返回一个空指针。
匹配过程不包括终止空字符,但它到此为止。

例子:

#include <stdio.h>
#include <string.h>
int main ()
{
  char str[] ="This is a simple string";
  char * pch;
  pch = strstr (str,"simple");
  
  printf("%s\n",pch);
  return 0;
}

simple string

模拟实现strstr()

代码:

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

	//str2为空直接返回str1首元素
	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;
}

由于我们要多次循环匹配str1是否有str2字符串,所以利用循环嵌套的方法,外面的循环的对str1中每条子字符串进行匹配判断,指针逐渐先后移动;内层循环是判断str1中的子字符串中的字符是否与str2中的字符一致;
这里用cp记住str1的每个子字符串的首元素,s1是用来遍历str1每个子字符串中的字符是否与str2匹配;倘若内层遍历完到s2走到str2的终止字符,那么直接返回cp。外层循环一直没有结果就返回空指针;

strncpy(),strncmp(),strncat()

这类函数可以说是 strcpy(),strcmp(),strcat()函数的进阶版;它们的函数意义都没有改变,只是可以具体到了某个字节位置,不再是只到字符串的终止字符才停止;

char * strncpy ( char * destination, const char * source, size_t num );
从字符串中复制字符
将源的第一个字符数复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到总共写入 num 个字符为止。
如果源长度超过 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,不应将目标视为以空结尾的 C 字符串(这样读取它会溢出)。
目的地和来源不得重叠

int strncmp ( const char * str1, const char * str2, size_t num );
比较两个字符串的字符
将 C 字符串 str1 的字符数与 C 字符串 str2 的字符数进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用以下对,直到字符不同,直到达到终止的空字符,或者直到两个字符串中的 num 字符匹配,以先发生者为准。

char * strncat ( char * destination, const char * source, size_t num );
从字符串追加字符
将源的第一个数字字符追加到目标,外加一个终止空字符。
如果源中 C 字符串的长度小于 num,则仅复制终止空字符之前的内容。

strtok()

char * strtok ( char * str, const char * delimiters );
将字符串拆分为标记
对此函数的一系列调用将 str 拆分为标记,这些标记是由分隔符中的任何字符分隔的连续字符序列。
在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描令牌的起始位置。在后续调用中,该函数需要一个空指针,并使用最后一个令牌末尾之后的位置作为扫描的新起始位置。
为了确定标记的开头和结尾,该函数首先从起始位置扫描分隔符中未包含的第一个字符(该字符将成为标记的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,该字符将成为令牌的末尾。如果找到终止空字符,扫描也会停止。
令牌的此结尾将自动替换为空字符,并且令牌的开头由函数返回。
一旦在对 strtok 的调用中找到 str 的终止空字符,则对此函数的所有后续调用(以空指针作为第一个参数)都将返回空指针。
找到最后一个令牌的点由要在下一次调用中使用的函数在内部保留(不需要特定的库实现来避免数据争用)。

具体使用请看下面代码:

int main()
{
	char arr[] = "zpengwei@yeah.net@666#777";
	char copy[30];
	strcpy(copy, arr);

	char sep[] = ".@#";
	char* ret = NULL;

	for (ret = strtok(copy, sep); ret != NULL; ret=strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}
	return 0;
}

在这里插入图片描述

在第一次调用的时候,需要对需要拆分的字符串写进去(程序中的copy),当使用strtok函数对copy拆分之后,该函数可以想象为有记忆功能,相当于该函数有某个指针,在第一次调用后,指针会指向”拆分的字符串“拆分标志字符之后的一个位置(@后的y),对该字符串拆分第一个参数用空指针即可,函数会根据你给的字符串(sep)中的字符进行匹配对比,只要”拆分的字符串“中有sep中的字符,那么将会对字符串进行拆分。直至走到”拆分的字符串“的终止字符就停下来。

memcpy()

void * memcpy ( void * destination, const void * source, size_t num );
复制内存块
将字节数的值从源指向的位置直接复制到目标指向的内存块。
源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本
该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。
为避免溢出,目标和源参数指向的数组大小应至少为字节数,并且不应重叠

相比于strcpy()不再只是局限于字符串,它是可以任意类型的。
如,可以将整型数组中的元素进行复制:

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[20] = { 0 };
	//将arr1中的内容,拷贝到arr2中
	memcpy(arr2, arr1, 40);
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		printf("%d ", arr2[i]);
	}

	return 0;
}

1 2 3 4 5 6 7 8 9 10 0 0 0 0 0 0 0 0 0 0

要记住memcpy是根据字节进行复制的,如将上面的memcpy改为:

memcpy(arr2, arr1, 37);

由于小端的存储方式所以打印出来和上面结果一致;
在这里插入图片描述

memcpy()的模拟实现

代码:

void* my_strcpy(void* dest,const void* src,size_t num)
{
    void* ret=dest;
    assert(src&& dest);

    while(num--)
    {
        *(char*)dest=*(char*)src;
        dest=(char*)dest+1;
        src=(char*)src+1;
    }
    return ret;
}

思路大体和strcpy()一致,主要当指针在移动的时候,由于参数类型是void*,且 num是size_t类型的,所以在移动指针的时候需要对指针先强制转换,赋值也一样,这样就可以在num的限制下完成操作;

在上面的介绍函数中说过,memcpy()是不可重叠的,具体如下:

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };	
	my_memcpy(arr1+2, arr1, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
			printf("%d ", arr1[i]);
	}
	return 0;
}

当我们想从数组中复制该数组成 1 2 1 2 3 4 5 8 9 10的时候,我们试着运行代码,结果是:1 2 1 2 1 2 1 8 9 10,发现答案与预想情况不一致,这是因为当要将3复制过去给5之前,3由于1的复制已经变为1了,所以无法达到效果;

在这里,如果我们要实现这种可重叠的效果,就可用到memmove()函数来解决。

memmove()

void * memmove ( void * destination, const void * source, size_t num );
移动内存块
将字节数的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样,允许目标和源重叠。
源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。
该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。
为避免溢出,目标参数和源参数指向的数组的大小应至少为字节数。

从描述介绍中知道,该函数功能与memcpy函数几乎一致,唯一不同的就是可重叠;

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };	
	memmove(arr1+2, arr1, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
			printf("%d ", arr1[i]);
	}
	return 0;
}

1 2 1 2 3 4 5 8 9 10

memmove()的模拟实现

代码:

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--)//20
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

在这里,需要判断dest与src的位置,
在这里插入图片描述
主要看重叠的位置,清楚谁给谁赋值,赋值不能改变源数组中的内容即可;

memset()

void * memset ( void * ptr, int value, size_t num );
填充内存块
将 ptr 指向的内存块的第一个字节数设置为指定值(解释为无符号字符)。

函数相当于对数组进行初始化,可以设置指定值,当然也是以字节为准;
所以不要出现以下这种错误:
例如说想要给整型数组初始化为1,那么这样结果将会不符合预期。

int main()
{
    int arr1[]={1,2,3};
    memset(arr1,1,12);
    printf("%d",arr1[0]);
    return 0;
}

答案:16843009 相当于0x01010101;

所以该函数经常使用在对字符数组的初始化;

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

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

相关文章

ELK + Filebeat 部署及 logstash 的四大插件(grok、date、mutate、multiline)

目录 FilebeatFilebeat 结合 logstash 带来好处&#xff1a;FluentdELK Filebeat 部署1&#xff0e;安装 Filebeat & Httpd2&#xff0e;设置 filebeat 的主配置文件4&#xff0e;在 Logstash 组件所在节点上新建一个 Logstash 配置文件5&#xff0e;浏览器访问 http://19…

凯迪正大数显电动调压控制台

数显电动调压控制台使用方法 1、核对试验变压器&#xff0c;测量绕阻额定输出电压&#xff0c;使之与操作箱&#xff08;台&#xff09;相吻合。 2、按接线示意图接好试验变压器与操作箱&#xff08;台&#xff09;及感应调压器之间的联线。 3、接通电源&#xff0c;通电源指…

el-select 右侧icon样式问题

el-select 右侧icon样式问题 样式问题如图&#xff1a; 解决方法&#xff1a; el-input__suffix {display: flex;align-items: center;justify-content: center; }注意&#xff1a;样式需写在没有scoped的style标签里

在VSCODE编辑器是用ctrl+c和ctrl+s(复制粘贴)失效怎么办

有时我们在开发过程中&#xff0c;由于使用vsccode太长时间导致复制ctrlc和ctrls会失效&#xff0c;之前我的处理方式是重启浏览器&#xff0c;但有时候这样太耗时间了&#xff0c;但发现一个方法可以解决&#xff0c;就是刷新下编辑器的timeline就行&#xff0c;如下图&#x…

快速构建一个 GitLab + Jenkins + Harbor 的云原生 DevOps 环境

今天我们要搭建一条怎样的工具链呢&#xff1f;且看效果图&#xff1a; GitLab Jenkins Harbor Toolchain Workflow 首先我们需要完成 GitLab、Jenkins 和 Harbor 三个工具的部署&#xff1b; 接着我们需要在 GitLab 上创建一个代码库&#xff0c;并且在 Jenkins 上创建相应…

若依系统学习笔记记录1

下载后的文件列表 先按照nocos. Nacos: 概览 欢迎来到 Nacos 的世界&#xff01; Nacos 致力于帮助您发现、配置和管理微服务Nacos: 概览 欢迎来到 Nacos 的世界&#xff01; Nacos 致力于帮助您发现、配置和管理微服务 cd nacos/ mvn命令如果是idea,需要用Ctrlshiftenter来执…

【软件测试】MySQL数据库场景问题+解决方案

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 问题1&#xff1a…

2023年测试岗,自动化测试如何学?如何卷出测试圈?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 怎么学习自动化测…

初识FreeRTOS

一、FreeRTOS 介绍 什么是 FreeRTOS &#xff1f; Free即免费的&#xff0c;RTOS的全称是Real time operating system&#xff0c;中文就是实时操作系统。 注意&#xff1a;RTOS不是指某一个确定的系统&#xff0c;而是指一类操作系统。比如&#xff1a;uc/OS&#xff0c;Fre…

STM32 Proteus仿真LCD12864火灾检测烟雾火焰温度报警器MQ2 -0064

STM32 Proteus仿真LCD12864火灾检测烟雾火焰温度报警器MQ2 -0064 Proteus仿真小实验&#xff1a; STM32 Proteus仿真LCD12864火灾检测烟雾火焰温度报警器MQ2 -0064 功能&#xff1a; 硬件组成&#xff1a;STM32F103R6单片机 LCD12864 液晶显示DS18B20 温度传感器多个按键电位…

阿里云配置端口安全组策略

文章目录 为何配置安全组安全组设置安全组应用到实例中 为何配置安全组 nginx正确配置了83端口&#xff0c;却无法访问资源&#xff0c;报502错误&#xff0c;这大概就是服务器的安全策略原因 安全组设置 安全组配置地址 安全组应用到实例中 配置地址

pandas 各种存储格式速度对比:CSV、hdf5、SQL、pickle、feather、parquet

前言&#xff1a;目前我在做一个callback函数&#xff0c;需要将数据重复的读取、写入&#xff0c;再供使用&#xff0c;并且数据量比较大&#xff0c;所以需要使用一个读写速度快的存储方式&#xff0c;不太考虑占用的磁盘空间 直接看结果 csv的文件存储&#xff0c;在读取的…

Java虚拟机(JVM)

Java虚拟机&#xff08;JVM&#xff09; 类加载 类加载 Java类加载的过程可以分为以下几个步骤&#xff1a; 加载&#xff08;Loading&#xff09;&#xff1a;类加载的第一步是将类的字节码文件加载到内存中。 通过类的全名&#xff0c;全限定名&#xff08;包括包名和类名&…

C++之std::enable_if_t用法(一百五十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

“人工智能的崛起:挑战与机遇并存“

近日&#xff0c;一位美国网络安全高级官员发出警告&#xff0c;称如果科技公司未能自我约束并与政府合作控制人工智能的力量&#xff0c;我们可能面临着巨大的风险。这位官员的言论是在数百名科技领袖和公众人物支持的联合声明之后发表的&#xff0c;该声明将人工智能的存在威…

mysql练习---存储过程/存储函数

创建表并插入数据 字段名 数据类型 主键 外键 非空 唯一 自增 id INT 是 否 是 是 否 name VARCHAR(50) 否 否 是 否 否 glass VARCHAR(50) 否 否 是 否 否 sch 表内容 id name glass 1 xiaommg glass 1 2 xiaojun glass 2 1、创建一个可以统计表格内记录条数的存储函数 &#…

W波段超外差LO两种倍频链路的比较

W波段&#xff08;75-110GHz&#xff09;由于其衰减小&#xff0c;分辨率高&#xff0c;超宽带等优势&#xff0c;受到越来越多的关注。之前由于成本高昂&#xff0c;主要是军事方面的应用。近些年随着Gotmic等厂家毫米波芯片的量产&#xff0c;成本大大降低&#xff0c;已被很…

LRU 缓存

题目链接 LRU 缓存 题目描述 注意点 如果插入操作导致关键字数量超过 capacity &#xff0c;则应该 逐出 最久未使用的关键字函数 get 和 put 必须以 O(1) 的平均时间复杂度运行 解答思路 如果想以O(1)的速度进行get&#xff0c;则需要将对应的key、value存到map中如果想…

word 公式序号自动按章节排序

1.在普通视图下&#xff0c;使用Alt和F9切换到域代码模式。 普通视图 域代码模式 2.光标放在章节标题最后一个字后面&#xff0c;使用Ctrl和F9添加域代码的大括号 3.在大括号中键入 SEQ ch \h 4.光标放在刚刚的大括号后面&#xff0c;再次按下Ctrl和F9添加域代码的大括号 5.…

SAP MESSAGE ID LB31的问题和解决办法

外协采购订单判退时报错 EA 返回交货(原因)-> 3700006717 /00009 (0000) 1 EA 不正确过帐返回给供应商&#xff0c;错误信息:对所需数量 XXXXXX00003122&#xff0c;1.000 PCS 仍然未清LB 31 搜索到lb31是和批次相关的错误 https://www.michaelmanagement.com/sap-error-mes…