前面介绍完字符函数,相信小伙伴们一定对其有了充分的认识,紧接着,我们所要学习的是字符串函数。在字符串函数中,我们主要学习strlen、strcpy、strcat、strcmp、strncpy、strncat、strncmp、strstr、strtok、strerror。
1. strlen函数
首先,我们要介绍的是strlen函数,在刚开始接触C语言的时候我们就简单介绍了strlen函数,并且还与sizeof关键字进行了对比:字符串、转义字符及注释。
那么今天我们需要更进一步的学习,边总结,边挖深这个函数的用法。
1.1 strlen函数计算字符串长度
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
//strlen-求字符串长度
//字符串的结束标志是\0
//strlen统计的是\0之前出现的字符的个数
char arr1[] = "abcdef";
size_t len1 = strlen(arr1);
printf("%u\n", len1);
//strlen函数要正确获得字符串的长度,字符串中必须得有\0
char arr2[] = { 'a', 'b', 'c' };
size_t len2 = strlen(arr2);
printf("%u\n", len2);
return 0;
}
运行结果:
1.2 要注意strlen的返回值类型是size_t
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
//要注意strlen的返回值类型是size_t
// 3 6
//-3<0,但是如果是无符号整型,就得 >= 0
if (strlen("abc") - strlen("abcdef") > 0)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
运行结果:
以上都是strlen函数的基本用法,我还需要掌握更进阶的用法—strlen函数的模拟实现!
1.3 strlen函数的模拟实现
方法一:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
//仿照strlen函数的参数,返回类型,功能写一个类似的函数
//assert
//const
//*指针解引用
//指针+整数
size_t my_strlen(const char* str)
{
size_t count = 0;
assert(str != NULL);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%u\n", len);
return 0;
}
运行结果:
方法二:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
//指针-指针得到的是中间元素的个数
size_t my_strlen(const char* str)
{
const char* start = str;
while (*str != '\0')
{
str++;
}
const char* end = str;
return end - start;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%u\n", len);
return 0;
}
运行结果:
除了上面两种方法外,我们也可以采用递归的方法来模拟实现。
方法三:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//第三种实现方法 ---> 递归的方式
//模拟实现strlen,不创建临时变量
//递归的思想,大事化小
//
//my_strlen("abcdef")
//1 + my_strlen("bcdef")
//1 + 1 + my_strlen("cdef")
//1 + 1 + 1 + my_strlen("def")
//1 + 1 + 1 + 1 + my_strlen("ef")
//1 + 1 + 1 + 1 + 1 + my_strlen("f")
//1 + 1 + 1 + 1 + 1 + 1 + my_strlen(" ")
//1 + 1 + 1 + 1 + 1 + 1 + 0
size_t my_strlen(const char* str)
{
if (*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%u\n", len);
return 0;
}
运行结果:
2. strcpy函数
2.1 strcpy函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//strcpy函数
//函数功能:拷贝字符串
//注意事项:
//1.源字符串必须包含\0,同时\0也会被拷贝到目标空间
//2.程序员要保证目标空间要足够大,能放得下拷贝来的数据
//3.保证目标空间必须可以修改
int main()
{
char arr1[] = "abcd";
char arr2[] = "xxxx";
strcpy(arr1, arr2);
printf("%s\n", arr1);
//char arr1[] = { 'a', 'b', 'c', 'd', '\0' };
//char arr2[] = "xxxx";
//strcpy(arr1, arr2);
//printf("%s\n", arr1);
//
return 0;
}
运行结果:
另外,如果把内容拷贝到常量字符串,程序会发生崩溃
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//strcpy函数
//函数功能:拷贝字符串
//注意事项:
//1.源字符串必须包含\0,同时\0也会被拷贝到目标空间
//2.程序员要保证目标空间要足够大,能放得下拷贝来的数据
//3.保证目标空间必须可以修改
int main()
{
//char arr1[] = "abcd";
//char arr2[] = "xxxx";
//strcpy(arr1, arr2);
//printf("%s\n", arr1);
//
//char arr1[] = { 'a', 'b', 'c', 'd', '\0' };
//char arr2[] = "xxxx";
//strcpy(arr1, arr2);
//printf("%s\n", arr1);
//
//程序崩溃
char arr1[] = "abcd";//—常量字符串来创建数组,本质是数组,数组内容是可以改变的
char* p = "wertoe";//常量字符串—不可修改
strcpy(p,arr1 );
printf("%s\n", p);
return 0;
}
运行结果:
2.2 strcpy函数模拟实现
方法一:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void my_strcpy(char* dest, char* src)
{
//拷贝的是'\0'之前的字符
while (*src != '\0')
{
*dest = *src;
src++;
dest++;
}
//拷贝'\0'字符
*dest = *src;
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
运行结果:
方法二:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void my_strcpy(char* dest, char* src)
{
//拷贝的是'\0'之前的字符
while (*src != '\0')
{
*dest++ = *src++;
}
//拷贝'\0'字符
*dest = *src;
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
运行结果:
方法三:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
void my_strcpy(char* dest, char* src)
{
assert(dest);
assert(src);
//拷贝的是'\0'之前的字符
while (*dest++ = *src++)
{
;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
运行结果:
方法四:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
//dest指向的空间是需要改变的,但是src指向的空间是不期望被改变的
void my_strcpy(char* dest, const char* src)
{
assert(dest);
assert(src);
//拷贝的是'\0'之前的字符
while (*dest++ = *src++)
{
;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
其实是在方法三的基础上进行优化。
方法五:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
//NULL
assert(dest);
assert(src);
//拷贝的是'\0'之前的字符
while (*dest++ = *src++)
{
;
}
return ret;//目标空间的起始地址
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
char* p = my_strcpy(arr2, arr1);
//链式访问
printf("%s\n", p);
return 0;
}
运行结果:
3. strcat函数
3.1 strcat函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
//strcat—字符串追加
//1.目标空间中得有\0(从哪里开始追加),源头字符串中得有\0(追加什么时候结束)
//2.目标空间要足够大,目标可以修改
int main()
{
char arr1[20] = "hello ";
char* p = "world";
strcat(arr1, p);
printf("%s\n", arr1);
return 0;
}
运行结果:
3.2 strcat函数模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
//模拟实现
char* my_strcat(char* dest, char* src)
{
char* ret = dest;
//NULL
assert(dest&&src);
//找到目标空间中的\0
while (*dest != '\0')
{
dest++;
}
//2.拷贝数据
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello \0xxxxxxxx";
char* p = "world";
my_strcat(arr1, p);
printf("%s\n", arr1);
return 0;
}
运行结果:
3.3 strcat函数自追加
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
//strcat函数自己给自己追加
char* my_strcat(char* dest, char* src)
{
char* ret = dest;
//NULL
assert(dest&&src);
//找到目标空间中的\0
while (*dest != '\0')
{
dest++;
}
//2.拷贝数据
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello \0xxxxxxxx";
char* p = "world";
my_strcat(arr1, arr1);
printf("%s\n", arr1);
return 0;
}
运行结果:
我们发现,程序会发生崩溃!
这是为什么呢?
其实很简单,我们可以用下面这幅图来解释:
当dest走到 ‘\0’ 的位置时,'\0’会被替换成a,而src永远比dest走得慢,src永远在dest的后面,这就会导致后面src永远也不会遇到 ‘\0’,就会导致死循环。
而要解决这个问题,就需要用到strncat函数,这个我们后面再讲。
4.strcmp函数
4.1 strcmp函数的使用
strcmp—string compare,比较字符串的大小
比较两个字符串中对应位置上的字符——按字典序比较。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
int main()
{
//比较2个字符
int ret = strcmp("abwdef", "abq");
if (ret > 0)
{
printf(">\n");
}
else if (ret == 0)
{
printf("==\n");
}
else
{
printf("<\n");
}//这里不能用ret == 1 / ret == -1来判断,因为strcmp函数return的是大于/小于0的随机数。
printf("%d\n", ret);
return 0;
}
运行结果:
4.2 strcmp函数模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
int my_strcmp(char* str1, char* str2)
{
assert(str1);
assert(str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
//return *str1 - *str2;
//如果*str1 == *str2,就进入到循环里面去
}
int main()
{
int ret = my_strcmp("abcdef", "abc");
if (ret > 0)
{
printf(">\n");
}
else if (ret == 0)
{
printf("==\n");
}
else
{
printf("<\n");
}
printf("%d\n", ret);
return 0;
}
运行结果:
5. strcpy、strcmp、strcat—长度不受限制的字符串函数,不关心目标空间放不放的下,存在安全性问题
这里我们以strcpy函数举例说明:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char arr1[3] = { 0 };
char arr2[] = "abcdef";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
运行结果:
我们发现,str2中的“abcdef”依然能够拷贝到str1中,但是拷贝以后程序就崩溃了!所以这些函数在一定程度上是不安全的,所以C语言中提供了一组带n的函数(strncpy,strncmp,strncat),这些函数在参数部分也多了一个参数,这些函数称为长度受限制的函数。
其中,n表示num,表示拷贝、追加、比较的字节数。
6. strncpy函数
6.1 strncpy函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[10] = "xxxxxxxxxx";
char arr2[] = "ab";
strncpy(arr1, arr2, 5);//不够的话就补\0
printf("%s\n", arr1);
return 0;
}
运行结果:
拷贝num个字符从源字符串到目标空间。如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加 ‘\0’,直到num个。
6.2 strncpy函数的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest&&src);
char* start = dest;//目标字符串的起始地址
while ((*src !='\0')&&(*dest !='\0'))// 拷贝字符串
{
*dest = *src;
dest++;
src++;
num--;
}
if (num) // 当num大于src的长度时,将补充空字符
{
while (num)
{
*dest++ = '\0';
num--;
}
}
return start;
}
int main()
{
char arr1[10] = "xxxxxxxxxx";
char arr2[] = "ab";
char* ret = my_strncpy(arr1, arr2, 11);//不够的话就补\0
printf("%s\n", ret);
return 0;
}
运行结果:
7. strncat函数
7.1 strncat函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[10] = "abc \0xxxxx";
char arr2[] = "abc";
strncat(arr1, arr2, 5);//将dest字符串最后的'\0'覆盖掉,字符追加完成后,再追加空字符,即'\0'
//如果num大于字符串src的长度,那么会将src全部追加到dest的后面;
//如果num小于字符串src的长度,仅将前num个字符追加在dest末尾
printf("%s\n", arr1);
return 0;
}
运行结果:
7.2 strncat函数模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strncat(char * dest, const char * src, size_t num)
{
assert(dest);
assert(src);
char* destination = dest + strlen(dest);//目标地址
size_t s = strlen(src);//src的长度
size_t i = 0;
if (num > strlen(src))
{
for (i = 0; i < s; i++)
{
*(destination++) = *(src++);
}
*(destination) = '\0';
}
else
{
for (i = 0; i < num; i++)
{
*(destination++) = *(src++);
}
*(destination) = '\0';
}
return dest;
}
int main()
{
char arr1[10] = "abc \0xxxxx";
char arr2[] = "abc";
char* ret = my_strncat(arr1, arr2, 0);
printf("%s\n", ret);
return 0;
}
运行结果:
8. strncmp函数
8.1 strncmp函数的使用
int strncmp ( const char * str1, const char * str2, size_t num );
比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字符,如果提前发现不⼀样,就提前结束,大的字符所在的字符串大于另外一个。如果num个字符都相等,就是相等,返回0。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char arr1[] = "abcwert";
char arr2[] = "abcrytg";
int ret = strncmp(arr1, arr2, 3);
printf("%d\n", ret);
return 0;
}
运行结果:
当比较3个字符的时候,return 0,即相等,那么比较4个字符的时候呢?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcwert";
char arr2[] = "abcrytg";
int ret = strncmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}
运行结果:
我们发现运行结果变为了1,即arr1 > arr2,这是由于 ‘w’ 的ASCII码值大于 ‘r’。
8.2 strncmp函数模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
int my_strncmp(const char* des, const char* src, size_t num)
{
assert(des && src);
while (num--)
{
if (*des == *src)
{
if ((*des == '\0') || (*src == '\0'))
{
return 0;
}
des++;
src++;
}
else if (*des > *src)
{
return 1;
}
else
{
return -1;
}
}
return 0;
}
int main()
{
char arr1[] = "abcwert";
char arr2[] = "abcrytg";
int ret = my_strncmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}
运行结果:
9. strstr函数
9.1 strstr函数的使用
strstr函数功能是在字符串中找寻另一个字符串。
Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
The matching process does not include the terminating null-characters, but it stops there.
翻译过来就是:
返回一个指针(位置),这个指针是str2在str1中首次出现的位置,如果str2不在str1中就返回一个空指针。
这个匹配过程不包括终止空指针,但是是在空指针处终止。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "defq";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("找不到\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
运行结果:
9.2 strstr函数模拟实现
思路分析:
完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
const char* my_strstr(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
const char* cp = str1;
const char* s1 = NULL;
const char* s2 = NULL;
//如果字符串是空字符串,直接返回str1
if (*str2 == '\0')
{
return str1;
}
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 == *s2 && *s1 != '\0' && *s2 != '\0')
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;
}
cp++;
}
return NULL;
}
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "defq";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("找不到\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
运行结果:
我们最好把const 修饰的指针传给const修饰的指针变量,不然编译器会报警告,如果把const修饰的指针交给非const修饰的指针,数据就变得不安全了。
10. strtok函数的使用
对于strtok函数,我们只需要学会它的用法即可,对于模拟实现我们不需要掌握。如果小伙伴们感兴趣的话,也可以自己去尝试模拟实现它,具体的模拟实现过程,我们后面再讲解。
在C语言库中,strtok函数定义为字符串分解函数,str 为一组字符串,delimiters 为分隔符。
该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "192.168.6.111";
char* sep = ".";
char *token = NULL;
//获取第一个子字符串
token = strtok(str, sep);
//继续获取其他的子字符串
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, sep);
}
return 0;
}
运行结果:
11. strerror函数
11.1 strerror函数的使用
返回一个错误码所对应的错误信息字符串的起始地址。
在C语言的库函数中,我们设计了一些错误码,当我们库函数在调用的过程中发生了各种错误,要记录下来,这时候记录的就是错误码。
0 ——> no error
1 ——> 错误信息1
2 ——> 错误信息2
3 ——> 错误信息3
…
举例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
for (i = 0; i < 10; i++)//打印10个错误码对应的信息
{
char* ret = strerror(i);
printf("%d: %s\n", i, ret);
}
return 0;
}
运行结果:
0:没有错误
1:命令不被允许
2:没有这个文件或路径
3:没有这个进程
4:函数调用被中断
5:输入/输出错误
6:没有这个设备或地址
7:列表太长(参数过多)
8:格式错误
9:坏文件描述符
当库函数调用失败的时候,会将错误码记录到errno这个变量中,errno是一个全局变量。
举例:打开文件—读写文件前,需要打开文件。
读写文件前,需要打开文件,如果打开成功,需要文件是存在的,如果文件不存在,则打开失败,fopen返回NULL。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pf = fopen("add.text", "r");
if (pf == NULL)
{
printf("打开文件失败,失败的原因:%s\n", strerror(errno));
}
else
{
printf("打开文件成功\n");
}
return 0;
}
运行结果:
除了strerror函数外,我们还有一个叫做perror的函数,perror函数相当于⼀次将上述代码中的第10行完成了,直接将错误信息打印出来。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pf = fopen("add.text", "r");
if (pf == NULL)
{
perror("打开文件失败,失败的原因:");
}
else
{
printf("打开文件成功\n");
}
return 0;
}
运行结果:
好了,以上就是本期博客的全部内容了,创作不易,希望小伙伴们多多点赞支持。