C语言字符函数和字符串函数详解

引言:

在编程的过程中,我们经常会遇到要处理字符和字符串相关的场景,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数,这篇我们来细致的讲一讲C语言中和字符串或字符有关的库函数的使用以及模拟实现

那么,话不多说,接下来我们进入字符函数和字符串函数详解正篇------------------>

​​​​​​​

1.字符分类函数

C语言中有一系列函数是来进行字符类型判断的,如果是对应类型就返回真,反之则返回0,

这些函数的使用都需要包含一个头文件:ctype.h

函数 如果他的参数符合下列条件就返回真

iscntrl 任何控制字符

isspace 空白字符:空格,换页'\f',换行'\n',回车'\r',水平制表符'\t',垂直制表符'\v'

isdigit 十进制数组0-9

isxdigit 十六进制数组,包括所有十进制数组,小写字符a-f和大小字符A-F

islower 小写字符a-z

isupper 大写字符A -Z

isalpha 字母a-z或A-Z

isalnum 字母或数字,a-z,A-Z,0-9

ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)

isgraph 任何图形字符

ispint 任何可打印字符,包括图形字符和空白字符

这些函数使用起来很简单,我们随便举个例子来看,比如如下代码

cpp 复制代码
int islower(int c);

islower是能够判断参数部分的c是否是小写字母的,通过返回值来说明是否是小写字母,当然,这个判断是判断ASCII值

我们用现在所学的这个来实现一个功能:将字符串中的小写字母转大写,其他字符不变

放在之前我们会先判断变量是不是在a到z之间,然后如果在的话再-32,但用函数实现的话就不用写那么多了,代码如下

cpp 复制代码
#include<stdio.h>
#include<ctype.h>

int main()
{
    int i = 0;
    char a[] = "Hello World.";
    while(a[i])
    {
        char b = a[i];
        if(islower(b))
            b-=32;
        putchar(b);
        i++;
    }
    return 0;
}

2.字符转换函数

C语言提供了俩个字符转换函数,分别是将大写转小写和小写转大写,如下

int tolower( int c ) //大写转小写

int toupper( int c ) //小写转大写

这俩个函数在转换前会先判断一下,如果不是小写也不是大写就不会进行转换

上面的代码中,我们将小写转成大写是用-32来实现的,那么写了这个后,我们直接用tolower就可以了,代码如下

cpp 复制代码
#include<stdio.h>
#include<ctype.h>

int main()
{
    int i = 0;
    char a[] = "Hello World.";
    while(a[i])
    {
        char b = a[i];
        if(islower(b))
            b = toupper(b)
        putchar(b);
        i++;
    }
    return 0;
}

上面这俩个知识点其实只要了解就行了,接下来,各个字符串函数的使用和模拟实现才是重头戏


3.strlen函数的使用和模拟实现

cpp 复制代码
size_t strlen( const char* str )

这就是个统计字符串长度的函数

3.1.strlen函数的使用

我们都知道,字符串是以\0作为结束标志的,strlen函数返回的是在字符串中\0前面出现的字符个数(不含\0)

参数指向的字符串必须要以\0结束,不然就会越界访问了,导致输出随机值

注意函数的返回值是size_t类型的,是无符号的(特别重要)

为什么第三点很重要呢,我们来看下面这个代码

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
    const char* str1 = "abcdef";
    const char* str2 = "bbb";
    if(strlen(str2)-strlen(str1)>0)
    {
        printf("str2>str1\n");
    } 
    else
    {
        printf("srt1>str2\n");
    }
    return 0;
}

这个代码会输出什么呢,输出的是str2>str1,为什么呢,我们来分析

首先strlen(str2)是3,strlen(str1)是6,那么,3-6是等于-3的,但是这俩个类型都是size_t类型的,那么相减的结果也是size_t类型的,那么,因为size_t类型是无符号类型 ,所以-3的那个符号位会当成正数来看,所以if的表达式会判为真,所以就输出str2>str1


3.2.strlen函数的模拟实现

strlen的模拟实现我这边讲三种方式

方式一:通过指针往后遍历顺便把个数统计出来,代码如下

cpp 复制代码
size_t my_strlen(const char * str)
{
    size_t count = 0;
    assert(str);
    while(*str)
    {
        count++;
        str++;
    }
    return count;
}

方法二:不创建新变量,用递归的方式来统计,代码如下

cpp 复制代码
size_t my_strlen(const char * str)
{
    assert(str);
    if(*str == '\0')
        return 0;
    else
        return 1+my_strlen(str+1);
}

方法三 :用指针-指针的方式来统计中间元素个数,代码如下

cpp 复制代码
size_t my_strlen(char *s)
{
    assert(str);
    char *p = s;
    while(*p != '\0' )
    p++;
    return p-s;
}

4.strcpy函数的使用和模拟实现

cpp 复制代码
char* strcpy(char * destination, const char * source );

这就是个将第二个参数的那个函数拷贝到第一个参数的函数的函数

4.1.strcpy函数的使用

源字符串必须以\0结束

这个函数会讲源字符串的\0拷贝到目标空间

目标空间要保证足够大,大到可以存放源字符串

目标空间必须可修改,不要给目标空间加const之类的

那么,规则讲完了,我们来使用一下这个函数

cpp 复制代码
int main()
{
	char a[20] = { 0 };
	char b[] = "dsawdsadw";
	printf("%s", strcpy(a, b));

	return 0;
}

代码运行结果如下 ,确实是拷贝过去了,为什么能这么输出呢 ,因为这个函数返回的是目标空间的首地址

我们也可以来验证下这个拷贝会不会把\0拷贝过去,我们把目标空间先初始化成全x,看拷贝完输出还会不会有x,代码如下

cpp 复制代码
int main()
{
	char a[20] = "xxxxxxxxxxxxxxxxxx";
	char b[] = "dsawdsadw";
	printf("%s", strcpy(a, b));

	return 0;
}

我们看运行结果可以发现,确实是把\0拷贝过去了

4.2.strcpy函数的模拟实现

模拟实现就直接放代码啦

cpp 复制代码
#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* p1, const char* p2)
{
	assert(p1 && p2);
	char* cnt = p1;
	while (*p1++ = *p2++);
	return cnt;
}

int main()
{
	char a[] = "wdaszwdassdsa";
	char b[20] = "xxxxxxxxxxxxxxxxxx";
	printf("%s", my_strcpy(b, a));

	return 0;
}

5.strcat函数的使用和模拟实现

5.1.strcat函数的使用

这个函数就是对字符串进行追加,比如strcat(a,b)就是把a字符串尾部再接上b字符串

这个函数的返回值就是a字符串的首地址

源字符串必须以\0结束

目标字符串中也要有\0,否则没办法知道追加从哪里开始,因为这个函数追加的开始就是目标字符串中的\0

目标空间必须可修改

我们来举个运用strcat函数的例子,代码如下

cpp 复制代码
int main()
{
	char a[40] = "xxxxxxxxxxxxxxxxxx";
	char b[] = "dsawdsadw";
	printf("%s", strcat(a, b));

	return 0;
}

运行代码如下图

我们也可以验证下追加是从哪里开始的,如下代码

cpp 复制代码
int main()
{
	char a[40] = "xx\0xxxxxxxxxxxxxxxx";
	char b[] = "dsawdsadw";
	printf("%s", strcat(a, b));

	return 0;
}

我们可以发现是从目标字符串的第一个\0开始进行追加的

那么,这个追加函数可不可以自己追加自己呢,我们来试一下,代码如下

cpp 复制代码
int main()
{
	char a[40] = "xxxawdwaa";
	printf("%s", strcat(a, a));

	return 0;
}

运行后结果如下,所以库函数的strcat自己追加自己是不行的

为什么不能自己追加自己,因为这个库函数底层是从前往后进行更新的,如果要自己追加自己,那么找到\0后这个\0是已经被改了,但是指针遍历到需要是\0的地址的时候,\0已经被改了,所以就会一直死循环了,当然这个也是可以优化的,之后模拟实现我尝试了一个新方式来优化

接下来strcat函数的模拟实现我会分俩块,一个是基本版本(不能自己追加自己),另一个是改良版(可以自己追加自己)

5.2.strcat函数的模拟实现

基本版本:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>

char* my_strcat(char* p1, char* p2)
{
	assert(p1 && p2);
	char* cnt = p1;
	while (*++p1);
	while (*p1++ = *p2++);
	return cnt;
}


int main()
{
	char a[20] = "hello ";
	char b[] = "world";
	printf("%s", my_strcat(a, b));
	return 0;
}

改良版:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>

char* my_strcat(char* p1, char* p2)
{
	assert(p1 && p2);
	char* cnt = p1;
	int ge = 0;
	char* lin = p2;
	while (*p1++);
	while (*p1++ = *++p2)
		ge++;
	*(p1 - ge - 2) = *lin;
	return cnt;
}


int main()
{
	char a[40] = "hello ";
	char b[] = "world";
	printf("%s\n", my_strcat(a, b));
	printf("%s", my_strcat(a, a));
	return 0;
}

6.strcmp函数的使用和模拟实现

6.1.strcmp函数的使用

strcmp函数就是比较字符串大小的一个库函数

例如strcmp(a,b),如果a字符串比b字符串大,那就返回大于0的数,如果a字符串比b字符串小 ,就返回小于0的数,如果俩个字符串相等 ,则返回0。(在VS编译器中是返回1,0,-1)这俩种实现方式都会在模拟实现中写出来

因为这个使用过于简单了,就不释放怎么使用了

6.2.strcmp函数的模拟实现

返回1,0,-1的版本

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>

int my_strcmp(const char* p1, const char* p2)
{
	assert(p1 && p2);
	while (p1 || p2)
	{
		if (*p1 > *p2)
			return 1;
		if (*p1 < *p2)
			return -1;
		p1++;
		p2++;
	}
	return 0;
}


int main()
{
	char a[] = "hello ";
	char b[] = "world";
	printf("%d\n", my_strcmp(a, b));
	return 0;
}

返回1,0,负数的版本

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>

int my_strcmp(char* p1, char* p2)
{
	assert(p1 && p2);
	while (*p1 == *p2)
	{
		if (*p1 == 0)
			return 0;
		p1++;
		p2++;
	}
	return *p1 - *p2;
}


int main()
{
	char a[] = "hello ";
	char b[] = "world";
	printf("%d\n", my_strcmp(a, b));
	return 0;
}

7.strncpy函数的使用和模拟实现

我们已经学了strcpy函数,strcat函数和strcmp函数后,接下来,我们学三个功能一样,但相对安全的函数,那便是strncpy函数,strncat函数和strncmp函数,这三个函数相比前三个函数可以自由控制操作源字符串的前几个字符,所以后面这三个函数使用时要多一个参数,用来表示操作前几个字符串元素

那么,我们先来讲strncpy这个函数

cpp 复制代码
char * strncpy ( char * destination, const char * source, size_t num );

7.1.strncpy函数的使用

上面那个函数意思就是strncpy的声明,意思就是拷贝num个字符从源字符串到目标空间,返回的就是目标字符串的首地址

如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加\0,知道num个

那么,我们来看看怎么使用这个函数,代码如下

cpp 复制代码
int main()
{
	char a[20] = "xxxxxxxxxxxxxxxxxx";
	char b[] = "dsawdsadw";
	printf("%s", strncpy(a, b,3));

	return 0;
}

运行结果如下图

7.2.strncpy函数的模拟实现

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>

int my_strncmpy(char* p1, char* p2,int len)
{
	assert(p1 && p2);
	char* cnt = p1;
	while ((*p1++ = *p2++) && --len);
	while ((len--)>0)
	{
		*p1++ = 0;
	}
	return cnt;
}


int main()
{
	char a[20] = "hello ";
	char b[] = "world";
	printf("%s\n", my_strncpy(a, b, 2));
	return 0;
}

8.strncat函数的使用和模拟实现

8.1.strncat函数的使用

cpp 复制代码
char * strncat ( char * destination, const char * source, size_t num );

这个也是一样,就是讲源字符串的前num个字符追加到目标字符串的末尾,再追加\0字符

这个函数的返回值是目标字符串的首地址

如果num比源字符串的长度还大的话,那么就会将所有的源字符串全部追加到目标字符串后面就结束了,这边就拿个代码举例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main ()
{
    char str1[20];
    char str2[20];
    strcpy (str1,"To be ");
    strcpy (str2,"or not to be");
    strncat (str1, str2, 6);
    printf("%s\n", str1);
    return 0;
}

8.2.strncat函数的模拟实现

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>

char* my_strncat(char* p1, char* p2,int len)
{
	assert(p1 && p2);
	char* cnt = p1;
	char* lin = p2;
	int xixi = 1;
	int ge = 0;
	len--;
	while (*p1++);
	while (len && (*p1++ = *++p2))
	{
		xixi = 0;
		len--;
		if (len <= 0)
			break;
		ge++;
	}
	if (!len && xixi)
		p1++;
	*(p1 - ge - 2) = *lin;
	return cnt;
}


int main()
{
	char a[20] = "hello ";
	char b[] = "world";
	printf("%s\n", my_strncat(a, b, 2));
	return 0;
}

9.strncmp函数的使用和模拟实现

9.1.strncmp函数的使用

cpp 复制代码
int strncmp ( const char * str1, const char * str2, size_t num );

比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提前发现不一样,就返回对应的情况,前面举例了这么多库函数的使用,这边就不举例如何使用了

9.2.strncmp函数的模拟实现

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>

int my_strncat(const char* p1,const char* p2,int len)
{
	assert(p1 && p2);
	while (*p1 == *p2 && len--)
	{
		if (!len)
			return 0;
		p1++;
		p2++;
	}
	return *p1 - *p2;
}


int main()
{
	char a[20] = "hello ";
	char b[] = "world";
	printf("%d\n", my_strncmp(a, b, 3));
	return 0;
}

10.strstr函数的使用和模拟实现

10.1.strstr函数的使用

这个函数就相当于是字符串匹配函数

比如strstr(a,b),这个就是找a字符串中是否有b这个子串,如果找到了,就返回b字符串第一次出现在a字符串的首地址,如果没找到就返回空指针

字符串的比较匹配不包含\0,以\0作为结束标志,所以如果b是空字符串的话,返回的就是a的首地址

具体使用就不讲了,接下来进入模拟实现环节

10.2.strstr函数的模拟实现

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>

char* my_strstr(const char* p1, const char* p2)
{
	char* str = p1;
	char* s1, * s2;
	if (!*p2)
		return str;
	while (*str)
	{
		s1 = str;
		s2 = p2;
		while (*s1 && *s2 && !(*s1 - *s2))
		{
			s1++;
			s2++;
		}
		if (!*s2)
			return str;
		str++;
	}
	return NULL;
}


int main()
{
	char a[20] = "sawdsad dawd";
	char* p = "d d";
	char* p1 = my_strstr(a, p);
	if (*p1)
		printf("%s", p1);
	else
		printf("没找到");
	return 0;
}

11.strtok函数的使用

cpp 复制代码
char * strtok ( char * str, const char * sep);

这个函数就是用来分割字符串的,比如strtok(a,b),如果a字符串是"2213@qq.com",b字符串是"@.",就说明分割的标记是@和. 只要是这俩个字符,就满足分割,把a字符串的标记字符写成\0

改成\0后,会保存当前\0的位置,然后返回进来时候的位置,就以2213@qq.com为例

首先我们使用一次strtok(a,b),此时返回的地址是首元素的地址,还会把@改成\0变成2213\0qq.com,我们用返回的值来打印的话就会得到2213

之后我们再用一次strtok,因为已经用过一次了,所以我们要用strtok(NULL, b),此时就是从\0的下一个位置开始往下找,找到第一个标记符后,改成\0,就是2213\0qq\0com,此时返回的地址就是q这个地址,所以我们用返回的地址来打印字符串的话就是qq

最后再进行一次strtok(NULL,b),此时因为到\0了也没有下一个标记符了,所以返回存的位置,输出的话就是com,因为没有下一个标记符了,所以再用这函数就会返回NULL

我们来总结一下

sep参数指向一个字符串,定义了作为分隔符的字符集合

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

strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时 拷贝的内容并且可修改)

strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置

strtok函数的第一个参数为NULL,函数 将在同一个字符串中被保存的位置开始,查找下一个标记

如果字符串中不存在更多的标记,则返回NULL指针

使用示例如下

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
    char arr[] = "192.168.6.111";
    char* sep = ".";
    char* str = NULL;
    for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
    {
        printf("%s\n", str);
    }
    return 0;
}

12.strerror函数的使用

cpp 复制代码
char * strerror ( int errnum );

strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。

什么意思呢,其实每个错误信息,在计算机中都用数存着,每个数都代表着一种错误信息,我们用代码来打印一下每个数字对应的错误信息

cpp 复制代码
#include<errno.h>
#include<string.h>
#include<stdil.h>

int main()
{
    int i = 0;
    for(i = 0;i<=10;i++)
    {
        printf("%s\n",strerror(i));
    }
    return 0;
}

这个代码在VS2022环境下的结果如下

0就代表没有错误

在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在errno.h这个头⽂件中说明 的,C语⾔程序启动的时候就会使用⼀个全⾯的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会讲对应的错误码,存放在errno中,而⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。

errno的使用举例:

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
    FILE * pFile;
    pFile = fopen ("unexist.ent","r");、//以读的形式打开文件
    if (pFile == NULL)//打开失败了
        printf ("Error opening file unexist.ent: %s\n", strerror(errno));
    return 0;
}

输出:

想要报错误信息还有更简单的,就是perror函数,perror函数是先将参数的字符串打印出来,再打印一个冒号和一个空格,随后直接将错误信息打印出来,如果传过去的是空字符串,就直接打印错误信息,这个有兴趣的可以了解下,这里就不过多赘述了


结语:

希望以上内容对你有所帮助,感谢观看,若觉得写的还可以,可以分享给朋友一起来看哦,毕竟一起进步更有动力嘛

相关推荐
CodeCraft Studio2 小时前
JavaScript图表库 DHTMLX Diagram 6.1 重磅发布:全新PERT模式上线,项目可视化能力再升级!
开发语言·javascript·ecmascript·dhtmlx·图表开发·diagram·javascript图表库
Dxy12393102162 小时前
Python的正则表达式如何做数据校验
开发语言·python·正则表达式
UP_Continue2 小时前
C++--右值和移动语义
开发语言·c++
222you3 小时前
Java线程的三种创建方式
java·开发语言
云上漫步者3 小时前
深度实战:Rust交叉编译适配OpenHarmony PC——unicode_width完整适配案例
开发语言·后端·rust·harmonyos
漫漫求3 小时前
Java内存模型【JMM】、JVM内存模型
java·开发语言·jvm
田姐姐tmner3 小时前
Python 全面语法指南
开发语言·python
white-persist3 小时前
【攻防世界】reverse | simple-check-100 详细题解 WP
c语言·开发语言·汇编·数据结构·c++·python·算法
wuguan_3 小时前
C#中的静态成员、常量和只读变量
开发语言·c#