目录
一.字符分类函数
C语言中有一系列函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。
这些函数的使用都需要包含一个头文件是ctype.h
|-----------------------------------------------------------------------------|------------------|
| 函数 | 作用 |
| isalnum | 检查一个字符是否是字母或数字 |
| isalpha | 检查一个字符是否是字母 |
| islower | 检查一个字符是否是小写字母 |
| isupper | 检查一个字符是否是大写字母 |
| isdigit | 检查字符是否为数字 |
| isxdigit | 检查一个字符是否是十六进制的字符 |
| iscntrl | 检查一个字符是否是控制字符 |
| isgraph | 检查一个字符是否是图形字符 |
| isspace | 检查一个字符是否是空白字符 |
| isblank | 检查一个字符是否是空格字符 |
| isprint | 检查一个字符是否是可打印字符 |
| ispunct | 检查一个字符是否是标点字符 |
参考网址: 空终止字节字符串 - cppreference.comhttps://zh.cppreference.com/w/c/string/byte
cpp
#include<ctype.h>
#include<stdio.h>
int main()
{
int ret = isalnum('a');
printf("%d",ret);
return 0;
}
这些函数的的参数都是是int类型的,即使我们传过去的是字符,但是其实是根据它的ASCII值进行计算的。返回类型是int,如果是就会返回一个非零的数字,比如上述代码返回值就是2.(这可能有所差异),但一定是非零值,如果不是上述的所有函数都会返回0.
练习:写一个代码将字符串中的小写转化为大写,其他字符不变。
cpp
int main()
{
char str[] = { "Hello World!" };
int i = 0;
char c;
while (str[i]) {
c = str[i];
if (islower(c)) {
c -= 32;
}
i++;
putchar(c);
}
}
这里-32的原因是因为ASCII值中a的值是97,A的值是65,也就是所小写字母减32就是大写字母。通过这一点我们就可以知道如何进行大小写的转化。
二.字符转化函数
ctype:<cctype> (ctype.h) - C++ Reference
字符转化函数只有两种;
|-----------------------------------------------------------------------------|-------------|
| 函数 | 功能 |
| toupper | 将小写字符转化为大写的 |
| tolower | 将大写字符转化为小写的 |
这两个函数的参数也是int 返回值也是int,返回值返回的就应该是该字符对应ASCII值。
如果这个字符无法转化,就只会返回原字符。
所以上述的题目就可以改写为:
cpp
int main()
{
char str[] = { "Hello World!" };
int i = 0;
char c;
while (str[i]) {
c = str[i];
if (islower(c)) {
c = toupper(c);
}
i++;
putchar(c);
}
}
注意传递的参数只能是一个字符,而不能是字符串
三.strlen函数
函数的介绍
函数原型:
cpp
size_t strlen(const char* str);
函数功能是返回字符串的长度。字符串的长度是有'\0'字符决定,它之前的字符个数就是字符串的长度。C字符串的长度等于字符串开头和'\0'字符之间的字符数.
注:字符串长度是不包括'\0'的
注意字符串的长度是与包含字符串的数组的大小不一样的。
cpp
int main()
{
char str[] = "abcdef";
return 0;
}
通过调试,我们可以发现,这个数组的类型是char[7]说明这个数组大小是7,但是字符串的长度是6。
所有我们如果给【】中加上值,这个值的大小至少要是7,不能比7小不如就存不下这个字符串。
那么多余的值就会被初始化为'\0'.
字符串以'\0'作为结束的标志,strlen返回的是'\0'之前的字符个数,不包含'\0'.
注意函数的返回值是size_t类型的是无符号的。
strlen的使用需要包含头文件<string.h>
strlen函数的模拟实现
1.计算器法
cpp
#include<assert.h>
#include<stdio.h>
size_t my_strlen(const char* str)
{
int count = 0;
assert(str);
while (*str) {
count++;
str++;
}
return (size_t)count;
}
2.递归
cpp
size_t my_strlen(const char* str)
{
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str++);
}
递归相比其他的方法是没有创建临时变量。
三.指针-指针的方式
回顾:指针减指针得到的是指针之间的元素个数+1,比如第五个字符的地址-第一个字符的地址就是4(中间有三个元素+1);
cpp
size_t my_strlen(const char* str) {
assert(str);
char* start = str;
while (*str != '\0') {
str++;
}
return str - start;
}
注意这里必须是高地址减低地址,不然就是负数了。
四.strcpy函数
函数介绍
strcpy是用于字符串拷贝的,第一个参数destination是目的地的地址,而第二个参数是源头的地址。
cpp
#include<stdio.h>
#include<string.h>
int main()
{
char dest[10] = "abc";
char* source = "abcdef";
strcpy(dest,source);
printf("%s",dest);
return 0;
}
这样我们就把source所指向的字符串成功复制到了dest字符数组中。
注意事项:
- 字符串在拷贝的过程中是包含字符'\0'的,而且会在这个字符停止拷贝。也就是所源字符串必须以'\0'结尾。
- 为了避免溢出,目标空间必须足够大,要确保能完整存放源字符串。
- 目标空间必须能够修改。
- 目标空间在内存上不能与源字符串相互重叠。
返回值是目标空间的地址。
strcmp的模拟实现
cpp
#include<stdio.h>
#include<string.h>
char* my_strcpy(char* destination,const char* source)
{
char* ret = destination;
while (*source)
{
*destination++ = *source++;
}
*destination = *source;//最后将'\0'复制
return ret;
}
int main()
{
char dest[10] = "abcxxxxxxx";
char* source = "abcdef";
my_strcpy(dest,source);
printf("%s",dest);
return 0;
}
简化一下:
cpp
char* my_strcpy(char* destination, const char* source)
{
char* ret = destination;
while (*destination++ = *source++)
{
;
}
return ret;
}
五.strcat函数
函数介绍
strcat这个函数是用于字符串附加的,将source所指向的字符串附加到destination的末尾。
cpp
int main()
{
char dest[20] = "hello ";
char* source = "world!";
strcat(dest,source);
printf("%s",dest);
return 0;
}
也就是将source的字符串链接在dest的后面。我们知道字符串的末尾是有一个隐藏的'\0'的,但是这里还是打印出了world!,说明在使用strcat进行附加的时候,末尾的'\0'是已经被覆盖了的。
注意事项:
- destination所指向的字符串最后的'\0'字符被source所指向的字符串的第一个字符覆盖了。
- 这个新的字符串末尾是含有'\0'的。
- 目标空间必须足够大,要能容纳新的字符串。
- destination和source所指向的空间在内存上不能重叠,否则运行结果是未知的。
返回值的是destination的起始地址。
strcat的模拟实现
首先我们需要先找到目标字符末尾的'\0'字符,然后进行拷贝。
cpp
#include<stdio.h>
#include<string.h>
char* my_strcat(char* destination,const char* source)
{
char* ret = destination;
while (*destination)
destination++;
while (*destination++ = *source++);
return ret;
}
int main()
{
char dest[20] = "hello ";
char* source = "world!";
my_strcat(dest,source);
printf("%s",dest);
return 0;
}
六.strcmp函数
参考网址:strcmp - C++ Referencehttps://legacy.cplusplus.com/reference/cstring/strcmp/?kw=strcmp
函数介绍
cpp
int strcmp(const char* str1, const char* str2);
This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.This function performs a binary comparison of the characters.
这个函数是用于比较两个字符串的。这个函数首先会比较两个字符串的首字符,如果相同,这个函数就会继续比较下一对字符,直到遇到不同的字符或者'\0'.这个函数的执行的是二进制的比较。
返回值
情况1:
cpp
"abc"//str1
"abf"//str2
这两个字符串的前两个字符相同,而第三个字符不同,c的ASCII值小于f的ASCII值。所以strcmp会返回一个小于0的整形。
情况2:
cpp
"abc"//str1
"abc"//str2
这两个字符串一模一样,这样就会strcmp就会返回一个0。
情况3:
cpp
"abc"//str1
"abb"//str2
这两个字符串的前两个字符相同,而第三个字符不同,c的ASCII值大于b的ASCII值。所以strcmp会返回一个大于0的整形。
情况4:
cpp
"abc"//str1
"aaz"//str2
这两个字符串的第一个字符相同,而第二个字符str1大于str2,这时候返回一个大于0的整形。第三个字符就不会进行比较了。
strcmp函数的模仿实现
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
int ret = 0;
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char* str1 = "abc";
char* str2 = "aaz";
printf("%d",my_strcmp(str1,str2));
return 0;
}
七.strncpy,strncat,strncmp函数
这三个函数与上面所讲的三个函数非常的相似,从函数民上我们发现只多了一个n字母。
那么实际上他们有什么区别呢?
cpp
char* strncpy(char* destination, const char* source, size_t num);
char* strncat(char* destination, const char* source, size_t num);
int strncmp(const char* str1, const char* str2, size_t num);
从他们的参数上来看,我们发现只多了一个size_t类型的参数num,也就是函数名中多的n。
那么这个num到底有什么用呢?
(1)strncpy
对于strncpy来说,这个num就是用于指定字数的字符串拷贝。
cpp
int main()
{
char dest[20] = "xxxxxxxxxxhello ";
char* source = "world!";
strncpy(dest,source,5);
printf("%s",dest);
return 0;
}
打印结果:
仔细观察发现source中的感叹号并没有复制到dest字符中。也就是只复制了source的前五个字符。所以这个num就是给我们来控制想复制的字符个数的。
如果num的值大于source所指向的字符串的长度 。会在拷贝完字符串后,在'\0'的后面一直追加0,直到满足num个字符。
模拟实现
cpp
#include<stdio.h>
char* my_strncpy(char* destination, const char* source, size_t num)
{
char* ret = destination;
for (int i = num; i > 0; i--)
{
if (*source) {
*destination = *source;
}
else {
*destination = '\0';
}
destination++;
source++;
}
return ret;
}
int main()
{
char str1[20] = "xxxxx world!!!";
char* str2 = "hello";
printf("%s\n", str1);
my_strncpy(str1,str2,7);
printf("%s",str1);
return 0;
}
(2)strncat
这个函数同理,num也是用于控制附加的个数的。
cpp
int main()
{
char dest[20] = "hello ";
char* source = "world!";
strncat(dest,source,5);
printf("%s",dest);
return 0;
}
虽然我们只附加了五个字符,这个是并没有包含'\0'的,所以这个函数会自动在新字符串的末尾补上斜杠零。
strcat的模拟实现
cpp
char* my_strncat(char* destination, const char* source, size_t num)
{
char* ret = destination;
while (*destination)
{
destination++;
}
for (int i = 0; i < num; i++)
{
if (*source) {
*destination = *source;
}
else{
*destination = '\0';
}
destination++;
source++;
}
return ret;
}
int main()
{
char str1[20] = "hello ";
char* str2 = "world!!!";
printf("%s\n", str1);
my_strncat(str1, str2, 7);
printf("%s", str1);
return 0;
}
(3)strncmp函数
这个函数也是,num控制比较的个数。只比较前num个字符,其他均与strcmp一样
注意事项
这六个函数的目标字符串和源字符串都不能在内存上有任何重叠,不然运行结果是未知的,可能复合要求,也可能不符合,这与编译器有关。
八.strstr函数
函数的介绍
cpp
char * strstr (const char * str1, const char * str2 );
这个函数是用于在一个字符串中查找是否具有另一个字符串。
返回值
这个函数的返回值是str1中具有str2字符串的首字符的地址。
比如:"abcdef"和"cdef".返回值就是"abcdef"中字符C的地址。
如果没有找到,就会返回NULL.
strstr函数的模拟实现
cpp
#include<stdio.h>
#include<string.h>
char* my_strstr(const char *str1,const char*str2)
{
if (!*str2)
return (char*)str1;
char* s1 = NULL;
char* s2 = NULL;
char* cur = (char*)str1;
while (*cur)
{
s1 = cur;
s2 = (char*)str2;
while (*s1 == *s2 && *s1 && *s2)
{
s1++;
s2++;
}
if (!*s2)
return cur;
cur++;
}
return NULL;
}
int main()
{
char* str1 = "this is a string";
char* str2 = "str";
char * ret = my_strstr(str1,str2);
printf("%s\n",ret);
return 0;
}
九.strtok函数
cpp
char* strtok(char* str, const char* delimiters);
这个函数是用于字符串的分隔的。
str指针所指向的是一个需要分隔的字符串。
delimiters所指向的是分隔符的合集。
cpp
#include<stdio.h>
#include<string.h>
int main()
{
char str[20] = "abc.hello_world/no";
char* del = "._/";
//第一次分隔
char* first = strtok(str,del);
printf("%s\n",first);
//第二次分隔
char*second = strtok(NULL,del);
printf("%s\n",second);
//第三分隔
char* third = strtok(NULL,del);
printf("%s\n",third);
return 0;
}
打印结果
最终我们发现str这个字符数组被分割了,而这个del就是分隔符的合集。这个函数会根据分隔符合集中的每一个字符分割这个字符串,并在新的字符串结尾加上'\0',以免读取时造成溢出的问题。
每一次调用这个函数只会分隔一次,并且返回初始地址,下一次调用的时候(如果还能进行分隔)只需要传NULL和分隔符。如果读取到了str中的'\0'就会停止分隔了。并且后续如果继续调用,只会返回NULL.
我们也可以使用for循环的方式对分隔后的字符串进行打印。
cpp
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "192.168.6.111";
char* sep = ".";
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
十.strerror函数
cpp
char* strerror(int errnum);
strerror函数可把参数部分错误码所对应的错误信息所对应的字符串的地址返回。
错误码
在不同的系统和C语言标准库中都规定了一些错误码,一般是放在<errno.h> 头文件中的,C语言程序的启动时候,就会使用一个全面的变量errno来记录程序的当前的错误码,只不过程序启动的时候errno是0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码放在errno中,而一个错误码的数字是整数,这是比较难以理解的,所以每一个错误码都是由对应的错误信息的,strerror这个函数就是用于将错误码对应的错误信息对应的字符串地址返回。
cpp
#include<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d = %s\n",i,strerror(i));
}
return 0;
}
通过上面的代码我们就可以依次打印出错误信息了。
在Windows11和VS2022的环境下输出结果是:
举例:
cpp
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
printf("Error opening file unexist.ent: %s\n", strerror(errno));
return 0;
}
这个fopen就是用于打开文件的,后续我们会继续了解。
但是我们运行这串代码会发现给出的错误信息是:
这个错误信息的意思是:没有这样的文件或者文件夹,也就是我们打开文件失败了。
perror函数
cpp
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
//printf("Error opening file unexist.ent: %s\n", strerror(errno));
perror("Error opening file unexist.ent");
return 0;
}
我们也可以使用perror这个函数,这个函数会先打印这个参数部分的字符串,然后就是一个冒号和空格,最后是错误信息。