目录
1.前言
字符串函数是编程语言中用于处理字符串(由字符组成的序列)的一类函数。在写代码过程中,或多或少都会接触到这类函数,而这类函数我们要实现的话,需要包含头文件<string.h>
。接下来我将介绍常见的几个字符串函数的使用方法和它是如何实现的。在接下来的代码中,就默认我包含了头文件<string.h>
2.字符串函数
1. strlen的使用和模拟实现
strlen函数是用来计算在’\0’之前字符串中字符的个数(不包括’\0)
我们先来看看strlen函数如何使用
int main()
{
char arr1[] = "abcdefg";
size_t ret = strlen(arr1);
printf("%d\n", ret);
return 0;
}
将代码运行之后屏幕上输出显示的是7,我们调试一下可以看到计算的个数就是’\0’之前字符的个数
我们再来看看strlen函数的声明
size_t strlen ( const char * str );
可以看到,srtrlen函数他的类型是size_t类型,也就是说它返回的是一个无符号整形,他的参数是const char *str,说明它接受的是一个地址,那么它使用的原理是什么呢?我们来用三种方法模拟实现下strlen函数
1.1 循环:
- 将字符串中字符的地址传递给指针变量str
- 定义一个size_t类型变量用来记录遇到‘\0’前字符的个数
- 用while循环,当字符不为‘\0’时,执行while循环中的语句。在每次循环中让记录的字符个数加有一,str指向的地址向后移动
- 最后返回记录的字符个数
接下来看代码是如何实现的:
size_t my_strlen(const char* str)
{
size_t ret = 0;
while (*str)
{
ret++;
str++;
}
return ret;
}
int main()
{
char arr[] = "abcdefgh";
size_t ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
1.2 指针减指针:
我们知道,指针减指针得到的是这两个指针间的元素个数,当然这个前提条件是这两个指针都处在同一个空间当中,利用这个知识,我们就可以来用指针减指针来模拟strlen函数
- 将字符串中字符的地址传递给指针变量str
- 定义一个char类型的指针ret,将str赋给这个指针
- while循环,遇到‘\0‘跳循环,每次循环将指针变量++。这样跳出while循环后这个指针指向的就是’\0‘的地址
- 返回ret-str得到str指向的地址到ret指向的地址之间元素的个数
接下来是代码实现这一过程
size_t my_strlen(const char* str)
{
char* ret = str;
while(*ret)
ret++;
return ret-str;
}
int main()
{
char arr[] = "abcdefgh";
size_t ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
1.3 递归:
- 将字符串中字符的地址传递给指针变量str
- 当*str为’\0’时返回0,否则将str指向的地址往后移,再次调用函数并加1,在返回。
- 这样每次函数进行释放,都会使返回的值增加1,最后得到的就是’\0’之前字符串中字符的个数
接下来是代码实现这一操作:
size_t my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return my_strlen(str+1) + 1;
}
int main()
{
char arr[] = "abcdefgh";
size_t ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
2. strcpy的使用和模拟实现
strcpy函数是一个字符串拷贝函数,它是将来源地的字符串拷贝到目的地的字符串当中,要注意的是,来源地字符串中含有’\0’,也同样会被拷贝进来
同样的,我们先来看看它是怎么使用的
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijk"};
char* ret = strcpy(arr1, arr2);
printf("%s\n", ret);
return 0;
}
这段代码运行后在屏幕上打印的是“hijk”,我们将其调试之后会发现arr1这个字符串数组里面的前5个元素被拷贝成了arr2这个字符串数组里的元素
我们再来看看strcpy函数的声明
char* strcpy(char * destination, const char * source );
可以看到strcpy返回类型是char *,也就是说这个函数返回一个地址。它的两个参数接收的都是字符串地址,第一个参数接收的是目的地字符串的地址,第二个参数接收的是来源地字符串的地址。接下来我们来模拟实现strcpy函数
- 将目的地字符串数组的地址和来源地字符串数组的地址分别传给str1和str2
- 用while循环,当*str2为‘\0’就跳出循环,否则执行while循环里的语句
- 每次循环将来源地字符串数组里的元素赋值给目的地字符串数组中对应元素,再将两指针向后移动一位
- 跳出while循环之后,再将当前str指向的地址内容赋值为’\0’
char* my_strcpy(char* str1, const char* str2)
{
char* ret = str1;
while (*str2)
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = '\0';
return ret;
}
int main()
{
char arr1[20] = { "abcdefg" };
const char arr2[] ="hijk";
char* ret = my_strcpy(arr1, arr2);
printf("%s", ret);
return 0;
}
这段代码还可以进行优化:
char* my_strcpy(char* str1, const char* str2)
{
char* ret = str1;
while (*str1++=*str2++)
{
}
return ret;
}
int main()
{
char arr1[20] = { "abcdefg" };
const char arr2[] = "hijk";
char* ret = my_strcpy(arr1, arr2);
printf("%s", ret);
return 0;
}
3. strcat的使用和模拟实现
将source(来源地)指向字符串的前num个字符追加到destination(目的地)指向的字符串末尾,再追加一个\0 字符
我们来看看他是如何使用的的
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijklmn" };
char* ret = strcat(arr1, arr2);
printf("%s\n", ret);
return 0;
}
将代码进行调试:
接着我们再来看看strcat函数的声明:
char * strcat ( char * destination, const char * source );
可以看到,strcat函数的返回值类型是char*,也就是返回一个地址,再看它的参数类型也是char*,说明传参传的是地址,也就是目的地和来源地字符串的地址。接下来模拟实现这个strcat函数:
- 定义一个指针变量记录目的地的地址
- 用两个while循环来实现字符串的追加,第一个while循环让目的地指针指向’/0’的地址,第二个while循环使来源地(source )的字符串从目的地指针指向的地址开始添加字符。
- 返回刚开始记录的最开始目的地字符串的地址。
接下来我们来看看代码实现:
char* my_strcat(char* str1, const char* str2)
{
char* ret = str1;
while (*str1)
str1++;
while (*str2)
{
*str1 = *str2;
str1++;
str2++;
}
return ret;
}
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijklmn" };
char* ret = my_strcat(arr1, arr2);
printf("%s\n", ret);
return 0;
}
4. strcmp的使用和模拟实现
strcmp函数是开始比较每个字符串的第一个字符。如果它们彼此相等,则继续处理以下对,直到字符不同或到达终止 null 字符>
同样我们来看看他是如何使用的
int main()
{
char arr1[] = "abcpde";
char arr2[] = "abchde";
int ret=strcmp(arr1, arr2);
if (ret > 0)
printf("arr1>arr2\n");
else if (ret < 0)
printf("arr1<arr2\n");
else
printf("arr1=arr2");
return 0;
}
strcmp函数在比较的过程中,两个字符串对应位置上的字符不相等时,一个字符串的字符比另外一个字符串对应的字符大,那么这个字符串就比另外一个字符串大,剩下的字符就不需要再进行比较了。
接下来我们再看看strcmp函数的声明:
int strcmp ( const char * str1, const char * str2 );
可以看到,strcmp函数的返回类型是int,这是因为当str1指向的字符串大于str2指向的字符串时,返回大于零的值,当str1指向的字符串等于str2指向的字符串时,返回0,当str1指向的字符串小于str2指向的字符串时,返回小于零的值。利用这个我们可以模拟实现strcmp函数:
- 首先利用while循环找到两个字符串对应不相等的字符的位置,这里会有两种情况,一个是遍历完两个字符串对应位置的字符都相等,另一种情况是没有遍历完两个字符串就出现了的对应位置字符不相等,我们接着分析
- 再判断是否在遍历完两个之前就出现了的对应位置字符不相等,如果是,就返回*str1-str2。否则的话,就跳出这个条件语句,返回0。
接下来是代码实现:
int my_strcmp( const char* str1, const char* str2)
{
while (*str1 == *str2&&*str1 != '\0' )
{
str1++;
str2++;
}
if (*str1 != '\0' )
if ((*str1 > *str2)||(*str1<*str2))
return (*str1 - *str2);
return 0;
}
int main()
{
char arr1[] = "abcpde";
char arr2[] = "abchde";
int ret=my_strcmp(arr1, arr2);
if (ret > 0)
printf("arr1>arr2\n");
else if (ret < 0)
printf("arr1<arr2\n");
else
printf("arr1=arr2");
return 0;
}
5. strncat函数的使用
从字符串附加字符
将 source 的前 num 个字符附加到 destination,外加一个终止 null 字符。
如果 source 中 C 字符串的长度小于 num,则只复制终止 null 字符之前的内容.
我们来看看他是如何使用的
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijklmn" };
int n = 0;
scanf("%d", &n);
char* ret = strncat(arr1, arr2, n);
printf("%s", ret);
return 0;
}
我们给n赋值为3,再将代码运行调试
接下来我们再看看strncat函数的声明:
char * strncat ( char * destination, const char * source, size_t num );
destination
指向目标数组的指针,该数组应包含 C 字符串,并且足够大以包含串联的结果字符串,包括其他 null 字符。
source
C 字符串。
num
要附加的最大字符数。size_t 是无符号整型
接下来我们模拟实现strncat函数,因为这个函数跟strcat多了一个size_t类型的参数,所以实现这个函数是跟实现strcat函数有些相似的
- 定义一个指针变量记录目的地的地址
- 用两个while循环来实现字符串的追加,第一个while循环让目的地指针指向’/0’的地址,第二个while循环实现n个字符的追加,这里要考虑两种情况,一种是n小于来源地字符串字符个数,一个是n大于来源地字符串个数,所以我们再加个while循环当n大于来源地字符串个数时,将多出的目标赋’\0’。
- 返回刚开始记录的最开始目的地字符串的地址。
接下来是整个代码:
char* my_strncat(char* str1, const char* str2, size_t n)
{
char* ret = str1;
while (*str1)
str1++;
while (n&&*str2!='\0')
{
*str1 = *str2;
str1++;
str2++;
n--;
}
while (n)
{
*str1++ = '\0';
n--;
}
return ret;
}
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijk" };
size_t n = 0;
scanf("%d", &n);
char* ret = my_strncat(arr1, arr2, n);
printf("%s", ret);
return 0;
}
6. strncpy函数的使用
将源的前 number 个字符复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到写入总数 num 个字符为止
同样我们来看看strncpy函数是怎么使用的:
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijk" };
int n = 0;
scanf("%d", &n);
char* ret = strncpy(arr1, arr2,n);
printf("%s", ret);
return 0;
}
我们给n一个值3,代码运行调试之后可以看到:
接下来再看看strncpy函数的声明:
char * strncpy ( char * destination, const char * source, size_t num );
destination
指向要复制内容的目标数组的指针。
source
要复制的 C 字符串。
num
要从源复制的最大字符数。size_t 是无符号整型。
接下来我们模拟实现strncpy函数
- 将目的地字符串数组的地址和来源地字符串数组的地址分别传给str1和str2
- 用while循环,实现对来源地字符串的字符的复制
- 当来源地字符串中字符的个数小于num个时,则将多出的目标赋’\0’
- 跳出while循环之后,再将当前str指向的地址内容赋值为’\0’
char* my_strncpy(char* str1, const char* str2, size_t num)
{
char* ret = str1;
while (num&&*str1!='\0'&&*str2!='\0')
{
*str1 = *str2;
str1++;
str2++;
num--;
}
while (num)
{
*str1 = '\0';
str1++;
num--;
}
return ret;
}
int main()
{
char arr1[20] = { "abcdefg" };
char arr2[] = { "hijk" };
size_t n = 0;
scanf("%d", &n);
char* ret = my_strncpy(arr1, arr2, n);
printf("%s", ret);
return 0;
}
7.strncmp函数的使用
比较两个字符串的字符
比较 C 字符串 str1 的字符数与 C 字符串 str2 的字符数。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续处理以下对,直到字符不同,直到到达终止 null 字符,或者直到两个字符串中的 num 个字符匹配,以先发生者为准。
我们来看看是怎么使用strncmp函数的:
int main()
{
char arr1[] = "abcpde";
char arr2[] = "abchde";
size_t n=0;
scanf("%d",&n);
int ret=strcmp(arr1, arr2,n);
if (ret > 0)
printf("arr1>arr2\n");
else if (ret < 0)
printf("arr1<arr2\n");
else
printf("arr1=arr2");
return 0;
}
我们给n一个值3,运行完代码之后打印输出的是arr1=arr2;如果给n一个值4,运行完代码之后打印输出的是arr1>arr2。
我们再看看strncmp函数的声明:
int strncmp ( const char * str1, const char * str2, size_t num );
str1
要比较的 C 字符串。
str2
要比较的 C 字符串
。
num要比较的最大字符数。 size_t 是无符号整型。
接下来模拟实现strncmp函数:
- 利用while循环,循环num-1次,因为我们创建的指针开始就指向了目的地字符串的地址,如果循环num次,就会指向我们第num+1个字符的地址。
- 每次循环判断两字符串对应字符是否相等且是否等于终止字符
- 跳出while循环之后返回对应字符相减。
int my_strncmp(char* str1,char* str2,size_t num)
{
while (--num)
{
if (*str1 == *str2 && *str1 != '\0' && *str2 != '\0')
{
str1++;
str2++;
}
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abcpe";
char arr2[] = "abcde";
size_t n = 0;
scanf("%d", &n);
int ret = my_strncmp(arr1, arr2, n);
if (ret > 0)
printf("arr1>arr2\n");
else if (ret == 0)
printf("arr1=arr2\n");
else
printf("arr1<arr2");
return 0;
}
8.strstr的使⽤和模拟实现
查找子字符串
返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回 null 指针。
匹配过程不包括终止 null 字符,但会在此处停止。
我们来看看怎样使用strstr函数:
int main()
{
const char arr1[20] = { "abbbcedfg" };
const char arr2[] = {"bce"};
const char* ret = my_strstr(arr1, arr2);
printf("%s", ret);
return 0;
}
将代码运行之后打印得到的是bcedfg。
我们再来看看strstr函数大的声明:
const char * strstr ( const char * str1, const char * str2 );
char * strstr ( char * str1, const char * str2 );
str1
C 字符串。
str2
C 字符串,其中包含要匹配的字符序列。
我们再来模拟实现strstr函数:
- 因为我们在str1指向的字符串中找到str2指向的字符串,所以当str1指向的地址为’\0‘时,那么在str1指向的字符串中找不到
- 所以用一个while循环,以*str1为条件,每次执行while循环,将str1和str2的初始地址赋值给ret和Ret
- 再用一个while循环判断ret和Ret指向的内容是否相等
- 再判断ret是否指向’\0‘,如果是,说明在str1指向的字符串中找到了str2指向的字符串
- 如果整个while循环结束了都没有找到,就返回空指针;
接下来是整个代码:
const char* my_strstr(const char* str1, const char* str2)
{
while (*str1) {
const char* ret = str1;
const char* Ret = str2;
while (*ret == *Ret)
{
ret++;
Ret++;
}
if (*ret == '\0')
{
return str1;
}
else
{
str1++;
}
}
return NULL;
}
int main()
{
const char arr1[20] = { "abbbcedfg" };
const char arr2[] = {"bce"};
const char* ret = my_strstr(arr1, arr2);
printf("%s", ret);
return 0;
}
9.strtok函数的使用
将字符串拆分为标记
1.对此函数的一系列调用将 str 拆分为标记,这些标记是由分隔符中的任何字符分隔的连续字符序列。
2.在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描标记的起始位置。在后续调用中,该函数需要一个 null 指针,并使用最后一个标记结束后的位置作为扫描的新起始位置。
3.为了确定令牌的开头和结尾,该函数首先从起始位置扫描分隔符中未包含的第一个字符(该字符将成为令牌的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,该字符将成为令牌的结尾。如果找到终止 null 字符,扫描也会停止。
4.令牌的此结尾将自动替换为 null 字符,令牌的开头由函数返回。
5.在对 strtok 的调用中找到 str 的终止 null 字符后,对此函数的所有后续调用(以 null 指针作为第一个参数)都将返回 null 指针。
6.找到最后一个令牌的点由函数在内部保留,以便在下次调用时使用(不需要特定的库实现以避免数据竞争)。
我们来解释一下:
(1)调用strtok函数将字符串以分隔符中的字符为标记进行分割。
(2)该strtok函数需要两个参数,也就是(字符串,分隔符)。第一次调用这个函数时需要一个字符串作为一个参数,这个字符串的第一个字符作为扫描和标记的起点。在之后调用这个函数时,这个字符串参数被替换成NULL指针,扫描的起点则是最后一次标记的字符的后一位字符的位置。
(3)令牌就是被分割后的字符串,确定令牌的开头就是该函数会从字符串的起点开始扫描得到一个分隔符中未包含的字符,这个字符作为令牌的开头,结尾就是继续扫描直到找到一个分隔符里面包含的一个字符,这个字符作为令牌的结尾。假如扫描到了终止null字符,扫描也会停止
(4)将令牌的此结尾的字符替换为 null 字符,函数返回令牌的开头。
5)如果找到了strtok函数中字符串的终止null字符后,之后再调用这个函数都会返回null指针
接下来我们来使用这个函数:
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = "abcde@fghij.klmno";
printf("%s", strtok(str, "@."));
printf("%s", strtok(NULL, "@."));
printf("%s", strtok(NULL, "@."));
return 0;
}
用vs2022运行代码之后我们会发现出现了报错,这是因为strtok是非安全的,我们在程序中添加一个#define_CRT_SECURE_NO_WARNINGS
就可以运行了:
但是,实际有时候我们得到的字符串是很长的,这样我们再像这个代码一样慢慢打印的话是十分麻烦的,所以我们将这个代码进行优化一下:
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = "abcde@fghij.klmno";
for(char*p=strtok(str,"@.");p!=NULL;p=strtok(NULL,"@."))
printf("%s\n",p);
return 0;
}
10.strerror函数的使用
获取指向错误消息字符串的指针
解释 errnum 的值,生成一个字符串,其中包含一条消息,该消息描述错误条件,就像由库的函数设置为 errno 一样。
返回的指针指向静态分配的字符串,该程序不得修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据竞争)。
strerror 生成的错误字符串可能特定于每个系统和库实现。
其实就是程序再运行时发生错误时,错误信息会被存储到errno这个变量当中,我们用strerror函数就可以查看这个错误信息是什么
我们来看看怎么使用strerror函数:
fopen()是一个用来打开文件的函数,我们可以利用这个函数来查看错误信息
int main()
{
FILE* p=fopen("text.txt","r");
if(p==NULL)
printf("%s\n",strerror(errno));
return 0;
}
将这个代码运行之后:
可以看到错误信息是没有找到这样的文件,因为我们没有提供这样的文件