在本篇博客中,我们主要介绍与字符以及字符串相关的一些库函数,以及它的模拟实现。
目录
一、求字符串长度的函数
1.1 strlen函数
我们可以在cplisplus网站上看有关strlen函数的介绍。
1.1.1 strlen的使用场景
我们先来简单使用一下strlen函数求字符串的长度吧!
#include<stdio.h>
#include<string.h>
int main()
{
char a[] = "abcdef";
int len = strlen(a);
printf("%d", len);
return 0;
}
1.1.2 strlen函数需要注意的点
- 字符串中'\0'作为结束标志,strlen函数返回的是在字符串中'\0‘前面出现的字符个数(不包含'\0')。
- 参数指向的字符串必须以'\0'结束。
- 注意函数的返回值是size_t,是无符号的 。
对于函数的返回值是无符号的这点,在一些代码中很容易出错,下面来通过一段代码看看吧!
#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("str1 > str2\n");
}
return 0;
}
运行结果如上,是不是很惊讶呢,下面我们来解释原因:
上述代码就是对strlen的返回值是size_t,即无符号整型知识的考察,
1.1.3 strlen函数的模拟实现
我们是对strlen函数的模拟实现,所以我们设计的函数的参数按照strlen库函数的参数来设计。
方法一:
size_t my_strlen(const char*str)
{
assert(str);
int count = 0;
while (*str != 0)
{
count++;
str++;
}
return count;
}
方法二:
主要利用了指针-指针的绝对值=两个指针之间的元素个数。
size_t my_strlen(const char*str)
{
assert(str);
const char* end = str;
while (*end)
{
end++;
}
return end - str;
}
二、长度不受限制的字符串函数
2.1 strcpy函数
2.1.1 strcpy函数的使用场景
当我们想要把字符串bde拷贝到另一个字符串的时候就可以使用strcpy函数。
#include <string.h>
#include <stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "bde";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
2.1.2 需要注意的点
- 源字符串必须以'\0'结束。
- 会将源字符串中的'\0'拷贝到目标空间。
- 目标空间必须足够大,已确保存放源字符串。
- 目标空间必须可变。
2.1.3 strcpy函数的模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* str1, const char* str2)
{
assert(str1 && str2);
char* p = str1;
/*while (*str2 != '\0')
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = *str2;*/
while(*str1++=*str2++)
{
;
}
return p;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "bde";
char* p = my_strcpy(arr1, arr2);
printf("%s\n", p);
return 0;
}
2.2 strcat函数
strcat是字符串追加函数,将soure指向的字符串追加到destination指向的字符串后面。
2.2.1 strcat函数的使用场景
当我们想要将lili追加到hello后面时,我们就可以使用strcat函数。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "hello ";
char arr2[] = "lili";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
2.2.2 需要注意的点
- 目标字符串必须以'\0'结束,方便我们知道从哪里开始追加,我们需要通过'\0'找到字符串的末尾在哪里。
- 源字符串必须以'\0'作为结束标志,知道什么时候追加停止。
- 目标空间必须足够大,能容纳下源字符串的内容。
- 目标空间可修改。
- 字符串自己给自己追加不能使用这个函数。
2.2.3 strcat函数的模拟实现
#include <stdio.h>
char* my_strcat(char* str1, const char* str2)
{
char* p = str1;
while (*str1 != '\0')
{
str1++;
}
/*while (*str2 != '\0')
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = *str2;*/
while (*str1++ = *str2++)
{
;
}
return p;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "lili";
char* p = my_strcat(arr1, arr2);
printf("%s\n", p);
return 0;
}
2.3 strcmp函数
从图中我们可以看出,strcmp函数是用来比较两个字符串的大小的,如果str1>str2,返回大于0的数,如果str1<str2,返回小于0的数,如果str1=str2,返回0。
其次,strcmp函数的返回值是int类型的,需要传的参数是 const char*类型的,由于只需要比较大小,所以不需要改动指针指向的内容,所以此处参数使用const修饰。
strcmp比较的是对应位置上的字符的大小,而非长度。
2.3.1 strcmp函数的使用场景
当我们想要比较"adcdef"与"abq"的大小时,我们就可以使用strcmp函数。
#include <stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int a = strcmp(arr1, arr2);
if (a > 0)
{
printf("arr1 > arr2\n");
}
else if (a == 0)
{
printf("arr1 == arr2\n");
}
else
{
printf("arr1 < arr2\n");
}
return 0;
}
2.3.2 strcmp函数的模拟实现
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
int i = 0;
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else if (*str1 < *str2)
{
return -1;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int a = my_strcmp(arr1, arr2);
if (a > 0)
{
printf("arr1 > arr2\n");
}
else if (a == 0)
{
printf("arr1 == arr2\n");
}
else
{
printf("arr1 < arr2\n");
}
return 0;
}
三、长度受限制的字符串函数介绍
3.1 strncpy函数
strncpy函数是用来拷贝字符串的,但是限制了拷贝字符的长度,即拷贝num个字符从源字符串到目标空间,如果源字符串的长度小于num,则拷贝完源字符串后,在目标的后面追加0,直到num个。
3.1.1 strncpy函数的使用场景
当我们想要将helloworld的前5个字符拷贝到"lili nihao"时,我们就可以使用strncpy函数。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "helloworld";
char arr2[] = "lili nihao";
char* p = strncpy(arr2, arr1,5);
printf("%s\n", arr2);
return 0;
}
3.1.2 strncpy函数的模拟实现
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strncpy(char* str1, const char* str2,size_t num)
{
int i = 0;
assert(str1 && str2);
char* p = str1;
for (i = 0; i < num; i++)
{
*str1 = *str2;
str1++;
str2++;
if (*str2 == '\0')
{
*str1 = 0;
}
}
return p;
}
int main()
{
char arr1[] = "helloworld";
char arr2[] = "lili nihao";
char* p = my_strncpy(arr2, arr1, 5);
printf("%s\n", p);
return 0;
}
3.2 strncat函数
strncat函数是用来把source指向的字符串的前num个字符追加到destination指向的字符串。
3.2.1 strncat函数的使用场景
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "1234567";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
}
3.2.2 需要注意的点
注意我们是要把第二个字符串的前几个字符追加到第一个字符串后面,在第一个字符串'\0'处开始追加,对于追加字符来说,函数需要在追加的字符后放'\0',这样追加上去的还是字符串。
例如:
从上面的代码及调试我们可以看出两个问题:
- 字符串追加从目标字符串的第一个'\0'处开始追加。
- 将字符追加到目标字符串后,函数需要在字符后面补'\0'。
- 自己给自己追加可以。
3.2.3 strncat函数的模拟实现
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strncat(char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
char* start = str1;
while (*str1 != '\0')
{
str1++;
}
int i = 0;
for (i = 0; i < num; i++)
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = '\0';
return start;
}
int main()
{
char arr1[50] = "abcdef\0xxxxxxxxxxx";
char arr2[] = "1234567";
char* p = my_strncat(arr1, arr2, 5);
printf("%s\n", p);
}
3.3 strncmp函数
strncmp函数用来比较两个字符串的前num个字符的大小。
3.3.1 strncmp函数的使用场景
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdgvgh";
int len = strncmp(arr1, arr2, 5);
printf("%d\n", len);
return 0;
}
3.3.2 strncmp函数的模拟实现
#include <stdio.h>
#include <string.h>
#include <assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
int i = 0;
for (i = 0; i < num; i++)
{
if (*str1 > *str2)
{
return 1;
}
if (*str1 < *str2)
{
return -1;
}
str1++;
str2++;
}
return 0;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdgvgh";
int len = my_strncmp(arr1, arr2, 5);
printf("%d\n", len);
return 0;
}
四、字符串查找相关函数
4.1 strstr函数
strstr函数用于定位子字符串,在一个字串中另外一个子串是否存在,返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回空指针。
4.1.1 strstr函数的使用场景
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abjddddd";
char arr2[] = "jdd";
char* p = strstr(arr1, arr2);
if (p != NULL)
{
printf("找到了\n");
}
else
{
printf("没找到\n");
}
return 0;
}
4.1.2 strstr函数的模拟实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* p = str1;
const char* s1 = str1;
const char* s2 = str2;
if (*str2 == '\0')
{
return NULL;
}
while (*p)
{
s1 = p;
s2 = str2;
while (*s2 != '\0' && *s1 != '\0' && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)p;
}
p++;
}
return NULL;
}
int main()
{
char arr1[] = "abjddddd";
char arr2[] = "jdd";
char* p = my_strstr(arr1, arr2);
if (p != NULL)
{
printf("找到了\n");
}
else
{
printf("没找到\n");
}
return 0;
}
4.2 strtok函数
对strtok函数的一系列调用将 str 拆分为标记,这些标记是由分隔符一部分的任何字符分隔的连续字符序列。
在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描令牌的起始位置。在后续调用中,该函数需要一个空指针,并使用最后一个令牌结束之后的位置作为扫描的新起始位置。
在上述函数的参数分别表示什么呢?
- delimiters参数是一个字符串,定义了用作分隔符的字符合集。
- 第一个参数指定一个字符串,它包含了0个或者多个由 delimiters 字符串中一个或者多个分隔符分隔的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
4.2.1 strtok函数的使用场景
#include<string.h>
#include <stdio.h>
int main()
{
char arr[] = "anjkbhbhvg@bhjb&cdvc";
char buf[200] = { 0 };
strcpy(buf, arr);
char a[] = "&@";
char* str = strtok(buf, a);
printf("%s\n", str);
str = strtok(NULL, a);
printf("%s\n", str);
str = strtok(NULL, a);
printf("%s\n", str);
str = strtok(NULL, a);
printf("%s\n", str);
}
五、错误信息报告函数
5.1 strerror函数
strerror函数是错误信息报告函数,把错误码转换为错误信息,在写代码时,我们会想好,在什么场景下要报一个什么样的错误,一般就有一个错误码与它相关联。
5.1.1 strerror函数的使用场景
我们在c语言中使用的库函数也有对应的错误码,我们可以通过错误码将错误信息打印出来。
#include <stdio.h>
#include <string.h>
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
printf("%s\n", strerror(5));
return 0;
}
5.1.2 strerror函数的应用举例
我们可以使用打开文件函数,如果打开文件函数打开文件成功就会返回一个地址,如果打开文件函数调用失败(即内存中没有这个文件),就会返回一个空指针,并且库函数调用失败的时候,会把一个错误码记录到存放错误码的变量中,errno是c语言提供的全局的错误变量,使用时需要引头文件errno.h,注意如果下一次函数调用错误,errno就会替换成新的错误码。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pf = fopen("test.txt","r"); //fopen是打开文件函数,text.txt是文件名,r代表以只读的形式打开
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
5.2 perror函数
perror函数可以直接打印错误信息,但是perror函数的错误信息也是从errno变量拿的,打印的依然是errno变量中错误码对应的错误信息。将 errno 的值解释为错误消息,并将其打印到 stderr(标准错误输出流,通常是控制台),可以选择在它前面加上在 str 中指定的自定义消息。
5.2.1 perror函数的使用场景
#include <stdio.h>
#include <errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r"); //fopen是打开文件函数,text.txt是文件名,r代表以只读的形式打开
if (pf == NULL)
{
perror("fopen");
}
fclose(pf);
pf = NULL;
return 0;
}
六、字符函数
6.1 字符分类函数
字符分类函数都需要引头文件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 任何可打印字符,包括图形字符和空白字符
我们来简单的使用一个函数吧!
使用isdigit函数判断是否为0-9的数字。
#include <stdio.h>
int main()
{
char ch = 'a';
int ret = isdigit(ch); //如果是0-9的数字就返回真即(非0),如果不是返回0
printf("%d\n", ret);
return 0;
}
6.2 字符转换函数
在学习c语言初期,我们都做过将一个大写字母转换为一个对应的小写字母的代码,但是其实在c语言的库函数中有将大写字母转换为小写字母的函数了。
tolower 将大写字母转换为小写
toupper 将小写字母转换为大写
#include <stdio.h>
#include <ctype.h>
int main()
{
char ch = 'w';
printf("%c\n", toupper(ch));
//注意ch本身不会改变,但是返回来的是转换之后的值
return 0;
}
当我们想要把一个英文短句中的所有大写字母转换为小写字母,我们就可以使用toupper函数和islower函数来实现。
include <stdio.h>
#include <ctype.h>
int main()
{
char arr[] = "Are you ok?";
char* p = arr;
while (*p)
{
if (islower(*p))
{
*p = toupper(*p);
}
p++;
}
printf("%s\n", arr);
return 0;
}
七、内存操作函数
7.1 memcpy函数
memcpy函数是内存拷贝函数,即把source指针指向空间里面的num个字节的数据拷贝放到destination指向的目的地,注意memcpy可以拷贝任意类型的数据。
7.1.1 memcpy函数的使用场景
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[20] = { 1,2,3,4,5,6 };
int arr2[] = { 5,8,9,7,6 };
memcpy(arr1, arr2, 12);
int i = 0;
for (i = 0; i < 20; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
7.1.2 需要注意的点
- memcpy函数遇到'\0'的时候并不会停下来。
- 当源空间与目标空间在内存上有重叠的时候,在拷贝数据时可能把目标空间的一些数据覆盖掉,放到目标空间去实际上覆盖的是源空间的一部分数据,这样源空间的数据如果没有被拿走的话就覆盖掉了,例如:我们想要把下面代码中1,2,3,4,5放到3,4,5,6,7的位置上,使用memcpy函数结果并不是和我们想象的一样。
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1+2, arr1, 20);
int i = 0;
for (i = 0; i < 20; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
那么是什么原因呢?
7.1.3 memcpy函数的模拟实现
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[20] = { 1,2,3,4,5,6 };
int arr2[] = { 5,8,9,7,6 };
my_memcpy(arr1, arr2, 12);
int i = 0;
for (i = 0; i < 20; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
在上面的代码中我们无法使用memcpy函数实现自己给自己拷贝的功能,那么c语言也提供了这样的函数那就是接下来的memmove函数。
7.2 memmove函数
memmove函数与memcpy函数相似,但是可以自己给自己拷贝,memmove函数需要实现重叠内存的拷贝。
7.2.1 memmove函数的使用场景
#include <string.h>
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr, arr + 2,20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
7.2.2 需要注意的点
-
和memcpy的差别是memmove函数处理的源内存块和目标内存块是可以重叠的。
-
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
7.2.3 memmove函数的模拟实现
我们发现如果我们不想要数据被覆盖掉,我们有时候需要从后向前拷贝,有时候需要从从前向后拷贝。
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (dest < src) //前后
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr, arr + 2,20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
7.3 memset函数
将 ptr 所指向的内存块的前 num 个字节设置为指定的值value,注意在这里num的单位是字节。
7.3.1 memset函数的使用场景
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6 };
memset(arr, 0, 9);
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
7.4 memcmp函数
memcmp函数是内存比较函数,不关注内存中放的是什么,只是让ptr1指向的空间的前num个字节的内容和ptr2指向的空间的前num个字节进行比较。
当ptr1指向的空间的前num个字节的内容比ptr2指向的空间的前num个字节大,返回1,当ptr1指向的空间的前num个字节的内容比ptr2指向的空间的前num个字节小,返回0,一对字节的比较。
7.4.1 memcmp函数的使用场景
#include<stdio.h>
#include<string.h>
int main()
{ //按照小端存储来看
int arr1[] = { 1,2,3,0,5 }; //01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 05 00 00 00
int arr2[] = { 1,2,3,4,5 }; //01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
int ret = memcmp(arr1, arr2, 13);
//比较arr1与arr2的前13个字节的内容的大小
printf("%d\n", ret);
return 0;
}
7.4.3 memcmp函数的模拟实现
#include<stdio.h>
#include<string.h>
int my_memcmp(const void* ptr1, const void * ptr2, size_t num)
{
char* str1 = (char*)ptr1;
char* str2 = (char*)ptr2;
int i = 0;
for (i = 0; i < num; i++)
{
if (*str1 == *str2)
{
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
if (*str1 < *str2)
{
return -1;
}
}
return 0;
}
int main()
{ //按照小端存储来看
int arr1[] = { 1,2,3,8,5 }; //01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 05 00 00 00
int arr2[] = { 1,2,3,4,5 }; //01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
int ret = my_memcmp(arr1, arr2, 13);
//比较arr1与arr2的前13个字节的内容的大小
printf("%d\n", ret);
return 0;
}