文章目录
前言
本篇博客主要本人是对C语言的字符串相关函数的所学知识的一个总结,本质上算是一篇学习笔记,如果我分享的内容能够帮助到你,那再好不过。
求字符串长度的函数
strlen
函数声明:size_t strlen ( const char * str );
功能介绍:测量以 \0
为结束标志的字符串长度,长度不包括 \0
。
注意点:
- 参数指向的字符串必须要以
\0
结束。 - 注意函数的返回值为
size_t
,是无符号整型( 易错 )
以一个错误的代码为示范,该代码输出结果永远为大于
int main(void)
{
if (strlen("abc") - strlen("abcdef") > 0)
printf("大于\n");
else
printf("小于或等于\n");
return 0;
}
解释:
在计算机中CPU只有加法逻辑,减法运算就是两个数的补码进行相加,所以
strlen(“abc”) - strlen(“abcdef”)
= 3 - 6
= 3 + (-6)
= 00000000000000000000000000000011 + 00000000000000000000000000000110
= 11111111111111111111111111111101
对于一个无符号数来说,原码反码补码相同这段代码永远也不会打印 “小于或等于”。
如果想要实现打印“小于或等于也非常简单,只需要转换一下类型即可
修改后的条件为:(int)strlen("abc") - (int)strlen("abcdef")
strlen函数的模拟实现的三种方法:
- 遍历计数
#include <assert.h>
size_t myStrlen(const char* str)
{
assert(str);
size_t len = 0;
while (*str++)
{
len++;
str++;
}
return len;
}
- 指针 - 指针
#include <assert.h>
size_t myStrlen(const char* str)
{
assert(str);
const char* begin = str;
while (*str)
{
str++;
}
return str - begin;
}
- 函数递归
#include <assert.h>
size_t myStrlen(const char* str)
{
assert(str);
return *str == '\0' ? 0 : 1 + myStrlen(str + 1);
}
以上三种模拟实现中均用到了断言函数assert
,使用该函数有两个原因:
- 应对函数接收到
NULL
,而引发程序崩溃的问题。 - 相对更见方便找到引发程序错误的位置。
断言不通过会直接返回错误的位置,能够剩下不少找bug的时间,对断言函数有兴趣的可以去cplusplus了解一下,这里附上链接:assert
长度不受限制的字符串函数
strcpy
函数声明:char* strcpy(char * destination, const char * source );
功能介绍:将source
指向的字符串复制到destination指向的数组中,包括结束的\0
字符(并在该点停止)。
注意点:
-
源字符串必须以
\0
结束。 -
函数会将源字符串中的
\0
拷贝到目标空间。 -
目标空间必须足够大,以确保能存放源字符串,否则会报错。
【举例】
-
目标空间必须可变,否则无法拷贝。
【举例】
strcpy函数的模拟实现:
#include <assert.h>
char* myStrcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*src)
{
*dest++ = *src++;
}
*dest = *src;
return ret;
}
strcat
函数声明:char * strcat ( char * destination, const char * source );
功能介绍:将源字符串追加到目标字符串。destination
中的结束\0
字符被source
的第一个字符覆盖,并且在destination
新字符串的末尾包含一个\0
字符。
注意点:
- 源字符串必须以
\0
结束。 - 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 字符串自己给自己追加,如何?——
报错,无法编译!
理由:两个指针指向同一块空间,字符追加导致字符串变长,造成类似死循环的后果,最后越界访问,程序崩溃。
strcat函数的模拟实现:
#include <assert.h>
char* myStrcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest) //目标空间指向'\0'
dest++;
while (*dest++ = *src++) //从'\0'开始复制
;
return ret;
}
strcmp
功能介绍:比较字符串大小的函数,本质上比较对应位置上的字符的ASCII码值大小。
函数声明:int strcmp(const char* str1, const char* str2)
规则规定:
strcmp函数的模拟实现:
int myStrcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 && *str2 && *str1++ == *str2++)
{
;
}
return *str1 - *str2;
}
长度受限制的字符串函数
strncpy
函数声明:char * strncpy ( char * destination, const char * source, size_t num );
功能介绍:大致功能与 strcpy
函数相似,增加了第三个参数 num
,该函数只会拷贝 num
个字符。
注意点:
-
secure
指向的字符串长度 <num
时,在末尾填充\0
直到写入num
个字符
-
secure
指向的字符串长度 >=num
时,函数拷贝完num
个字符后不会在末尾添加\0
,且该函数不强制要求目标空间字符串以\0
结束。
-
由于该函数不强制要求目标空间字符串以\0结束,有可能造成溢出、非法访问等问题
strncpy函数的模拟实现
char* myStrncpy(char* destination, const char* source, size_t num)
{
assert(destination && source);
char* ret = destination;
size_t i = 0;
for (i = 0; *source && i < num; i++)
*destination++ = *source++;
for (i = 0; i < num; i++) //源字符串长度 < num 才执行
*destination++ = '\0';
return ret;
}
strncat
函数声明:char * strncat ( char * destination, const char * source, size_t num );
功能介绍:将指定的 num
个字符从source
追加到 destination
注意点:
source
指向的字符串长度 <num
时,追加完source
指向的字符串后,自动在末尾添加终止字符\0
。
source
指向的字符串长度 >=num
时,追加完num
个字符后会自动在末尾添加终止字符\0
。
strncat的模拟实现
char* myStrncat(char* destination, const char* source, size_t num)
{
assert(destination && source);
char* ret = destination;
while (*destination) //目标空间指向'\0'
destination++;
for (size_t i = 0; *source && i < num; i++)//从'\0'开始复制
{
*destination++ = *source++;
}
*destination = '\0';
return ret;
}
strncmp
函数声明:int strncmp ( const char * str1, const char * str2, size_t num );
功能介绍:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用以下对,直到字符不同,直到达到终止的空字符,或者直到两个字符串中的num
字符匹配,以先发生者为准。
规则规定:
strncmp函数的模拟实现
int myStrncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
size_t i = 0;
for (i = 0; i < num-1 && *(str1 + i) == *(str2 + i); i++)
{
;
}
return *(str1 + i) - *(str2 + i);
}
对指针有了解的还可以有这种写法
int myStrncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
size_t i = 0;
for (i = 0; i < num - 1 && str1[i] == str2[i]; i++)
{
;
}
return str1[i] - str2[i];
}
字符串查找
strstr
函数声明:char * strstr ( const char * str1, const char * str2 );
功能介绍:字符串查找子字符串,找到返回第一次出现的地址,否则返回NULL
。
注意点:
- 匹配过程不包括终止空字符,但到此为止
strstr函数的模拟实现:
char* myStrstr(char* str1, char* str2)
{
char *cur = str1;
char *s1 = NULL;
char *s2 = NULL;
while (*cur)
{
s1 = cur;
s2 = str2;
if (*str2 == '\0') //默认设定
return str1;
while(*s1 && *s2 && *s1 == *s2)
s1++, s2++;
if (*s2 == '\0')
return cur;
cur++;
}
return NULL;
}
strtok
功能介绍:以指定字符为分隔符,分割字符串。
函数声明:char* myStrstr(char* str1, char* str2)
注意点:
sep
参数是个以\0
字符串,定义了用作分隔符的字符集合。- 第一个参数指定一个字符串,它包含了0个或者多个由
sep
字符串中一个或者多个分隔符分割的标记。 strtok
函数找到str
中的下一个标记,并将其用\0
结尾,返回一个指向这个标记的指针。(注:strtok
函数会改变被操作的字符串,所以在使用strtok
函数切分的字符串一般都是临时拷贝的内容并且可修改。)strtok
函数的第一个参数不为NULL
,函数将找到str中第一个标记,strtok
函数将保存它在字符串中的位置。strtok
函数的第一个参数为NULL
,函数将在同一个字符串中被保存的位置开始,查找下一个标记。- 如果字符串中不存在更多的标记,则返回
NULL
指针。
文字苍白,代码为例,我们来看下面的代码例子:
这样写虽然能够完成将字符串分割的功能,但是如果arr
很长、分割字符很多时,就变得非常麻烦,下面介绍一种便捷的写法:
函数使用相关细节:
有一次我很好奇,把分割字符集写成空字符串会怎样,我的猜想是会直接返回NULL
, 但实际上不是。
函数参数sep
写成字符串的形式之后容易下意识的忽略掉了字符串中的\0
,看似没有分割字符,但是其实还有\0
。例子中以一个空字符串作为字符集按道理说第一次调用函数就应该返回NULL
输出应该全部都是空,但实际上第二次之后才是全为空。
另外,在查证过程中,发现了一片对strtok
函数研究颇深的的博客,想了解更多的可以去看看。
推荐:《到处是“坑”的strtok()—解读strtok()的隐含特性》
错误信息报告
strerror
函数声明:char * strerror ( int errnum );
功能介绍:
- C语言中定义了一个全局变量
errno
,每当有程序错误发生就会把errno
修改为错误信息的对应信息码。 - 该函数的作用就是获取信息码对应的错误信息,错误信息为一个字符串。
- 返回的指针指向静态分配的字符串,程序不应修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。
- strerror 生成的错误字符串可能特定于每个系统和库实现。
以下代码为是使用for
循环测试数字0~9所代表的错误信息是什么:
除了strerror
外,库里面还有一个功能作用相似的函数perror
使用该函数需要包含头文件stdio.h
函数声明:void perror ( const char * str );
功能介绍:解释全局变量errno
指代的错误信息并将其打印;参数为一个用户自定义的字符串,能够添加辅助性的文字提示