字符函数及字符串函数

目录

[1 · 字符分类函数](#1 · 字符分类函数)

[2 · 字符转换函数](#2 · 字符转换函数)

[3 · strlen](#3 · strlen)

[4 · strcpy](#4 · strcpy)

[5 · strcat](#5 · strcat)

[6 · strcmp](#6 · strcmp)

[7 · strncpy](#7 · strncpy)

[8 · strncat](#8 · strncat)

[9 · strncmp](#9 · strncmp)

[10 · strstr](#10 · strstr)

[11 · strtok](#11 · strtok)

[12 · strerror](#12 · strerror)

总结


在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数。


1 · 字符分类函数

C语言中有一系列的函数是专门做字符分类的,也就是⼀个字符是属于什么类型的字符的。
这些函数的使用都需要包含⼀个头文件是 ctype.h
如下:

这些函数的使用方法都是类似的,我们随便挑一个来使用一下试试:

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

int main()
{
	char c = 0;
	scanf("%c", &c);
	if (islower(c))
	{
		c -= 32;
	}
	printf("%c\n", c);
}

效果是输入一个字符并打印,如果输入的是小写字符则会转换成大写字符再打印:

这里用的是 islower ,判断参数部分是否是小写字母,如果是,则返回一个非0值,如果不是则返回0。


2 · 字符转换函数

C语言提供了两个字符转换函数

复制代码
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写

这两个函数也需要包含头文件ctype.h 。

那么我们上面写的小写字母转大写就可以这样写:

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

int main()
{
	char c = 0;
	scanf("%c", &c);
	if (islower(c))
	{
		c = toupper(c);
	}
	printf("%c\n", c);
}

3 · strlen

strlen 是用来求字符串长度的函数,使用需包含头文件 string.h

原型如下:

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

strlen 会统计传参的字符串中 第一个 '\0' 前的字符个数,并作为返回值返回。(不包含'\0')。

注意:strlen 的返回值是一个无符号的整型数据。

所以 strlen - strlen 的结果也是个无符号数,就算相减得负数,也会被当作一个无符号数,将符号位的 1 当作数值位来计算,结果是大于0的。

下面我们模拟实现一下 strlen :

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

int MyStrlen1(const char* str)
{
	assert(str);
	int count = 0;
	while (*(str++))
	{
		count++;
	}
	return count;
}
int MyStrlen2(const char* str1)
{
	assert(str1);
	const char* str2 = str1;
	while (*(++str2))
	{
		;
	}
	return str2 - str1;
}

int MyStrlen3(const char* str)
{
	assert(str);
	if (*str == '\0')
	{
		return 0;
	}
	else
	{
		return 1 + MyStrlen3(str + 1);
	}

}

int main()
{
	printf("%d\n", strlen("abcdef"));
	printf("%d\n", MyStrlen1("abcdef"));
	printf("%d\n", MyStrlen2("abcdef"));
	printf("%d\n", MyStrlen3("abcdef"));
	return 0;
}

运行一下:

这里我们用了三种方式来模拟,分别是 创建变量计数,指针-指针,递归。


4 · strcpy

strcpy 是用来进行字符串拷贝的 ,使用需包含头文件 string.h

我们可以去官网查查这个函数:

原型如下:

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

可以看到,需要两个参数,前一个是目的地,后一个是源头,有返回值,返回值是目标空间的起始地址。

效果是将源头的内容拷贝到目的地中(包括 '\0')。

并且还需要保证 source 的源字符串以 '\0' 结尾, destination 指向的空间比打算拷贝的 源字符串 的空间大。

当然,我们可能还有疑虑,如果我们的目的地已有数据,那么会怎样呢,以及如果目的地的空间不足,又会怎样呢?

我们可以调试看看:

覆盖原字符串:

可以看到,如果原本就有数据,并且长于需要拷贝的字符串,那么会覆盖。

目的地不足

可以看到,依然会一个一个拷贝,并且会越界。

下面我们模拟实现一下:

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

char* MyStrcpy(char* des, const char* sou)
{
	assert(des);
    assert(sou);
	char* n = des;
	while (*sou)
	{
		*des = *sou;
		des++;
		sou++;
	}
	*des = *sou; //将'\0'进行拷贝
	return n;
}

int main()
{
	char arr[20] = { "xxxxxxxxxxx"};
	strcpy(arr, "abc");
	printf("strcpy后:%s\n", arr);
	MyStrcpy(arr,"efgh");
	printf("MyStrcpy后:%s\n", arr);
	return 0;
}

MyStrcpy 也可以这样写:

cpp 复制代码
char* MyStrcpy(char* des, const char* sou)
{
	assert(des);
	assert(sou);
	char* n = des;
	while (*des++ = *sou++)
	{
		;
	}
	return n;
}

运行一下看看:

由于strcpy 的返回值是目标空间的起始地址,而我们写的代码中 des 会变化,所以建立了一个变量先存储 des 起始地址,最后返回n 。


5 · strcat

strcat 是用来进行字符串的连接的。使用需包含头文件 string.h

我们可以去官网查查:

原型如下:

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

可以看到,有两个参数,前一个是目的地,后一个是源字符串,有返回值,返回值是目的地 destination 的首元素地址。

效果是将源字符串接到 目的地字符串的后面,从目的地字符串的 '\0' 开始接(包括'\0')。

注意:

目的地处字符串必须要有 '\0',不然不知道从哪开始接

源字符串也需要有 '\0'

目的地的空间要足够大,能容纳下源字符串

目标空间必须可修改

当然,我们可能还有疑虑 目标空间不够会怎么办 , 能不能多次追加,自己追加自己会怎么样。

目标空间不够其实和 strcpy 差不多,会继续往后接,并且会越界。

多次追加和自己追加自己:

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

int main()
{
	char arr1[20] = { "abcd" };
	char arr2[20] = { "xyz" };
	strcat(arr1, "efg");
	printf("第一次追加:%s\n", arr1);
	strcat(arr1, "efg");
	printf("第二次追加:%s\n", arr1);
	strcat(arr2, arr2);
	printf("自己追加自己:%s\n", arr2);
	return 0;
}

运行一下看看:

下面我们来模拟实现一下:

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

char* MyStrcat(char* des, const char* sou)
{
	assert(des);
	assert(sou);
	char* p = des;
	while (*++des)
	{
		;
	}
	while (*sou)
	{
		*des = *sou;
		sou++;
		des++;
	}
	return p;
}

int main()
{
	char arr1[20] = { "abcd" };
	char arr2[20] = { "xyz" };
	MyStrcat(arr1, "efg");
	printf("第一次追加:%s\n", arr1);
	MyStrcat(arr1, "efg");
	printf("第二次追加:%s\n", arr1);
	//MyStrcat(arr2, arr2);//做不到,会死循环
	//printf("自己追加自己:%s\n", arr2);
	return 0;
}

MyStrcat 也可以写成:

cpp 复制代码
char *my_strcat(char *des, const char*sou)
{
 assert(dest != NULL);
 assert(src != NULL);
 char *p = des;
 while(*des)
 {
     des++;
 }
 while((*des++ = *sou++))
 {
     ;
 }
 return p;
}

运行一下看看:

我们写的这个模拟实现能够完成追加另一个字符串的任务,但是无法完成自己追加自己,会死循环。而库函数中的 strcat 是可以做的自己追加自己的。


6 · strcmp

strcmp 是用来进行字符串的比较的,使用需包含头文件 string.h

我们可以去官网查查:

原型如下:

复制代码
int strcmp ( const char * str1, const char * str2 );

从str1 和 str2 的首字符开始比较,如果相同就比较下一个,直到相异或同为 '\0'。

比较的是 ASCII 码值。

第一个字符串大于第二个字符串,返回大于0的数字。

第一个字符串等于第二个字符串,返回0。

第一个字符串小于第二个字符串,返回小于0的数字。

strcmp 我们上一篇中有提到过,下面我们来模拟实现一下:

复制代码
int MyStrcmp(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	while (*str1 == *str2)
	{
		if (str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

7 · strncpy

作用和 strcpy 差不多,只是长度受限。使用需包含头文件 string.h

原型如下:

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

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


8 · strncat

作用和 strcat 差不多,只是长度受限。使用需包含头文件 string.h

原型如下:

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

从 目标字符串的 '\0' 开始(包括'\0'),链接 源字符串中前num个字符,并在结尾补上一个'\0'。
如果源字符串的长度小于num,只会将字符串中到 '\0 '的内容追加到destination指向的字符串末尾。
源字符串长度大于num 为了看是不是函数加上的 '\0' , 我们赋值一个 '\0'

源字符串长度小于 num 的情况:


9 · strncmp

作用和 strcmp 差不多,只是长度受限。使用需包含 string.h

原型如下:

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

比较str1 和 str2 的前 num 个字符 ,如果相等就往后比较,最多比较num 个字符。如果前num 个字符都相等,则返回0。


10 · strstr

作用是查找字符串。使用需包含头文件 string.h

我们可以去官网查查:

原型如下:

复制代码
const char * strstr ( const char * str1, const char * str2 );

去官网我们会看到有两个原型,但是在 C语言中,只有我们上面的这个原型。

在 str1 字符串中搜索 str2 字符串(不包括 str2 字符串中作为结束的 '\0')

如果找到了,返回 str1 中首次出现str2 的地址,如果没找到,返回NULL。

下面我们模拟实现一下:

cpp 复制代码
const char* MyStrstr(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	char* tmp = (char*)str1;
	char* p1 = (char*)str1;
	char* p2 = (char*)str2;
	if (!*p2)
	{
		//如果是str2 是 "\0" 直接返回
		return tmp;
	}
	while (*tmp)
	{
		p2 = (char*)str2;
		if (*tmp == *p2)
		{
			p1 = tmp;
			while (*p2 && *p1)
			{
				if (*p1 == *p2)
				{
					p1++;
					p2++;
				}
				else
				{
					break;
				}
				if (*p2 == '\0')
				{
					return tmp;
				}
			}
		}
		tmp++;
	}
	return NULL;
}

也可以这样写:

cpp 复制代码
const char* MyStrstr(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	char* tmp = (char*)str1;
	char* p1 = (char*)str1;
	char* p2 = (char*)str2;

	if (!*p2)
	{
		//如果是str2 是 "\0" 直接返回
		return tmp;
	}
	while (*tmp)
	{
		p1 = tmp;
		p2 = (char*)str2;
		while (*p1 && *p2 && !(*p1-*p2))
		{
			p1++;
			p2++;
		}
		if (*p2 == '\0')
		{
			return tmp;
		}
		tmp++;
	}
	return NULL;
}

由于我们的 str1 和 str2 不可修改,所以我们新建一个变量,用来遍历 str1,再建两个变量用来比较,p1 指向当前遍历的位置 ,p2 指向要查找的字符串的首元素。

如果 *p1 == *p2 ,则让 p1 p2 继续向后比较 相同继续,相异退出本次比较,然后继续遍历,由于我们的 p1 p2 可能发生变化了 所以每次循环要先将p1 p2 置为它们应在的位置。

如果p1 p2 比较过后,p2 为 '\0' 说明找到了,如果遍历完毕后都没有提前返回,说明没有找到,返回NULL。


11 · strtok

作用是提取由分隔符隔开的字符串。使用需包含头文件 string.h

原型如下:

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

sep参数指向⼀个字符串,定义了用作分隔符的字符集合
第⼀个参数指定⼀个字符串,它包含了0个或者多个由sep字符串中⼀个或者多个分隔符分割的标
记。
比如 "abcd@efg/hij"
这里就包含了被两个分隔符 @ / 分割的三个标记 , abcd efg hij
strtok函数找到str中的下⼀个标记,并将其用 \0 结尾,返回⼀个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以被strtok函数切分的字符串⼀般都是临时拷贝的内容并且
可修改。)
如果 strtok 的第一个参数不为 NULL ,将找到第一个 sep 中的分隔符,并保存这个位置,然后返回这个分隔符前面的 标记字符串的首元素地址。
如果 strtok 的第一个参数为 NULL ,则会从保存的位置开始,继续向后寻找分隔符。
所以我们使用 strtok 时 ,首次使用时,前一个参数需传 str ,后续使用时前一个参数就要传NULL了。
如果字符串中不存在更多标记,则返回 NULL
我们使用一下看看:

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

int main()
{
	char arr1[] = { "abcd@efg/hij" };
	char arr2[30] = { 0 };
	char* p = NULL;
	strcpy(arr2, arr1);
	p = strtok(arr2, "@/");
	printf("%s\n", p);
	p = strtok(NULL, "@/");
	printf("%s\n", p);
	p = strtok(NULL, "@/");
	printf("%s\n", p);
	return 0;
}

运行一下看看:

这里我们知道一共有几个分隔符,所以这样写。那如果我们不知道一共有几个分隔符,那就应该这样写:

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

int main()
{
	char arr1[] = { "abcd@efg/hij" };
	char arr2[30] = { 0 };
	char* p = NULL;
	strcpy(arr2, arr1);
	for (p = strtok(arr2, "@/"); p != NULL;p = strtok(NULL,"@/"))
	{
		printf("%s\n",p);
	}
	return 0;
}

12 · strerror

strerror 函数可以把参数部分错误码对应的错误信息的字符串地址返回来。使用需包含头文件 string.h
原型如下:

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

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

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

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

在 VS2022 运行如下:

下面我们举个栗子:

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 函数相当于⼀次将上述代码中的
printf ("Error opening file unexist.ent: %s\n", strerror(errno));
直接将错误信息打印出来。perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误信息。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
	FILE * pFile;
	pFile = fopen("unexist.ent", "r");
	if (pFile == NULL)
		perror("Error opening file unexist.ent");
	return 0;
}

运行一下:

perror == printf + strerror

想获得错误码就可以用 strerror

想直接打印错误信息就可以用 perror


总结

以上简单介绍了字符和字符串函数相关内容,关于C语言的其余内容,请期待后续更新


以上内容如有错误或不准确之处,欢迎指出,或者你有更好的想法,也欢迎交流。

相关推荐
摇滚侠2 小时前
Java 进阶教程,全面剖析 Java 多线程编程
java·开发语言
KevinCyao2 小时前
php彩信接口代码示例:PHP使用cURL调用彩信网关发送图文消息
android·开发语言·php
装疯迷窍_A2 小时前
以举证方位线生成工具为例,分享如何在Arcgis中创建Python工具箱(含源码)
开发语言·python·arcgis·变更调查·举证照片
网域小星球2 小时前
C 语言从 0 入门(二十五)|位运算与位段:底层开发、嵌入式核心
c语言·开发语言
wuminyu2 小时前
专家视角看JVM_StartThread
java·linux·c语言·jvm·c++
Gofarlic_OMS2 小时前
ENOVIA基于Token的许可证消费模式分析与分点策略
java·大数据·开发语言·人工智能·制造
XY_墨莲伊2 小时前
【实战项目】基于B/S结构Flask+Folium技术的出租车轨迹可视化分析系统(文末含完整源代码)
开发语言·后端·python·算法·机器学习·flask
沐知全栈开发2 小时前
XML Schema 复合类型 - 混合内容
开发语言
Fleshy数模2 小时前
PyQt5 登录界面开发全流程:从环境配置到可视化设计
开发语言·python·qt