C语言相关概念和易错语法(6)(字符串函数)

本文详细介绍了C语言中的字符串函数如strlen、strcpy、strncpy、strcat、strcmp、strncat、strtok、strerror和strstr,强调了参数的正确使用、空指针处理、字符数组大小和边界检查的重要性。
摘要由CSDN通过智能技术生成

前言:这篇博客主要用于字符串函数的实现,而淡化了相应的语法细节,具体的使用细节可以在C语言相关概念和易错语法(5)中查看,这篇博客中只会重复比较重要的。

size_t引用的头文件(选其一):<stddef> <stdio> <stdlib> <string> <time> <wchar>

NULL引用的头文件(选其一):<stddef> <stdlib> <string> <wchar> <time> <locale> <stdio>

字符串函数和内存函数共有注意事项:传的所有参数均不能为NULL

1.strlen

标准格式:size_t strlen(const char* str);    

(1)size_t是无符号数导致的陷阱

对strlen返回值的任何操作均为无符号数(正数),因此在遇到1-2这种算式时,size_t类型对应计算结果是个很大的数字,因为其补码首位并不代表符号位

315b04c893cc40dc919e6095b4d81ca2.png

当然,你可以使用强制类型转换来达到你原来想要达到的目的,但前提是你必须知道要这么做。

(2)strlen传递空指针导致程序的错误

strlen不能传入空指针,因此在自己实现strlen函数的同时要注意使用assert来进行断言,使函数更加安全

128da5ac16e84400a764c7a42695ae13.png

以下是利用递归模拟实现strlen函数的方法:


#include <stdio.h>

#include <assert.h>

size_t my_strlen(const char* arr)
{
	assert(arr);
	
	if (!*arr)
	{
		return (size_t)0;

	}
	else
	{
		return (size_t)1 + (size_t)my_strlen(arr + 1);
	}
}

int main()
{

	char arr[] = "Hello,world!";

	printf("%d\n", (int)my_strlen(arr));

	return 0;
}

(3)由strlen延伸的函数strnlen

标准格式:size_t  strnlen(const char* str, size_t num)

这个函数不太常用,但根据其它该类型的函数,你也可以猜出它的功能:

i有限定作用,决定strnlen计算大小的最大值

ea36cc547dae423da3fb65544b300df7.png

2.strcpy

标准格式:char * strcpy ( char * destination, const char * source )

返回的是char * destination中的char*

(1)这个函数需要注意的是在copy时源字符串包括\0都会被拷贝

(2)不能传NULL

在这里只有p1是空指针,p2是常量字符串

(3)目标数组一定要给大小且大小一定要合适,否则拷贝可能会出现越界访问的情况

可以看到,arr1被越界访问,原有字符全部被替换,\0也被#替换,导致printf在打印时也出现越界访问

下面是模拟实现strcpy的代码:


#include <stdio.h>

#include <string.h>

#include <assert.h>


char* my_strcpy(char* arr2, const char* arr1)
{

    assert(arr2 && arr1);
 
	char* str = arr2;

	while (*arr2++ = *arr1++);

	return str;
}


int main()
{

	char arr1[] = "Hello,World!";

	char arr2[20] = "####################";

	my_strcpy(arr2, arr1);

	printf("%s\n", arr2);

	return 0;


}

实现效果如下

需要注意的是,while括号内部的表达式都会正常执行,执行之后的返回值作为while评判的值,如果为0(假)就停止,这里当读到最后一个\0时,表达式先执行,所以arr1中的\0成功被赋给arr2,然后表达式返回0被判为假,跳出循环。

(4)strcpy延伸函数strncpy

标准格式char * strncpy ( char * destination, const char * source, size_t num )

注意:strncpy拷贝时有num参数确定要拷贝多少个字符,源字符串大小<num时,默认补\0,

中途遇到\0会停止对source的访问,默认将剩下要求的个数的元素全部改为\0

源字符串大小>num时,不会拷贝\0,这点要和strcpy区分

3.strcat

标准格式:char * strcat ( char * destination, const char * source );

根据这3个函数,可以总结出参数部分一般源在后,目标在前

(1)strcat覆盖目标字符串\0,并将源字符串包括\0一并追加过来,之后停止 strncat回将指定大小的字符追加过来,但最后还会补一个\0,追加的大小相当于num+1,如果追加的个数刚好是sizeof arr,最后会有两个\0,因为无论什么情况,strcat都会再加上一个\0

(2)追加找的是destination的\0,如果在你的字符串中有\0,会直接从\0开始追加并把后面的值全部覆盖

(3)destination一定要指定大小,不然很大可能会越界访问

下面是模拟实现strcat的代码


#include <assert.h>


#include <stdio.h>


#include <string.h>


char* my_strcat(char* arr1, const char* arr2)
{

	assert(arr1 && arr2);

	int i = 0;

	int j = 0;

	int sz1 = (int)strlen(arr1);

	int sz2 = (int)strlen(arr2);


	for (i = 0; i < sz1; i++)
	{
		if (!arr1[i])
			break;

	}//i就是要追加的位置,也表示前面有多少个字符


		while (arr1[i++] = arr2[j++]);


	return arr1;

}


int main()
{

	char arr1[20] = "Hello,";

	char arr2[] = "World!";

	printf("%s\n", my_strcat(arr1, arr2));


	return 0;
}

(4)strcat延伸函数strncat

标准格式

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

strncat中追加的num源字符串大小时,只会将源字符串到\0追加,不会再多加\0

其他功能均与前面的函数相似,不再阐述

4.strcmp

标准格式:

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

(1)arr1 - arr2,大于就返回大于0的数,小于就返回小于0的数

(2)strcmp不是比较字符串的长度,而是比较对应下标字符的ASCII码值谁大。比如"abcdef"和"acb",后面的字符串比前面的字符串大,因为同为下标1时,c的ASCII值比b大。

下面是strcmp的模拟实现



#include <assert.h>


#include <stdio.h>


#include <string.h>


int my_strcmp(const char* arr1, const char* arr2)
{

    assert(arr1 && arr2);

	int i = 0;

	int j = 0;//不能只用一个i,arr1[i++] == arr2[i++]这种行为未定义,属于无效代码

	while (arr1[i++] == arr2[j++])//当两者不等时,i和j依然会继续进行一次++操作,因此i和j在任何情况下都会大1
	{
		if (!arr1[i - 1]|| !arr2[i - 1])//防止越界访问,这里i已经多加了一个1,所以是i-1,不能用i--,这样会导致i连续减2次
			break;

	}

	i--;

	j--;

	return arr1[i] - arr2[j];
}




int main()
{
	char arr1[] = "Hello!";

	char arr2[] = "Hola!";

	printf("%d\n", my_strcmp(arr1, arr2));


	return 0;
}

(3)strcmp的延伸函数strncmp

标准格式:

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

strncmp只比较num个字符,这个函数比较简单,不再阐述

5.strtok

标准格式:

char * strtok ( char * str, const char * delimiters );

(1)这个函数要学会使用,主要用于分割字符串,达到分离信息的目的,可借助for的巧妙之处一次性达到目的

(2)分割过程忽略连续的分隔字符,且分隔字符串中字符的顺序没有任何影响

(3)分割字符串中最好不要有\0,\0之后的字符不会起效

同样,被切割字符串也要注意\0的位置,\0之后的字符都会被忽略

下面是strtok的模拟实现:



#include <stdio.h>


#include <string.h>


#include <assert.h>


char* my_strtok(char* arr1, const char* arr2)
{
	assert(arr2);//arr1可以是NULL

    //当第一次传进来arr1时,初始化一个静态变量

	static char* str = NULL;

	if (!arr1)
	{
		arr1 = str;
	}

	int sz1 = (int)strlen(arr1);

	int sz2 = (int)strlen(arr2);//字符串中最好都不要\0,strlen有限制


	if (!sz1)
		return NULL;
	
	int i = 0;

	for (i = 0; i < sz1; i++)
	{
		int j = 0;


		for (j = 0; j < sz2; j++)
		{

			if (arr1[i] == arr2[j])//在i这个位置应该分割
			{
	
				arr1[i] = '\0';

				str = arr1 + i + 1;//跳过\0,记录新的字符串的起始点

				return arr1;

			}

		}

	}

	str++;

	return arr1;

}




int main()
{
	char* p = NULL;

	char arr1[] = "192.168.200.2";

	char arr2[] = ".";

	for (p = my_strtok(arr1, arr2); p != NULL; p = my_strtok(NULL, arr2))
	{
		printf("%s\n", p);

	}

	return 0;
}

6.strerror

标准格式:

char * strerror ( int errnum );

这个函数是用来储存错误信息(在调用完一次库函数的时候),我们可以利用它来获取在调用库函数的过程中可能遇到的一些问题,一般来说要和errno配合使用,因为库函数的错误码是写在errno这个全局变量上的,注意要引用头文件<errno.h>

下面用一段模拟库函数给errno赋值的代码来说明strerror的作用:


#include <errno.h>

#include <stdio.h>

#include <string.h>

int Fun(void* p)
{

	if (!p)
	{
		errno = 1;

		return -1;

	}

	else
	{
		errno = 0;

		return 0;

	}

}


int main()
{
	int i = 0;

	Fun(NULL);

	printf("%s\n", strerror(errno));

	Fun((void*) & i);

	printf("%s\n", strerror(errno));


	return 0;
}

运行的结果是

这个错误信息是我自己设置的,因此打印的时候就会显示我设置的错误码对应的信息,在我们使用的库函数的内部,都会有这样一个操作,在当库函数执行异常时修改errno的值,以供程序员参考。值得注意的是,errno存在覆盖现象,所以要注意及时使用。

7.strstr

标准格式:

const char * strstr ( const char * str1, const char * str2 );

char * strstr ( char * str1, const char * str2 );

strstr是找到参数中后一个字符串在前一个字符串中出现的位置,找不到就返回NULL,找到就返回第一次出现的首元素的首地址

下面是模拟实现strstr的一个方法:



#include <stdio.h>


#include <string.h>


#include <assert.h>


const char* my_strstr(const char* arr1, const char* arr2)
{

	assert(arr1 && arr2);

	int sz1 = (int)strlen(arr1);

	int sz2 = (int)strlen(arr2);

	if (sz2 > sz1)
		return NULL;

	if (sz1 == sz2)
	{
		if (arr1[0] != arr2[0])
			return NULL;
	}

	if (!sz2)
		return arr1;

	

	int i = 0;

	for (i = 0; i < sz1; i++)
	{
		int j = 0;

		int k = 0;


		for (j = 0, k = 0; j < sz2; j++, k++)
		{
			if (arr1[i + k] != arr2[j])
				break;
			if (k == sz2 - 1 && arr1[i + k] == arr2[j])
				return (const char*)arr1 + i;


		}


	}

	return NULL;


}

int main()
{


	char arr1[] = "Hello,World!";

	char arr2[] = "W";

	printf("%s\n",my_strstr(arr1, arr2));

	return 0;
}

在这里,我使用的是双重for的方式,当然,你也可以使用双指针来实现这个函数,它们的本质都是BF算法,还有一种KMP算法更加高效,后面的博客我会专门讲解。

代码上的注意事项:

在前面strcpy,strcmp我都采用了一种基于while(表达式)来实现一定的技巧。这其中有一点容易混淆:

先看下面这段代码,思考为什么num是-1而不是0

这就涉及到后置减的执行逻辑了,这里比较细节,但又确实很重要

(1)num值2传给while作为表达式的返回值,为真,但在进行下一步操作前(执行while内部表达式之前)num--为1

(2)执行了内部表达式(空表达式)num的值依然是1,这时num再传给while值1作为表达式的返回值,为真,但在进行下一步表达式之前num--为0

(3)在执行完空表达式后num的值是0,这时传给while值0作为表达式的返回值,为假,应该跳出循环,但就像前面两步一样,在进行“跳出循环”操作前,num--为-1,于是,num在经历这样的操作后最终值是-1

其根本原因是num--是后置减,返回值是在减操作前就传过去了,然后紧接着就会进行减操作,该操作和前面的传回表达值的操作相绑定,也就是说无论外界什么情况,无论发生什么,num最终都会减1

  • 30
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值