《C语言》字符函数和字符串函数

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

1 字符分类函数

C语言中有一系列的函数是专门做字符分类的,也就是一个字符属于什么类型的字符。

这些函数的使用都需要包含一个头文件 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 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

这些函数的使用方法非常相似,我们就介绍一个函数的使用,其他的非常相似:

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

islower 是能够判断参数部分的 c 是否是小写字母的。

通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0。

**练习:**写一个代码,将字符串中的小写字母转大写,其他字符不变。

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

int main()
{
    char arr[] = "My Name Is piku.";
    int i = 0;
    while (arr[i])
    {
        if (islower(arr[i]))
        {
            arr[i] = arr[i] - 32;
        }
        putchar(arr[i]);  //putchar - 将字符写入标准输出
        i++;
    }
    printf("\n");
    return 0;
}

输出结果:

2 字符转换函数

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

cpp 复制代码
int tolower( int c );  //将参数传进去的大写字母转小写
int toupper( int c );  //将参数传进去的小写字母转大写

上面的代码,我们将小写转大写,是 -32 完成的效果,有了转换函数,就可以直接使用**tolower** 函数。

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

int main()
{
    char arr[] = "My Name Is piku.";
    int i = 0;
    while (arr[i])
    {
        if (islower(arr[i]))
        {
            //arr[i] = arr[i] - 32;
            toupper(arr[i]);
        }
        putchar(arr[i]);  //putchar - 将字符写入标准输出
        i++;
    }
    printf("\n");
    return 0;
}

输出结果:

3 strlen的使用和模拟实现

cpp 复制代码
size_t strlen ( const char* str );
  • 字符串以**'\0'** 作为结束标志,strlen函数返回的是在字符串中**'\0'** 前面出现的字符个数(不包含 '\0')。
  • 参数指向的字符串必须要以 '\0' 结束。
  • 注意函数的返回值为size_t 类型,是无符号的(易错)。
  • strlen的使用需要包含头文件string.h

那我们看看下面的代码输出什么呢?

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

int main()
{
    const char* str1 = "abc";
    const char* str2 = "abcdef";
    if(strlen(str1) - strlen(str2) > 0)
    {
        printf("str1 大于 str2\n");
    }
    else
    {
        printf("str1 小于等于 str2\n");
    }
    return 0;
}

输出结果为:

strlen(str1) 的结果为3,strlen(str2) 的结果为6,那么 3-6=-3,-3应该是小于0,然后打印str1 小于等于 str2 的才对,那么问题出在哪呢?答案是strlen的返回值类型是size_t类型的,是无符号的,所以3-6计算出的-3,会被当作是无符号数,那么它就是一个很大的正数了,所以会打印大于。

想要达到我们原先想要的结果可以如下改写:

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

int main()
{
    const char* str1 = "abc";
    const char* str2 = "abcdef";
    if ((int)strlen(str1) - (int)strlen(str2) > 0)  //强制类型转换为有符号整型即可
    {
        printf("str1 大于 str2\n");
    }
    else
    {
        printf("str1 小于等于 str2\n");
    }
    return 0;
}

输出结果为:

strlen模拟实现:

方式1:计数器方式

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

size_t my_strlen(const char* str)
{
    size_t count = 0;
    assert(str != NULL);  //确保我们访问str的时候不是空指针
    while(*str != '\0')
    {
        count++;
        str++;
    }
    return count;
}

方式2:指针-指针方式

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

size_t my_strlen(const char* str)
{
    const char* start = str;
    assert(str != NULL);  //确保我们访问str的时候不是空指针
    while (*str)  //'\0'的ASCII码值是0
    {
        str++;
    }
    return str - start;
}

方式3:不创建临时变量 - 递归

cpp 复制代码
//计算的过程可以拆解成下面步骤
my_strlen("abcdef")
->1 + mystrlen("bcdef")
->1 + 1 + mystrlen("cdef")
->1 + 1 + 1 + mystrlen("def")
->1 + 1 + 1 + 1 + mystrlen("ef")
->1 + 1 + 1 + 1 + 1 + mystrlen("f")
->1 + 1 + 1 + 1 + 1 + 1 + mystrlen("")
->1 + 1 + 1 + 1 + 1 + 1 + 0
cpp 复制代码
#include <assert.h>

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

4 strcpy的使用和模拟实现

cpp 复制代码
char* strcpy(char* destination, const char* source);
  • Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
  • 源字符串必须以**'\0'**结束。
  • 会将源字符串中的**'\0'**拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可修改。
  • strcpy的使用需要包含头文件string.h
  • srtcpy返回的值是目标空间的起始地址。
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[20] = { 0 };
	char arr2[] = "PIKU";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

输出结果为:

strcpy模拟实现:

基础功能实现:

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

void my_strcpy(char* dest, const char* src)
{
	while (*src)
	{
		*dest = *src;
		dest++;
		src++;
	}
	*dest = *src;
}

符合原函数并且更巧妙的实现:

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

char* my_strcpy(char* dest, const char* src)
{
	assert(dest);
	char* ret = dest;
	assert(src);
    //'\0'赋值之后,整个表达式结果就是'\0',ASCII码值是0,表达式为假,就跳出循环
    //后置++是先使用再+1,也就是先对指针解引用,找到指针所指向的值,然后再对这个指针+1
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

5 strcat的使用和模拟实现

cpp 复制代码
char* strcat ( char* destination, const char* source );
  • Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source , and a null-character is included at the end of the new string formed by the concatenation of both in destination.
  • 源字符串必须以 '\0' 结束。
  • 目标字符串中也得有 \0 ,否则没办法知道追加从哪里开始。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标内容必须可修改。
  • 字符串自己给自己追加是有风险的,不建议这样使用。
  • strcat的使用需要包含头文件string.h
cpp 复制代码
#include<stdio.h>
#include<string.h>

int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "PIKU";
	strcat(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

上面程序的运行结果是什么?

strcat模拟实现:

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

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	//1 找到目标空间的\0
	while (*dest)
	{
		dest++;
	}
	//2 拷贝
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

6 strcmp的使用和模拟实现

cpp 复制代码
int strcmp ( const char* str1, const char* str2 );
  • This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.
  • 标准规定:
    第一个字符串大于第二个字符串,则返回大于0的数字
    第一个字符串等于第二个字符串,则返回0
    第一个字符串小于第二个字符串,则返回小于0的数字
    那么如何判断两个字符串?比较两个字符串中对应位置上字符ASCII码值的大小。
  • strcmp的使用需要包含头文件string.h

strcmp模拟实现:

方式1:仅返回0,1,-1

cpp 复制代码
int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
			return 0;
		s1++;
		s2++;
	}
	if (*s1 > *s2)
		return 1;
	else
		return -1;
}

方式2:返回0,大于零的数,小于零的数

cpp 复制代码
int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
			return 0;
		s1++;
		s2++;
	}
	return *s1 - *s2;
}

7 strncpy函数的使用

cpp 复制代码
char* strncpy ( char* destination, const char* source, size_t num );
  • Copies the first num characters of source to destination . If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
  • 拷贝num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
  • strmcpy的使用需要包含头文件string.h

8 strncat函数的使用

cpp 复制代码
char* strncat ( char* destination, const char* source, size_t num );
  • Appends the first num characters of source to destination , plus a terminating null-character.(将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加一个**\0** 字符)

  • If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.(如果source指向的字符串的长度小于num的时候,只会将字符串中到 \0 的内容追加到destination指向的字符串末尾)

  • strncat的使用需要包含头文件string.h

9 strncmp函数的使用

cpp 复制代码
int strncmp ( const char* str1, const char* str2, size_t num );
  • Compares up to num characters of the C string str1 to those of the C string str2.
  • This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ, until a terminating null-character is reached, or until num characters match in both strings, whichever happens first.
  • 比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提前发现不一样,就提前结束,大的字符所在的字符串大于另外一个。如果num个字符都相等,就是相等返回0。
  • strncmp的使用需要包含头文件string.h

10 strstr的使用和模拟实现

cpp 复制代码
char* strstr ( const char* str1, const char* str2 );
  • Returns a pointer to the first occurrence of str2 in str1 , or a null pointer if str2 is not part of str1 .(函数返回字符串str2在字符串str1中第一次出现的位置)。
  • The matching process does not include the terminating null-characters, but it stops there.(字符串的比较匹配不包含 \0 字符,以 \0 作为结束标志)。
  • strstr的使用需要包含头文件string.h

strstr模拟实现:

cpp 复制代码
#include<assert.h>
//暴力求解
char* my_strstr(const char* str1, const char* str2)
{
	const char* cur = str1;
	const char* s1 = NULL;
	const char* s2 = NULL;
	assert(str1 && str2);
	if (*str2 == '\0')  //处理特殊情况,str2是空指针的话就不用找了
	{
		return (char*)str1;
	}
	while (*cur)
	{
		s1 = cur;
		s2 = str2;
		//1.s1找到了\0,*s2 == '\0' - 找到了
		//2.s2找到了\0 - 找到了
		//3.*s1 != *s2
		while (*s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')  //如果跳出循环之后,s2指向的是'\0',就说明找到了
		{
			return (char*)cur;
		}
		cur++;  //没找到就继续往后找,新的起始位置
	}
	return NULL;
}

11 strtok函数的使用

cpp 复制代码
char* strtok ( char* str, const char* sep );
  • 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 arr1[] = "PIKU666@163.com";
	char arr2[30] = { 0 };
	strcpy(arr2, arr1);
	const char* p = "@.";

	char* s = strtok(arr2, p);
	printf("%s\n", s);
	s = strtok(NULL, p);
	printf("%s\n", s);
	s = strtok(NULL, p);
	printf("%s\n", s);

	return 0;
}

上面程序运行结果为:

一般我们在使用的时候是不知道一个字符串中有几个sep字符串的几个分隔符的,我们采用如下方式实现:

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

int main()
{
	char arr1[] = "PIKU666@163.com#3648&SHUBAO";
	char arr2[30] = { 0 };
	strcpy(arr2, arr1);
	const char* p = "@.#&";
	char* s = NULL;

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

	return 0;
}

运行结果如下:

12 strerror函数的使用

cpp 复制代码
char* strerror ( int errnum );
  • strerror函数可以把参数部分错误码对应的错误信息的字符串的地址返回来。
  • 在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在 errno.h 这个头文件中说明的,C语言程序启动的时候就会使用一个全局变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误,存放在errno中,而一个错误码的数字是整数很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。
cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<errno.h>

int main()
{
	//我们打印一下0~10这些错误码对应的信息
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		printf("%d : %s\n", i, strerror(i));
	}
	return 0;
}

在Windows+VS2022环境下输出的结果如下:

cpp 复制代码
0 : No error
1 : Operation not permitted
2 : No such file or directory
3 : No such process
4 : Interrupted function call
5 : Input/output error
6 : No such device or address
7 : Arg list too long
8 : Exec format error
9 : Bad file descriptor
10 : No child processes

举例:

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

int main()
{
	FILE* pFlie;
	pFlie = fopen("study.txt", "r");
	if (pFlie == NULL)
		printf("Errot opening flie study.txt: %s\n", strerror(errno));  //第十行
	else
		printf("文件打开成功\n");
	return 0;
}

输出:

cpp 复制代码
Errot opening flie study.txt: No such file or directory

也可以了解一下perror函数,perror函数相当于一次将上述代码中的第10行完成了,直接将错误信息打印出来。perror函数打印完参数部分的字符串之后,再打印一个冒号和空格,再打印错误信息。

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

int main()
{
	FILE* pFlie;
	pFlie = fopen("study.txt", "r");
	if (pFlie == NULL)
		perror("Errot opening flie study.txt");
	else
		printf("文件打开成功\n");
	return 0;
}

输出:

cpp 复制代码
Errot opening flie study.txt: No such file or directory
相关推荐
嵌入小生0072 小时前
线程间通信---嵌入式(Linux)
linux·c语言·vscode·嵌入式·互斥锁·线程间通信·信号量
被克制了2 小时前
C语言学习记录(1)
c语言
小刘爱玩单片机2 小时前
【stm32简单外设篇】- MQ-2烟雾传感器模块
c语言·stm32·单片机·嵌入式硬件
无限进步_2 小时前
138. 随机链表的复制 - 题解与详细分析
c语言·开发语言·数据结构·算法·链表·github·visual studio
m0_531237172 小时前
C语言-内存函数
c语言·开发语言·算法
wengqidaifeng2 小时前
数据结构(四)二叉树初步:计算机科学中的分叉树
c语言·数据结构
m0_5312371714 小时前
C语言-指针终阶
c语言·开发语言
散峰而望14 小时前
C++ 启程:从历史到实战,揭开命名空间的神秘面纱
c语言·开发语言·数据结构·c++·算法·github·visual studio
水饺编程15 小时前
第4章,[标签 Win32] :TextOut 测试案例3代码改编
c语言·c++·windows·visual studio