文章目录
前言
对基本的常用的库函数不仅要掌握并且能熟练地运用这些库函数,而且最好可以自己熟悉底层逻辑;
一、函数介绍
1.1 strlen的使用和模拟实现
strlen的模拟实现和用法博客
参数指向的字符串必须要以 ‘\0’ 结束。
注意函数的返回值为size_t,是无符号的( 易错 )
1.2 strcpy的使用和模拟实现
1.3 strcat的使用和模拟实现
这个库函数的作用是在一个字符串后面追加一个字符串,注意:这哥们是不能自己追加自己后面的哈,因为会把’\0’覆盖导致死循环;
char * strcat ( char * destination, const char * source );
括号里面第一个参数是目的地指针,第二个是源头指针,把源头指针的字符串追加到目的地字符串中;
源字符串必须以 ‘\0’ 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
模拟实现的思路是先找到目的地字符串中的\0,再把要放到后面的字符串直接从目的地的\0开始追加字符串,也就是把目的地字符串的\0覆盖;
#include <stdio.h>
#include <assert.h>
#include <string.h>//strcat的头文件
char* my_strcat(char* dest, const char* src) {
assert(dest && src);
char* ret = dest;
while (*dest) {
++dest;
}
while (*dest++ = *src++) {
;
}
return ret;
}
int main() {
char arr1[20] = "abcdef";
char arr2[] = "ghijklmn";
char* p=my_strcat(arr1, arr2);
printf("%s\n", arr1);
printf("%s\n", p);
return 0;
}
1.4 strcmp的使用和模拟实现
用途就是比较字符串是否相等,返回值的话就是相等就返回值为0,大于就返回大于0的数,小于就返回小于0的数;
int strcmp ( const char * str1, const char * str2 );
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
int my_strcmp(const char* p1, const char* p2) {
assert(p1 && p2);
while ( *p1 == *p2 ) {
if (*p1 == '\0')
return 0;
++p1;
++p2;
}
return p1 - p2;
}
int main() {
/*char arr1[20] = "abcdef";
char arr2[] = "ghijklmn";
char* p=my_strcat(arr1, arr2);*/
char* p1 = "abcdef";
char* p2 = "abcde";
int ret = my_strcmp(p1, p2);
if (ret == 0)
printf("=\0");
else if (ret < 0)
printf("<\0");
else
printf(">\0");
/*printf("%s\n", arr1);
printf("%s\n", p);*/
return 0;
}
1.5 strncpy的使用
这个库函数和strcpy库函数功能差不多,都是拷贝字符串的,不过这个库函数的话是我们自己来控制要拷贝多少字节的空间,也可以说拷贝多少个字符,因为一个字符占一个字节嘛,使用的话就多了一个控制拷贝字节数的参数就是了;
char * strncpy ( char * destination, const char * source, size_t num );
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个,就是源字符串数量不够,后面全部用0(\0)来凑嘛。
用法和strcpy是一样的,模拟实现也就多个参数就是了;
1.6 strncat的使用
- 和strcat的使用差不多,和上面的一样,也比原库函数多一个参数,也是控制字节数的,我们控制最后一个参数进而控制在目的地字符串后追加多少个字符的字符串,注意:这个库函数是可以自己追加自己后面的,而strcat是不可以在自己后面追加自己的,因为会把’\0’覆盖导致死循环;
- char * strncat ( char * destination, const char * source, size_t num );
- 如果num小于等于源字符串长度的话,这个库函数会自动帮我们在目的地字符串最后面追加一个‘\0’;如果num大于源字符串长度的话,库函数不用追加’\0’,因为会把’\0’拷过去,还有就是不会再把后面越界的内容拷贝过去,即使num要拷贝的长度大于源字符串,我们把源字符串全部包括’\0’都拷贝过去就行了,不用考虑没有拷贝到那么多的内容过去;
1.7 strncmp的使用
- int strncmp ( const char * str1, const char * str2, size_t num );
- 一样的,用法就多了个num参数,控制要比较的源字符串的字节数;
- 返回值还是一样的,相等就返回0,大于就返回大于0的数,小于就返回小于0的数;
- 上面三个加了n的库函数叫做长度受限制的字符串函数,对应的没有加n的三个库函数就叫做长度不受限制的字符串函数;
1.8 strstr的使用和模拟实现
- strstr是判断源字符串是否可以在目的地字符串中找到,是连续的哦,不是目的地字符串中有源字符串的那些字符就行,而是可以找到和源字符串一样的字符串,所以找到的的话源字符串也叫作目的地字符串的子串;
- char * strstr ( const char *str1, const char * str2);
- 找到的的话返回在目的地字符串中的和源字符串的首字符一样的字符的地址,如果不是的话就返回空指针(NULL);
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2) {
assert(str1 && str2);
//我们不能只是遍历一遍就行,因为看下面这种情况,匹配一遍没有成功,或许
//从下一个或者下下个字符再开始匹配就成功了也说不定的;所以这里的话是都
//慢慢遍历一遍去匹配,不过我这里写的效率不高,好像KMP啥的效率高,不过我
//没有去了解哈;
const char* p1 = str1;
const char* p2 = str2;
const char* p = str1;
while (*p) {//要是str1中走到最后的'\0'还没有找到,跳出这个循环了,说明
//匹配不到了,不是子串;
p1 = p;
p2 = str2;
while (*p1!='\0' && *p2!='\0' && *p1 == *p2) {//其实最左边的p1可以去掉,这里左边的
//p1,p2是为了到了'\0'还++导致指针越界
p1++;
p2++;
}
if (*p2 == '\0')
return p;
++p;
}
return NULL;
}
int main() {
char arr1[] = "abbbcdef";
char arr2[] = "bbcdef";
char* p = my_strstr(arr1, arr2);
if (p == NULL) {
printf("arr2不是arr1的子串\n");
}
else
printf("%s\n", p);
return 0;
}
1.9 strtok的使用和模拟实现
- 这个库函数使用的时候是很奇怪的,先讲一下作用吧,我们自己写一个字符串,这个字符串中的字符都是作为分隔符的,把目标字符串用这些字符分割开来;
- char * strtok ( char * str, const char * sep );
- 为了不改变原字符串,我们应该使用拷贝的一份字符串来分割,因为这个库函数会把分隔符变成’\0’;
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置(自动的)。
- 第二次分割开始strtok函数的第一个参数为 NULL(很重要),函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记(最后面了),则返回 NULL 指针。当然,要是一个标记也没有,也就返回NULL了呗;
#include <stdio.h>
#include <string.h>//strtok的头文件
int main() {
char arr[] = "3038911537@qq.com";
char* p1 = "@.";
char arr1[25] = { 0 };
strcpy(arr1, arr);
for (char* ret = strtok(arr1, p1); ret != NULL; ret = strtok(NULL, p1)) {
printf("%s\n", ret);//要是可以找到分隔符,返回的就是被分割的字符串首元素地址
}//用for循环来运用这个strtok库函数真的再合适不过了,第一次第一个参数用目标字符串,后面都是用NULL;
return 0;
}
1.10 strerror的使用
- 这个是用来显示错误信息的,将错误码转换为错误信息,方便我们查找错误; 而一般错误码会自动存到errno这里,我们只需要用strerror讲erron转换为错误信息就行了;
- char * strerror ( int errnum ); int类型的,其实直接用0,1,2这样的都会显示对应的错误信息,用strerror的话;
- 不过简单再介绍一下更简单好用的perror;
void perror ( const char * str );里面是字符串,它会直接打印错误信息,比strerror的话更方便;
#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");//文件操作,只读文件
//perror("unexist.ent");这个就这样用就行,更简单;
if (pFile == NULL)
printf ("Error opening file unexist.ent: %s\n",strerror(errno));
//errno: Last error number
return 0;
}
1.11 字符分类函数:
函数 如果他的参数符合下列条件就返回真,返回非零,假就返回0
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 任何可打印字符,包括图形字符和空白字符
字符转换:
int tolower ( int c );
int toupper ( int c );
#include <stdio.h>
#include <ctype.h>//这个头文件上面的库函数基本就用这个
int main ()
{
int i=0;
char str[]="Test String.\n";
char c;
while (str[i])
{
c=str[i];
if (isupper(c))
c=tolower(c);
putchar (c);
i++;
}
return 0;
}//上面那些字符函数用法和返回值都一样的,就判断括号里面是否符合这个库函数的用法;判断是否为字母?或者是否大小写这样的;
1.12 memcpy的使用和模拟实现
- 这个库函数区别于strcpy,strcpy只可以拷贝字符串,上面也介绍了,而memcpy是啥类型都可以拷贝的,准确来说,strcpy是按照字符内容来拷贝,而memcpy是按照内存来拷贝;
- void * memcpy ( void * destination, const void * source, size_t num );
- 比较strcpy来说,还是多了个控制参数,你来控制拷贝多少个字节内容;
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 ‘\0’ 的时候并不会停下来,看下面模拟实现就知道,要循环拷贝num次,不会因为’\0’而停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的,就是不确定是否可以自己在自己身上拷贝,比如memcpy(arr+2,arr,20),如果是int类型的,意思是把自己第一个元素开始的五个整形拷贝到自己第三个元素开始的五个整形上,要实现这个就要用我们的memmove这个库函数。
#include <stdio.h>
#include <string.h>//memcpy的头文件
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num) {//这里都用void* 的原因是设计
//这个库函数的人是不知道用的人会传什么类型的数据过来的,故使用void*容纳一切指针
assert(dest && src);
void* ret = dest;
while (num--) {//一个字节一个字节来拷贝,总共num次
*(char*)dest = *(char*)src;//void* 是不可以解引用的,也不可以+-整数
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main() {
double arr[] = { 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0 };
double arr1[10] = { 0.0 };
my_memcpy(arr1, arr, sizeof(arr));
int i = 0;
for (i = 0; i < 10; ++i) {
printf("%lf ", arr1[i]);
}
return 0;
}
1.13 memmove 的使用和模拟实现
- void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
- 看下面图片,自己拷贝自己身上就是有重叠的可能,这个时候就用memmove;
#include <stdio.h>
#include <string.h>//memmove的头文件
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num) {
assert(dest && src);
void* ret = dest;//重叠的情况可以分成两种,如果src<dest的话,如果我们从前往后拷贝,就是
//把1拷贝到3的位置,然后把2拷贝到4的位置,多少把3拷贝到5上的时候,3的位置上已经是拷贝好的1
//所以会导致拷贝失败;这个时候我们可以从后往前拷贝,把5拷贝到8的位置是,把4拷贝到7位置是
//如此拷贝导致拷贝失败;这就是从前往后和从后往前拷贝方法;当src>=dest的话,就使用
//从前往后法,src<dest的话,就使用从后往前法;
if (src >= dest) {
while (num--) {
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--) {
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);//把3~8拷贝成1~5
int i = 0;
for (i = 0; i < 10; ++i) {
printf("%d ", arr[i]);
}
return 0;
}
1.14 memcmp的使用
- 和strcmp也是多个控制参数,还是可以比较其它内容,因为它是用内存在进行比较的,模拟实现的话简单,这里就不进行实现了,就是换个void *和强制转换(char *)就可以模拟实现了;
- int memcmp ( const void * ptr1,const void * ptr2,size_t num );
- 返回值和strcmp,strncmp是一样的,相等就返回0,小于就返回小于0的数,大于就返回大于0的数嘛,就这样使用的;
1.15 memset的使用
- void * memset ( void * ptr, int value, size_t num );
- 看第二个参数,是整形,我们的字符类型也是分到整形家族的,因为字符类型在储存的时候也是用asscll值的,也是整形,作用就是把ptr指向的数据上num个字节内容都改成中间参数,比如中间参数是1,那么把ptr指向的数据上num个字节内容都改成1,要是中间参数是字符a,一样的都改成字符a,但是要注意是以字节为单位的哈;
总结
那么多的库函数,一下子肯定都记不过来的,正常,多用几次,熟悉了就得心应手了,也不用刻意求背,重要的是理解和会模拟实现;