C语言:字符函数和字符串函数详解及部分函数的模拟实现(前篇)

news2024/11/28 22:50:35

文章目录

  • 求字符串长度
    • strlen
    • strlen函数的模拟实现:
  • 长度不受限制的字符串函数
    • strcpy
    • strcat
    • strcmp
    • 总结
  • 长度受限制的字符串函数介绍
    • strncpy
    • strncat
    • strncmp

前言:

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串 中或者 字符数组 中。
字符串常量 适用于那些对它不做修改的字符串函数。
本篇文章将会重点介绍处理字符和字符串的库函数的使用和注意事项。

求字符串长度

strlen

strlen函数是我们在操作字符串时常用的计算字符串长度的函数。
我们为了更加了解strlen函数,可以打开cplusplus.com来查看:
在这里插入图片描述
从正规网站上我们可以更清楚地了解到strlen函数的使用方法。
函数模板:

size_t strlen ( const char * str );

函数的参数是一个char*的指针,且被const修饰,表示这个指针在函数里不能被修改,保护了指针指向的内容。
函数的返回值是size_t类型,表示一个无符号的整型,这里使用size_t类型的原因是字符串的长度不可能小于0,所以用size_t类型来返回长度。


使用时的注意事项:

  • 字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )
#include <string.h>
#include <stdio.h>
int main()
{
	char* a = "abcdef";
	char arr[] = "asdfgh";
	int b = strlen(a);
	int c = strlen(arr);
	printf("%d\n", b);
	printf("%d\n", c);
	return 0;
}

在这里插入图片描述

注:字符串的最后会自动补上一个'\0',但不显示出来。


  • 参数指向的字符串必须要以 ‘\0’ 结束。
#include <string.h>
#include <stdio.h>
int main()
{
	char arr1[] = "asdfgh";
	char arr2[6] = "asdfgh";
	int a1 = strlen(arr1);
	int a2 = strlen(arr2);
	printf("%d\n", a1);
	printf("%d\n", a2);
	return 0;
}

在这里插入图片描述

观察此段代码中arr1arr2的差异,并对结果进行分析。
我们可以通过调试发现:arr1arr2数组中存储的内容有些许差异。
在这里插入图片描述
我们在监视中可以发现,arr1中存放的是字符串"asdfgh",后面还跟着一个'\0',一共有7个元素,而arr2中只存放了"asdfgh"6个字符元素,后面没有'\0'
又因strlen函数计算字符串长度时以'\0'作为结束标志,所以arr1中字符串的长度就是6,arr2中字符串后没有'\0',strlen函数会一直向后计算,直到在内存中遇到'\0'为止,所以arr2的长度是一个随机值。


  • 注意函数的返回值为size_t,是无符号的( 易错 )
#include <string.h>
#include <stdio.h>
int main()
{
	char arr1[] = "bcde";
	char arr2[] = "asdfgh";
	if ((strlen(arr1) - strlen(arr2)) > 0)
		printf(">\n");
	else
		printf("<\n");
	return 0;
}

猜猜结果是什么?
在这里插入图片描述
和你的预期一样吗?这里为什么是大于号呢?
这是因为strlen函数的返回值的类型是size_t类型的,是无符号整型,无符号整型相加减还是无符号整型,永远是大于等于零的,所以打印的是大于号。


strlen函数的模拟实现:

strlen函数的模拟实现有三种方法:计数器,递归,指针相减
计数器版本:

#include <stdio.h>
#include <assert.h>
//计数器
size_t my_strlen(const char* str)
{
	assert(str);//防止传空指针
	size_t count = 0;
	while (*str++)
	{
		count++;
	}
	return count;
}

在这里插入图片描述


递归版本:

#include <assert.h>
//递归
size_t my_strlen(const char* str)
{
	assert(str);//防止传空指针
	if (*str == '\0')
		return 0;
	return 1 + my_strlen(str + 1);
}

在这里插入图片描述


指针相减版本:

#include <stdio.h>
#include <assert.h>
//指针相减
size_t my_strlen(const char* str)
{
	assert(str);//防止传空指针
	char* des = (char*)str;//使类型兼容
	while (*des)//找到\0
	{
		des++;
	}
	return des - str;
}
int main()
{
	char arr[] = "abcdef";
	int ret = my_strlen(arr);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述


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

这里我们继续来了解一些关于字符串的相关函数。

strcpy

同样,我们先看一下官方解释,了解一下strcpy函数
在这里插入图片描述
函数模板:

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

函数的参数是两个char*的指针,第一个参数是目标空间的地址,第二个参数是源头空间的地址。第二个参数用const修饰,表示第二个参数在函数内不能被修改,而第一个参数没有用const修饰,是因为第一个参数是目标空间的地址,要存放被拷贝的字符串,是可以被修改的,所以没有用const修饰。
函数的返回类型是char*类型的指针,返回的是目标空间的地址。


函数功能:

将源头空间的字符串拷贝到目标空间,包括’\0’也要拷贝


使用时的注意事项:

  • 源字符串必须以 ‘\0’ 结束。

因为strcpy函数从源头地址处拷贝字符串是以'\0'为结束标志的,如果源字符串没有以'\0' 结束,且目标空间够大,那么strcpy函数会一直拷贝内存中的字符,直到遇到 '\0'才会结束。


  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "xxxxxxxxx";
	strcpy(arr2, arr1);
	printf(arr2);
	return 0;
}

在这里插入图片描述


  • 目标空间必须足够大,以确保能存放源字符串。

当目标空间的大小小于源字符串的大小:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[3];
	strcpy(arr2, arr1);
	printf(arr2);
	return 0;
}

在这里插入图片描述
这里程序发生了报错,但依然将源字符串拷贝到目标空间里。


  • 目标空间必须可变。

当我们把字符串拷贝到一个常量字符串中时:

#include <stdio.h>
#include <string.h>
int main()
{
	char* p = "xxxxxxxxxxxx";
	char arr1[] = "abcdef";
	strcpy(p, arr1);
	printf(p);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
程序直接崩溃,编译器报出警告:写入位置发生访问冲突。
原因是常量字符串的内容是不可修改的。


strlen函数的模拟实现:

#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

在这里插入图片描述


strcat

strcat函数
在这里插入图片描述
函数模板:

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

函数的参数是两个char*的指针,第一个参数是目标空间的地址,第二个参数是源头空间的地址。第二个参数用const修饰,表示第二个参数在函数内不能被修改,而第一个参数没有用const修饰,是因为第一个参数是目标空间的地址,要存放追加的字符串,是可以被修改的,所以没有用const修饰。
函数的返回类型是char*类型的指针,返回的是目标空间的地址。


函数功能:

将源头空间的字符串追加到目标空间中,从目标空间开始的的第一个’\0’位置开始追加,包括源字符串的’\0’也要追加


使用时的注意事项:

  • 源字符串必须以 ‘\0’ 结束。

因为strcat函数从源头地址处开始追加字符串是以'\0'为结束标志的,如果源字符串没有以'\0' 结束,且目标空间够大,那么strcpy函数会一直向目标空间中追加内存中的字符,直到遇到 '\0'才会结束。


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

当目标空间的大小小于源字符串的大小:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[7] = "jkl";
	strcat(arr2, arr1);
	printf(arr2);
	return 0;
}

在这里插入图片描述
这里程序发生了报错,但依然将源字符串追加到目标空间里。


  • 目标空间必须可修改。

当我们把字符串追加到一个常量字符串中时:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcdef";
	char *p = "jkl";
	strcat(p, arr1);
	printf(p);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

程序直接崩溃,编译器报出警告:写入位置发生访问冲突。
原因是常量字符串的内容是不可修改的。


思考:字符串能否自己给自己追加,如何?

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = "abcdef";
	strcat(arr1, arr1);
	printf(arr1);
	return 0;
}

在这里插入图片描述

这里发现代码一直在运行,最后崩溃了。
我们通过调试里的监视来观察一下arr1的变化。

在这里插入图片描述

在这里插入图片描述
当我们走完strcat函数的指令后,程序发生了报错,此时我们观察arr1数组中没有了'\0',但是又因为strcat函数是以'\0'为结束标志的,而此时arr1数组中又没有了'\0',这就导致了strcat函数在对arr1数组进行无限重复的追加,从而导致程序崩溃了。


strcat函数的模拟实现:

char* my_strcat(char* dest, const char* src)
{
		assert(dest && src);
		char* ret = dest;
		while (*dest)
		{
			dest++;
		}
		while (*dest++ = *src++)
		{
			;
		}
		return ret;
}

在这里插入图片描述


strcmp

strcmp函数

在这里插入图片描述


函数模板:

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

函数的参数是两个char*的指针,表示两个要比较的字符串的地址。两个参数都用const修饰,表示两个参数在函数内都不能被修改。
函数的返回类型是int*类型的整型。
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字


函数功能:

比较两个字符串的大小
从两个字符穿的第一个位置开始比较,比较的是两个字符的ASCII码值,如果两个字符的ASCII码值相等,则继续比较两个字符串后面一个字符的大小,如果第一个的字符大于第二的个字符,返回大于0的整型;如果第一个的字符小于第二的个字符,返回小于0的整型;如果两字符串都比较到’\0’也没比出大小,就返回0,表示两个字符串相等。


使用时的注意事项:

  • 两个字符串必须有’\0’作为结束标志。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[6] = "abcdef";
	char arr2[6] = "abcdef";
	printf("%d\n", strcmp(arr1, arr2));
	return 0;
}

在这里插入图片描述
这里我们从调试里的监视观察arr1arr2的内容,发现arr1arr2都没有以'\0'作为结束标志,所以当strcmp进行比较两字符串时,比完两个数组后会继续向后进行比较直到比出大小或都遇到'\0'为止。

注意:

strcmp函数在不同编译器返回的值是不同的
在VS编译器中,大于返回1,等于返回0,小于返回-1
其他编译器中,大于返回大于0的数字,等于返回0,小于返回小于0的数字


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

在这里插入图片描述


总结

当我们了解完上面这些字符串函数时,我们会发现,无论当我们在进行字符串的拷贝、追加还是比较时,使用的条件都非常苛刻,都非常依赖于两个字符串的大小、是否有足够的长度,是否包含'\0'等等。
如果使用不当可能导致结果错误,甚至是导致程序崩溃,单我们之前的一些例子表明在一些情况下,程序崩溃归崩溃,但该函数还是把它的功能给实现了,对于这些函数,我们称为长度不受限制的字符串函数
这些函数的停止标识是'\0',但当我们的字符串中没有'\0'时,这些函数的使用就有点危险了。
我们在VS编译器中使用这些函数时,如果不做出下列声明

#define _CRT_SECURE_NO_WARNINGS 1

在这里插入图片描述
编译器也是会报出警告的:
在这里插入图片描述


为了防止程序崩溃这些意外的情况的发生,接下来我会介绍一些功能相似的长度受限制的字符串函数,他们的操作我们是可以自已操控的。


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

strncpy

在这里插入图片描述


函数模板:

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

我们与strcpy函数比较一下,发现strncpy函数多了一个参数size_t num,这个参数就是我们可以进行控制的参数,它代表着我们字符串要拷贝的字符个数。


示例:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "xxxxxxxx";
	strncpy(arr2, arr1, 3);
	printf(arr2);
	return 0;
}

在这里插入图片描述
在这里插入图片描述


注意:

拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

示例:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "xxxxxxxx";
	strncpy(arr2, arr1, 6);
	printf(arr2);
	return 0;
}

在这里插入图片描述

在这里插入图片描述


strncat

在这里插入图片描述


函数模板:

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

我们与strcat函数比较一下,发现strncat函数多了一个参数size_t num,这个参数就是我们可以进行控制的参数,它代表着我们字符串要追加的字符个数。


示例:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[20] = "xxxxxxxx";
	strncat(arr2, arr1, 3);
	printf(arr2);
	return 0;
}

在这里插入图片描述


注意:

当我们要追加的字符个数如果超过目标空格的大小时,程序还是会崩溃的

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[10] = "xxxxxxxx";
	strncat(arr2, arr1, 3);
	printf(arr2);
	return 0;
}

在这里插入图片描述
在这里插入图片描述


strncmp

在这里插入图片描述
函数模板:

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

我们与strcmp函数比较一下,发现strncmp函数多了一个参数size_t num,这个参数就是我们可以进行控制的参数,它代表着我们字符串要比较的字符个数。
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。


示例:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[10] = "xxxxxxxx";
	printf("%d\n", strncmp(arr1, arr2, 3));
	return 0;
}

在这里插入图片描述


注意:

当我们要比较的字符数超过其中一个比较的字符串长度时,函数只会比较到遇到'\0'为止

示例:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[10] = "abcde";
	printf("%d\n", strncmp(arr1, arr2, 9));
	return 0;
}

在这里插入图片描述


总结:
这些关于字符串的操作函数可以帮助我们更精确的操作字符串,帮助我们避免程序的出错,但我们日常练习使用中还是会以长度不受限制的字符串函数的使用稍居多一些,只要我们正常使用这些函数,一般是不会造成意外情况的。


感谢大家的阅读哦!
写到这里这篇关于字符串函数详解及函数的模拟实现的文章就结束了,在本章后篇中我们还会了解到字符串查找、错误信息报告、、字符操作、内存操作等函数。


感兴趣的的小伙伴点点赞,点点关注,谢谢大家的阅读哦!!!
精彩不容错过,点点关注,后期不错过哦!😘
你们的鼓励就是我的动力,欢迎下次继续阅读!!!😘😘😘

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

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

相关文章

【LeetCode】382. 链表随机节点

382. 链表随机节点&#xff08;中等&#xff09; 方法一 思路 定义两个链表&#xff0c;一个origin&#xff0c;用于每次调用 getRandom() 时进行初始化&#xff0c;一个 l 用于每次调用 getRandom() 时进行遍历&#xff0c;找到随机选定的元素。首先在 Solution() 的时候&am…

SpringBoot原理——起步依赖与自动装配

文章目录 SpringBoot原理一、起步依赖二、自动配置2.1 概述2.2 工具类准备工作2.2.2 HeaderConfig2.2.3 HeaderGenerator2.2.4 HeaderParser2.2.5 MyImportSelector2.2.6 TokenParser2.2.7 pom.xml文件 2.3 自动配置原理2.3.1 引入工具类2.3.2 案例 &#xff1a; 访问第三方Bea…

GPT专业应用:撰写工作简报

●图片由Lexica 生成&#xff0c;输入&#xff1a;Workers working overtime 工作简报&#xff0c;作为一种了解情况、沟通信息的有效手段&#xff0c;能使上级机关和领导及时了解、掌握所属部门的政治学习、军事训练、行政管理等方面的最新情况&#xff1b;同时&#xff0c;能…

BERT输入以及权重矩阵形状解析

以下用形状来描述矩阵。对于向量&#xff0c;为了方便理解&#xff0c;也写成了类似(1,64)这种形状的表示形式&#xff0c;这个你理解为64维的向量即可。下面讲的矩阵相乘都是默认的叉乘。 词嵌入矩阵形状&#xff1a;以BERT_BASE为例&#xff0c;我们知道其有12层Encoder&…

记录--Vue中如何导出excel表格

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 一、导出静态数据 1、安装 vue-json-excel npm i vue-json-excel注意&#xff0c;此插件对node有版本要求&#xff0c;安装失败检查一下报错是否由于node版本造成&#xff01; 2、引入并注册组件(以全…

【CSS语法应用在Qt中的QSS和文本】第一天

CSS语法应用在Qt中的QSS和文本 【1】CSS语法【1】QSS使用以上CSS语法【1.1】QTextBrowser设置样式表【1.2】QTextBrowser使用CSS语法设置文本样式 【1】CSS语法 &#x1f49b;&#x1f49b;&#x1f49b;&#x1f49b;&#x1f49b;&#x1f49b;&#x1f49b;&#x1f49b;&am…

Redis的五大类型

一、String数据类型 概述&#xff1a;String是redis最基本的类型&#xff0c;最大能存储512MB的数据&#xff0c;String类型是二进制安全的&#xff0c;即可以存储任何数据、比如数字、图片、序列化对象等 1. SET/GET/APPEND/STRLEN: append命令&#xff1a;append key valu…

【mysql】explain执行计划之id列

目录 一、说明二、示例2.1 id相同&#xff0c;执行顺序从上到下2.2 id不相同&#xff0c;id值越大越先执行2.3 既有id相同也有id不同的情况&#xff0c;先执行序号大的&#xff0c;再同级从上往下执行2.4 id列显示为null的最后执行。表示结果集&#xff0c;不需要使用它来进行查…

记录一次windows mysql5.7安装失败的过程

首先下载mysql安装包 windows版本 https://dev.mysql.com/downloads/installer/ 接着 在执行安装mysql msi安装包最后一步的时候&#xff0c;显示 Failed to start service MySQL57. 只有在任务处于完成状态(RanToCompletion、Fau 这时候 检查要么windows下面mysql的卸载残留没…

AUTOSAR-文档命名说明

文章目录 AUTOSAR_TR_PredefinedNamesAutosar验收测试基本说明 AUTOSAR_TR_PredefinedNames AUTOSAR_TR_PredefinedNames&#xff08;Predefined Names in AUTOSAR&#xff09;.pdf对基础软件标准规范文档的分类信息做出了介绍&#xff0c;其中常用的文档包括EXP、PRS、RS、SR…

【C++】类和对象(中)---取地址及const取地址操作符重载、const成员函数的使用

个人主页&#xff1a;平行线也会相交&#x1f4aa; 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】&#x1f48c; 本专栏旨在记录C的学习路线&#xff0c;望对大家有所帮助&#x1f647;‍ 希望我们一起努力、成长&…

缓存穿透的解决办法有哪些?

一、概述 缓存穿透是指查询一个不存在的数据&#xff0c;由于缓存和数据库都没有命中&#xff0c;导致每次请求都需要从数据库中读取数据&#xff0c;增加了数据库的负担。解决缓存穿透的方法有以下几种&#xff1a; 布隆过滤器(Bloom Filter):使用位数组来表示一个集合&#…

iptables防火墙概念

iptables防火墙 一、iptables概述1.netfilter 与 iptables 的关系1&#xff09;netfilter2&#xff09;iptables 2.四表五链1&#xff09;四表2&#xff09;五链3&#xff09;表的匹配优先级4&#xff09;规则链之间的匹配顺序5&#xff09;规则链内的匹配顺序 二、iptables防火…

国外大神用 ChatGPT 成功打造一个「虚拟空间传送」系统!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 相信大家小时候躺在床上&#xff0c;都曾设想过这么一个场景&#xff1a; 当你闭上眼睛时&#xff0c;感觉身心十分安宁&#xff0c;物理世界慢慢淡出&#xff0c;身体也随着变得飘逸&…

【mysql】explain执行计划之select_type列

目录 一、说明二、示例2.1 simple&#xff1a;简单表&#xff0c;不使用union或者子查询2.2 primary&#xff1a;主查询&#xff0c;外层的查询2.3 subquery&#xff1a;select、where之后包含了子查询&#xff0c;在select语句中出现的子查询语句&#xff0c;结果不依赖于外部…

5.21下周黄金走势分析及开盘独家交易策略

近期有哪些消息面影响黄金走势&#xff1f;下周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周五(5月19日)美市尾盘&#xff0c;现货黄金收报1977.54美元/盎司&#xff0c;大幅上升19.99美元或1.02%&#xff0c;日内最高触及1984.22美元/盎司&#xff0c;最低…

【LeetCode: 10. 正则表达式匹配 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【工程化】记录在react工程中eslint、prettier等formatter以及git提交等规范的知识点

文章目录 前言创建eslint安装prettier安装.eslintrc.js完善独立的vscode设置到这一步要重启vscodehuskycommit-lint一切准备就绪&#xff0c;开干&#xff01; 前言 由于使用ACR的方式创建react工程时&#xff0c;并不会像vue一样有每一步的安装提示&#xff0c;需要我们在创建…

用爬虫分析沪深300指数超长走势

我们知道&#xff0c;一个股市里面有非常多的股票&#xff0c;我们如何能够量化整个股市整体的行情呢&#xff0c;答案是通过一些综合性的指数。本文所选用的沪深300就是这类指数中的一个。我们先来看一下百度百科对于沪深300的解释。 由于股票价格起伏无常&#xff0c; 投资者…

跟姥爷深度学习6 卷积网络的数学计算

一、前言 前面简单用TensorFlow的全连接网络做了气温预测然后深入了解了一下全连接网络的数学计算&#xff0c;接着用CNN&#xff08;卷积&#xff09;网络做了手写数字识别&#xff0c;本篇就接着这个节奏来看卷积网络的数学计算。 二、卷积网络回顾 前面我们使用卷积网络时…