hello,大家好呀,感觉我之前有偷偷摸鱼了,今天又开始学习啦。加油!!!
文章目录
1. 字符分类函数
C语言中有一系列的函数是专门做字符分类的,且这些函数在使用时都需要包含头文件:ctype.h
这些函数的使用大同小异,我举其中的一个例子:
从上图可以看出,函数islower是用来判断是否为小写字母,我们可以在cplusplus搜索islower函数,它给出的是:如果是小写字母,则返回非0值;如果不是小写字母,则返回0.
int main()
{
int c = islower('A');
int q = islower('a');
printf("c=%d\n", c);
printf("q=%d\n", q);
return 0;
//打印结果是:c=0;q=2
}
练习写一个代码:将字符串中的小写字母转大写,其他字符不变。(在学习字符转换函数之后有另外一种方式,大家可以尝试一下)
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[] = "I am very sad." ;
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] = arr[i] - 32;
}
i++;
}
//再将字符串输出,看是否变成大写
printf("%s\n", arr);
return 0;
}
2. 字符转换函数
C语言提供了2个字符转换函数:
int tolower ( int c ); ------- 转成小写--------- //将参数传进去的大写字母转小写
int toupper ( int c );------- 转成大写----------//将参数传进去的小写字母转大写
int c的意思是传入一个字符(字符的本质就是ascll码值),int tolower的意思是返回值为整数
int main()
{
char ch = tolower('A');
printf("%c\n", ch);
char hh = toupper('a');
printf("%c\n", hh);
}
之前小写转大写可以将if语句里面改掉
int main()
{
char arr[] = "I am very sad." ;
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] = toupper(arr[i]);
}
i++;
}
//再将字符串输出,看是否变成大写
printf("%s\n", arr);
return 0;
}
3. strlen的使用和模拟实现
3.1 strlen 的使用
1.使用strlen需要包含头文件:string.h
2.字符串以’\0’ 作为结束标志。
- 比如:“abc” 其实就是‘a’ ‘b’ ‘c’ ‘\0’,一共4个字符。但是strlen只统计’\0‘之前的字符个数。(不包含’\0’)
- 如果字符数组中是char arr[]={‘a’,‘b’,‘c’};的话,则是没有\0的,什么时候遇到\0不确定。
int main()
{
char arr[] = "aaattt";
size_t len = strlen(arr);
printf("%d\n", len);//6
char qqq[]={'a','b'};
size_t lee = strlen(qqq);
printf("%d\n",lee);//随机值
return 0;
}
3.strlen的返回值是size_t(即无符号整数)
int main()
{
char ch1[] = "abc";
char ch2[] = "abcdef";
if (strlen(ch1) - strlen(ch2) < 0)
//3 - 6
{
printf("ch1<ch2");
}
else
{
printf("ch1>ch2");
}
}
//最终的结果是ch1>ch2
3-6=-3,小于0,为什么输出是大于?
在这里说明,strlen的返回值是size_t,无符号整数-无符号整数=还是无符号整数,所以strlen(ch1) - strlen(ch1)的结果其实>0,所以输出>。
如果非要比较,可以将strlen的返回值强制类型转换为int,(int)strlen(ch1)-(int)strlen(ch2)。或者直接比较: if (strlen(ch1) < strlen(ch2) )
3.1 strlen 的模拟
1.计算器方法
(1.) 这是最初的版本,之后优化。
size_t my_strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;//个数+1
str++;//下一个元素的地址
}
return count;
}
int main()
{
char ch[] = "aaaqqq";
size_t len = my_strlen(ch);//传数组传的是数组名(这里代表的是数组首元素地址)
printf("%d\n", len);
}
(2.)1.万一指针是空指针呢?我们先用断言判断一下
2.我们只是想遍历一遍数组的每个元素,找到\0,并不希望有人将元素修改了,所以用const修饰*ch
#include<assert.h>
size_t my_strlen(const char* str)//用指针变量来接收地址
{
int count = 0;
assert(str != NULL);
while (*str != '\0')
{
count++;//个数+1
str++;//下一个元素的地址
}
return count;
}
int main()
{
char ch[] = "aaaqqq";
size_t len = my_strlen(ch);//传数组传的是数组名(这里代表的是数组首元素地址)
printf("%d\n", len);
}
2.指针-指针的方法
用起始地址-\0的地址=就是中间的元素个数
这几种方法int main函数里的内容都一样,之后就不再展示,只展示my_strlen函数里的内容
size_t my_strlen(const char* str)
{
int count = 0;
assert(str != '\0');
int start = str;//这里str还是起始元素的地址
while (*str != '\0')
{
//通过这个循环,使得str里面放的是\0的地址
str++;
}
int end = str;
return end - start;
}
3.递归的方法
size_t my_strlen(const char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
长度不受限制的字符串函数strcpy,stracat,strcmp
4. strcpy的使用和模拟实现
4.1 strcpy的使用
解析:从source那里复制字符串,给目的地。
注意内容:
- 源字符串必须以’\0’ 结束。(没有’\0’,strcpy无法结束)
int main()
{
char arr1[] = "aaajjjkkkiii";
char arr2[90] = { 0 };
strcpy( arr2 , arr1);
//接收内容 , 被复制的
//arr2数组首元素地址,arr1首元素地址
printf("%s\n", arr2);
}
- 会将源字符串中的’\0’ 拷贝到目标空间。
从下图可知:\0是被复制的
- ⽬标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
char* p = "xxxxxxxxxxxx";//这个是无法修改的
4.2 strcpy的模拟实现
void my_strcpy(char* dest, char* src)
{
//assert(dest != NULL);
//assert(src != NULL);
assert(dest && src);
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//这里是为了'\0'
}
int main()
{
char arr1[] = "aaajjjkkklllhhh";
char arr2[90] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
}
- 即我们自己写一个函数,使其可以实现strcpy函数的功能。在形式上,要与strcpy函数的形式保持一致。
my_strcpy(arr1,arr2);
- 看我上图的绿色粗框框,为什么返回值是char*类型?
为的是实现链式访问,strcpy函数返回的是目标空间(目的地)的起始地址(将源头的内容拷贝到目的地之后,将目的地的起始地址返回回来。)
char* my_strcpy(char* dest, char* src)
{
assert(dest && src);
char* ret = dest;
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;
return ret;
}
那有没有一种方法将内容和\0一起复制,不用分开呢
后置++的级别是之后再执行,在*dest++中,先解引用旧的dest,再++。
*dest++ = *src++;
//这是先*src,复制给*dest,然后再src++,dest++..........
//这和 //*dest = *src;
//dest++;
//src++;一样
char* my_strcpy(char* dest, char* src) //这里的src意思是source
{
assert(dest && src);
char* ret = dest;
//拷贝过去字符串后,判断表达式的值。最后一次是\0,已经复制过去了,发现是\0,判断为假,不再执行++,停止循环
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "aaajjjkkklllhhh";
char arr2[90] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
}
5. strcat的使用和模拟实现
5.1 strcat的使用(头文件:string.h)
简单来说,就是将源字符串(source)接到目的地字符串(d )后边(将\0覆盖(因为在\0之后有东西,我们也是看不到的),在接完之后,再加上\0)
- 先找到目标空间的末尾\0(要求目标空间有\0)
- 再将源字符串拷贝过来(但是结尾也得是\0,所以源字符串末尾也必须有\0)
- 保证目标空间必须足够大,且可以修改。
int main()
{
char arr1[90] = "nihao";
char arr2[90] = "shijie";
strcat(arr1, arr2);
//arr1变长的目标空间,arr2是被接的部分
printf("%s\n", arr1);
}
5.2 strcat的模拟实现
不要自己给自己追加昂.
- 自定义函数形式与我们所查询的保持一致
- 遍历目标空间,找到\0,从第一个元素开始,如果不是\0就dest++,知道*dest等于\0
- 拷贝,从\0开始
char* my_strcat(char* dest, char* src)
{
while (*dest != '\0')
dest++;
//找到\0了,接下来拷贝
while (*dest++ = *src++)
;
//之后再返回目标空间的起始地址
return dest;
}
int main()
{
char arr1[90] = "nihao";
char arr2[90] = "shijie";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
}
6. strcmp的使用和模拟实现
6.1 strcmp的使用
strcpm是用来来比较字符串的,是比较对应位置的字符的大小,并不是看字符串长度。
返回值:如果左边字符串>右边,返回值>0。左边字符串=右边,返回值=0。左边字符串<右边,返回值<0。
int main()
{
char arr1[] = "aaajjjkkkl";
char arr2[] = "jinifoshljnflweij";
int c=strcmp(arr1, arr2);
printf("%d\n", c);
}
6.2 strcmp的模拟实现
在模拟实现时,需要注意的点
- 我们只是希望通过指针变量找到开始的变量,比较它们,并不希望它们被改变,所以用const修饰。
- 如果指向的两个元素它们相等,则不用做什么改变,只需要++,到下一个元素的地址。
- 当不相等时,则需要比较
- 如果两个数组的所有元素都相等,则返回0(在那个循环比较里)
- 保险起见,可以用assert来判断是否为空指针。
int my_strcmp(const char* str1,const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
int main()
{
char arr1[] = "aaajjjkkkl";
char arr2[] = "jinifoshljnflweij";
int c = my_strcmp(arr1, arr2);//arr1,arr2代表的都是首元素地址,用指针变量接收地址
printf("%d\n", c);
}
长度受限制的字符串函数strncpy,strncat,strncmp
7. strncpy函数的使用
- 让拷贝几个就是几个,不会有\0。
strncpy可控制复制的字符串的长度,而不是像strcpy一样全部复制。
num是从source那里可拷贝的最大字符个数。
8. strncat函数的使用
- 假设让追加3个,在追加完之后,还有附带一个\0。
num是用来控制追加的字符个数。
9. strncmp函数的使用
int main()
{
char arr1[90] = "abcdef";
char arr2[90] = "abcq";
int ret = strncmp(arr1, arr2, 3);
//意思是只比较两个字符串前三个字符
printf("%d\n", ret);
return 0;
}
10. strstr的使用和模拟实现
10.1 strstr的使用
- 在str1中寻找str2字符串第一次出现的位置,如果找到了就返回str2在str1中第一次出现的起始地址。如果找不到,就返回空指针。
- 注意,并不是第一个字符一样就会返回第一次出现的地址,需要都一样。
比如arr1:abcdef,arr2是cq。它们两个只有c是一样的,c之后的并不一样。该返回值是空指针,null。
int main()
{
char arr1[] = "asdfhkl";
char arr2[] = "df";
char* ret=strstr(arr1, arr2);
//为什么用char*接收,因为这个函数返回的是地址
//arr2在arr1中第一次出现时d的地址,打印则从d开始往后打印
printf("%s\n", ret);
//最终的打印结果是dfhkl
}
10.2 strstr的模拟实现
- 刚开始,一直在找*str1中与str2的首元素相等的元素。当我们在str1中找到第一个与str2首元素相同的元素,将那个位置记录下来。然后再往后走,看之后的几个是否一样,若在str2找到\0时,那几个都一样,√
-
有可能存在多次匹配的情况。
比如arr1:abbder。arr2:bde。如果是arr1的第一个b,则后面就不对了。如果匹配的是arr1的第二个b,则后面的de就配上了。 -
有可能遇见arr1较短的情况。
比如arr1:abcdefg。arr2:fgfg。(返回空指针) -
还有一种特殊情况:如果arr2是空字符串呢?这时会直接返回arr1。
#include<assert.h>
//arr11是用来接收数组arr1的首元素地址,arr22同理
const char* my_strstr(const char* arr11,const char* arr22)
{
assert(arr11 && arr22);
//创建三个指针变量s1,s2,cur(cur用来记录遇到第一个相同元素的地址,他刚开始是在arr1的首元素地址那)
const char* s1 = NULL; //为什么用const,当我们把受限制(const)的arr11交给不受限制的s1,编译器会报警告
const char* s2 = NULL;
const char* cur = arr11;
if (*arr22 == '\0')//特殊情况,arr22指向的是空字符串
return arr22;
//循环判断
while (cur != '\0') //其实这里也可以写成while(cur),当cur=0时,无法进入循环
{
s1 = cur;
s2 = arr22; //每次重新匹配,s2都是arr22的第一个元素的地址
while ((s1 != '\0') && (s2 != '\0') && (*s1 == *s2))//如何能进入真正的判断,有三个条件
{ //还可以写成while(s1&&s2&&(*s1==*s2))
s1++;
s2++;
}
//如果退出循环是因为,s1和s2一直相等,直到s2遇到\0了,那么这时就可以输出刚开始元素一样的起始位置:cur
if (*s2 == '\0')
return cur;
//如果不满足循环条件(比如在之后*s2!=*s2)给退出了,接下来做什么?给cur++,然后给s1继续。
cur++;
}
return NULL;
}
int main()
{
char arr1[] = "asddfhkl";
char arr2[90] = "s";
char* ret = my_strstr(arr1, arr2); //大家别忘了arr1代表的是数组arr1的首元素的地址
//根据我们自定义的函数来返回内容,是否找到相关内容
if (ret == NULL)
printf("未在arr1里找到与arr2相同的内容\n");
if (ret != NULL)
printf("%s\n", ret);
}
#include<assert.h>
const char* my_strstr(const char* arr11, const char* arr22)
{
assert(arr11 && arr22);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = arr11;
if (*arr22 == '\0')
return arr22;
while (cur)
{
s1 = cur;
s2 = arr22;
while (s1 && s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
return cur;
cur++;
}
return NULL;
}
int main()
{
char arr1[] = "asddfhkl";
char arr2[90] = "s";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
printf("未在arr1里找到与arr2相同的内容\n");
if (ret != NULL)
printf("%s\n", ret);
}
11. strtok函数的使用
- 开头解析:
- 注意事项:
int main()
{
char arr1[] = "houppuur@qq.com";
char sep[] = "@."; //存放分隔符
//arr1是肯定被修改的,我们可以将内容复制到另一个字符数组
char str[90] = { 0 };
strcpy(str, arr1);
char* ret=strtok(str, sep);
printf("%s\n", ret);
//strtok函数的第⼀个参数不为NULL,函数将找到str中第⼀个标记,strtok函数将保存它在字符串中的位置
//即先找到了@ 将它变为了\0,再返回⼀个指向[这个标记]的指针(指针用指针变量来接收,char*)
ret = strtok(NULL, sep);
//此时strtok函数的第⼀个参数为NULL,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标记
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
}
大家看着前面的代码试想一下,第一次用的buf,之后是NULL,像不像for循环的只需要第一次初始化,之后不用一样。(初始化;判断;调整部分)
此处的初始化是:ret=strtok(str, sep)
判断是:ret!=NULL
调整部分是:ret = strtok(NULL, sep)
char arr1[] = "houppuur@qq.com";
char sep[] = "@.";
//arr1是肯定被修改的,我们可以将内容复制到另一个字符数组
char str[90] = { 0 };
strcpy(str, arr1);
char* ret = NULL;
//初始化 //判断 //调整
for (ret = strtok(str, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n", ret);
}
12. strerror函数的使用
- strerror的头文件:#include<stdio.h>
- strerror将错误码对应的错误信息的字符串的起始地址返回(需要自己去printf)
- perror是将errno中错误的对应信息打印出来(先打印str指向的字符串,再打印冒号:和空格 ,再打印错误码对应的错误信息)【使用errno需要包含头文件:#include<stdio.h>】
- perror==printf+strerror
#include<errno.h>
int main()
{
FILE* pf = fopen("data.text", "r");//"r"是以只读的形式打开文件,如果文件不存在,则打开失败
//打开失败之后会传给指针变量pf空指针
if (pf == NULL)
{
printf("打开文件失败,原因是: %s\n", strerror(errno));//一旦调用库函数失败,会将错误码放在errno
perror("打开文件失败,原因是");
//大家运行出来后对比一下,会发现两者打印出来的一样,perror确实会自己打印:和空格
return 1;
}
else
{
printf("打开文件成功\n");
//如果想关闭文件
fclose(pf);
pf = NULL;
}
return 0;
}
打印结果: