目录
大家好,曳渔又来了~
希望可以获得大家的点赞和关注
前言:
大家在编写程序的时候,经常会用到字符和字符串,为了方便操作呢,C语言标准库中呢提供了一系列的关于字符和字符串的函数,今天就让我们来学习一下吧!!!
在下面介绍的函数都需要引用头文件<string.h>
一:字符串函数:
1. strlen——求字符串的长度
函数的原型:
函数的作用:
• 字符串以'\0'作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包
含'\0' )
• 参数是一个字符指针变量
• 参数指向的字符串必须以'\0'结束
• 注意函数的返回值是size_t,是无符号的
使用strlen函数的注意事项:
咱们演示一下size_t是什么意思
OK 我们可以看到,arr1长度应该是6,arr2的长度应该是5,那么arr2-arr1应该是-1,所以应该是打印arr2<arr1啊,为什么打印arr2>arr1呢,这就是size_t的作用了。6和5都是以无符号形式存储的,当他们相减后得到的-1也应该以无符号形式存储,而-1在内存中以补码形式存在时,存储的数会很大!!!这就是size_t的作用所以,我们要减少写此类的代码!!!
三种模拟实现的方法:
在我们会使用后,我们也要明白它是如何实现的。
第一种:计数器方法
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen1(const char* a)
{
//计数器
assert(a);
int count = 0;
while (*a)
{
++count;
++a;
}
return count;
}
int main()
{
char arr[] = {"abcdefgh"};
printf("%zd\n",my_strlen1(arr));
return 0;
}
第二种:指针-指针
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen2(const char* b)
{
//指针减指针
assert(b);
char* p = b;
while (*p != '\0')
{
++p;
}
return p- b;
}
int main()
{
char arr[] = {"abcdefgh"};
printf("%zd\n", my_strlen2(arr));
return 0;
}
第三种:不创建临时变量(递归方法)
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen3(const char* c)
{
//不创建临时变量
assert(c);
if (*c == '\0')
{
return 0;
}
else
{
return 1 + my_strlen3(c+1);
}
}
int main()
{
char arr[] = { "abcdefgh" };
printf("%zd\n", my_strlen3(arr));
return 0;
}
2.长度不受限制的字符串函数:
1):strcpy——字符串的拷贝:
函数原型
函数的作用:
把源字符串拷贝到目标字符串上
• 源字符串必须以 '\0' 结束
• 会将源字符串中的 '\0' 拷贝到目标空间
• 目标空间必须足够大,以确保能存放源字符串
• 目标空间必须可修改
使用函数时的特殊情况:
会把'\0'拷贝进去遇到'\0'终止
函数的模拟实现:
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* a, const char* b)
{
assert(a != NULL);
assert(b != NULL);
char* c = a;
while (*a++ = *b++)
{
;
}
return c;
}
int main()
{
char arr1[] = { "hello word" };
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
2):strcat——追加字符串
函数模型:
函数的作用:
追加函数:在目标字符串后面追加源字符串
• 源字符串必须以 '\0' 结束
• 目标字符串中也得有 \0 ,否则没办法知道追加从哪里开始
• 目标空间必须有足够的大,能容纳下源字符串的内容
• 目标空间必须可修改
注意:不能自己追加自己,那样会陷入死循环
函数的模拟实现:
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* a, const char* b)
{
assert(a!=NULL);
assert(b!=NULL);
char* c = a;
while (*a)//先找被添加的\0
{
a++;
}//循环结束*a为\0
while (*a++ = *b++)
{
;
}
return c;
}
int main()
{
char arr1[20] = { "hello " };
char arr2[20] = { "word" };
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
3):strcmp——字符串之间的比较
函数原型:
函数的作用:
比较函数:比较两个字符串的大小,根据ASCLL值进行比较
比较规则:
• 第⼀个字符串大于第二个字符串,则返回大于0的数字
• 第⼀个字符串等于第二个字符串,则返回0
• 第⼀个字符串小于第二个字符串,则返回小于0的数字
举个例子:
函数的模拟实现:
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* arr1, const char* arr2)
{
assert(arr1);
assert(arr2);
while (*arr1 == *arr2)
{
if (*arr2 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
return *arr1 - *arr2;
}
int main()
{
char arr1[] = { "abcdef" };
char arr2[] = { "abcdgh" };
printf("%d\n", strcmp(arr1, arr2));
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}
3.长度受限制的字符串函数:
1):strncpy——拷贝一定长度的字符串:
会有人问为什么会有长度受限制的函数,直接用不受限制的不行么,答案是有时候是不可以的因为,不受限制的函数如果目标空间的内存不够大,直到找到源字符串的'\0'才结束的话会造成越界,所以不安全,所以才会出现长度受限制的函数。
函数模型:
函数的作用:
把拷贝的函数受限制
• 拷⻉num个字符从源字符串到⽬标空间
• 如果源字符串的⻓度⼩于num,则拷⻉完源字符串之后,在⽬标的后边追加0,直到num个
模拟实现:
#include<stdio.h>
#include<string.h>
//模拟实现strncpy
char* my_strncyp(char* dest, const char* src, int num)
{
int i = 0;
char* b = dest;
for (i = 0;*src!='\0' && i < num; i++)
{
*dest = *src;
dest++;
src++;
}
if (i < num)
{
*dest = '\0';
}
return b;
}
int main()
{
char arr1[20] = { "abcdef" };
char arr2[20] = { "ghjk" };
char* a = strncpy(arr1, arr2, 4);
printf("%s\n", a);
char arr3[20] = { "abcdef" };
char arr4[20] = { "ghjk" };
char* b = my_strncyp(arr3, arr4, 5);
printf("%s\n", b);
return 0;
}
2):strncat——追加一定长度的字符串
函数模型:
函数的作用:
追加一定长度的字符
• 将字符串的前num个字符追加指向的字符串末尾,再追加⼀个 \0 字符
• 如果源字符串的长度小于num时,会再拷贝完成后,在目标字符串后面追加'\0'直到num
函数的实现:
#include <stdio.h>
#include <string.h>
char* my_strncat(char* dest, const char* src, int num)
{
int i = 0;
char* a = dest;
while (*dest)
{
dest++;
}
for (i = 0; *src!='\0'&&i < num; i++)
{
*dest = *src;
dest++;
src++;
}
if (i < num)
{
*dest = '\0';
}
return a;
}
int main()
{
char str1[20] = { "abcdef " };
char str2[20] = { "ghigklmn" };
strncat(str1, str2, 10);
printf("%s\n", str1);
char str3[20] = { "abcdef " };
char str4[20] = { "ghigklmn" };
char* a = my_strncat(str3, str4, 10);
printf("%s\n", a);
return 0;
}
3):strncmp——比较一定长度的字符串
函数模型:
函数的模拟实现:
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* arr1, const char* arr2)
{
assert(arr1);
assert(arr2);
while (*arr1 == *arr2)
{
if (*arr2 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
return *arr1 - *arr2;
}
int main()
{
char arr1[] = { "abcdef" };
char arr2[] = { "abcdgh" };
printf("%d\n", strcmp(arr1, arr2));
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}
4.字符串的查找:
1):strstr——查找子字符串
函数的模型:
函数的作用:
判断是否是子字符串
• 函数返回字符串str2在字符串str1中第⼀次出现的位置
• 字符串的比较匹配不包含 \0 字符,以 \0 作为结束标志
函数的模拟实现:
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strstr(const char* arr1, const char* arr2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = arr1;
if (*arr2 == '\0')
{
return (char*)arr1;
}
while (*cur)
{
s1 = cur;
s2 = arr2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cur;
}
cur++;
}
return NULL;
}
int main()
{
char arr1[] = { "abcdddedefgh" };
char arr2[] = { "ddede" };
char* str = strstr(arr1, arr2);
printf("%s\n", str);
char* str1 = my_strstr(arr1, arr2);
printf("%s", str1);
return 0;
}
2):strtok——将字符串拆分为标记
函数的模型:
函数的作用:
• delimiters参数指向⼀个字符串,定义了用作分隔符的字符集合
• 第⼀个参数指定⼀个字符串,它包含了0个或者多个由delimiters字符串中⼀个或者多个分隔符分割的标记
• strtok函数找到str中的下⼀个标记,并将其用 \0 结尾,返回⼀个指向这个标记的指针
• strtok函数的第⼀个参数不为 NULL ,函数将找到str中第⼀个标记,strtok函数将保存它在字符串
中的位置。
• strtok函数的第⼀个参数为 NULL ,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标
记。
• 如果字符串中不存在更多的标记,则返回 NULL 指针。
函数的使用:
3):strerror——获得字符串的错误信息
函数模型:
函数的作用:
可以把参数部分错误码对应的错误信息的字符串地址返回来
• 可以把参数部分错误码对应的错误信息的字符串地址返回来
函数应用:
在Windows11+VS2022环境下输出的结果如下:
这里还有一个函数为perror函数,相当于printf+errno
5.特殊的字符函数:
1):字符分类函数:
这类函数的使用时需要引用ctype.h
2):字符转换函数:
1. tolower:将参数传进去的大写字母转小写
int tolower ( int c );
2. toupper:将参数传进去的小写字母转大写
int toupper ( int c );
二:内存函数:
1. memcpy——内存拷贝函数
函数模型:
函数的作用:
• 函数从源函数开始复制个字节的数据到目标函数指向的内存里
• 这个函数在遇到'\0'的时候不会停下来
• 如果源函数和目标函数有任何的重叠,那么复制的结果的都是未定义的
函数的模拟实现:
#include<stdio.h>
#include<string.h>
//模拟实现memcpy
void* my_memcpy(void* dest, const void* src, int num)
{
void* a = dest;
while (num--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
return a;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
printf("\n");
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[10] = { 0 };
my_memcpy(arr4, arr3, 20);
for (i = 0; i < 10; i++)
{
printf("%d ", arr4[i]);
}
return 0;
}
然而这个函数是有缺陷的,当拷贝的有重叠时,会存在问题:
例如:我把arr中的1 2 3 4 5拷贝到 4 5 6 7 8 上是会有问题,理论上是1 2 3 1 2 3 4 5 9 10
其实是这个:
那么当我们需要这样的复制时,要怎么办呢?这是我们就需要另一个内存函数了——memmove
2. memmove——内存拷贝函数
函数模型:
函数的作用:
• 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
• 如果源空间和目标空间出现重叠,就得使用memmove函数处理
分析情况:
当把1->4的时候2->5的时候4和5就发生了改变,后面再把4->7,5->8的时候4和5就不是原来的4和5了。那么怎么改变之个问题呢?当我们从后往前走时就不会发生这种情况了,就是先把4->7,5->8之后再把1->4,2->5,这样就不会发生问题了。但是我们只用后往前走就可以解绝所有问题了吗?答案当然是不可以的。我们举个例子来看看。
这种情况从后往前走就会把4,5改变,就是8->5,7->4之后再把5->2,4->1是4和5就不是原来的值了,所以这时就需要从前往后走。那么我们什么时候从前往后什么时候从后往前呢?让我们来解决这个问题。
函数的模拟实现:
#include<stdio.h>
#include<string.h>
#include<assert.h>
//模拟实现memmove
void* my_memmove(void* dest, const void* src, int num)
{
void* a = dest;
assert(dest && src);
if (dest < src)
{
//src从前往后
while (num--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
}
else
{
//src从后往前
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
memmove(arr + 2, arr, 5 * sizeof(int));
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
my_memmove(arr1 + 2, arr1, 5 * sizeof(int));
for (i = 0; i < sz1; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
3. memset——填充内存的函数
函数的模型:
作用和注意事项:
作用:memset是⽤来设置内存的
注意:将内存中的值以字节为单位设置成想要的内容
函数的实现:
4. memcmp——比较两个内存的函数
函数模型:
函数作用:
• 比较从ptr1和ptr2指针指向的位置开始,向后的num个字节
• 两个内存中不匹配的第一个字节在ptr1>ptr2时候,返回大于0的数
• 两个内存中不匹配的第一个字节在ptr1<ptr2时候,返回小于0的数,相当返回0
总结:
本次是从函数的介绍来初步了解字符串函数和内存函数,再通过模拟库函数的实现来深刻理解函数的使用。
这次我们的文章就到这里结束了,当然这篇文章还有很多不足之处,希望大家可以指正。
OK 让我们下个文章再见吧!!!