前言
在日常编程实践中,我们频繁遭遇对字符和字符串进行各种复杂操作的需求,诸如追加、删除、更新等。自行设计并实现这类功能的函数,尽管可行,但往往无法达到标准库函数那样的高效性能。正因如此,C语言内建了一系列丰富的字符函数和字符串函数,它们能帮助开发者高效完成字符串拼接、删除、比较等一系列任务,极大地提升了编码效率。
本文旨在深入浅出地向读者阐述C语言中字符函数和字符串函数的使用方法,与此同时,为了更好地理解这些内置函数的底层运作原理,我们将通过实践的方式,尝试自定义类似功能的字符函数和字符串函数。这样的学习过程不仅有助于掌握标准库函数的应用,更能深化对底层数据处理逻辑的认知。
现在,就让我们一起踏上这段探索之旅,揭开C语言字符和字符串函数的神秘面纱,动手实践,亲自打造属于我们的字符及字符串处理工具集吧!
字符分类函数
在C语言中,确实存在一系列强大的字符分类函数,它们内置于 <ctype.h>
标准库头文件中,能够帮助开发者轻松地对单个字符进行类别判断。这些函数通常用来测试字符是否属于特定的字符类别,如字母、数字、标点符号或其他控制字符等。下面是一些常见的C语言字符分类函数及其用途:
函数名 | 如果参数符合下列条件则返回值为真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格,换页'\f',换行'\n',回车'r',制表符'\t',垂直制表符'\v' |
isdigit | 十进制数字0~9 |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 大写或小写字母 |
isalnum | 大写或小写字母或者十进制数字0~9 |
ispunct | 标点符号,任何不属于数字和字母的图形符号(可打印的) |
isgraph | 任何图形符号 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
int islower (int c)
这个代码可以用来判断参数部分是否为小写字母。
用代码将小写字母转换成大写字母
下面我们通过一段代码来实现将一个字符串里的小写字母转换为大写。
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char a[100] = "I LOVE ElectroNic MusIc";
char c = { 0 };
while (a[i])
{
c = a[i];
if (islower(c))
{
c -= 32;
}
putchar(c);
i++;
}
return 0;
}
当然随着我们的继续深入学习,我们会发现将小写转换成大写有更简单的方法
字符转换函数
在ctype中,还给我们提供了两个函数,分别是toupper和tolower,他们的作用就是将大写和小写互相转换,因此我们上面提高增加和减少ASCII值来转换大小写的方法可以直接换成这两个函数,更为简单便捷。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char a[100] = "I LOVE ElectroNic MusIc";
char c = { 0 };
while (a[i])
{
c = a[i];
if (islower(c))
{
c = toupper(c);
}
putchar(c);
i++;
}
return 0;
}
strlen的使用和模拟实现
相信大家对这个函数一定不陌生,我们用他来求字符串的长度,strlen在遇到\0的时候会停下,也就是说它统计的是\0之前的字符串的个数,若一个字符串后面没有\0,那么它就会一直往后统计,直到遇到\0为止,所以此时输出的值就是一个随机值。
strlen的使用
char a[6] = "abcdef";
int len = strlen(a);
上述代码就可以 计算字符变量a中的字符串长度,其结果为6.
需要注意的是,这个字符串数组里面除了有6个字母以外,在结尾还有一个\0,用来表示字符串的结束,strlen也是统计\0之前字符串的个数,我们打开监视窗口就能够清楚地看见。
strlen的模拟实现
计数器方式实现strlen模拟
对于strlen这样的简单函数,我们可以通过自己写的函数来模拟实现它,下面就跟着我一起来做吧
#include <stdio.h>
#include <string.h>
void my_strlen(char* a)
{
int count = 0;
while (*a)
{
a++;
count++;
}
printf("%d", count);
}
int main()
{
char a[] = "abcdef";
my_strlen(a);
return 0;
}
我们来逐行分析这个代码,首先是main函数部分,我们定义了一个char类型的数组a,里面存放了abcdef这几个字符串,然后我们调用自己的strlen函数,将a作为参数传进去
接下来我们看看my_strlen的部分,首先我们需要一个char类型的指针,用来接收传过来的变量,函数返回值为void,接着我们就可以看看函数内部是如何实现计算字符串长度了。
首先定义一个count的整形变量,用来存放字符串的个数,然后是一个while循环,条件是*a,也就是当*a不为0的时候就进入循环,为什么*a会为0呢,因为当字符串遇到\0的时候,它的本质就是0,所以仅仅只是*a就足以遍历整个字符串了,进入while循环内部,我们让a指针向后移动,并且同时count也+1,这样我们就成功统计了一个字符串,并把长度放进了count变量中。
最后,当*a遇到\0时,就相当于while循环的条件为0,跳出while循环,此时我们打印count的值,便是字符串的长度。
对于指针还不是很理解的同学,可以看看我之前做的一期关于指针的文章,对指针有一个深入的了解。
当然,我们不止可以通过这种方式来实现strlen,还有其他更多的方式来实现,下面就举两个例子来实现。
递归方式实现strlen模拟(不创建临时变量的方式)
话不多说,先上代码
int my_strlen(char* a)
{
if (*a == '\0')
return 0;
else
return 1 + my_strlen(a+1);
}
int main()
{
char a[] = "abcdef";
int len = my_strlen(a);
printf("%d", len);
return 0;
}
我们还是逐行分析,直接进入函数部分,这是一个递归函数,需要两个条件,当*a为0即当a指针遇上\0时,我们就返回0,代表没有字符串,否则我们就返回一个1+后面的字符串个数,这个意思就是直到a指针遇到\0之后就返回0,递归结束,我们通过一张图来解释更为直观
通过这样一次一次地递推,我们可以把每一个字符串都遍历一遍,直到最后a指针遇到\0就回归,这样递归就结束了。因此就算出了字符串a的值。
指针-指针实现strlen模拟
int my_strlen(char* a)
{
char* s = a;
while (*s)
{
s++;
}
return s - a;
}
int main()
{
char a[] = "abcdef";
int len = my_strlen(a);
printf("%d", len);
return 0;
}
这个函数,我们首先引入一个s变量,初始值为a的首元素地址,然后我们通过while循环遍历整个数组,最后我们用这个s地址减去初始的a地址,得到的就是指针之间的地址差,而char刚好站1个字节,所以得到的指针之差就是字符串的个数。通过指针的减法运算我们成功将字符串的个数表示出来。
strcpy的使用和模拟实现
strcpy的使用
C语言中的strcpy函数,是用来将现有的字符串内容复制到另外一个字符串里面,通过官方定义我们可以看到这是一个函数,参数有两个,均为char指针类型,第一个destination是用来被复制的字符串,第二个source是将要拷贝的字符串内容。
使用方法如下:
int main()
{
char a[] = "abcd";
char b[] = "edgh";
strcpy(a, b);
printf("%s", a);
return 0;
}
这样,我们就把b数组里的内容复制到了a数组中去了
int main()
{
char a[] = "abcdtttttt";
char b[] = "edgh";
strcpy(a, b);
printf("%s", a);
return 0;
}
即便两个数组的内容长度不一样,依旧可以复制。
strcpy的模拟实现
既然这样,那我们可不可以通过自己写的strcpy函数来实现呢?当然可以,下面跟着我一起来做吧!
#include <stdio.h>
void my_strcpy(char* dest, char* src)
{
char* init = dest;//用一个初始指针来记录dest的起始位置
while (*dest++ = *src++)
; //空语句
printf("%s", init);
}
int main()
{
char a[] = "abcdtttttt";
char b[] = "edgh";
my_strcpy(a, b);
return 0;
}
让我们来分析这个代码,首先我们引入一个init指针,类型为char*,用来记录dest的初始位置,之后,我们通过while循环来一边遍历内容,一边将src的内容赋值给dest,这样无论dest和src谁长谁短,都可以被成功赋值,while循环内部为空语句,之后我们打印的时候就可以通过打印init来实现,因为init存放的是dest首元素的地址,可以从首元素开始向后依次打印dest的内容,也就是被复制之后的内容。
strcat的使用和模拟实现
strcat的使用
strcat是C语言的一个字符串函数,作用是向目标类型追加新的字符串,用法如下:
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "hello ";
char b[] = "world";
printf("%s",strcat(a, b));
return 0;
}
通过strcat函数我们就可以在hello 后面追加一个world字符串从而打印出hello world
当然,值得注意的是,源字符串中必须有\0,追加的字符串中也需要有\0,否则不知道从哪里开始追加以及不知道追加到哪里结束,并且追加的字符串的容量必须足够容纳新追加之后的字符串,否则会出现越界访问。
strcat的模拟实现
我们依然可以通过自己编写函数来模拟实现strcat的功能,话不多说直接上代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void my_strcat(char* dest, const char* src)
{
char* init = dest;
while (*dest)
{
dest++;
}
while ((*dest++ = *src++))
;
printf("%s", init);
}
int main()
{
char a[] = "hello ";
char b[] = "world";
my_strcat(a, b);
return 0;
}
我们来分析这个函数,首先依旧是引入一个char类型的指针变量init,然后我们通过while循环让dest指针到字符串的结尾处,之后我们还是和上面的代码一样将src的内容赋值给dest,最后我们通过init指针来打印出追加后的内容。这样我们就成功实现了strcat的功能。还是一样,对于指针不熟悉的同学,可以看看我之前发的关于指针的文章哦,记得多多点赞和关注,你的关注和收藏是我最大的更新动力,感谢大家。
strcmp的使用和模拟实现
strcmp的使用
字符可以比较大小吗?当然可以,strcmp就是用来比较字符的大小的,比如a<b,是通过比较其ASCII码值来比较的,用法如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
char a[] = "a";
char b[] = "b";
int ret = strcmp(a, b);
printf("%d", ret);
return 0;
}
strcmp的返回值是一个整数,如果前面的字符大于后面的,那么返回一个正数,反之则返回一个负数,相等则为0,那么如果遇到诸如abcd和abcq这样的字符串该如何比较呢?strcmp是一个一个比较的,例如,a和a相等,那么就比较下一个,直到d<q,那么就停止比较,无论后面是什么都不会再往下比较了,如果比较完之后都是相同的,那么就返回0.
如果其中一个字符串较短,那么当比较完较短的那个字符串之后,就会直接停止比较,谁长谁就赢,就是这么个道理。
strcmp的模拟实现
我们还是通过自己编写函数来模拟strcmp的是实现,老套路,代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void my_strcmp(char* a, const char* b)
{
while (*a != '\0' && *b != '\0' && *a==*b)
{
if (*a == '\0')
printf("0");
a++;
b++;
}
printf("%d",(*a-*b));
}
int main()
{
char a[] = "abcd";
char b[] = "abcqqq";
my_strcmp(a, b);
return 0;
}
通过这个函数我们就可以实现字符串的比较,当然真正的C语言函数写的一定比这个复杂得多,这里我们只是写一个简略的版本,对于C语言写的可以通过转到定义来看或者打开对应的头文件去查看。
strncpy的使用和模拟实现
strncpy的使用
前面我们了解了strcpy,那么这里的strncpy多了一个n,是用来干什么的呢?这里的函数多了一个参数,size_t类型的,可以指定拷贝多少个字符串进去,举个例子来看:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "hello";
char b[] = "world";
strncpy(a, b, 3);
printf("%s", strncpy(a, b, 3));
return 0;
}
这个代码的运行结果是worlo,即将world的前三个字符复制到a字符串里,其他的位置不动。
strncpy的模拟实现
用法+模拟实现,这个套路想必大家都已经很清楚了,我们直接上代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void my_strncpy(char* a, char* b, size_t n)
{
char* init = a;
for (int i = 0; i < n; i++)
{
*a = *b;
a++;
b++;
}
printf("%s", init);
}
int main()
{
char a[] = "hello";
char b[] = "world";
my_strncpy(a, b, 3);
return 0;
}
我们还是引入一个指针来记录a的初始位置,然后我们通过一个for循环来遍历数组的内容,循环的次数就是想要复制的字符串个数,最后我们打印init变量,就可以实现strncpy的模拟了。
strncat的使用和模拟实现
strncat的使用
类似于strcat,这个函数只不过多加了一个变量n,用来指定追加的字符串个数。用法如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "hello ";
char b[] = "world";
printf("%s", strncat(a, b, 3));
return 0;
}
上述代码的运行结果是hello wor,也就是只追加了b字符串中的前3个字符,还是非常好理解的对吧,那么下面我们就还是来模拟实现一下这个函数吧!
strncat的模拟实现
话不多说,直接上代码,相信好多人不用说都已经会写了哈哈哈哈
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void my_strncat(char* a, char* b, size_t n)
{
char* init = a;
while (*a)
{
a++;
}
for (int i = 0; i < n; i++)
{
*a++ = *b++;
}
*a = '\0';
printf("%s", init);
}
int main()
{
char a[] = "hello ";
char b[] = "world";
my_strncat(a, b, 3);
return 0;
}
害,这么多代码都是一样的套路,先让指针a到结尾处,再通过for循环来指定需要追加的个数,也不必多说,还是很容易看懂的。我们继续吧。
strncmp的使用和模拟实现
strncmp的使用
看到这里应该可以举一反三了,strncmp就是可以指定比较几个字符串,也不用多啰嗦,直接看使用方法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "abcd";
char b[] = "abcq";
printf("%d", strncmp(a, b, 3));
return 0;
}
这里由于我们只比较了前三个字符串,所以其结果是0,因为第三个字符串是相等的。
strncmp的模拟实现
下面我们用代码来实现一下这个函数:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
void my_strncmp(char* a, char* b, size_t n)
{
for (int i = 0; i < n; i++)
{
if (*a == *b)
printf("0");
break;
a++;
b++;
}
printf("%d", *a - *b);
}
int main()
{
char a[] = "abcd";
char b[] = "abcq";
my_strncmp(a, b, 3);
return 0;
}
这个代码相较于上面的代码只是再外部嵌套了一个for循环,用来指定需要比较字符串的个数。
strstr的使用和模拟实现
strstr的使用
strstr的作用是用来打印一个字符串子啊另一个字符串第一次出现的位置并从这个位置开始一直往后打印,直到遇到\0为止。
直接看代码示意:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "abcdefgcd";
char b[] = "cd";
printf("%s", strstr(a, b));
return 0;
}
这个代码中b中的字符串是cd,那么也就是在a字符串中寻找cd第一次出现的位置,并且从这个位置开始往后打印,直到遇到\0为止。那么如果b中想要找的字符串没有出现在a中,那么则返回空值。
strstr的模拟实现
这个函数的实现有一定难度,简单来说就是我也不太会哈哈哈,不过还是可以来实现一下,代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
char* my_strstr(char* a, char* b)
{
char* cp = (char*)a;
char* s1, * s2;
if (!*b)
return((char*)a);
while (*cp)
{
s1 = cp;
s2 = (char*)b;
while (*s1 && *s2 && !(*s1 - *s2))
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
}
int main()
{
char a[] = "abcdefgcd";
char b[] = "cd";
char* ret = my_strstr(a, b);
printf("%s", ret);
return 0;
}
strtok的使用
strtok的使用
`strtok` 函数的主要任务是对字符串中的分隔符进行解析和处理。事实上,当首次调用 `strtok` 函数时,它并不会返回空值,而是返回指向第一个子串(即分隔符之间的连续字符序列)的指针。同时,为了方便后续操作,`strtok` 会在每个找到的分隔符处用 `\0` 字符替换,从而有效地在原字符串内部将各个子串“分隔”开来,但这并不意味着首次执行 `strtok` 后返回的是空值。
具体来说,`strtok` 的工作流程如下:
1. 首次调用 `strtok`,传入待处理的字符串和分隔符集,它会返回第一个子串的首地址。
2. 每次后续调用 `strtok` 时,可以直接传入 `NULL`,此时函数会从上次结束的位置开始继续查找下一个子串,并返回该子串的首地址。
3. 当没有更多的子串可分割时,`strtok` 返回 `NULL` 表示处理完毕。
总结一下,`strtok` 并非在首次执行后返回空值,而是在原字符串中用 `\0` 替换分隔符,然后返回首个有效子串的指针。如果你希望完全去除分隔符并打印完整的子串,应当在每次 `strtok` 处理后,保存并打印对应的子串内容。
使用方法如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "192.168.11.4";
char b[] = ".";
printf("%s", strtok(a, b));
return 0;
}
这段描述中提到的现象确实与 strtok
函数的工作机制有关。strtok
在找到分隔符并返回一个子串的同时,它会在分隔符的位置上插入一个空字符 \0
,使得原字符串在该位置被截断,后续对原字符串的读取只会读到 \0
为止。因此,如果试图直接打印原字符串,的确只会输出到遇到的第一个 \0
结束。
针对这种情况,若要完整保留并打印原始字符串的所有部分(即使它们之间已经被\0
分隔),我们需要分别保存每次 strtok
分割得到的子串,并逐一打印这些子串。
下面是如何完整打印出来的办法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "192.168.11.4";
char b[] = ".";
char c = NULL;
fot(c = strtok(a, b), c != NULL; c = strtok(NULL, b))
{
printf("%s ", c);
}
printf("%s", strtok(a, b));
return 0;
}
通过利用该函数,我们可以采用如下方式实现目标:首先,通过 strtok
函数对字符串进行首次分割处理,并将其结果作为循环的初始值。接着,设置循环条件为每次分割得到的指针(即子字符串)非空,这意味着只要还有未处理的部分,循环就会继续。在循环体内,不断调用 strtok
获取下一个子字符串,并以此实现逐步去除分隔符并将整个字符串内容逐一分段打印出来。具体实现代码可能如下所示:
strerror的使用
strerror的使用
strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。 在不同的系统和C语言标准库的实现中都规定了⼀些错误码,一般是放在 errno.h 这个头文件中说明 的,C语言程序启动的时候就会使用⼀个全面的变量errno来记录程序的当前错误码,只不过程序启动 的时候errno是0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会讲对应 的错误码,存放在errno中,而一个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是 有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。
我们来打印一下一些错误:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
for (int i = 0; i < 20; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
我们可以发现,在此列出来的每一个错误信息都是独特且具有指示性的,涵盖了如“找不到指定设备”、“未知错误”等多种情况。这样一来,我们就能够准确识别出系统可能出现的各种问题。例如,当我们需要检查某个文件是否切实存在的时候,就可以巧妙地运用相关函数来进行判断。接下来,我将为您展示如何通过代码实现这一功能:
以下是用于检测文件是否存在的一段示例代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE* pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
printf("%s\n", strerror(errno));
return 0;
}
这个代码的运行结果为找不到指定的文件或文件夹,这样我们就知道没有这个文件的存在了。
结语
亲爱的观众朋友们,每一个精心制作的内容背后都凝聚着无数的心血与努力,因此,我在此诚挚地请求您能够慷慨地给予我们点赞、收藏与关注,这不仅是对我们付出的肯定,更是我们持续创作优质内容的动力源泉。据说,每一位积极互动的朋友都已成功迈入了理想的大门,期待您也能把握这一份好运。
今天的内容就暂告一段落了,亲爱的程序员们,我们虽然暂时告别,但精彩永不止步。让我们在未来的日子里,以知识为舟,探索无尽的技术海洋。敬请期待下一次的相聚,届时,我们再会!
在此,衷心地道一声:再见啦,各位!愿你们一切顺利,编程之路越走越宽广,我们后会有期!拜拜~