目录
1.字符切割函数
strtok
作用:一个字符串中包含0或多个要分割字符,会根据该字符进行标记对此字符串进行分割
函数声明:char * strtok ( char * str, const char * sep );
sep参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。
(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok的使用:
#include <string.h>
int main()//strtok -- 字符切割函数
{
char arr[] = "printfize@yeah.new";//这就是一个包含0或多个分割字符的字符串
const char p []= "@.";//这是要作为标记的分割的字符集合 (用常量或者数组变量都可以)
char buf[30] = { 0 };//arr的临时拷贝,因为strtok会更改目的地字符串
char* s1;//用于接收strtok返回的指针
strcpy(buf, arr);
//for循环初始化第一次传buf找到第一个标记,如果找到标记则会返回开始找这个标记的地方
//并且strtok会记录第一个标记的位置,第二次传NULL空指针进去就会从记录第一个标记的位置
//开始找第二个标记,循环往复
for (s1 = strtok(buf, p); s1 != NULL; s1 = strtok(NULL, p))
{
printf("%s\n", s1);
}
return 0;
}
strtok的模拟实现:
char* my_strtok(char* dest, const char* src)
{
static char* sign = NULL;//保存位置,设置为静态变量,本次函数结束不会被销毁;
char* ret = dest;//记录查找的开始位置
char* find = src;//用于重置分割字符指针起始位置
if (NULL == dest)//判断是否是二次调用,如果是二次调用则进入
{
if (sign != NULL)//判定上次查找是否有找到分隔符 没找到分隔符sign一定为NULL ! 如果不为NULL 就从上次标记的位置开始往后
{
ret = sign + 1;//记录查找开始的位置;
dest = sign + 1; //从标记后一位开始赋值查找;
}
else
return NULL;//如果sign是NULL 那就返回NULL;
}
while (*dest)
{
src = find;//如果在下面的while循环中没有匹配成功就重置分割字符指针位置
while (*src)
//这个while循环弹出的时候就证明当前的dest指针指向的字符,不是我们设定的分割符
{
if (*dest == *src)
{
sign = dest;
*dest = '\0';
return ret;
}
else
{
src++;
}
}
dest++;
}
//当跳出外层while循环并且没有结束这个函数的时候证明,这次查找没有找到分隔符 并且遇到了'\0',那就返回这次开始查找的位置,并且把sign置为空指针
sign = NULL;
return ret;
}
int main()//模拟实现strtok
{
char arr[] = "printfize@yeah.new";
char* p = "@.";
char* s1 = NULL;
char buf[30];
strcpy(buf, arr);
for (s1 = my_strtok(buf, p); s1 != NULL; s1 = my_strtok(NULL, p))
{
printf("%s\n", s1);
}
return 0;
}
2.返回错误码所对应错误信息函数
strerror
作用:可以把返回的错误码,翻译成错误码信息以供查看是什么错误
头文件为: #include <errno.h>
此函数不进行模拟..(因为太底层现在还不懂哈哈哈~)
函数声明: char * strerror ( int errnum );
strerror的使用:
对于strerror的使用这里要涉及另外一个函数
首先要了解,C语言是可以打开文件的
打开文件的函数 fopen 函数声明 FILE* fopen(const char* filename(要打开的文件名), const char* mode(要打开文件的方式) );
打开方式有 :
字符串模式指定文件的访问类型,如下所示:
“r”
打开阅读。如果文件不存在或找不到,fopen调用将失败。
“w”
打开一个空文件进行写入。如果给定的文件存在,它的内容将被销毁。
“a”
在文件的末尾(追加)打开写入,在向文件写入新数据之前不删除EOF标记;如果文件不存在,首先创建该文件。
“r+”
打开,既可以读也可以写。(该文件必须存在。)
“w+”
打开一个空文件进行读写。如果给定的文件存在,它的内容将被销毁。
“a+”
打开阅读和附加;附加操作包括在将新数据写入文件之前删除EOF标记,并在写入完成后恢复EOF标记;如果文件不存在,首先创建该文件。
注: 在这个工程里面打开的文件是打开此工程所在的文件路径底下的文件
#include <errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");//返回类型是个FILE*类型 所以我们也用FILE*类型指针来接收 如果失败的话fopen会返回一个NULL指针
if (NULL == pf)
{
//打印出错误的原因
//当库函数使用的时候,发生错误会把errno这个全局的错误变量设置为本次执行库函数产生的错误码
//errono是C语言提供的一个全局变量,可以直接使用,放在errno.h文件中
printf("%s\n", strerror(errno));
return 0;
}
//如果打开成功读文件...
//...
//
//关闭文件
fclose(pf);//如果上面打开都出错,那么下面关闭的时候可能也会出错,基于打开方式
//关闭文件的时候可能也会把全局的错误变量重置为另一个错误变量信息
pf = NULL;//使用完之后要把pf手动再置为空
return 0;
}
3.字符分类函数
函数 如果他的参数符合下列条件就返回真 头文件为 <ctype.h>
注:作为了解,以及要知道怎么使用,这个的底层比较简单不做模拟实现哈~
// iscntrl 任何控制字符
// isspace 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
// isdigit 十进制数字 0~9
// isxdigit 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF
// islower 小写字母a~z
// isupper 大写字母A~Z
// isalpha 字母az或AZ
// isalnum 字母或者数字,az,AZ,0~9
// ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
// isgraph 任何图形字符
// isprint 任何可打印字符,包括图形字符和空白字符*/
使用方式,以isspace为例:
#include <ctype.h>
int main()//字符分类函数
{
//printf("%d\n", isspace('!'));
char ch = 'w';
if (isspace(ch))//是否为空白字符
{
printf("是\n");
}
else
{
printf("不是\n");
}
return 0;
}
4.字符转换函数
int tolower ( int c ); /转换小写
int toupper ( int c );/转换大写
作用:将字符转换成大小写
使用方式:
#include <ctype.h>
int main()//字符转换函数 头文件<ctype.h>
{
//int tolower(int c);
//int toupper(int c);
//字符转换的使用
char a;
a = getchar();
if (islower(a))//如果是小写,转换成大写
{
a = toupper(a);
}
else//否则就是大写,转换成小写
a = tolower(a);
printf("%c", a);
return 0;
}
内存函数
前言:
C语言中字符和字符串通常要非常经常的进行处理,所以产生了专门作用于字符和字符串的函数,并不能作用于其他类型上
那么对于其他类型也有专门的函数,叫内存函数,内存函数比较特别,它的所有处理不针对任何类型,也就是说只要是任意一种类型,它都可以处理,它的特别之处就在于它是以高精度的单个字节对此块内存块进行单独处理
memcpy
函数声明 void * memcpy ( void * destination, const void * source, size_t num );
和strncpy一样使用方式,参数一样,只不过内存函数是是以单个字节进行处理,所以使用者在传参的时候要注意好字节个数
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
memcpy模拟实现:
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* srt = dest;
assert(dest && src);//确保指针有效性
while (num--)
{
//因为是单字节处理,所以临时强转成char*是最合适的!
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return srt;
}
int main()//memcpy模拟实现
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 11,12,13,14,15,16,17,18 };
my_memcpy(arr1, arr2, 20);
int i;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
memmove
函数声明: void * memmove ( void * destination, const void * source, size_t num );
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
给出这样一个代码 这一段代码想要的是只对同一块内存块进行处理,处理后输出的结果为1 2 1 2 3 4 5 8 9 10 :
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr1+2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
但是用我们自己写的模拟memcpy进行实现结果却是这样的
证明了我们自己写的memcpy并不能实现我们想要的结果,但是如果我们在VS编译器环境下用它的库函数memcpy会是这样的结果:
这是为什么呢? 是我的模拟实现写错了吗? !!!!
但其实并不是,自己写的my_memcpy在拷贝重叠的数组时会覆盖掉 但是vs环境下库里面的memcpy不会覆盖,实际上memcpy在C语言标准里面不要求memcpy处理拷贝重叠的内容所以自己写的memcpy不支持重叠拷贝是可以的,在其他编译器下memcpy不一定支持重叠拷贝
只能说VS编译器对这个库函数进行了优化,使得这个函数原本只能在考试中拿到60分,但是最后却拿到了100分,当然这个结果是好的,但是我们也一定要深刻的认识这个函数在其他编译器中并不一定具有这种不覆盖的功能, 相反 memmove函数并不是一开始就存在的,而是在大多数人发现memcpy的问题之后,创造出来基于memcpy功能之上的加强版,它可以对两个不同的内存块进行处理,也可以对同一内存块进行处理,这一些一定要做个了解!
memmove又可以处理不重叠的数据,又可以处理重叠数据,那么它是怎么做到能够让处理后输出的结果为1 2 1 2 3 4 5 8 9 10 :的呢? 这里我有一点思路
接下来开始对这个思路进行模拟实现
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* set = dest;
assert(dest && src);
if (dest < src)//这个就是如果dest在src左边也就代表着dest小于src
{
while (num--)
{
//进行num个字节次数交换
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else //否则就是 dest>src&&<src+num || dest>src+num
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
//这里不难理解,num先使用,是20 然后-- 进到这里面强转加上num的时候num已经是19了
//所以不需要再向上面一样特意的写上强转之后+1
}
}
}
int main()//memmove模拟实现
{
/* 函数声明: void * memmove ( void * destination, const void * source, size_t num );
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。*/
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1+2, arr1, 20);//20就是20个字节 移动5个整形的内容
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
memcmp
作用:比较从ptr1和ptr2指针开始的num个字节
函数声明:int memcmp ( const void * ptr1,
const void * ptr2,
size_t num );这个函数也比较简单,直接进行模拟实现,和strncmp模拟实现是一个道理的
memcmp的模拟实现:
#include <assert.h>
int my_memcmp(const void* srt1, const void* srt2, size_t num)
{
assert(srt1 && srt2);
while (num--)
{
if (*(char*)srt1 == *(char*)srt2)
{
srt1 = (char*)srt1 + 1;
srt2 = (char*)srt2 + 1;
}
else
return *(char*)srt1 - *(char*)srt2;
}
return 0;
}
int main()//memcmp模拟实现
{
/* 函数声明:int memcmp ( const void * ptr1,
const void * ptr2,
size_t num );
比较从ptr1和ptr2指针开始的num个字节*/
int arr1[] = { 1,4,3,4,5 };
int arr2[] = { 1,3,3,4,5 };
int ret = my_memcmp(arr1, arr2, 8);
printf("%d ", ret);
return 0;
}
memset
作用: 将指定内存区域设置为指定的字符。
// 返回值
// Memset返回dest的值。
// 参数
// dest
// 指针指向目的地
// c
// 要设置的字符
// count
// 要设置的字符个数
// memset函数将dest的第一个开始的count个字节设置为字符c。函数声明: void *memset( void *dest, int c, size_t count );
其实也就是把多少个字节设置成自己指定想要的字符或者数值, 函数第二个参数是int 是因为只要是字符,那么就有它的asscii值,也是个整形,内存中的修改都是对于二进制的修改,最后如果想要打印出字符以%c打印,一样是翻译你当初输入的字符,基于看自身怎么对它进行输出
memset的模拟实现:
#include <assert.h>
void* my_memset(void* dest, int c, size_t count)
{
void* set = dest;
assert(dest);
while (count--)
{
*(char*)dest = c;
dest = (char*)dest + 1;
}
return set;
}
int main()//memset模拟实现
{
int arr[10] = { 0 };
my_memset(arr, 'x', 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出结果为
这个可能看的比较蒙, 那么从调试中内存视角来看一下
78是十六进制,翻译成10进制就是120,转换成assci码表里对应的就是x 后面4个xxxx其实就是对它进行的翻译
这就是字符串函数的结尾+内存函数
所有模拟都是以本人个人角度去思考这个函数的实现方式以及查阅函数功能的基础上进行的模仿,难免可能会存在一些缺陷,如果有大佬看出来还请留言点出~
创作不易~希望可以点赞评论转发~能关注最好哈~感谢您