目录
字符串函数
对于字符串函数,我大概把他们分成了这样几类,希望能够帮助大家记忆与理解,顺便也能对这几个函数先稍稍熟悉
1.长度不受限制的字符串函数:
strcpy,strcat,strcmp
2.长度受限制的字符串函数:
strncpy,strncat,strncmp
3.字符串查找:
strstr,strtok
4.错误信息报告
strerror
strlen
这个函数的功能是计算字符串的字符数量,它从一个字符串的第一个字符一直计数到’\0’前的字符,比方说我们有这样一个数组
char arr1[] = "abc";
我们知道在引号内的字符串,编译器会自动在它的末尾加上一个’\0’,所以当我们用strlen对该数组计数时,输出结果就是3,又比方说我们有这样一个字符串
char arr2[] = "abc\0def";
int len = strlen(arr2);
那么这时的len等于几呢,我相信大家一定能想出答案,len等于3,因为strlen会接受一个元素的地址,并不断向后寻找计数到’\0’,而在这里c后面就出现了’\0’,所以计数也就到此为止了。值得注意的是,strlen的返回值是size_t类型,即unsigned int类型,是一个无符号的整型,所以当我们看到这样的代码时,我们会发现一个奇怪之处
int main()
{
char arr[] = "abc";
char arr1[] = "abcde";
if ((strlen(arr) - strlen(arr1)) > 0)
{
printf("arr is bigger");
}
else
{
printf("arr1 is bigger");
}
return 0;
}
显然arr1的长度应该是大于arr的,所以我们的输出结果理应是arr1 is bigger,但我们会看到这样一个结果
这就很让人疑惑了,这电脑不是睁着眼睛说瞎话么,arr1明明是肉眼可见的更大,为什么会发生这种情况呢,很简单,问题就出在strlen的返回值类型上,既然它是一个无符号整形数据,那么当两个无符号整形数据相减得到的结果也会是一个无符号整形,为了避免这一种情况,我们也可以编写一个我们自创的my_strlen
int my_strlen(const char* arr)
{
assert(arr != NULL);
int count = 0;
while (arr[count] != '\0')
{
count++;
}
return count;
}
int main()
{
char arr[] = "abc";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
这是让我们来运行程序,就会得到我们想要的结果了
strcpy
strcpy函数的功能是拷贝一个字符串的内容到另一个字符串,且strcpy会把’\0’也给拷贝到目标字符串,它的大致格式如下
strcpy(char* dest, char*src);
也就是把逗号之后的字符串给拷贝到前一个字符串中,比方说我有如下代码
int main()
{
char arr1[20] = "###########";
char arr2[] = "Hello";
strcpy(arr1, arr2);
return 0;
}
让我们来看看它的调试结果:
我们可以发现,arr2的内容包括’\0’全被拷贝进了arr1中,但是我们也得注意一个问题,当我们遇见了这样的代码:
int main()
{
char* p = "###########";
char arr2[] = "Hello";
strcpy(p, arr2);
return 0;
}
我们要思考,这里的拷贝是可行的吗?如果不可行,那么不可行的原因是什么?我们会发现这样的报错
因为在这样的写法中,p后面的是一个常量字符串,p记录的则是常量字符串中第一个元素的地址,所以常量字符串的元素自然不能被改变
strcat
字符串追加(字符串连接)
当我们想要把一串字符追加到字符的后面时,其实我们也有一个库函数可以使用,他就是strcat,它的基本格式跟strcpy一样,也是
strcat(char* dest, char* src);
也就是把逗号后面的字符串追加到逗号前的字符串,我们同样也可以放上来一串代码作为示例
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "World";
strcat(arr1, arr2);
return 0;
}
我们想把“World”给追加到“Hello”后面,让我们看看结果
这里我们就很好的把arr2中的字符追加到arr1中了,并且他还将目标字符串中的’\0’给覆盖掉
模拟实现strcat
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);//在这里确保传过来的不是一个空指针
while (*dest)
{
dest++;//首先我们让dest指向'\0'
}
while (*((dest++)) = *(src++))
{
;//在这里我们把src追加给dest,并在追加完'\0'后自动跳出循环
}
return ret;
}
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "World";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
那么函数可以给自己追加呢,答案是可以的,但不能用strcat,而要用strncat函数,至于为什么不能用strcat呢,我们可以从这个函数的原理来分析
为什么strcat不能给自己追加
比方说我创建了一个字符串“abcdef”,我要将它给自己追加
在my_strcat中我们可以看见我们将arr分别传输了给了dest和src,而在第一次循环后,dest指向了’\0’,所以此函数会把src中的a替代’\0’,以至于函数出现死循环
strcmp
它是在比较字符串中字符ascII码值的大小,基本格式为int strcmp(const char* str1, const char* str2);
大小 | str1>str2 | str1=str2 | str1<str2 |
---|---|---|---|
返回值 | >0 | =0 | <0 |
模拟实现strcmp
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
if (*s1 > *s2)//当然我们也可以把这些代码直接写成return *s1 - *s2;
{
return 1;//但因为在vs的编译器中返回值均为1,0,-1,所以这里也与vs的返回值对应
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abcde";
char arr2[] = "abde";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
strncpy
这个函数与strcpy的功能差不多,我们在strcpy函数中会发现,这个函数简直是桀骜不驯,比方说我们有两个字符串
arr1[] = "abcde";
arr2[] = "fghijkl";
我们会发现arr1数组只能容纳6个字符,但是当我们想把arr2中的字符串用strcpy拷贝给arr1时,这个函数会毫不犹豫地拷贝过去,并在拷贝之后猜想我们报错,这就很容易造成程序崩溃,所以为了相对安全,就出现了strncpy,他的基本格式为
strncpy(arr1, arr2, num);
在我们可以用num参数确定要拷贝元素的个数也就是说当我们给num传参1时,他就会将arr2中的第一个元素拷贝到arr1中的第一个元素
strncmp
这个函数和strcmp类似,也是用来比较字符串ascII码值大小的函数,但他有一个用来指定比较元素个数的参数,他的基本格式为
int strncmp(const char* s1, const char* s2, num);
他的返回值为整形
大小 | str1>str2 | str1=str2 | str1<str2 |
---|---|---|---|
返回值 | >0 | =0 | <0 |
我们可以拿几行代码来参考一下
int main()
{
char* arr1 = "abcdef";
char* arr2 = "abcefg";
int ret1 = strncmp(arr1, arr2, 3);
int ret2 = strncmp(arr1, arr2, 4);
printf("%d %d\n",ret1, ret2);
return 0;
}
我们分别比较了两字符串前3个与4个元素的大小,所以返回值分别为0,-1
strstr
我们看到这个函数中有两个str,所以很容易理解,这个函数实际上就是在一个字符串中寻找另一个字符串,比方说有一个字符串”abcdef”和”bcd”,显然我们可以在第一个字符串中找到第二个字符串,他的基本格式是
const char* strstr(const char* arr1, const char* arr2);
也就是在arr1寻找arr2,当找到时会返回第一次找到时的地址,找不到时则会返回一个空指针,比方说有这么一串代码
int main()
{
char* arr1 = "abcdefabcdef";
char* arr2 = "bcd";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("没找到");
}
else
{
printf("找到了: %s", ret);
}
return 0;
}
我们可以发现这个函数返回的是arr1中第一次出现arr2时的地址,而如果找不到时则返回一个空指针
模拟strstr的实现
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
while (*cp)
{
s1 = cp;
s2 = str2;
if (*str2 == '\0')
{
return (char*)str1;
}
while (*s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;
}
cp++;
}
return NULL;
}
int main()
{
char* str1 = "abbbbcdef";
char* str2 = "bbc";
char* ret = my_strstr(str1, str2);
if (ret == NULL)
{
printf("没找到");
}
else
{
printf("找到了: %s", ret);
}
return 0;
}
strtok
这个函数比较抽象,所以笔者会尽量详细地介绍,它的功能是切割字符串,比方说我们有这样一串儿字符”duzhe.doushi.dushuren”,我们想把它给切割成给duzhe,doushi,dushuren三个字符串我们就可以使用这个函数,那么还是老样子,我先展现一下这个函数的基本格式:
char* strtok(char* str, const char* sep);
我先写出这个函数的特性,语言有点生涩,但没关系,笔者会在后面用实例进行解释
1.sep参数是个字符串,定义了用作分隔符的字符集合。
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。
3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
记。
6.如果字符串中不存在更多的标记,则返回 NULL 指针。
让我们从一个简单的代码中理解这个函数
int main()
{
char arr[] = "abc.def@gh";
char* sep = ".@";
char* ret = NULL;
ret = strtok(arr, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
return 0;
}
我们现在有一个字符串”abc.def@gh”,想把它分割成abc,def和gh
我们又有一个名为sep的字符串,这里存在着分割标志.和@,在第一次strtok的调用中,它找到了第一个分隔符’.’,并将它用\0覆盖,并返回了a的地址,保存了这个\0的地址,在第二次调用中,它的第一个参数为NULL,所以他会从上一次保存的地址开始继续向后寻找下一个分隔符@,并重复以上步骤,在第三次调用中,strtok的第一个参数仍为NULL,他又会从记录的地址向后寻找直到\0结束,并返回g的地址,当然我们在实际使用中不会这样写,我们会用以下的写法
int main()
{
char arr[] = "abc.def@gh";
char* sep = ".@";
char* ret = NULL;
for (ret = strtok(arr, sep); ret != NULL; ret = strtok(NULL,sep))
{
printf("%s\n", ret);
}
return 0;
}
我们会发现一件比较讨厌的事情,strtok在每次调用时都会把源字符串中的分隔符给改变成’\0’,所以我们在使用这个函数时分割的往往是这个字符串的临时拷贝,比方说我们可以添加这样的代码
char arr1[30] = { 0 };
strcpy(arr1, arr);
并用arr1进行分割操作
strerror
这是我们讲解的最后一个字符串函数,坚持就是胜利!
让我们来想想我们使用库函数时会不会出错呢,答案是会的,而且出错时会返回一个错误码,可是我们看不懂这个错误码是什么意思,这是就需要使用strerror函数
int main()
{
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
printf("%s\n", strerror(5));
return 0;
}
下图为输出结果,即每个错误码的对应错误信息