目录
1 字符分类函数
2 字符转换函数
3 strlen的模拟实现
4 strcpy的使用和模拟实现
5 strcat的使用和模拟实现
6 strcmp的使用和模拟实现
7 strncpy strncat strncmp的使用和模拟实现
8 strstr的使用和模拟实现
9 strerror的使用
10 strtok的使用
C语言有一类函数是专门做字符分类的,即一个字符是属于什么类型的。
这些函数的使用都需要引用头文件ctype.h,现在将这些函数一一列举。
这些就是C语言中专门做字符分类的函数了,从英文的角度来看是很好理解的,比如isspace就是
is (是)space(空格),判断是不是空格,很好理解。同理,digit是十进制数字,xdigit是十六进制数字,upper就是大写,lower就是小写,都是挺好理解的,alpha就是字母,alnum就是alpha num的总和,punct就是标点符号了,所以英文学一点对C语言的函数还是有点用的。
如果判断结果为真的话就是返回为真的值,但是不一定是1,像这样。
有字符识别函数也会有转化函数,C语言中提供了两个字符转化函数。
大写转小写,小写转大写咯,结合英文就是很好记的了。
也是要引用头文件ctype的。小小的示范一下。
int main()
{
char arr[] = "abcEFGhi";
char* p = arr;
while (*p)
{
*p = tolower(*p);
p++;
}
printf("%s", arr);
return 0;
}
有关strlen的使用相信大家都知道了,我们现在要学习的是模拟实现strlen函数,函数章节提及学习一个函数的最好方法有模拟实现它,试试咯。
法1:
我们知道strlen函数的原理就是找'\0',所以我们可以找一次就+1,找到了就不加了再返回值就行。
int Count(char* pa)
{
int count = 0;
while (*pa)
{
count++;
pa++;
}
return count;
}
int main()
{
char arr[] = "abcdefg";
int ret = Count(arr);
printf("%d ", ret);
return 0;
}
这里可以巧用while循环,如果*pa是'\0'的话,刚好while循环就停止了,也就不会再count++了,最后返回count的值就行。
法2;
前面讲解指针的时候,我们提及在同一块空间中,指针-指针是中间的元素个数,那么也使用这里。
int Count(char* pa)
{
char* pb = pa;
while (*pa++)
{
;
}
return pa - pb - 1;
}
int main()
{
char arr[] = "abcdefg";
int ret = Count(arr);
printf("%d ", ret);
return 0;
}
因为是指针- 指针,所以在最开始的时候我们应该创建一个指针变量用来存放最开始的地址,利用while循环是pa指向的是斜杠0,因为指向的是'\0',所以相减的时候要多减1,这样才避免了因为'\0'导致的数多了。
法3:
你看,1 + 2 + 3 + 4 + 5是不是可以用递归,计数的我们都可以用递归,strlen何尝不是一种计数呢?所以我们同样可以使用递归实现。
int Count(const char* pa)
{
assert(pa);
if (*pa == '\0')
return 0;
else
return 1 + Count(pa + 1);
}
int main()
{
char arr[] = "abcdefg";
int ret = Count(arr);
printf("%d ", ret);
return 0;
}
至于为什么加const,因为我们只是计算长度,不希望字符串的内容被修改,所以加个const修饰一下,结合前面的递归知识,这个是很好理解的。
学习一个函数我们可以先去cplusplus里面看看。
引用的头文件是string,返回类型是char*,根据Return Value我们可以得知是目的地字符串被返回,参数类型是目标字符串的地址和被复制的字符串的地址,因为被复制的字符串我们是不期望被修改的,所以用const修饰。
那么使用的话,我们先举个例子。
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "gf";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
提问,打印的结果是什么?
是gf?还是gfbcefg?这里涉及到的就是斜杠0有没有被打印了。
看,斜杠0也是被拷贝过去了的,这是重点。
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "gf";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
但是如果这样写的话,arr2的空间是2,就会导致越界访问,就报错了,所以[]里面最好写上给它多少空间。
那知道了strcpy是用来复制字符串的,并且会复制斜杠0,且不希望修改被复制的字符串的内容,我们就可以开始模拟实现了。
char* my_strcpy(char* dest, const char* sou)
{
assert(dest);
assert(sou);
char* p1 = dest;
while (*sou)
{
*dest = *sou;
dest++, sou++;
}
*dest = '\0';
return p1;
}
int main()
{
char arr1[100] = "abcdefg";
char arr2[100] = "gf";
char* pa = my_strcpy(arr1, arr2);
printf("%s\n", pa);
return 0;
}
这是最基础的写法,为什么基础呢?因为代码量多了点,先看为什么返回值是p1而不是dest,因为根据cpiusplus的记叙我们返回的应该是最开始的目的字符串的地址,所以我们先暂存一下,在返回。
我们要简洁一下代码量的话就应该从while循环里面入手,因为循环判断条件总是比循环体次数多执行一次,所以我们可以把循环体的内容放在循环条件里面执行。
char* my_strcpy(char* dest, const char* sou)
{
assert(dest);
assert(sou);
char* p1 = dest;
while (*dest++ = *sou++)
{
;
}
return p1;
}
int main()
{
char arr1[100] = "abcdefg";
char arr2[100] = "gf";
char* pa = my_strcpy(arr1, arr2);
printf("%s\n", pa);
return 0;
}
像这样,代码量一下就少了很多,而且还不用单独赋值'\0',但是分号是必须写的,就是空语句的意思而已,当然有兴趣的话,你也可以使用for循环来完成。
strcat这个函数是用来连接字符串的,根据cplusplus
我们得知,引用的头文件是string,返回值是目的字符串的地址,返回类型是char*,参数有两个,目的字符串的地址和来源字符串的地址,简单的使用一下。
int main()
{
char arr1[100] = "abc";
strcat(arr1, "def");
char arr2[100] = "ghi";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
这里可以注意到的就是两个字符数组我都是指定了空间大小的,如果不指定空间大小是很容易出现越界访问的问题的,其次就是目的字符串不能是常量字符串,因为常量字符串是不能被修改的。
有个值得注意的点就是这个函数复制的时候是斜杠0在哪里就从哪里开始复制,如果复制的是\0提前了也就不会继续复制了。
那么基本原理我们知道了现在来实现这个函数。
实现这个函数我们首先要找的目的函数的结束标志,那么可以模仿strlen的while循环来找到结束标志,找到了之后就是根据来源字符串的结束标志决定循环的次数,所以while里面是*pb,当然你用for循环都是可以实现的,循环体内就是赋值的过程,赋值好之后就是指针指向的空间往后跳一个的操作,最后赋值完成,因为pb最后指向的是\0,但是pa是没有赋值到\0的,所以我们需要手动给它一个\0。
char* my_strcat(char* pa, const char* pb)
{
char* p1 = pa;
while (*pa)
{
pa++;
}
while (*pb)
{
*pa++ = *pb++;
}
*pa = '\0';
return p1;
}
int main()
{
char arr1[100] = "a\0bc";
char* p1 = my_strcat(arr1, "de\0f");
char arr2[100] = "ghi";
p1 = my_strcat(arr1, arr2);
printf("%s\n", p1);
return 0;
}
如果不初始化,还不手动给\0,那么结果就是这样的。
strcmp就是string compare,意思是字符串比较的意思,那么这个比较不是我们一般理解的比较长度什么的,这个是比较的大小,比如abc acc比较,后者就大一点,该函数遍历字符串,相同的部分就跳过,直到指向不一样的字符串,根据ASCII码值来比较。abc aaaa比较的话是abc大一点,也就是说它比较到一个字符不相等就返回值了。
根据cplusplus的记载,头文件是string,返回值是int类型的,实际上返回的时候只会返回1 0 -1,在前面的qsort的模拟实现的时候我们也利用了这点,参数是两个字符串的地址,因为我们只是比较不希望改变值,所以加const修饰,简单是使用一下。
int main()
{
char arr1[100] = "abcd";
char arr2[100] = "aaaa";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
我们用ret来接收值,因为abcd > aaaa,所以ret的值是1。
实现它我们只需要做到让指针指向两个字符不相同的地方,可以使用if while,不同了一个if语句就解决了,所以这个函数还是挺好实现的。
int my_strcmp(const char* p1, const char* p2)
{
assert(p1);
assert(p2);
while (1)
{
if (*p1 == *p2)
{
p1++, p2++;
}
else
{
break;
}
}
if (*p1 > *p2)
{
return 1;
}
else if(*p1 < *p2)
{
return -1;
}
else
{
return 0;
}
}
int main()
{
char arr1[100] = "abcd";
char arr2[100] = "aaaa";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
int my_strcmp (const char * str1, const char * str2)
{
int ret = 0 ;
assert(src != NULL);
assert(dest != NULL);
while(*str1 == *str2)
{
if(*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1-*str2;
}
这串代码是升级版,别看是指针减去指针,实际结果还是1 0 -1,但是这串代码确实很简洁,也更加验证了return 的妙用。
熟悉吧?好像就是前面的三个函数加了个n,那么,物以类聚人以群分,我们通过一个函数的学习,自然就可以掌握其他相同的两个。
函数的返回值返回类型都没有变,唯独变化的是参数部分,多了一个size_t类型的num
多了一个n,参数就多了一个num嘛,很正常咯。实际上该函数就是指定了连接字符的个数,n就是num,数字的意思,这里就不免提到了为什么strcpy ctrcat strcmp被称为危险函数了,因为没有数字的限制,是有出现越界的可能性的,scanf也是的,所以在vs里面给它们加上_s的后缀,至于为什么,问vs咯。
现在我们就模拟实现一下三个函数,无非就是加一个循环条件而已。
模拟实现strncpy
char* my_strncpy(char* p1, char* p2, size_t num)
{
char* ret = p1;
while (num)
{
*p1 = *p2;
p1++, p2++;
if ('\0' == *p2)
{
break;
}
num--;
}
*p1 = '\0';
return ret;
}
int main()
{
char arr1[100] = "abcdefg";
char arr2[100] = "gf\0xy";
//strncpy(arr1, arr2, 4);
char* ret = my_strncpy(arr1, arr2, 4);
printf("%s\n", arr1);
return 0;
}
代码部分并没有差多少,稍加思索就理解了的,那么其他两个函数应该就不用进行解释了吧?
相信你可以自己独立完成!
strstr就是string string咯,这个函数的功能是在A字符串中寻找B字符串第一次出现的位置,并返回该函数的指针,如果没有找到,返回的就是空指针。
参数有两个,两个字符串的地址,参数要用const修饰的话,就全都用const修饰咯,毕竟我们只是想要找位置,并不是想要修改。
根据Parameters我们可以知道,通过扫描字符串str1,来匹配字符串str2。
根据Return Value我们可以知道,如果找到了就返回匹配成功的那个首元素地址,没有成功就返回NULL,那你可能问了,成功找到的话是返回字符串的首元素地址吗?那打印的不就是字符串2了吗?当然不是,你可以看看如下例子。
int main()
{
char arr1[100] = "Happy is so simple";
char arr2[100] = " ";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
我要在字符串1中寻找空格这个字符,在y的后面就找到了,于是ret就接收了这个地址,并在str1中开始打印,直到碰到'\0'。
int main()
{
char arr1[100] = "Happy is so simple";
char arr2[100] = "1";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
如果我是在字符串1中寻找1字符,看来就找不到了,所以返回的是空指针,打印的结果就是如此。
那么以下代码阁下该如歌应对呢?
int main()
{
char arr1[100] = "Happy is so simple";
char arr2[100] = "\0";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
在arr1里面找结束标志,最后的结果是不是结束标志呢?显然不是。
是整个arr1数组,那么可不可以理解为strstr寻找的时候,是以arr2的'\0'作为比较的结束标志呢?要验证很简单。
int main()
{
char arr1[100] = "Happy is so simple";
char arr2[100] = "s\0o";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
如果是以'\0'作为标志的话,那么结果应该是s so simple。
猜想成立,所以比较的时候使用arr2的'\0'作为标志的。
使用我们会了,现在就是模拟实现了。
模拟实现的时候我们要考虑一下几种情况,1 字符2数组首元素为'\0',2 找得到 3 前半段有一样的但是没有完全一样。
第一种情况很简单,就一个if return完成了,我们侧重找得到和找不到,它比较就是从字符1数组每个元素挨个挨个的比较,所以我们可以用字符1数组的元素作为循环变量,如果遍历完字符1数组还没有满足条件的,就返回return NULL,那么循环体里面的东西怎么写呢?
第三种情况是有相似的但不完全相似,比如字符1数组是so1 so2 simple,字符2数组是so2,那么最开始返回的指针会落在第一个s上面,但是到后面不满足这个条件了,我们就需要重置它,所以在每次循环结束后,需要重置两个东西,分别是返回的指针变量和字符数组2的临时变量,因为每次循环完它们的值都改变了。
当两个临时变量都不是结束标志的时候,并且它们相等,就让他们的指向的位置一直往后移,直到这两个不相等了或者是某个变量已经是结束标志了,这里有个很巧妙的判断它们是否相等,对他们相减的结果进行取反,如果相等,相减就是0,取反了就是1,while循环继续下去,循环体内的内容就是s1++,s2++,最后跳出循环的时候判断一下s2指向的内容是不是'\0',如果是的话,返回这个时候的字符1中的临时变量,最后如果整个while循环过去了都没有找到,就直接返回NULL就行了。
所以模拟实现strstr的时候是需要5个临时变量的,字符1数组的地址,字符数组2的地址,用于遍历字符2数组的变量,用于遍历字符1数组的变量,用于重置遍历字符1数组的指针变量。
代码如下。
const* my_strstr(const char* str1, const char* str2)
{
char* cp = str1;
char* s1, * s2;
if (!*str2)
return str1;
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 && *s2 && !(*s1 - *s2))
s1++, s2++;
if (!*s2)
return cp;
cp++;
}
}
int main()
{
char arr1[100] = "Happy is si1 simple";
char arr2[100] = "si";
char* ret = my_strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
现在讲的函数就不进行模拟实现了,因为点啥呢?你想,printf不能被模拟实现吧?这个也是,我们只是可以看到它的参数,功能,返回值,返回类型等,实现就不是我们的事儿了。
按照前面的方法,了解这个函数还是比较容易的,那么这个函数的功能是什么呢?
strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来,比如参数部分是0,那么就没有出错,这是规定的,所有错误码都是放在error的头文件里面的,在这个头文件里面,0代表的是No error,也就是没有错误,所以我们现在想main函数为什么要返回0,为什么C语言认为返回0就是程序正常,就是因为这个咯。
每个错误都是有自己对应的错误码的,但是程序报错的时候不可能就给你一个1或者2什么的,strerror通过错误码,返回错误码对应的那串字符串,我们现在就打印1-10对应的错误码试试。
int main()
{
for (int i = 0; i <= 10; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
这就是打印结果,我们可以看到0对应的就是No error,再次验证了0是正常,因为返回类型是char*的,也就是那串字符串的首元素地址,所以我们用%s打印。
先看一段记载,啊,好多,看不懂,好,那我介绍介绍。
返回值是指针,第一个参数是指向字符串的指针,第二个参数是指针,指向的是分隔符,分隔符是什么?先不管,直接上代码可能更容易理解。
int main()
{
char arr[] = "198.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;
}
最基本的情况就是这个,我们令.是分隔符,那么str接收函数返回的地址,这个函数的功能就是遍历传过去的字符串,在里面找分隔符,从第一个元素开始找,如果找到了,就使那一个分隔符变成\0,然后返回起始位置的地址,然后进行第二次寻找,第三次,直到找了\0,函数就结束了。
注意:这个函数是会修改字符串的,所以传进去的不能是常量字符串,也不能用const修饰
调试的结果,不出所料的所有.都变成了\0,那如果分隔符变多了呢?比如多个分隔符在一起了。
int main()
{
char arr[] = "198..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;
}
事实上,多个分隔符在一起的时候只会修改第一个分隔符,但是实际打印的时候,分割符是不会被打印的。
int main()
{
char arr[] = "1981686111";
char* sep = ".";
char* str = NULL;
/*
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
*/
str = strtok(arr, sep);
printf("%s\n", str);
return 0;
}
但是如果分隔符,那么就跟打印字符串一样了。
如果首元素是\0,循环就直接结束了,可以自行试试。
还有一个要注意的点就是如果strtok的第一个参数是NULL的话,那么分割的位置就是从上一次分割完的位置继续遍历的,代码如下。
int main()
{
char str[] = "apple,bnanan,cherry,grape";
char* token;
token = strtok(str, ",");
while (token != NULL)
{
printf("%s\n", token);
token = strtok(NULL, ",");
}
return 0;
}
以上就是常用的字符类函数的介绍,感谢阅读!