今天我分享一下我学习一些新的函数的心得。
主要有以下两大类:
一.字符函数
二.函数使用或模拟其实现(这个对于提高能力有重要作用)
一.字符函数
1.字符分类函数 (注意:都要包含<ctype.h>的头文件)
我们看一下有哪些函数:
(假如这个函数符合条件,就会返回一个非0的数表示真,反之返回0表示假)
通俗来讲,你把相应的参数传进这个函数里面,它会自动判断你输入的符不符合要求,符合则返回真,不符合则返回假。
这些我们最好记住,以后写题目会有一定的优势。
2.字符转换函数
我们C语言提供了两个转换函数,tolower(将大写字母转换成小写字母),toupper(将小写字母转换成大写字母),下面我们来结合实例来看看这些函数的使用方法和便利性:
*将大写字母统一转换成小写字母
思路:遍历一下先判断是不是大写字母,然后再转换,最后打印出来。
代码如下:
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[11] = "HELLOworld";
int x = 0;
for (x = 0; x < strlen(arr); x++)
{
if (isupper(arr[x]))//判断是不是大写字母
{
arr[x]=tolower(arr[x]);//是就改为小写字母
}
}
printf("%s", arr);//打印
return 0;
}
这里注意:我们只是单纯的把大写字母传过去是不会改变原来的内容的,我们要把原来的位置去接受改变了的元素,才可以正确的打印在我们面前。
其它的函数如islower,isupper,isdigit,isspace,isblank等都非常类似,感兴趣的小伙伴们可以自己去尝试。
上面的函数我们想要去模拟实现的话,抓准ascii码值加上加减判断等,应该就可以较为容易的完成模拟实现了。(例如:A的ascii码值是65,a的ascii码值是97,以此类推,完成字母转换的实现)
二.函数使用或模拟其实现
1.strlen函数的使用和模拟实现(注意:无符号+-无符号=无符号,即使得到负数也会把负号抹去)
我们在C++的官方网站上去寻找strlen方面的内容:
这里我们要注意这个函数的返回类型是size_t(无符号整型),参数是起始元素的地址,我们不希望我们的元素被改变,故加上const。
我们先去用一下strlen函数:(功能:求字符串的长度)
#include<stdio.h>
#include<string.h>
int main()
{
char arr[11] = "helloworld";
printf("%zd", strlen(arr));
return 0;
}
%zd是专门用来打印无符号整型的。
下面我们来模拟实现一下strlen函数:
我认为,满足模拟的条件包括但不限于:
字符(串)要有‘\0’,指针要避免野指针,常量字符串不能改变内容,数组要足够大。
我们看一下模拟实现的效果:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char*str)
{
assert(str != NULL);
if (*str != '\0')
{
return 1 + my_strlen(str + 1);//用递归可以避免创建新变量
}
else
{
return 0;
}
}
int main()
{
char arr[11] = "helloworld";
printf("%zd", my_strlen(arr));
return 0;
}
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* arr)
{
char* p = arr;//先保留一个
assert(p);//防止arr是野指针
while (*p != '\0')
{
p++;
}
return p - arr;
}
int main()
{
char arr[11] = "helloworld";
printf("%d", my_strlen(arr));//指针-指针的方法获取元素个数
return 0;
}
注意:里面的str+1不建议写成++str或str++(先使用再加加,总是传进去h的地址,永远没有尽头),可读性差,而且前者没问题,后者死循环,容易搞错,要写得清楚明白。
2.strcpy函数的使用和模拟实现
我们先在c++官网上查看strcpy的使用:(功能:拷贝字符串)
我们如果要去模拟实现,就要重点模仿它的形式,功能等,而且不仅仅是会模仿,而且要去不断简化。
我们先去使用它:
#include"20230905定义.h"
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[11] = "helloworld";//将要被替换的字符串
char* p = "xxxxxxxxxx";//主动替换别人的字符串
printf("%s\n", strcpy(arr1,p));
return 0;
}
看一下结果:
我们顺便去剖析一下strcpy函数:它的返回类型是char*,说明它要传回去最终的地址,然后打印出来,左边的char*destination是被拷贝的数组的首元素地址,const char*source是主动去替换别人的数组首元素地址,而且这个数组的内容我们不希望被改变。
考虑到这里,我们应该想到,要模拟实现这个函数,我们至少要注意下面几个内容:包括但不限于:被替换的数组足够大,斜杠0要同时拷贝过去,注意指针要有效,尽力去简化等。
我们在原来的基础上再去改造一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* source)
{
char* tmp = dest;//因为下面dest会被改变,故存到一份来返回,等一下传出来就好
assert(dest && source);
while (*dest++ = *source++)//既满足了遍历,又考虑了把字符'\0'弄进去,跳出循环,简洁明了
{
;
}
return tmp;//返回被改变后的原地址
}
int main()
{
char arr1[11] = "helloworld";//将要被替换的字符串
char* p = "xxxxxxxxxx";//主动替换别人的字符串(且它是常量字符串,根本不能被更改,上面的const只是为了更严谨)
printf("%s\n",my_strcpy(arr1,p));
return 0;
}
注意:中间的循环也可以分步骤去写,但是斜杠0要在循环外面加上。
3.strcat的使用及模拟实现(功能:拼接字符串)
我们先来了解一下strcat函数:
它是用来连接字符串的,返回前面这个字符串的首元素的地址,将待拼接的字符串的首元素覆盖掉上一个字符串末尾的\0,且防止后面的字符串的内容被改变。
我们先来使用一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[20] = "hello";
char* p = "world";
printf("%s", strcat(arr1, p));
return 0;
}
我认为我们若要模拟,包括但不限于注意以下几点:
保证数组足够大,能塞得下;常量字符串的内容不可以被修改;注意后面接上去的内容不可以被修改;找到前面那个字符串的\0然后用后面这个字符串覆盖;指针防野;内存可以改动。
我们模拟一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* tmp = dest;//存一份备用
while (*dest!= '\0')//使arr挪动到'\0'的位置
{
dest++;
}
//然后偷偷依次把后面字符串的内容塞过去,相当于拷贝部分
while (*dest++ = *src++)
{
;
}
return tmp;
}
int main()
{
char arr[20] = "hello";
char* p = "world";
printf("%s\n", my_strcat(arr, p));
return 0;
}
注意:灵活去使用拷贝的思路,使代码简洁明了。
4.strcmp的使用和模拟实现(功能:比较字符串的大小)
我们先来了解一下这个函数:
它的返回类型是int类型,输入两个字符串的首元素的地址,且不希望两个字符串的内容被改变,当str1<str2返回负数,str1>str2返回正数,str1==str2返回0。
现在我们来操作一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[10] = "hello";
char arr2[10] = "world";
printf("%d", strcmp(arr1, arr2));
}
然后我们来模拟实现一下,我认为我们至少应该注意以下几点:
字符串里面最好要有\0,创建的数组不要越界,传入的是字符,不希望字符的内容被更改。
我们来试着模拟一下:
int my_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)//按照常理来说,一般比较的就是前段部分相同的字符串
{
if (*str1 == '\0')//双双碰到\0就停下来,没有必要再比下去了。
{
return 0;
}
str1++;
str2++;
}//当不相等的时候会跳出来
if (*str1 > *str2)
{
return 1;
}
else//上面因为不相等才跳出来,所以我们不需要考虑是否相等的情况,思维要严谨
{
return -1;
}
}
int main()
{
char arr1[10] = "hello";
char arr2[10] = "world";
printf("%d", my_strcmp(arr1, arr2));
return 0;
}
这里我忘记了断言,以防止传入的是空指针,伙伴们记得加上去哦。
5.strncpy的使用和模拟实现(功能:有限制的去拷贝字符串)
我们先来看一下它的大致情况:
它比strcpy更好的地方在于它可以限制拷贝多少个字符过去,安全性更高。
num指的是能拷贝过去的字符个数,假如用三个字符"ab\0"拷贝过去,但num传递5过去,剩下两个多余的用\0补充。
我们试着使用一下它:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[11] = "helloworld";
char arr2[11] = "xxxxxx";
printf("%s",strncpy(arr1, arr2, 3));
return 0;
}
它很老实,说了几个就几个,\0也不会乱补充。
再试一下:改8个:
现在我们要去模拟一下:
我认为我们至少要在strcmp函数的基础上,搞清楚它的循环条件。
我们在原来的基础上改造一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* arr1, const char* arr2, size_t n)
{
char* p = arr1;
int i = 0;
for (i = 0; i < n; i++)
{
if (*arr2 != '\0')
{
*arr1 = *arr2;
arr1++;
arr2++;
}
else
{
*arr1 = '\0';//为了尽量和strncpy的样子保持一致
arr1++;
}
}
return p;
}
int main()
{
char arr1[10] = "helloworld";
char arr2[7] = "xxxxxx";
printf("%s", my_strcpy(arr1, arr2, 8));
return 0;
}
我忘记了断言,伙伴们记得加上去!
6.strncat函数的使用和模拟实现(功能:有条件的去拼接字符串)
我们也来一起看一下:
它就是能限定拼接的字符串个数,如果到了\0后面就停止拼接。
我们先来一起使用一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[20] = "hello\0twuwuwwqq";
char arr2[11] = "world";
printf("%s", strncat(arr1, arr2, 7));
return 0;
}
是没有问题的。
现在我们来模拟实现一下,我认为我们至少要注意在strcat的基础上,尽量不要超过数组的最大容量,而且拼接过头了也不要多送"\0"。
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strncat(char* arr1, const char* arr2, size_t num)
{
char* p = arr1;
int n = 0;
while (*arr1 != '\0')
{
arr1++;//跳到'\0'的位置上
}
for (n = 0; n < num; n++)
{
if (*arr2 != '\0')
{
*arr1 = *arr2;
arr1++;
arr2++;
}
else
{
*arr1 = *arr2;
return p;
}
}
return p;
}
int main()
{
char arr1[20] = "hello\0twuwuwwqq";
char arr2[11] = "world";
printf("%s", my_strncat(arr1, arr2, 7));
return 0;
}
记得凡是遇到指针,都要考虑断言!
如果有一些改进或不足的话,欢迎各位大佬指点。
7.strncmp的使用和模拟实现(功能:有条件的去比较字符串)
我们先看一下这个函数:
它可以规定双双比较字符的大小,直到不相等返回数据。
我们先来使用一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[20] = "abcdefg";
char arr2[20] = "abcdefgh";
printf("%d", strncmp(arr1, arr2, 5));
return 0;
}
然后我们去模拟实现一下,我认为我们应该至少注意停止的条件。
看代码:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int my_strncmp(const char* arr1, const char* arr2, size_t num)
{
int i = 0;
for (i = 0; i < num; i++)
{
if (*arr1 == *arr2)//判断是否相等即可
{
if (*arr1 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
else
{
break;
}
}
if (*arr1 - *arr2 > 0)
{
return 1;
}
else if(*arr1-*arr2<0)
{
return -1;
}
else
{
return 0;
}
}
int main()
{
char arr1[20] = "abcdefg";
char arr2[20] = "abcdefgh";
printf("%d", my_strncmp(arr1, arr2, 5));
return 0;
}
看见指针就要去断言啊!
其实还可以继续简化,待各位大佬指点。
7.strstr函数的使用和模拟实现(功能:在字符串中查找子字符串)
我们先来看一下这个函数:
我们不希望这两段字符串被改变,故用上const修饰;函数返回的指针对应的内容我们不希望被改变,返回类型我们在模拟实现的时候可以加上const修饰。
我们现在来使用一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[60] = "helloworwoworworldnnxxd";
char arr2[60] = "world";
char* p=strstr(arr1, arr2);
printf("%s\n", p);
return 0;
}
那我们来模拟实现一下:
至少,我们要注意:
指针的有效性,对应内容的不可改性,循环等停止的条件,还有要考虑要不要预留指针等。
我们看一下代码:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
const char* my_strstr(const char* str1, const char* str2)//分可以找到和不可以找到的两种情况
{
assert(str1);
if (*str2 == '\0')
{
return str1;//这是一种特殊的情况,我们要考虑在内,这在原版strstr函数里面是包括在内的
}
const char* cp = str1;//用来定位
const char* s1 = NULL;//用来跟在cp及后面,依次和s2++,s1++检验是否符合
const char* s2 = NULL;//从str2处开始走,和s1判断
while (*cp != '\0')//cp对应0了还怎么去判断呢,退出来就是了
{
s1 = cp;//从cp的位置继续走
s2 = str2;
while (*s1 == *s2&&s1&&s2)//要循环多次判断,双方都还在的情况下
{
s1++;
s2++;//相同就一起往后面移动
}
if (*s2 == '\0')//s2走到了头
{
return cp;//找到了
}
cp++;//移动cp,如果寻找失败,分别确定新位置
}
return NULL;//没有找到
}
int main()
{
char arr1[60] = "helloworwoworworldnnxxd";
char arr2[60] = "world";
char* p=my_strstr(arr1, arr2);
printf("%s\n", p);
return 0;
}
我们来画图来解释一下:
也可以用调试的方法去观察,这里就不展示了。
拓展:我们也可以使用KMP算法,有兴趣的小伙伴们可以去试一下。
8.strtok的使用(功能:分割字符和符号)
我们先来看一下这个函数:
这里我们传的第一个指针是指向整个字符串的指针,第二个参数是指向里面分隔符的指针,这个函数会把标点符号等转变为‘\0’,这样就起到了可以分割字符串的作用。
所以这里我们的str指向的内容是需要被修改的,而delimiters指向的内容是不希望被改变的,我们用const修饰;它返回str。
我们现在来使用一下:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[100] = "helloworld@122.234.554.net.com";
char arr2[100] = "@....";
printf("%s\n",strtok(arr1, arr2));
return 0;
}
现在我们来监视一下看一下:
它就刚好改了一个为‘\0’。
如果要把它连续打印呢?
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[100] = "helloworld@122.234.554.net.com";
char arr2[100] = "@....";
char* p = NULL;
for (p = strtok(arr1, arr2); p != NULL; p = strtok(NULL, arr2))
{
printf("%s\n",p);
}
return 0;
}
虽然这个循环很奇怪,但是它就是不断这样记住这些地址然后去打印,但是我们这里传了空指针,所以这个函数内部应该是有static修饰,记住了上一次的位置等,有兴趣的小伙伴们可以自行去尝试。
9.strerror函数以及改版perror函数的使用(功能:前者可以用来读取错误信息代号对应的代码的地址,后者可以在前者的基础上顺便进行打印)
我们先看一下strerror函数的基本情况:
在这种情况我们一般在使用的时候传参进去errno,这个errno可以自动判断上面的程序出现了什么问题,并把错误的代号赋值到errno里面去,然后经过这个strerror函数的加工可以返回具体错误信息的地址,我们把它拿过来打印一下就好了。
我们看一下代码:(用来先理解一下)
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("i:%s\n", strerror(i));
}
return 0;
}
这样我们就可以很好的查看代号对应的代码了。
我们不妨看一下其它的情况:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
#include<errno.h>
int main()
{
FILE * pFile=NULL;
pFile = fopen("test.txt","r");
if (pFile == NULL)
{
printf("打不开的原因是:%s", strerror(errno));
}
else
{
printf("打开成功\n");
}
return 0;
}
要查找的文件必须在test系列里的文件夹才能找到,而且要是展开了的文件名对应才可以找到。
而perror函数多了一个打印的效果,如图:
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
#include<errno.h>
int main()
{
FILE * pFile=NULL;
pFile = fopen("test.txt","r");
if (pFile == NULL)
{
perror("打不开");
printf("打不开的原因是:%s", strerror(errno));
}
else
{
printf("打开成功\n");
}
return 0;
}
效果一目了然。