C语言中有一系列的函数是专门用来做字符分类的,也就是一个字符它是属于什么类型的字符。这些函数的使用都需要包含一个头文件是ctype.h。
islower的函数声明如下:
islower函数能够判断参数部分的c是否是小写字母。通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0。(参数c设置成整型的原因是:传字符,就是传递了字符的ASCII码值)
练习: 写一个代码,将字符串中的小写字母转为大写,其他字符不变。
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[] = "welcome to C!";
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] -= 32;
}
i++;
}
printf("%s\n", arr);
return 0;
}
程序运行结果:
C语言提供了2个字符转换函数:
int tolower ( int c ); //将参数传进去的大写字母转成小写
int toupper ( int c ); //将参数传进去的小写字母转成大写
比如我们使用toupper函数来将小写字母转为大写:
#include<stdio.h>
#include<ctype.h>
int main()
{
printf("%c\n", toupper('a'));
printf("%c\n", toupper('B'));
return 0;
}
程序运行结果:
上面的那个练习,我们将小写转大写,是-32完成的效果。现在有了转换函数,就可以直接使用 toupper 函数:
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[] = "welcome to C!";
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] = toupper(arr[i]);//toupper函数将小写字母转为大写
}
i++;
}
printf("%s\n", arr);
return 0;
}
程序运行结果:
strlen函数是用来求一个字符串的长度的。函数声明如下:
size_t strlen ( const char* str );
🏀字符串以’\0’作为结束标志,strten函数返回的是字符串中’\0’前面出现的字符个数(不包含’\0’)
🏀参数指向的字符串必须要以’\0’结束
🏀注意函数的返回值为size_t类型,也就是一个无符号的数(易错)
🏀strlen函数的使用需要包含头文件string.h
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "hello world";
printf("%d\n", strlen(arr));
return 0;
}
程序运行结果:
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "abcd";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1\n");
}
else
{
printf("srt1>=str2\n");
}
return 0;
}
程序运行结果:
11111111 11111111 11111111 11111110
既然strlen(str2)-strlen(str1)的结果是size_t类型的,那么-2就会被解析成一个无符号整数,就是一个很大的正数。那么该表达式的结果肯定是大于0的,所以输出了str2>str1。
#include<stdio.h>
#include<assert.h>
//1.计数器方式
size_t my_strlen(const char* str)
{
int count = 0;
assert(str);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdefghijk";
printf("%u\n", my_strlen(arr));
return 0;
}
#include<stdio.h>
#include<assert.h>
//2.不创建临时变量计数器(递归)
size_t my_strlen(const char* str)
{
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char arr[] = "abcdefghijk";
printf("%u\n", my_strlen(arr));
return 0;
}
#include<stdio.h>
#include<assert.h>
//3.指针-指针的方式
size_t my_strlen(char* str)
{
assert(str);
char* p = str;
while(*p != '\0')
p++;
return p - str;
}
int main()
{
char arr[] = "abcdefghijk";
printf("%u\n", my_strlen(arr));
return 0;
}
上面三种函数求得的字符串长度都为:
(1) strcpy函数的作用是:将source指向的C字符串复制到destination指向的数组中,包括结束的null(‘\0’)字符(并在该点停止)。
char* strcpy(char* destination, const char* source );
🥥源字符串必须以’\0’结束。
🥥会将源字符串中的’\0’拷贝到目标空间。
🥥目标空间必须足够大,以确保能存放源字符串。
🥥目标空间必须可修改。
比如我们现在要把字符数组arr1里的内容拷贝放到字符数组arr2里面,就可以使用strcpy函数:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "hello world";
char arr2[20] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
程序运行结果:
(2) strcpy函数的模拟实现:
#include<stdio.h>
#include<assert.h>
void my_strcpy(char* dest, const char* src)
{
assert(src != NULL);
assert(dest != NULL);
char* ret = dest;
while (*src != '\0')//循环体中拷贝的是'\0'之前的字符
{
*dest = *src;
src++;
dest++;
}
*dest = *src;//拷贝字符'\0'
return ret;
}
int main()
{
char arr1[] = "hello world";
char arr2[] = "xxxxxxxxxxxxxxx";
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
程序运行的结果为:
上面的my_strcpy函数还可以简化为下面的形式:
#include<assert.h>
void my_strcpy(char* dest, const char* src)
{
assert(src != NULL);
assert(dest != NULL);
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
解释一下while循环中的循环条件: *dest++ = *src++ 是什么意思?首先后置++操作符的优先级比解引用操作符(*)的高,所以指针dest和src会先和后置++结合,而dest++和src++表示的是先使用指针dest和src的值,然后再让指针dest和src加1。既然是先使用dest和src的值,那对dest++和src++进行解引用,其实就分别找到了指针dest和src所指向的对象,表达式*dest++ = *src++就是把src所指向的对象(的值)赋给dest指向的对象。当*dest++ = *src++这个表达式执行完毕后,指针dest和src都会自增1,会指向下一个字节的空间,只要*src赋值给*dest的内容不是’\0’字符,那么表达式的值就不是0,循环条件就为真,就会执行循环体内的空语句;当*src的值为’\0’(字符’\0’的ASCII码值为0)赋给*dest后,表达式的值就为0,循环条件就为假,结束循环。此时就完成了全部字符串的拷贝(包括’\0’)。
(1) strcat函数是用来连接(追加)字符串的。函数声明如下:
char* strcat ( char* destination, const char* source );
将源字符串的副本追加到目标字符串的后面。destination中的结束null(‘\0’)字符被source的第一个字符覆盖,并且在destination中由两者串联形成的新字符串的末尾包含一个空字符(‘\0’)。
🍊源字符串必须以’\0’结束。
🍊目标字符串中也得有\0,否则没办法知道追加从哪里开始。
🍊目标空间必须有足够大的空间,能够容纳得下源字符串的内容。
🍊目标空间必须可修改。
比如我们想把字符数组arr2里面的内容追加到字符数组arr1的后面,就可以使用strcat函数:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
程序运行结果:
(2) 模拟实现strcat函数:
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//1.找到目标空间的\0
while (*dest != '\0')
{
dest++;
}
//2.追加字符串
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
程序运行结果:
现在再思考一个问题,那就是如果把一个字符数组的内容,追加在自己的后面,那strcat函数能实现吗?
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20] = "abcde";
strcat(arr, arr);
printf("%s\n", arr);
return 0;
}
程序运行结果:
我使用的编译器是VS2022版本,可以看到上面的结果是可以让一个数组的内容,自己追加在自己的后面。对于其他的编译器,就不知道了。如果使用上面我们模拟的my_strcat函数来实现一个字符数组自己给自己追加,是行不通的,会出现死循环。因为写入后会覆盖掉目标空间的\0字符,从而源空间也会被改掉,由于遇不到\0字符,从而死循环的进行拷贝。大家可以自己思考模拟一个能实现自己给自己追加的函数。
(1) strcmp函数是用来比较两个字符串的内容的。这个函数从两个字符串的第一个字符开始比较。如果它们彼此相等,则继续比较下一对,直到遇到不同的字符或达到终止空字符为止。
int strcmp ( const char* str1, const char* str2 );
● 那么如何比较两个字符串呢?
答案是比较两个字符串中对应位置上的字符的ASCII码值大小即可。标准规定,strcmp函数的返回值:
⚽如果第一个字符串大于第二个字符串,则返回大于0的数字
⚽如果第一个字符串等于第二个字符串,则返回0
⚽如果第一个字符串小于第二个字符串,则返回小于0的数字
举一个例子:(注意以字符串形式初始化的字符数组,数组末尾还存储着一个’\0’字符)
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abccgh";
char arr3[] = "abcxyz";
char arr4[] = "abcdef";
int ret1 = strcmp(arr1, arr2);
int ret2 = strcmp(arr1, arr3);
int ret3 = strcmp(arr1, arr4);
printf("%d\n", ret1);
printf("%d\n", ret2);
printf("%d\n", ret3);
return 0;
}
程序运行结果:
strcmp函数是将str1和str2两个字符串对应位置上的字符进行逐一比对的,如果对应位置上的字符的ASCII码值一样,则继续往后比较下一对字符,当比较到某一对字符的ASCII码值不相等时,此时如果str1中的字符的ASCII码值大于str2的,则返回一个大于0的数(VS中返回1);如果str1中的字符的ASCII码值小于str2的,则返回一个小于0的(VS中返回-1)。当比较到两个字符串的字符都是’\0’的情况,说明两个字符串的内容一样,则返回0。
如果比较到其中一个字符串先达到空字符(‘\0’)时,有两种情况:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
char arr3[] = "abcdefgh";
int ret1 = strcmp(arr1, arr2);
int ret2 = strcmp(arr1, arr3);
printf("%d\n", ret1);
printf("%d\n", ret2);
return 0;
}
程序运行结果:
因为字符\0的ASCII码值为0,所以其他字符的ASCII码值都是大于’\0’的,此时就是哪个字符数组存储的字符多,就哪个就大。(注意strcmp函数的两个参数的先后位置)
(2) strcmp函数的一个简单模拟实现:
#include<stdio.h>
#include<assert.h>
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[] = "abcde";
char arr2[] = "abc";
char arr3[] = "abcghi";
char arr4[] = "abcde";
int ret1 = my_strcmp(arr1, arr2);
int ret2 = my_strcmp(arr1, arr3);
int ret3 = my_strcmp(arr1, arr4);
printf("%d\n", ret1);
printf("%d\n", ret2);
printf("%d\n", ret3);
return 0;
}
程序运行结果:
上面我们学习四个有关字符串的函数,其中strcpy、strcat、strcmp这三个函数是长度不受的字符串函数。也就是拷贝追加比较,不字符串的长度。接下来学习的三个函数,就是长度受的字符串函数。
char* strncpy ( char* destination, const char* source, size_t num );
🍉将源空间的前num字符复制到目标空间。如果在复制完num个字符之前找到源C字符串的结尾(空字符’\0’),则目标空间里将用零(字符\0的ASCII码值)填充,直到向其写入的总数为num个字符为止。
🍉如果源空间里的字符个数大于num,则不会在目标空间的末尾隐式添加空字符。
//1.源字符串不够num个:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc";
char arr2[] = "xxxxxxxxxx";
strncpy(arr2, arr1, 5);
printf("%s\n", arr2);
return 0;
}
程序运行结果:
从arr1数组里拷贝5个字符到arr2数组里,但是arr1数组里是不够5个字符的,所以会在拷贝了abc字符后,再在后面补上两个\0字符。因为传字符数组名给printf函数,printf函数只会打印’\0’之间的字符,所以结果中只看到了abc三个字符。我们可以逐过程调试起来,在监视窗口查看arr2数组:
可以看到确实当拷贝的源字符串不够num个字符时,会在后面继续填充\0字符,直至补够num个字符为止。
但如果源字符串里是够num个字符的,那就不会在后面填充\0字符:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "xxxxxxxxxx";
strncpy(arr2, arr1, 5);
printf("%s\n", arr2);
return 0;
}
程序运行结果:
● 从源空间拷贝num个字符到目标空间。
● 如果源字符串的长度小于num,则拷贝完源字符串之后,会在目标空间的后边追加\0字符,直至补满num个字符。
char* strncat ( char* destination, const char* source, size_t num );
🍑将source指向字符串的前num个字符追加到destination指向的字符串末尾,之后再追加一个\0字符。
🍑如果source指向的字符串的长度小于num的时候,只会将字符串中到\0字符的内容追加到destination指向的字符串末尾。
由于源字符串追加到目标字符串的后面,是要先找到目标空间的\0字符,所以我们来实验一下,看在将源字符串追加到目标字符串的末尾后,会不会再补一个\0字符:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "xx\0xxxxxxx";
strncat(arr2, arr1, 3);
printf("%s\n", arr2);
return 0;
}
程序运行的结果:
从监视窗口可以看到,再将abc这三个字符拷贝到arr2数组的末尾后,补了一个\0字符。
那要是源字符串不够num个字符,那会怎么样呢?
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc";
char arr2[10] = "xx\0xxxxxx";
strncat(arr2, arr1, 5);
printf("%s\n", arr2);
return 0;
}
程序运行结果:
可以看到如果源字符串里不够num个字符,那么只会将源字符串中到\0字符的内容追加到destination指向的字符串末尾,而不是补够num个字符。
int strncmp ( const char* str1, const char* str2, size_t num );
🥝比较str1和str2字符串的前num个字符,如果相等就继续往后比较,最多比较num个字符,如果提前发现不一样,就提前结束。大的字符所在的字符串大于另外一个字符串。如果num个字符都相等,就返回0。
strncmp函数和strcmp函数是很相似的,只是strncmp函数比较两个字符串时是有长度的,最多比较两个字符串的前num对字符。
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcghijk";
int ret1 = strncmp(arr1, arr2, 5);
int ret2 = strncmp(arr1, arr2, 3);
printf("%d\n", ret1);
printf("%d\n", ret2);
return 0;
}
程序运行结果:
学习了有关字符串的一些函数,但也需要注意strcpy、strcat、strcmp函数是不安全的;strncpy、strncat、strncmp函数是相对安全的。为什么这么说呢?就拿strcpy函数来说,如果拷贝的源字符串是大于目标空间的,那么就会出现越界使用空间,因为strcpy函数是不会管目标空间能不能存储得下源字符串的。所以是不安全的。而使用strncpy函数来拷贝字符串时,就多了一层考虑。会思考我们要从源字符串里拷贝几个字符到目标空间里,源字符串有多长,目标空间有多长。
(1) strstr函数的声明如下:
char* strstr ( const char* str1, const char* str2);
strstr函数就是在一个字符串(str1)中查找另一个字符串(str2)是否出现过。如果出现过,则返回str2在str1中(从左往右找)第一次出现的位置(指针),如果在str1中没有查找到str2所指向的字符串,则strstr函数返回空指针:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "this is an apple";
const char* p1 = "is";
const char* p2 = "appl";
const char* p3 = "An";
char* ret1 = strstr(arr1, p1);
char* ret2 = strstr(arr1, p2);
char* ret3 = strstr(arr1, p3);
printf("%s\n", ret1);
printf("%s\n", ret2);
printf("%s\n", ret3);
return 0;
}
strstr函数的模拟实现稍微难一点,因为需要考虑到很多情况。我们一 一列举有哪些情况:
如果笼统一点,基本上就是上面的三种情况。情况1是最简单的一种情况,在str1中找str2所指向的字符串,顺序找下去就能找到。但是情况2就是一种特殊情况,因为第一次遇到并开始对比b字符时,一开始的两对b字符能对得上,但是对比到第三对字符时,就匹配不上了。那就要回到原来开始匹配位置的下一个位置,继续找str2所指向的字符串,这样第二次匹配bbc字符串就能找到。那在代码中就得有一个指针变量cur,用来记录开始匹配的位置,这样在后面匹配失败的时候就能通过cur指针回到开始匹配的位置,然后到下一个位置去继续查找字符串str2。情况3就是找不到的情况。代码的实现如下:
#include<stdio.h>
char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = str1;//该指针用来记录开始匹配的位置
if (*str2 == '\0')//如果要查找的str2字符串为空字符串,那么就直接返回str1字符串
return (char*)str1;
while (*cur) //如果*cur=='\0'表示把str1字符串给找完了也没找到str2所指向的字符串
{
s1 = cur;
s2 = str2;
while (*s1!='\0' && *s2!='\0' && *s1==*s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cur;
}
cur++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
printf("找不到该字符串\n");
else
printf("%s\n", ret);
return 0;
}
程序运行结果:
上面my_strstr函数中,while循环里面还有一个while循环且循环的条件是:*s1!=‘\0’、*s!=‘\0’、*s1==*s2这三个条件的逻辑与,意思是这三个条件要同时成立才,循环才能进行。解释一下这三个条件:
在str1中找str2字符串,在匹配字符串时,如果str1字符串先遇到\0字符,说明在匹配过程中,字符串str2还没找完而str1字符串已经先遇到结尾,这种是找不到的。如果是第二种情况,str2匹配完了str1都还没有遇到结尾,那这种就是找到了要找的字符串。第三情况就是str1和str2字符串在匹配过程中,同时遇到结尾的\0字符。这样也是找到了要找的str2字符串。试想,如果三个条件缺少了 *s1!=‘\0’、*s2!=‘\0’ 这两个条件而只有 *s1==*s2 这一个条件,那么在匹配过程中如果遇到情况三,那么就会出现越界访问。我们只需要比较到\0字符即可。上面只是strstr函数的一种实现方式,还有其他的方法,大家可以下来自己思考尝试。
strtok函数是用来提取由分隔符隔开的一系列连续字符串的,函数声明如下:
char* strtok ( char* str, const char* sep);
● sep参数指向一个字符串,定义了用作分隔符的字符集合。
● 第一个参数指定了一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
● strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以被strtok函数切分的字符串一般都是临时拷贝的内容并且可修改的)
● strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将会保存它在字符串中的位置。
● strtok函数的第一个参数为 NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
● 如果字符串中不存在更多的标记,则返回 NULL 指针。
比如现在有两个字符串:
字符串1:12345670@qq.com
字符串2:192.168.112.43
现在逐一解释上面:
(1) 参数sep定义了用作分隔符的字符串集合,就像上面的两个字符串的分隔符,由它们组成的字符串集合分别就是 “@.”、“.”(没有顺序要求),并且sep这个字符指针指向这个字符串。我们可以将这些分隔符组成的字符串,保存到一个字符数组中,这样传sep参数时就传这个字符串。
(2) strtok函数的第一个参数str指向的字符串,就是由sep指向的分隔符字符串里的一个或者多个字符分隔的字符串。比如上面的字符串1: 12345670@qq.com,这个字符串就是由分隔符@和 . 分隔的字符串。被分隔符分隔的一段字符串,是一个标记。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "12345670@qq.com";
const char* sep = "@.";
char* ret = strtok(arr, sep);//第一次调用strtok函数,第一个参数不传空指针
printf("%s\n", ret);
ret = strtok(NULL, sep);//第二次(乃至以后都)调用strtok函数,第一个参数要传NULL
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
return 0;
}
程序运行的结果:
如果一个字符串很长,分隔符也很多,那上面调用strtok函数的方式就太繁琐麻烦,我们可以通过一个for循环来提取由分隔符分隔的所有字符串:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "192.168.112.43";
const char* sep = "@.";
char* ret = NULL;
for (ret = strtok(arr, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n",ret);
}
return 0;
}
程序运行结果:
char* strerror ( int errnum );
我们可以将错误码1~10对应的错误信息打印出来看一下:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件(以读的形式)
FILE* pf = fopen("test.txt", "r");;
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//读文件…
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
void perror ( const char* str );
perror函数是用来打印错误信息的,它相当于printf+strerror的功能。perror函数在打印的时候,会先打印传给它的字符串str,然后打印一个冒号,再然后打印一个空格,最后打印错误码对应的错误信息。需要注意的是,如果给perror传的是一个空字符串:
“” //其实双引号里面还隐藏了一个\0字符
那么perror函数就只打印错误码对应的错误信息。
上面strerror的例子中,就可以把printf函数改为用perror函数,可以得到一样的效果:(注意fopen、fclose函数的使用需要包含头文件stdio.h和stdlib.h)
#include<stdio.h>
#include<stdlib.h>
int main()
{
//打开文件(以读的形式)
FILE* pf = fopen("test.txt", "r");;
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件…
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
程序运行结果:
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务