文章目录
前言
在计算机编程过程中,友友们都会使用到大量的字符或字符串相关的函数,但大多只晓得相关的用法,却不知道其背后的实现过程,了解的不够深入,时间久了也许会这样…
在本小节中将会向大家展示一下字符函数和字符串函数的实现,这里将向大家举例一些非常常见的函数,通过对函数的使用以及自我实现来加深对这些知识的了解,话不多说,走起😏
📖汇总:
1、strlen
关于这个函数,使用的频率可谓是相当的高,相信大家一定不陌生,在之前的博客中也有总结过,在这里回顾一下。
显而易见,这是一个计算字符串长度的函数
✏️小试牛刀:
(1)关于’\0’
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a', 'b', 'c' };
//例①
int len1 = strlen(arr1);
printf("%d\n", len1);
//例②
int len2 = strlen(arr2);
printf("%d\n", len2);
return 0;
}
👁效果展示:
在编译器中,例①显示的结果是3,例②显示的结果是随机值15。
因为strlen函数计算的是’\0’之前的字符个数。
在例①中,字符串是以’\0’为结束标志,因此字符的个数为3;
在例②中,不包含’\0’,用strlen求长度时,未见到’\0’时,就会持续的向后数,直到遇见’\0’为止
(2)关于strlen的返回值类型
可以看见库里面strlen的返回类型是无符号整数,这会有什么影响吗?当然
#include <stdio.h>
int main()
{
const char*str1 = "abcdef";
const char*str2 = "bbb";
if(strlen(str2)-strlen(str1)>0)
{
printf("str2>str1\n");
}
else
{
printf("str1>str2\n");
}
return 0;
}
这段代码你认为结果会是多少呢?如果不了解strlen函数返回值类型的友友萌一定认为是str1>str2,事实上!
搞错了!重来
答案是str2>str1,因为strlen的返回类型是size_t(无符号整型)
在平时模拟实现strlen时,用int作为返回值就会避免这样的情况。
🔖说明总结:
- 字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
- 参数指向的字符串必须要以 ‘\0’ 结束。
- 注意函数的返回值为size_t,是无符号的( 易错 )
strlen的模拟实现
#include <stdio.h>
#include <assert.h>
//1.方法一:计数器的版本
int my_strlen(const char* arr)
//arr并不会被修改,所以加上const修饰,指针所指向的内容将不能够通过指针 被修改,函数会变得更加安全
{
assert(arr);//断言指针,arr不得是空指针,保证指针的有效性,使用更安全
int count = 0;
//计数器count,放字符串的长度
while (*arr != '\0')
//取地址arr不等于'\0'说明找到了一个字符
{
count++;
arr++;//使其找到下一个字符
}
return count;
}
//2.方法二:递归的版本
//int my_strlen(char* arr)
//{
// if(*arr == '\0')
// return 0;
// else
// return 1 + my_strlen(arr + 1);
//}
//3.方法三:指针减指针的版本
//int my_strlen(char* arr)
//{
// char*str = arr;
// while(*str != '\0')
// str++;
// return str - arr;
//}
int main()
{
char arr[20] = "asdfg";
int ret = my_strlen(arr);
printf("%d", ret);
return 0;
}
2、strcpy
这个函数使用的频率更加的频繁,因此需要大家深刻了解
这是一个字符串拷贝的函数
✏️小试牛刀:
int main()
{
char* str = "xxxxxxxxxxxxxxxxxxx";
char arr[15] = "##########";
char arrr[5] ="###";
//例①
strcpy(arr, "hello");
printf("%s\n", arr);
//例②
char arr2[] = { 'a', 'b', 'c' };
strcpy(arr,arr2)
printf("%s\n", arr);
//例③
strcpy(arrr,"hello");
printf("%s\n", arrr);
//例④
char* p = "hello";
strcpy(str, p);
printf("%s\n", str);
return 0;
}
👁效果展示:
在例①中:
hello在传参时。传的时h的地址,把地址传给了strSource,因此strSource 指向hello中h的地址,然后将其指向的内容拷贝到arr中
拷贝hello时’\0’也被拷贝过去了
在例②中,arr2不含有’\0’,因此拷贝时就会出现问题,不知道何时遇见’\0’,此时已经越界很久了
挂了!!!
在例③中:
hello字符拷贝过去打印出来了,但因为目标空间不足,导致报错,作为合格的程序员应该保证不犯这样的错误
在例④中:
str指向的是一个字符串常量,不能被修改,因此…程序崩溃
🔖说明总结:
- 源字符串必须以’\0’结束
- 会将字符串中的’\0’拷贝到目标空间
- 目标空间必须有足够的大,能够容纳下源字符串的内容
- 目标空间必须可以修改
strcpy的模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
//拷贝,连同'\0'一起
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "$$$$$$$$$";
char arr2[6] = "hello";
printf("%s", my_strcpy(arr1, arr2));
return 0;
}
3、strcat
这是一个字符串追加函数
✏️小试牛刀:
int main()
{
//例①
char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1, arr2);//字符串追加(连接)
printf("%s\n", arr1);
//例②
char str1[20] = "hello \0##########";
char str2[] = "world";
strcat(str1, str2);//字符串追加(连接)
printf("%s\n", str1);
//例③
char tmp[15] = "abcd";
strcat(tmp, tmp);
printf("%s\n", tmp);
return 0;
}
👁效果展示:
在例①中:
我们可以看见,arr[6]处的’\0’被’w’所替代,arr[7]处的’\0’被’o’所替代,以此类推
在例②中:
为了检验字符串追加过程中,‘world’后面的’\0’是一起追加过去的,还是原来本就自带的,我们通过例②来实现。
如果’\0’一起被追加过去了,那么后面有一个’#‘将会被’\0’覆盖,结果中将不会打印出’#‘来。如果是自带的,’#'们将会被打印出来。
结果表明源字符串中的’\0’会被追加过去。
因此例①的字符串追加过程应当如下:
在例③中:
用strcat函数能否实现自己给自己追加呢?
结果显示不可行。
🔖说明总结:
- 源字符串必须以’\0’结束
- 目标空间必须有足够的大,能够容纳下源字符串的内容
- 目标空间必须可以修改
- 无法实现自己追加自己
strcat的模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* tmp = dest;//记录目标空间的起始位置
//1.找到目标字符串中的'\0'
while (*dest)
{
dest++;
}
//当循环结束时dest指向'\0'
//2.源字符串追加过去,包括'\0'
while (*dest++ = *src++)
{
;
}
return tmp;//返回目标空间的起始位置
}
int main()
{
char arr1[20] = "hello ";
char arr2[20] = "world!";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}
4、strcmp
strcmp函数之前有所介绍
这是字符串比较函数
在还未认识strcmp函数之前,你是否以为比较两个字符串是这样的:
int main()
{
char* p = "obc";
char* q = "abcdef";
if (p > q)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
这样的代码代表着将’obc’的首元素地址放入指针变量p中,'abcdef’的首元素地址放入指针变量q中,把俩指针进行大小的比较,字符串本身并未比较。
✏️小试牛刀:
int main()
{
int ret1 = strcmp("abbb", "abq");
int ret2 = strcmp("aaa", "aaa");
int ret3 = strcmp("acb","abb");
printf("%d\n", ret1);
printf("%d\n", ret2);
printf("%d\n", ret3);
return 0;
}
👁效果展示:
🔖说明总结:
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
strcmp的模拟实现
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
//当比到\0时,说明字符串相等,返回0
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
//*s1大于*s2,返回大于0的数;*s1小于*s2,返回小于0的数
}
int main()
{
char* p = "abcdef";
char* q = "abcdef";
int ret = my_strcmp(p, q);
if (ret > 0)
{
printf("p > q\n");
}
else if (ret < 0)
{
printf("p < q\n");
}
else
{
printf("p == q\n");
}
return 0;
}
5、strncpy strncat strncmp
可以看出前面的几个函数都是长度不受限制的,你源字符串有多少,就给你拷贝多少到目标空间,有多少就给你追加到目标空间里多少,有多少就给你字符串比较多少。
因此也就会有长度受限制的字符串函数,这里介绍一下。
可以看出和原来的长度不受限制的字符串函数相比,长度受限制的字符串函数多了一个参数count,就是通过该参数来决定拷贝多少,追加多少,比较多少。
✏️小试牛刀:
int main()
{
//strncpy函数
char arr1[20] = "abcdef";
char str1[20] = "abcdefghi";
char arr2[] = "qwer";
strncpy(arr1, arr2, 2);
printf("%s\n", arr1);//qwcdef
strncpy(str1,arr2, 6);
printf("%s\n", str1);//qwer
//arr2不够6个用'\0'补
//strncat函数
char ar1[20] = "hello ";
char st1[20] = "hello \0###########";
char ar2[] = "world";
strncat(ar1, ar2, 3);
printf("%s\n", ar1);//hello wor\0
strncat(st1, ar2, 10);
printf("%s\n", st1);//hello world\0
//'\0'都追加过去了,就不必继续,实际上只追加了6个字符
//strncmp函数
char* p = "aqcdef";
char* q = "abcqwert";
int ret = strncmp(p, q, 4);//小于0
int ret = strncmp(p, q, 3);//等于0
printf("%d\n", ret);
return 0;
}
👁效果展示:
strncpy函数:
和strcpy相比strncpy更加的安全。因为strcpy不会管目标空间是否放的下,咔咔一顿拷贝,如果目标空间不够放,就会产生问题。
strncat函数:
strncmp函数:
🔖说明总结:
strncpy
拷贝num个字符从源字符串到目标空间
若源字符串的长度小于num,则拷贝完源字符串后,在目标的后面追加 ‘\0’ ,直到num个
strncat:
- 追加的字符串长度小于源字符串的长度,只需要追加相印的字符串个数后加’\0’
- 追加的字符串个数大于源字符串的长度,追加完源字符串就结束
strncmp:
- 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完为止
strncpy的模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strncpy(char* dest, const char* src, size_t count)
{
assert(dest && src);
char* tmp = dest;//记录目标空间起始位置
while (count && (*dest++ = *src++))
//拷贝字符串,在本例子中,包含末尾的'\0'
{
count--;
}
if (count)
{
while (--count)
{
*dest++ = '\0';
}
}
//拷贝个数(7)大于源字符串的字符个数(6),则用'\0'补齐个数,此处补一个'\0'
return tmp;//返回目标空间起始位置
}
int main()
{
char arr1[20] = "############";
char arr2[] = "hello";
printf("%s\n", my_strncpy(arr1, arr2, 7));
return 0;
}
strncat的模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strncat(char* dest, const char* src, size_t count)
{
assert(dest && src);
char* tmp = dest;//记录目标空间起始位置
while (*dest)//找到目标字符串中的'\0'
{
dest++;
}
while (count--)
{
//追加字符串,'\0'都被追加过去了,就直接返回目标空间起始地址
if ((*dest++ = *src++) == '\0')
return tmp;//返回目标空间起始位置
}
*dest = '\0';
//源字符串中的'\0'未被追加过去时,需要在追加完后补一个'\0'
return tmp;//返回目标空间起始位置
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
printf("%s\n", my_strncat(arr1, arr2, 10));
return 0;
}
strncmp的模拟实现
#include <stdio.h>
#include <assert.h>
int my_strncmp(const char* arr1, const char* arr2, size_t count)
{
assert(arr1 && arr2);
while (count-- && *arr1 == *arr2)
{
if (*arr1 == '\0' || count == 0)
{
return 0;
}
arr1++;
arr2++;
}
return *arr1 - *arr2;
//*arr1大于*arr2,返回大于0的数;*arr1小于*arr2,返回小于0的数
}
int main()
{
char* p = "aqcdgg";
char* q = "aqcp";
int ret = my_strncmp(p, q, 4);
if (ret > 0)
{
printf("p > q\n");
}
else if (ret < 0)
{
printf("p < q\n");
}
else
{
printf("p == q\n");
}
return 0;
}
6、strstr
strstr函数此前并未介绍过,让我们来一探究竟
该函数的作用就是判断strCharSet字符串是否是string字符串的子串
✏️小试牛刀:
int main()
{
char arr1[20] = "abcdcdbcde";
char arr2[10] = "bcd";
char* tmp = strstr(arr1, arr2);
if (tmp == NULL)
{
printf("没找到!\n");
}
else
{
printf("找到了:%s\n",tmp);
}
return 0;
}
👁效果展示:
🔖说明总结:
如果没找到的话,返回空指针
找到了的话,返回第一次出现的位置的地址
strstr的模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* arr1, const char* arr2)
{
assert(arr1 && arr2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* flag = arr1;//记录匹配的位置
if (*arr2 == '\0')//arr2是一个空字符串
{
return (char*)arr1;
}
while (*flag)
{
s1 = flag;//s1指向记录匹配位置的变量flag处
s2 = arr2;//s2指向arr2起始位置
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
//需要保证s1和s2指向的内容不为'\0'
//如果没有该条件
//当s1指向的内容为'\0',就会访问到非该字符串的内容
//当s2指向的内容为'\0',说明匹配成功,无需进入循环
if (*s2 == '\0')
{
return (char*)flag;//匹配成功,返回匹配成功的字符的地址
}
flag++;//下一个字符进行匹配
}
return NULL;//所有字符都无法匹配,返回空指针
}
int main()
{
char arr1[20] = "abbbcde";
char arr2[10] = "bbc";
char* tmp = my_strstr(arr1, arr2);
if (tmp == NULL)
{
printf("没找到!\n");
}
else
{
printf("%s\n",tmp);
}
return 0;
}
7、strtok
strtok函数也是一个比较奇怪陌生的函数
该函数的作用是strDelimit(分隔符字符)分解strToken字符串为一组字符串
✏️小试牛刀:
int main()
{
char arr[] = "abc@258.211";
char* p = "@. ";//分隔符
char tmp[30] = { 0 };
strcpy(tmp, arr);
char* ret = NULL;
//只有第一次调用时传tmp,以后调用都传空指针
//当ret为NULL时,说明找不到分隔符了,结束
for (ret = strtok(tmp, p); ret != NULL; ret =strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
👁效果展示:
🔖说明总结:
- strDelimit 参数是个字符串,定义了用作分隔符的字符集合,分隔符不可以是’\0’。
- 第一个参数指定一个字符串,它包含了0个或者多个由 strDelimit 字符串中一个或者多个分隔符分割的标记。
- strtok 函数找到 str 中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok 函数会改变被操作的字符串,所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok 函数的第一个参数不为 NULL ,函数将找到 str 中第一个标记,strtok 函数将保存它在字符串中的位置。
- strtok 函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
8、strerror
这也是一个陌生领域
返回错误码所对应的错误信息,如果有需要的话,用printf函数进行打印
✏️小试牛刀:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
//例①
printf("%s\n", strerror(0));
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));
//例②
FILE* pf = fopen("test.txt", "r");//打开文件
if (pf == NULL)//打开文件失败,就会返回一个空指针
{
printf("%s\n", strerror(errno));//打印失败的原因
return 1;
}
//...(读文件)
fclose(pf);//关闭文件
pf = NULL;
return 0;
}
👁效果展示:
在例①中:
在例②中:
🔖说明总结:
使用库函数的时候,调用库函数失败的时候,都会设置错误码,把错误码放入C语言中的一个全局变量errno中,这时使用strerror函数将错误码翻译成错误信息,该函数返回的是错误信息(一个字符串)的首字符地址。
若要使用全局错误码errno,需要引头文件 #include <errno.h>
perror
perror函数的功能和strerror函数类似,但和其相比更加方便
perror函数可以直接打印错误信息
✏️小试牛刀:
#include <stdio.h>
#include <string.h>
int main()
{
//打开文件失败的时候,会返回NULL
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
//这里是调用fopen之后,如果有错误,就进行错误信息打印
return 1;
}
//...(读文件)
fclose(pf);//关闭文件
pf = NULL;
return 0;
}
👁效果展示:
🔖说明总结:
perror的参数是一个字符串,是自定义的信息。
无需传errno,因为该函数可以直接拿取errno的值,直接转化成错误无信息,并打印(打印的格式参考效果展示)