一些较为常见的库函数
0.前言
如果觉得这篇博客不错的话,点个赞再走吧😊
这篇博客主要讲的是一些较为常见且实用的字符串函数。
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
字符串常量 适用于那些对它不做修改的字符串函数.
int main()
{
//字符数组
char arr[] = "asdfghh";
//常量字符串
char* p = "asdfghh";
return 0;
}
1.求字符串长度
strlen
1.1 函数介绍
strlen
size_t strlen ( const char * str );
这个函数非常常见,就是求一下字符串的长度。
从第一个字符开始计数,只要找到’\0’就停止计数。
int main()
{
char arr[] = "abcd";
printf("%d\n", strlen(arr));//结果为4
return 0;
}
1.2 注意事项
- 字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )
- 参数指向的字符串必须要以 ‘\0’ 结束
- 注意函数的返回值为size_t,是无符号的( 易错 )
1.3 经典例子
例一: 注意函数的返回值为size_t,是无符号的( 易错 )
int main()
{
const char*str1 = "abcdef";
const char*str2 = "bbb";
if(strlen(str2)-strlen(str1)>0)
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}
这里打印出来的是str2 > str1,因为虽然str1的长度为6,str2的长度为3,但是strlen函数返回值为size_t类型的,也就是无符号数,所以在比较的时候补码转换出来的数是3 > 6的,结果也就跟我们平常相像中的不一样。
例二:参数指向的字符串必须要以 ‘\0’ 结束
int main()
{
char arr1[] = { 'a', 'b', 'c' };
char arr2[] = "abc";
printf("%d\n", strlen(arr1));//打印一个未知数
printf("%d\n", strlen(arr2));//打印3
return 0;
}
上面arr1的字符串数组没有’\0’,所以strlen当找到字符c之后找不到’\0’,就会在内存中继续寻找,直到在内存中找到了’\0’。这时候结果就是一个未知数。
例三:字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )
int main()
{
char arr[] = "qwert \0awer";
printf("%d\n", strlen(arr));//结果是6而不是11
return 0;
}
1.4 模拟实现
int my_strlen(char* str)
{
assert(str);//这是断言str不为空指针
int count = 0;
while (*str++)
{
count++;
}
return count;
}
int main()
{
char arr[] = "awedf";
printf("%d\n", my_strlen(arr));
return 0;
}
2. 长度不受限制的字符串函数
strcpy
2.1.1 函数介绍
字符串拷贝函数
strcpy
char* strcpy(char * destination, const char * source )
字符串是不能用等号直接赋值的,所以得要用strcpy这个函数。
这个函数将source这个字符串拷贝到了destination这个字符串中。并且会将’\0’拷到其中。
2.2.1 注意事项
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变
2.3.1 经典例子
例一:会将源字符串中的 ‘\0’ 拷贝到目标空间。
例二:源字符串必须以 ‘\0’ 结束。
例三:目标空间必须足够大,以确保能存放源字符串。
这样的代码运行起来会直接崩掉。但是编译器还是会将长的字符串拷贝到短的字符串中。
例四:destination必须可变
这样也会崩掉,因为常量字符串是无法被修改的。
2.4.1 模拟实现
//函数的返回值为目标空间的起始地址
char* my_strcpy(char* destination, const char* source)
{
assert(destination && source);
char* res = destination;
while (*destination++ = *source++);
return res;
}
int main()
{
char arr1[] = "asqwerref";
char arr2[] = "asdf";
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
这里可以将返回值给void,如果这样的话打印的时候就不能跟上面一样了,就只能打印arr1。
strcat
2.1.2 函数介绍
字符串追加函数
strcat
char * strcat ( char * destination, const char * source );
这个函数将source字符串的内容追加到了destination的后面。也会将’\0’放到最后。
2.2.2 注意事项
- 源字符串必须以 ‘\0’ 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 自己给自己追加字符串
2.3.2 经典例子
例一:源字符串必须以 ‘\0’ 结束。
例二:目标空间必须有足够的大,能容纳下源字符串的内容。
跟上面的strcpy一样,也会崩掉。
例三:目标空间必须可修改。
也跟strcpy一样。
例四:自己给自己追加字符串
程序会崩掉,原因如下:
2.4.2 模拟实现
char* my_strcat(char* destination, const char* source)
{
assert(destination && source);
//返回目标空间的地址
char* res = destination;
//找到目标空间中的\0
while (*destination)
{
destination++;
}
//追加
while (*destination++ = *source++)
{
;
}
return res;
}
int main()
{
char arr1[20] = "bad";
char arr2[] = "asdf";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}
strcmp
2.1.3 函数介绍
字符串对比函数
strcmp
int strcmp ( const char * str1, const char * str2 );
这里二者对比的时候比的是每一个的字符的大小,如果第一个字符相等,则继续往后比,直到不相等或者同时到了\0为止。如果前一个字符比后一个字符大,返回大于零的数,小则返回小于零的数。
2.2.3 注意事项
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
2.3.3 经典例子
小于
这里a和b先比,直接出结果,a的ASCII比b小就返回小于0的数。
相等
2.4.3 模拟实现
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
//二者是否同时到达\0
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
//这里标准规定是返回大于或小于0的数,没有说一定要返回-1或者1
return *str1 - *str2;
}
int main()
{
char arr1[] = "abcedf";
char arr2[] = "cbcedf";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
3. 长度受限制的字符串函数介绍
strncpy
3.1.1 函数介绍
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
3.2.1 注意事项
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
3.3.1 经典例子
例一:拷贝num个字符从源字符串到目标空间。
例二:如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
这里拷贝6个字符,因为arr2只有bnm\0,所以拷贝时会再加两个\0。
strncat
3.1.2 函数介绍
strncat
char * strncat ( char * destination, const char * source, size_t num );
3.2.2 注意事项
- 追加的时候先找destination的\0,然后再追加
- 追加n个字符,并且会在最后加上\0
- n若大于source长度,则追加时只会加一个\0,然后就不再追加。
3.3.2 经典例子
例一:追加的时候先找destination的\0,然后再追加
例二:追加n个字符,并且会在最后加上\0
例三:n若大于source长度,则追加时只会加一个\0,然后就不再追加。
strncmp
3.1.3 函数介绍
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
3.2.3 注意事项
于strcmp一样,只不过比较的长度规定了
3.3.3 经典例子
4. 字符串查找
strstr
4.1 函数介绍
strstr
char * strstr ( const char *str1, const char * str2);
这个函数是用来找str1中str2第一次出现的位置,并返回出现位置的首字符的地址。如果没找到,则返回NULL。
4.2 模拟实现
char* my_strstr(char* str1, char* str2)
{
char* s1 = str1;
char* s2 = str2;
char* src = str1;
while (*src)
{
//每次记住str1内部找的首元素
s1 = src;
//如果首字符相等了
if (*s1 == *s2)
{
s1++;
s2++;
//每个字符都判断一次
while (*s1 == *s2)
{
s1++;
s2++;
}
//除了循环之后无非两种情况
//第一种就是匹配成功就返回了字符串中的首位置
if (*s2 == '\0')
return src;
//第二种就是匹配失败
else
s2 = str2;
}
//到这里就是某一趟没找到,就要将str1回溯到匹配的首字符的下一个位置
src++;
}
//到这就是str1都走完了还没找到
return NULL;
}
int main()
{
char arr1[] = "abcdefgh";
char arr2[] = "bcd";
printf("%s\n", my_strstr(arr1, arr2));
return 0;
}
5. 字符串截断
strtok
5.1 函数介绍
strtok
char * strtok ( char * str, const char * sep );
- sep参数是个字符串,定义了用作分隔符的字符集合
- str指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
5.2 使用示例
但是一般不这么用,这样用起来太麻烦了。
方便一点的是下面这种方式。
6. 错误信息报告
strerror
6.1 函数介绍
strerror
char * strerror ( int errnum );
这个函数会返回错误码(errnum)所对应的错误信息的首字符的地址。
每一个数字对应一个错误信息。
看例子:
C语言库函数调用失败的时候,会将失败的错误码放在errno这个变量中。
当我们想要知道调用库函数的时候发生了什么错误,就可以将errno中的错误码转换为错误信息。
errno的头文件就是errno.h
比如说:
字符操作
is****
下面的这些函数都是判断某一个字符是什么的函数
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 任何可打印字符,包括图形字符和空白字符
这些函数有什么用呢?
就拿大写字母转小写来说吧:
7. 内存操作函数
7.1 memcpy
1.1 函数介绍
memcpy
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
1.2 函数示例
普通示例:
1.3 模拟实现
我们再来看一下这个函数的参数:
void * memcpy ( void * destination, const void * source, size_t num );
这两个参数的类型都是void*的,为什么这么设计呢?
因为void*可以接收任何类型的指针,所以当你想使用这个函数的时候,就不需要纠结传参的类型了。但是void*的指针也有一个缺点,就是不能直接解引用。那么如何解决这个问题呢?
我们还可以看到,第三个参数是需要拷贝的字节大小,num个字节挨个复制,结合前面所学,我们就可以知道,只需要将两个指针强制类型转换为char*类型即可。
下面我们就来模拟实现一下。
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);
//返回目标空间的首地址
void* ret = dest;
//count个字节要复制
while (count--)
{
//拷贝
*(char*)dest = *(char*)src;
//拷贝一次就往后挪
dest = (char*)dest + 1;
src = (char*)src + 1;
}
//返回目标空间首地址
return ret;
}
但是如果我们想要自己拷贝自己的话,就不能这样用了,因为有内存重叠的部分。
那么这时候就要用到memmove了。
7.2 memmove
2.1 函数介绍
memmove
void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
这样的话就不会出现循环的情况。
2.2 模拟实现
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
if (dest > src)
{
//如果dest>src就得从后往前拷贝
while (count--)
{
*((char*)dest + count) = *((char*)src + count);
}
}
else
{
//dest>src就得从前往后拷贝
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
return ret;
}
结果就是这样的:
从后往前拷贝:
从前往后拷贝:
7.3 memcmp
3.1 函数介绍
memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
这个函数和strncmp对比的方式是一样的,只不过是可以用别的类型来进行对比,num就是比较的字节数。
这里就直接上例子了:
相同的情况:
小的情况:
大的情况:
7.4 memset
memset
void* memset( void* dest, int c, size_t count );
这个函数就是你给定字节大小(count), 然后从dest开始的地址往后到count的空间的内容改为c。
这里要注意当我们count可不是元素个数,而是字节数,若记错了会导致意想不到的后果,像这样:
到此结束。