谢谢观看!希望以下内容帮助到了你,对你起到作用的话,可以一键三连加关注!你们的支持是我更新地动力。
因作者水平有限,有错误还请指出,多多包涵,谢谢!
目录
一、字符分类函数
C语言中有⼀系列的函数是专门做字符分类的,也就是⼀个字符是属于什么类型的字符的。这些函数的使用都需要包含⼀个头文件是 ctype.h
。比如:
iscntrl
:任何控制字符isspace
:空白字符:空格‘ ’,换页‘\f ‘,换行’\n’,回车‘|r’,制表符’\t’或者垂直制表符’\v’isdigit
:十进制数字‘0’~‘9’字符isxdigit
:十六进制数字,包括所有十进制数字字符,小写字母a-f,大写字母A~Fislower
:小写字母a~zisupper
:大写字母A~Zisalpha
:字母a-z或A~Zisalnum
:字母或者数字,a-z,A-Z,0~9ispunct
:标点符号,任何不属于数字或者字母的图形字符(可打印)isgraph
:任何图形字符isprint
:任何可打印字符,包括图形字符和空白字符
由于这些函数的用法类似,我们就讲其中的islower
。
int islower(int c);//可以传递字符,也可以传递ASCII码值,因为字符本质在内存中存放的是acsll码值,
//所以用int c来接收参数。
//通过返回值(类型是int)来说明是否是⼩写字⺟,如果是⼩写字⺟就返回⾮0的整数,如果不是⼩写字⺟,则返回0。
#include<stdio.h>
#include<ctype.h>
int main()
{
int ret;
ret = islower('a');
printf("%d\n", ret);//结果为2。因为islower是判断字符是否为小写字符,是就返回大于0的数
ret = islower('A');
printf("%d\n", ret);//结果为0。因为字符A是大写字符,所以返回0
return 0;
}
下面我们来做个练习,实际运用一下:写一个代码,将字符串中的小写字母转大写,其他字符不变。
思路:将字符串存放在字符数组中,然后遍历数组的每一个元素,遍历元素时,要判断该元素是否为小写字母,如果是小写字母就将其转化为大写字母,其他字符不变。
int main()
{
char arr[] = "I am a Student.";
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] -= 32;//小写字母的ASCII码值 - 32 = 大写字母的ASCII码值
}
i++;
}
printf("%s\n", arr);
return 0;
}
二、字符转换函数
C语言中提供了2个字符转换函数:
int tolower ( int c )
; 将参数传进去的大写字母转小写int toupper ( int c )
; 将参数传进去的小写字母转大写
有了字符转换函数,我们就可以对第一节的练习进行修改了。
int main()
{
char arr[] = "I am a Student.";
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] = toupper(arr[i]);
}
i++;
}
printf("%s\n", arr);
return 0;
}
三、strlen
的使用和模拟实现
strlen
函数的形参部分:
size_t strlen ( const char * str );
strlen
函数的基本用法:
int main()
{
char arr[] = "abcdefghijklmnopqrstuvwxyz";
printf("%zd\n",arr);//26
return 0;
}
使用strlen
函数的注意事项:
- 字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
- 参数指向的字符串必须要以 ‘\0’ 结束。
- 注意函数的返回值为 size_t,是无符号的( 易错 )
- strlen的使用需要包含头文件
- 学会strlen函数的模拟实现
对没有遵循注意事项的进行解析代码:
部分一:参数指向的字符串没有要以 ‘\0’ 结束。
int main()
{
char arr[] = {'a','b','c'};
printf("%zd\n",strlen(arr));//结果为随机值
//因为strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )
//因为字符数组中没有\0字符,所以当统计到字符c时,不会停止统计。
return 0;
}
部分二:注意函数的返回值为 size_t,是无符号的( 易错 )。
int main()
{
if(strlen("abc") - strlen("abcdef") > 0)
{
printf(">\0");//打印结果为>
}
else
{
printf("<\0");
}
return 0;
}
这时就疑惑非常大了,strlen("abc")
计算结果为3
,strlen("abcdef")
计算结果为6
,3-6=-3
是负数,怎么可能是>0
呢?
其实这就和函数的返回类型有关了,因为函数的返回值为 size_t,是无符号整型,可能是短整型,也可能是长整型,反正一定是无符号,即把符号位当作数值位。那么虽然3-6=-3
看起来是负数,但size_t - size_t
的类型还是size_t
,写成-3
的原码、反码、补码。补码就是数据在内存中的存放形式,由于类型是size_t
,没有符号位,即就是将补码写成真值,是一个非常大的数字。
那么上面的代码就达不到我们想要比较一下两个字符串的长度,这就需要强制类型转化成int
整型。
int main()
{
if((int)strlen("abc") - (int)strlen("abcdef") > 0)
{
printf(">\0");
}
else
{
printf("<=\0");//打印结果为<
}
return 0;
}
但其实最简单的是,不要将它们作差去比较大小,直接放二边比较就可以了
int main()
{
if(strlen("abc") > strlen("abcdef"))
{
printf(">\0");
}
else
{
printf("<=\0");//打印结果为<
}
return 0;
}
在上面我们对strlen
函数的用法进行了解释,但对于学习strlen
函数,重要的是模拟实现strlen
函数。
方法一:计数器的方式
//计数器⽅式
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
int count = 0;
assert(str != NULL);//判断指针str是否为NULL(空指针),是空指针就不可以直接使用str,不为空,才可以解引用操作等
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
printf("%zd\n",my_strlen(arr));//结果为6
return 0;
}
方法二:指针 - 指针
size_t my_strlen(const char * str)
{
char * start = str;
assert(str != NULL);//在对指针解引用操作之前,断言一下指针不为NULL
while(*str != '\0')
{
str++;
}
return str - start
}
int main()
{
char arr[] = "abcdef";
printf("%zd\n",my_strlen(arr));//结果为6
return 0;
}
方法三:递归的方式(不能创建临时变量),递归的思想是大事化小,递归下去,在回归。
#include<stdio.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[] = "abcdef";
printf("%zd\n",my_strlen(arr));//结果为6
return 0;
}
四、strcpy
函数的使用和模拟实现
strcpy
函数的参数部分:
char* strcpy(char * destination, const char * source );//destination目的地,source源头,将源头的数据,复制到目的地的,包涵结束的\0字符也会被拷贝过去。
strcpy
函数的基本用法:
int main()
{
char arr1[] = "hello world";
char arr2[20] = {0};
strcpy(arr2,arr1);
printf("%s",arr2);//结果为:hello world
return 0;
}
使用strcpy
函数的注意事项:
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
- 学会模拟实现。
对没有遵循注意事项的进行解析代码:
部分一:目标空间没有足够的大
int main()
{
char arr1[] = "hello world";
char arr2[] = { 0 };//其实你想将元素全部存放成0,但是由于没有给数组arr2元素个数,所以arr2只有一个元素,元素为0,应该改为char arr2[20] = {0};
strcpy(arr2, arr1);
printf("%s", arr2);//会报错
return 0;
}
部分二:源字符串没有以 ‘\0’ 结束
char arr1[] = {'a','b','c','d'};
char arr2[20] = {0};
strcpy(arr2,arr1);//想一想如果要将arr1的内存复制到arr2中,那么也就需要知道数组arr1中有多少个元素,如果没有\0,那么元素就随机了,会复制arr1中没有的元素到arr2中
printf("%s",arr2);
部分三:目标空间是不可以修改的
char arr1[] = "hello world";
char * p = "xxxxxxxxxxxxxxxxxxxxx";//常量字符串是不能被修改的
strcpy(p,arr1);//报错
printf("%s\n",p);
在上面我们对strcpy
函数的用法进行了解释,但对于学习strcpy
函数,重要的是模拟实现strcpy
函数。
//返回值类型是char*,是为了实现链式访问
//strcpy函数返回的是目标空间的起始位置
char* my_strcpy(char* dest,const char* src)
{
char* dest_start = dest;
assert(dest && src);
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//这里处理的是将\0复制给dest
return dest_start;//返回目标空间的起始地址
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
char* pstr = my_strcpy(arr2, arr1);
//三种打印形式是一样的意思
printf("%s\n",arr2);
printf("%s\n",pstr);
printf("%s\n",my_strcpy(arr2, arr1));
return 0;
}
我们可不可以将\0
和a,b,c,d,e,f
放在一起复制给dest
,而不是分开的,看下面的代码:
//返回值类型是char*,是为了实现链式访问
//strcpy函数返回的是目标空间的起始位置
char* my_strcpy(char* dest,const char* src)
{
char* dest_start = dest;
assert(dest && src);
while (*dest ++= *src++)//拷贝过去字符后,判断表达式的值,当\0拷贝过去后,判断为假,停止循环
//虽然++的优先级高于*,但是这里的++是后置++,加1的效果是延后产生的
{
;
}
*dest = *src;//这里处理的是将\0复制给dest
return dest_start;//返回目标空间的起始地址
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
char* pstr = my_strcpy(arr2, arr1);
//三种打印形式是一样的意思
printf("%s\n",arr2);
printf("%s\n",pstr);
printf("%s\n",my_strcpy(arr2, arr1));
return 0;
}
五、strcat
函数的使用和模拟实现
strcat
函数的参数部分:
char* strcat(char * destination, const char * source );//将source的数据,追加到destination的数据末尾
strcpy
函数的基本用法:
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1,arr2);//字符串追加
printf("%s",arr1);//结果为:hello world
return 0;
}
使用strcat
函数的注意事项:
- 源字符串必须以 ‘\0’ 结束。
- 目标字符串中也得有 \0 ,否则没办法知道追加从哪里开始。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 字符串自己给自己追加,如何?(不能,会破坏\0,不满足原字符串和目的字符串都要要\0)
对没有遵循注意事项的进行解析代码:
部分一:目标空间没有足够的大,不能容纳下源字符串的内容
int main()
{
char arr1[7] = "hello ";//目标空间就7个字节大小,都已经放入了数据,此时追加world,是不能的,程序会报错
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
其他错误的部分和上面函数的分析是一样的
在上面我们对strcat
函数的用法进行了解释,但对于学习strcat
函数,重要的是模拟实现strcat
函数。
char* my_strcat(char* dest,const char* src)
{
assert(dest && src);
char* dest_start = dest;
//1. 找目标空间的\0
while (*dest != '\0')
dest++;
//2. 拷贝(和函数strcpy的一样)
while (*dest++ = *src++)
;//表示空语句
return dest_start ;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
my_strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
补充知识:
- 0 — 数字0
- ‘0’ — 数字字符0 — 本质是字符 —ASCII值是48
- ‘\0’ — 字符\0 — \ddd(ddd表示1~3个8进制数字)---- \0的ASCII值是0
- NULL ---- 也是0,是赋值给指针变量的
六、strcmp
函数的使用和模拟实现
strcat
函数的参数部分:
int strcmp (const char * str1, const char * str2)
strcpy
函数的基本用法
int main()
{
char arr1[] = "abp";
char arr2[] = "abcdef";
printf("%d\n", strcmp(arr1, arr2));//结果为: 1,因为比较的不是字符串的长度,而是字符串对应的ASCII值
char arr1[] = "abcdef";
char arr2[] = "abcdef";
printf("%d\n", strcmp(arr1, arr2));//结果为0
char arr1[] = "abcdef";
char arr2[] = "abp";
printf("%d\n", strcmp(arr1, arr2));//结果为-1
return 0;
}
使用strcmp
函数的标准规定:
- 第一个字符串大于第二个字符串,则返回⼤于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
- 那么如何判断两个字符串? 比较两个字符串中对应位置上字符ASCII码值的大小。
在上面我们对strcmp
函数的用法进行了解释,但对于学习strcmp
函数,重要的是模拟实现strcmp
函数。
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)//思路是把两个字符串相同的字符就不管,一直到比较到字符不相同就可以比较出字符串的大小
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
//写法1
if (*str1 > *str2)
return 1;
else
return -1;
//写法二
//return (*str1 - *str2);
}
int main()
{
char arr1[] = "abq";
char arr2[] = "abcdef";
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}
在上面的介绍中,我们新学习了strcpy
、strcat
、strcmp
三个字符串函数,它们是属于长度不受限制的字符串函数,因为在使用它们的时候是需要传递两个字符串的首元素地址,它们就会对整个字符串进行操作。
那么让我们再来学习一下,长度不受限制的字符串函数,strncpy
、strncat
、strncmp
3个字符串函数,与原来的比,不同在于多了一个n
。n
就表示在对字符串进行追加、复制、比较时,可以指定追加的字符数n
,复制的个数n
,和比较n
个字符。因为这样可以灵活的符合需求。
七、 strncpy
函数的使用
strncpy
函数的参数部分:
//strncpy函数
char* strcpy(char * destination, const char * source ,size_t num);//num表示最大从source拷贝到destination的个数
//strcpy函数
char* strcpy(char * destination, const char * source );
strncpy
函数的基本用法:
int main()
{
char arr1[] = "abcdef";
char arr2[20] = "xxxxxxxx";
strcpy(arr2,arr1,3);
printf("%s\n",arr2);//结果为abcxxxxx
return 0;
}
使用strncpy
函数的标准规定:
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个
注意事项:需要从source中复制大于source空间的个数,不够的会补\0
字符
int main()
{
char arr1[] = "abcdef";
char arr2[20] = "xxxxxxxxx";
strcpy(arr2,arr1,8);//由于arr1的字符个数为7个,abcdef\0,由于小于8,所以补一个\0,即拷贝内容为abcdef\0\0
printf("%s\n",arr2);//
return 0;
}
八、 strncat
函数的使用
strncat
函数的参数部分:
//strncat函数
char* strncat(char * destination, const char * source ,size_t num);
//strcat函数
char* strcat(char * destination, const char * source ;
strncat
函数的基本用法:
int main()
{
char arr1[20] = "abcdef";
char arr2[20] = "xxxxxxxxx";
strncat(arr1,arr2,3);//字符串从\0字符开始追加3个字符,然后补一个\0到末尾
printf("%s",arr1);//结果为:abcdefxxx
return 0;
}
使用strncat
函数的标准规定:
- 将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加一个 \0 字符
- 如果source指向的字符串的长度小于num的时候,只会将字符串中到\0 的内容追加到destination指向的字符串末尾
九、strncmp
函数的使用
strncmp
函数的参数部分
int strncmp ( const char * str1, const char * str2, size_t num );//strncmp
int strcmp ( const char * str1, const char * str2);//strcmp
strncmp
函数的基本用法:
int main()
{
char arr1[20] = "abcdef";
char arr2[20] = "abcq";
int ret = strncmp(arr1,arr2,3);//字符串从\0字符开始追加3个字符,然后补一个\0到末尾
printf("%d\n",ret);
return 0;
}
比较str1和str2的前num
个字符,如果相等就继续往后比较,最多比较num
个字母,如果提前发现不一样,就提前结束,大的字符所在的字符串大于另外一个。如果num
个字符都相等,就是相等返回0
.
十、strtok
函数的使用
strtok
函数的参数部分
char * strtok ( char * str, const char * sep);
sep
参数指向一个字符串,定义了用作分隔符的字符集合- 第一个参数指定一个字符串,它包含了0个或者多个由
sep
字符串中一个或者多个分隔符分割的标记。 strtok
函数找到str中的下一个标记,并将其用\0
结尾,返回一个指向这个标记的指针。(注:strtok
函数会改变被操作的字符串,所以被strtok
函数切分的字符串⼀般都是临时拷贝的内容并且可修改。)strtok
函数的第一个参数不为NULL
,函数将找到str
中第一个标记,strtok
函数将保存它在字符串中的位置。strtok
函数的第一个参数为NULL
,函数将在同一个字符串中被保存的位置开始,查找下⼀个标记。- 如果字符串中不存在更多的标记,则返回
NULL
指针。
strtok
函数的基本用法:
int main()
{
char arr[] = "zpengwei@yeah.net";
char buf[256] = { 0 };//因为函数strtok会改变字符串原本的内容,所以弄一个临时拷贝后,再用strtok函数进行分割
strcpy(buf, arr);//zpengwei\0yeah\0net
char* sep = "@.";
char* ret = strtok(buf, sep);//只需要执行一次,含有buf的strtok
printf("%s\n", ret);//zpengwei
ret = strtok(NULL, sep);//如果第一个参数是NULL,那么会从上面记住的位置(被改为了\0)开始向后分割
printf("%s\n", ret);//yeah
ret = strtok(NULL, sep);//如果第一个参数是NULL,那么会从上面记住的位置(被改为了\0)开始向后分割
printf("%s\n", ret);//net
ret = strtok(NULL, sep);
printf("%s\n", ret);//(NULL)
return 0;
}
上面讲的是strtok
函数的基本用法是怎么回事的,但一般不是这么用。
int main()
{
char arr[] = "zpengwei@yeah.net";
char buf[256] = { 0 };//因为函数strtok会改变字符串原本的内容,所以弄一个临时拷贝后,再用strtok函数进行分割
strcpy(buf, arr);//zpengwei\0yeah\0net
char* sep = "@.";
char* ret = NULL;
for(ret = strtok(buf,sep);ret != NULL; ret = strtok(NULL,sep))//初始化部分只会执行一次strtok(buf,sep)
{
printf("%s\n",ret);
}
return 0;
}
通过上面的了解,我们可能会对strtok
函数的用法感到有点奇怪,因为第一次只需要传入指向需要分割的字符串的首元素的地址即可,后面就只需要传入NULL空指针就可以将字符串分割开,想一想,它应该是有记忆功能,不然当变量出了函数就会被销毁,所以应该有static
修饰的变量(处在静态区,出函数也不会被销毁)。
十一、strstr
函数的使用和模拟实现
strstr
函数的参数部分
const char * strstr (const char * str1, const char * str2);
char * strstr ( char * str1, const char * str2);
strstr
函数的功能:在str1
中找str2
这个字符串,第一次出现的位置。如果找到了,就返回这个第一次出现的起始地址;如果找不到,就返回NULL
。
strstr
函数的基本用法:
int main()
{
char arr[] = "abcdefabcdef";
char* p = "efab";
char * ret = strstr(arr,p);
if(ret == NULL)
{
printf("不存在\n");
}
else
{
printf("存在\n");
printf("%s",ret);//efabcdef
//这个函数返回的是第一次找到的字符串,然后从找到的字符串的起始地址,那么打印字符串用%s的话,就是从传入的地址开始,到\0字符前。
}
return 0 ;
}
在上面我们对strstr
函数的用法进行了解释,但对于学习strstr
函数,重要的是模拟实现strstr
函数。
const char* my_strstr(const char * str1,const char* str2)
{
assert(str1 && str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = str1;
//特殊情况:str2指向的是空字符串"",直接返回str1
if(*str2 == '\0')
return str1;
while(*cur != '\0')
{
s1 = cur;
s2 = str2;
while(*s1 != '\0' && *s2 != '\0' && *s1 == *s2)//也可以写成(*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if(*s2 == '\0')
return cur;
cur++;
}
return NULL;
}
int main()
{
char arr[] = "abcdefabcdef";
char* p = "efab";
const char * ret = my_strstr(arr,p);
if(ret == NULL)
{
printf("不存在\n");
}
else
{
printf("存在\n");
printf("%s",ret);//efabcdef
//这个函数返回的是第一次找到的字符串,然后从找到的字符串的起始地址,那么打印字符串用%s的话,就是从传入的地址开始,到\0字符前。
}
return 0 ;
}
十二、strerror
函数的使用
strerror
函数的参数部分
char* strerror ( int errnum );//strerror 函数可以把参数部分错误码(数字)对应的错误信息的字符串地址返回来。
//一个错误码对应一个错误信息,这个信息是字符串。
补充知识:在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在 errno.h
这个头文件中说明的,C语言程序启动的时候就会使用一个全局的变量errno
来记录程序的当前错误码,只不过程序启动的时候errno
是0
,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码,存放在errno
中,而⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror
函数就可以将错误对应的错误信息字符串的地址返回
strerror
函数的基本用法:
int main()
{
int i = 0;
for (i = 0;i <= 10;i++)
{
printf("%d: %s\n",strerror(i));
}
return 0;
}
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");
if (pFile == NULL)
printf ("文件打不开的原因是: %s\n", strerror(errno));
//文件打不开的原因是: No such file or directory
return 0;
}
上面讲了strerror
函数,那么让我们再来了解一下和strerror
函数类似的函数perror
。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("data.txt", "r");//"r" - 表示只读,以读文件的形式,来打开文件,
//如果文件不存在,就打开失败,然后返回空指针
if (pf == NULL)
{
printf("打开文件失败的原因是:%s\n", strerror(errno));
perror("打开文件失败的原因是");
//perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误信息。
}
return 0;
}
通过上面的代码图片可以看到,perror函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。