6.字符串拼接函数
0、知识点:
-
内存污染(存储字符串的目的内存不够用时,后面的内存会被污染,也就是被修改,类似于下标越界)
- strcat其实就是另一种形式的strcpy,一个从末尾开始复制,一个从头开始复制。
1、strcat函数:复习以下知识点后,练习 习题5,自己实现strncat()函数
- 函数原型:char* strcat(char *dest, const char *src);
- 操作数:①目的字符数组的首地址dest;②来源字符串的首地址src。
- 函数功能:把src所指向的字符串(包括“\0”)拼接到dest所指向的字符串的末尾(末尾指的是dest所指字符串的第一个空字符'\0',并删除*dest原来末尾的“\0”),然后返回指向dest的指针。
- 缺点:①当*dest不足以容纳被复制进来的*src时,字符串会溢出,造成内存污染。尽量避免。
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char str[20] = "abcde"; char *src = "hello"; printf("拼接前:%s\n", str); strcat(str, src); printf("拼接后:%s\n", str); return 0; }
2、strncat函数:复习以下知识点后,练习 习题6,自己实现strncat()函数
- 函数原型:char* strncat(char *dest, const char *src, size_t count);
- 操作数:①目的字符数组的首地址dest;②来源字符串的首地址src;③限制拼接的字节数count。
- 函数功能:把src所指向的字符串中以src地址开始的后count个字节,拼接到dest所指向的字符串末尾(删除*dest原来末尾的“\0”)。返回指向dest的指针。
- 内部决策:当count大小 > src所指字符串长度时,多拼接的部分会用空字符'\0'填充。
- 缺点:①当*dest不足以容纳被复制进来的*src时,字符串会溢出,造成内存污染。尽量避免。
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char str[20] = "abcde"; char *src = "hello"; printf("拼接前:%s\n", str); strncat(str, src, 2); printf("拼接前2个字节后:%s\n", str); return 0; }
7.字符串比较函数
1、strcmp函数:复习以下知识点后,练习 习题7,自己实现strcmp()函数
- 函数原型:int strcmp(const char *str1, const char *str2);
- 操作数:①字符串1的首地址str1;②字符串2的首地址str2。
- 函数功能:逐字符比较str1 和 str2 指向的字符串中相同位置字符的ASCII码,如果第0个字符相同,就比较下一个字符,依此类推,直到比较出大小,若str1=str2,则返回0;若str1<str2,则返回-1;若str1>str2,则返回1。
- 使用方法:一般用来判断两个字符串是否一样,我们不关心两个字符串谁大谁小。
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char *str1 = "abcd2chen3"; char *str2 = "abcdeche13567"; int ret = strcmp(str1, str2); printf("RET=%d\n", ret); if(ret == 0){ printf("两个字符串一样\n"); }else{ printf("两个字符串不一样"); } return 0; }
2、strncmp函数:复习以下知识点后,练习 习题8,自己实现strncmp()函数
- 函数原型:int strncmp(const char *str1, const char *str2, size_t count);
- 操作数:①字符串1的首地址str1;②字符串2的首地址str2;③最大比较字节数count。
- 函数功能:逐字符比较str1 和 str2 指向的字符串中相同位置字符的ASCII码,如果第0个字符相同,就比较下一个字符,依此类推,最多比较前 count 个字节。若str1=str2,则返回0;若str1<str2,则返回-1;若str1>str2,则返回1。
- 使用方法:一般用来判断两个字符串的前count个字节是否一样,我们不关心两个字符串谁大谁小。
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char *str1 = "abcd2chen3"; char *str2 = "abcdeche13567"; int ret = strncmp(str1, str2, 10); printf("RET=%d\n", ret); if(ret == 0){ printf("两个字符串的前10个字符一样\n"); }else{ printf("两个字符串的前10个字符不一样"); } return 0; }
8.字符串查找函数
1、strchr函数:复习以下知识点后,练习 习题10,自己实现strchr()函数
- 函数原型:char* strchr(const char *str, int c);
- 操作数:①字符串的首地址str;②被查找字符c的ASCII码。
- 函数功能:在字符指针 str 所指向的字符串中按照ASCII码,搜索第一次出现字符 c(一个无符号字符)的位置。并返回字符 c 的地址。
- 使用方法:①一般用来判断str所指字符串中是否含有字符c(如下示例)。②用来找出字符串str中所有字符c的个数和位置,见习题9。
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char *str = "abcde12345 abcde12345 abcde12345"; char *p = strchr(str, '3'); if(p == NULL){ printf("找不到字符'3'"); return 0; } printf("第一个字符'3'距离字符串首地址%d个字节", p - str); return 0; }
2、strrchr函数:略。它与strchr唯一的不同就是从尾部开始查找,r是英文“rear”(尾部)的首字母。
3、strstr函数:复习以下知识点后,练习 习题11,自己实现strstr()函数
- 函数原型:char* strstr(const char *str, const char *substr);
- 操作数:①字符串的首地址str;②被查找的子字符串substr。
- 函数功能:在str指向的字符串中从头开始查找,若substr是str的子串,则返回substr在str中首次出现的地址。
- 使用方法:一般用来判断str所指字符串中是否含有子字符串substr。
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char *str = "abcde1234$#$ abcde5678$#$"; char *substr = "$#$"; char *p = strstr(str, substr); if(p == NULL){ printf("找不到子字符串\n"); return 0; } printf("第一个子字符串%s距离字符串首地址%d个字节", substr, p-str); return 0; }
9.字符串分割函数
1、strtok函数:复习以下知识点后,练习习题12(解析信息),巩固strtok函数的用法
- 函数原型:char* strtok(char *str, const char *delim);
- 操作数:①字符数组的首地址str;②分隔符的首地址delim。
- 函数功能:按照字符指针delim指向的分隔符中的字符对str所指字符串进行逐字符查找,如果在str中发现了delim中的字符,那么就使得str中的这个字符变成'\0',函数结束并返回从str中切割出来的子字符串的首地址。
- 内部决策:①strtok函数遇到str中的空字符'\0',则代表分割失败;②strtok函数具有记忆功能,在上一次切割完成后,strtok函数的起始分割位置会往后偏移,不能再将str所指字符串的首地址传递给strtok函数(因为会造成分割失败),strtok函数的设定是:再次调用strtok函数时,需要将NULL传进第一个参数中;③如果在str所指字符串中出现了连续的几个delim字符,strtok函数只会将第一个delim字符变成'\0';
- 使用方法:只要没切到'\0',就一直调用strtok函数,用一个字符指针数组来保存子字符串首地址。如图:
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char str[80] = "xiaoming:21,,,.男.,北京:haidian"; const char *delim = ":,."; //分隔符是冒号、逗号、句号 char *token[10]; int i = 0; token[i] = strtok(str, delim); printf("第%d次切割,p[%d]=%s\n", i+1, i, token[i]); while(token[i] != NULL){ i++; token[i] = strtok(NULL, delim); printf("第%d次切割,p[%d]=%s\n", i+1, i, token[i]); } return 0; }
10.字符串转数值函数(<stdlib.h>)
1、atoi函数:复习以下知识点后,练习 习题13,自己实现atoi()函数。
- 函数原型:int atoi(const char *str);
- 操作数:①字符串的首地址str。
- 函数功能:在字符串str中逐字符读取,直到读到的字符不是①'-'(首次读取的'-'才有效,并且必须在字符串首位的才有效)和 ②'0' ~ '9' ,将读取到的字符转换成int型数据。读取失败返回0。
#include <stdio.h> #include <stdlib.h> int main(int argc, char const *argv[]) { char *str1 = "0123abc"; char *str2 = ".5f"; char *str3 = "abc123.5"; char *str4 = "-0123.5"; printf("atoi(%s)=%d\n", str1, atoi(str1)); printf("atoi(%s)=%d\n", str2, atoi(str2)); printf("atoi(%s)=%d\n", str3, atoi(str3)); printf("atoi(%s)=%d\n", str4, atoi(str4)); return 0; }
2、atof函数:复习以下知识点后,练习 习题14,自己实现atof()函数。
- 函数原型:double atof(const char *str);
- 操作数:①字符串的首地址str。
- 函数功能:在字符串str中逐字符读取,直到读到的字符不是①'-'(首次读取的'-'才有效,并且必须在字符串首位的才有效)和 ②'0' ~ '9' 和③ '.'(首次读取的'.'才有效),将读取到的字符转换成double型数据。读取失败返回0。
#include <stdio.h> #include <stdlib.h> int main(int argc, char const *argv[]) { char *str1 = "123abc"; char *str2 = ".5f"; char *str3 = "abc123.5"; char *str4 = "-0123.5"; printf("atof(%s)=%lf\n", str1, atof(str1)); printf("atof(%s)=%lf\n", str2, atof(str2)); printf("atof(%s)=%lf\n", str3, atof(str3)); printf("atof(%s)=%lf\n", str4, atof(str4)); return 0; }
习题
习题5:试着实现strcat的函数功能
- 思路:
f1. 封装实现strcat函数功能的API: char* my_strcat(char *dest, const char *src); f1.1 判断指针dest或者指针src的值是否是NULL f1.1.1 如果是, 那么,代表函数执行失败,提前结束函数调用,返回NULL f1.2 定义一个备份指针ptr指向dest所指内容 f1.3 while循环,控制循环的变量是*dest,当指针dest没有偏移到末尾 时,进入循环 //内在逻辑:先让dest指针自行偏移到字符数组的末尾(第一个'\0') f1.3.1 偏移字符指针dest的指向: dest++; f1.4 while循环,控制循环的变量是*src,指针src没有偏移到末尾 时,进入循环 //内在逻辑:从dest指向的字符数组的末尾(第一个'\0')开始复制src f1.4.1 通过指针间接改变dest字符数组中的字符,将src中的字符复制到dest对应位置: *dest = *src; f1.4.2 偏移字符指针str的指向: str++; f1.4.3 偏移字符指针dest的指向: dest++; f1.5 通过指针间接修改,令拷贝过来的最后一位是空字符'\0': *dest = '\0'; f1.6 返回ptr的值 1. 初始化或者输入一个字符串,只可以放在栈区、堆区,比如: char str[20] = "abcde"; 2. 调用API1. 将某个字符串拼接到str的末尾,比如: my_strcat(str, "hello"); 3. 打印拼接前后的字符串
- 代码:
#include <stdio.h> char* my_strcat(char *dest, const char *src); int main(int argc, char const *argv[]) { char str[20] = "abcde"; char *src = "hello"; printf("拼接前:%s\n", str); my_strcat(str, src); printf("拼接后:%s\n", str); return 0; } char* my_strcat(char *dest, const char *src) { if(dest==NULL || src==NULL){ return NULL; } char *ptr = dest; while(*dest != '\0'){ dest++; } while(*src != '\0'){ *dest = *src; dest++; src++; } *dest = '\0'; return ptr; }
习题6:试着实现strncat的函数功能
- 思路:
f1. 封装实现strcat函数功能的API: char* my_strncat(char *dest, const char *src, int count); f1.1 判断指针dest或者指针src的值是否是NULL f1.1.1 如果是, 那么,代表函数执行失败,提前结束函数调用,返回NULL f1.2 定义一个备份指针ptr指向dest所指内容 f1.3 while循环,控制循环的变量是*dest,指针dest没有偏移到末尾时,进入循环 //内在逻辑:先让dest指针自行偏移到字符数组的末尾(第一个'\0') f1.3.1 偏移字符指针dest的指向: dest++; f1.4 while循环,控制循环的变量是*src和count,当指针src没有偏移到末尾 并且count>0 时,进入循环 //内在逻辑:不妨先假设count<src所指字符串长度,这时条件count>0先不满足 //那么,就从dest指向的字符数组的末尾(第一个'\0')开始复制src f1.4.1 通过指针间接改变dest字符数组中的字符,将src中的字符复制到dest对应位置: *dest = *src; f1.4.2 偏移字符指针str的指向: str++; f1.4.3 偏移字符指针dest的指向: dest++; f1.4.4 修改循环变量count: count--; f1.5 判断count是否大于0 //内在逻辑:条件*src!='\0'先不满足,说明count>src所指字符串长度,那么让多拼接的部分用'\0'填充 f1.5.1 如果是,那么说明count>src所指字符串长度 f1.5.1.1 while循环,循环变量count,当count>0 时,进入循环 f1.5.1.1.1 通过指针间接修改: *dest = '\0'; f1.5.1.1.2 偏移字符指针dest的指向: dest++; f1.5.1.1.3 修改循环变量count: count--; f1.6 返回ptr的值 1. 初始化或者输入一个字符串,只可以放在栈区、堆区,比如: char str[20] = "abcde"; 2. 调用API1. 将某个字符串的前几个字节拼接到str末尾,比如: my_strncat(str, "hello", 2); 3. 打印拼接前后的字符串
- 代码:
#include <stdio.h> char* my_strncat(char *dest, const char *src, int count); int main(int argc, char const *argv[]) { char str[20] = "abcde"; char *src = "hello"; printf("拼接前:%s\n", str); my_strncat(str, src, 2); printf("拼接前2个字节后:%s\n", str); return 0; } char* my_strncat(char *dest, const char *src, int count) { if(dest==NULL || src==NULL){ return NULL; } char *ptr = dest; while(*dest != '\0'){ dest++; } while(count>0 && *src!='\0'){ *dest = *src; dest++; src++; count--; } if(count > 0){ while(count > 0){ *dest = '\0'; dest++; count--; } } return ptr; }
习题7:试着实现strcmp的函数功能
- 思路:
f1. 封装实现strcmp函数功能的API: int my_strcmp(const char *str1, const char *str2); f1.1 判断指针dest或者指针src的值是否是NULL f1.1.1 如果是, 那么,代表函数执行失败,提前结束函数调用,返回2(打个比方) f1.2 定义结果变量ret,不妨先假设两个字符串相等,即: int ret = 0; f1.3 while循环,控制循环的变量是*str1和*str2,当指针str1和str2都没有偏移到末尾 时,进入循环 //内在逻辑:不对字符串长度进行判断,直接在两个字符串的有效长度内进行逐字符的ascii码大小比较 f1.3.1 判断*str1是否大于*str2 f1.3.1.1 如果是, f1.3.1.1.1 那么结果就是str1>str2,直接返回1,提前结束函数: return 1; f1.3.1.2 否则,判断*str1是否小于*str2 f1.3.1.2.1 如果是, f1.3.1.2.1.1 那么结果就是str1<str2,直接返回-1,提前结束函数: return -1; f1.3.1.3 否则, f1.3.1.3.1 偏移字符指针str1的指向: str1++; f1.3.1.3.2 偏移字符指针str2的指向: str2++; f1.4 判断指针str1是否没有偏移到字符串末尾,即*str1 !='\0' //内在逻辑:如果上述循环一直正常结束,那么肯定有一个字符串读到了'\0',因此 //不妨假设str1的长度比str2的长,这时条件*str2!='\0'先不满足 f1.4.1 如果是, 那么不用往下比较了,str1肯定大于str2,令: ret = 1; f1.4.2 否则,判断指针str2是否没有偏移到字符串末尾,即*str2 !='\0' f1.4.2.1 如果是, 那么不用往下比较了,str1肯定小于str2,令: ret = -1; f1.6 返回ret的值 1. 初始化或者输入两个字符串,可以放在栈区、堆区、常量区,比如: char *str1 = "abcdechen"; char *str2 = "abcdechem"; 2. 调用API1. 比较str1和str2,结果保存在变量ret中: ret = my_strcmp(str1, str2); 3. 打印ret的值
- 代码:
#include <stdio.h> int my_strcmp(const char *str1, const char *str2); int main(int argc, char const *argv[]) { char *str1 = "abcd2chen3"; char *str2 = "abcdechem3"; int ret = my_strcmp(str1, str2); printf("RET=%d\n", ret); if(ret == 0){ printf("两个字符串一样\n"); }else{ printf("两个字符串不一样"); } return 0; } int my_strcmp(const char *str1, const char *str2) { if(str1==NULL || str2==NULL){ printf("无效字符串\n"); return 2; } int ret = 0; while(*str1!='\0' && *str2!='\0'){ if(*str1 > *str2){ return 1; }else if(*str1 < *str2){ return -1; }else{ str1++; str2++; } } if(*str1!='\0'){ ret = 1; }else if(*str2!='\0'){ ret = -1; } return ret; }
习题8:试着实现strncmp的函数功能。
- 思路:
f1. 封装实现strncmp函数功能的API: int my_strncmp(const char *str1, const char *str2, int count); f1.1 判断指针dest或者指针src的值是否是NULL f1.1.1 如果是, 那么,代表函数执行失败,提前结束函数调用,返回2(打个比方) f1.2 定义结果变量ret,不妨先假设两个字符串相等,即: int ret = 0; f1.3 while循环,控制循环的变量是*str1、*str2、count, 当指针str1和str2都没有偏移到末尾 并且count>0 时,进入循环 //内在逻辑:不对字符串长度进行判断,直接在两个字符串的有效长度内进行逐字符的ascii码大小比较 //同时假设count是小于这两个字符串的有效长度的 f1.3.1 判断*str1是否大于*str2 f1.3.1.1 如果是, f1.3.1.1.1 那么结果就是str1>str2,直接返回1,提前结束函数: return 1; f1.3.1.2 否则,判断*str1是否小于*str2 f1.3.1.2.1 如果是, f1.3.1.2.1.1 那么结果就是str1<str2,直接返回-1,提前结束函数: return -1; f1.3.1.3 否则, f1.3.1.3.1 偏移字符指针str1的指向: str1++; f1.3.1.3.2 偏移字符指针str2的指向: str2++; f1.3.1.3.3 修改循环变量count: count--; f1.4 判断count是否仍然大于0 //内在逻辑:如果count>0,那么str1和str2两个指针必定有一个读到结束符'\0'了 f1.4.1 判断指针str1是否没有偏移到字符串末尾,即*str1 !='\0' //内在逻辑:不妨假设str1的长度比str2的长,这时条件*str2!='\0'先不满足 f1.4.1.1 如果是, 那么不用往下比较了,str1肯定大于str2,令: ret = 1; f1.4.1.2 否则,判断指针str2是否没有偏移到字符串末尾,即*str2 !='\0' f1.4.1.2.1 如果是, 那么不用往下比较了,str1肯定小于str2,令: ret = -1; f1.6 返回ret的值 1. 初始化或者输入两个字符串,可以放在栈区、堆区、常量区,比如: char *str1 = "abcd2chen3"; char *str2 = "abcdeche13567"; 2. 调用API1. 比较str1和str2的前几个字节,结果保存在变量ret中: ret = my_strcmp(str1, str2, 10); 3. 打印ret的值
- 代码:
#include <stdio.h> int my_strncmp(const char *str1, const char *str2, int count); int main(int argc, char const *argv[]) { char *str1 = "abcd2chen3"; char *str2 = "abcdeche13567"; int ret = my_strncmp(str1, str2, 10); printf("RET=%d\n", ret); if(ret == 0){ printf("两个字符串的前10个字符一样\n"); }else{ printf("两个字符串的前10个字符不一样"); } return 0; } int my_strncmp(const char *str1, const char *str2, int count) { if(str1==NULL || str2==NULL){ printf("无效字符串\n"); return 2; } int ret = 0; while(*str1!='\0' && *str2!='\0' && count>0){ if(*str1 > *str2){ return 1; }else if(*str1 < *str2){ return -1; }else{ str1++; str2++; count--; } } if(count > 0){ if(*str1!='\0'){ ret = 1; }else if(*str2!='\0'){ ret = -1; } } return ret; }
习题9:统计字符串中某个字符的个数和具体位置
- 思路:需要对字符串有深刻认识,字符串都能看作字符数组,某个字符串中的任意字符到末尾的空字符'\0'都能形成一个字符串(一个末尾为字符'\0'的字符数组)。
1. 初始化或输入一个字符串,可以放在栈区、堆区、常量区,比如: char *str = "abcde12345 abcde12345 abcde12345"; 2. 先调用strchr函数对str做一次字符查找,并将返回地址保持在字符指针p中,比如: char *p = strchr(str, '3'); 3. 紧接着判断查找的字符是否存在,如果不存在就结束程序 4. while循环,控制循环遍的变量是代表字符地址的p,当p != NULL 时,进入循环 4.1 修改用于记录字符'3'总数的变量num: num++; //不要忘了初始化为0 4.2 打印第num个字符'3'距离字符串str的首地址多少字节数 4.3 修改代表查找字符地址的循环遍变量p,让它指向str中第num个字符'3'后一个字节: p++; 4.4 修改代表查找字符地址的循环遍变量p,让它指向下一个字符'3'的地址: p = strchr(p, '3'); 5. 打印字符'3'的总数num
- 代码:
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char *str = "abcde12345 abcde12345 abcde12345"; int num = 0; char *p = strchr(str, '3'); if(p == NULL){ printf("找不到字符'3'"); return 0; } while(p != NULL){ num++; printf("第%d个字符'3'距离字符串首地址%d个字节\n", num, p-str); p++; p = strchr(p, '3'); } printf("共有%d个字符'3'", num); return 0; }
习题10:试着实现strchr的函数功能
- 思路:
f1. 封装实现strchr函数功能的API: char* my_strchr(char *str, int c); f1.1 判断指针str的值是否是NULL f1.1.1 如果是, 那么,代表函数执行失败,提前结束函数调用,返回NULL f1.2 while循环,死循环 f1.2.1 判断当前指针str是否偏移到末尾 f1.2.1.1 如果是, 那么,断定没有找到字符c,提前结束函数调用,返回NULL f1.2.1.2 否则,判断当前指针str指向的内容是否等于字符c f1.2.1.2.1 如果是, 那么,代表找到字符c,提前结束函数调用,返回str f1.2.1.3 否则,修改循环变量str的值,让指针str指向下一个字符: str++; //内在逻辑:当str既没有偏移到末尾,有没有匹配上时,就让str自加,继续进行匹配 1. 初始化或者输入一个字符串,可以放在栈区、堆区、常量区,比如: char *str = "abcde12345 abcde12345 abcde12345"; 2. 调用API1. 在字符串str中找字符,首地址保持在字符指针p中,比如: char *p = my_strchr(str, '3'); 3. 紧接着判断查找的字符是否存在,如果不存在就结束程序 4. 打印第一个字符'3'距离字符串str首地址多少字节
- 代码:
#include <stdio.h> char* my_strchr(char *str, int c); int main(int argc, char const *argv[]) { char *str = "abcde12345 abcde12345 abcde12345"; char *p = my_strchr(str, '3'); if(p == NULL){ printf("找不到字符'3'"); return 0; } printf("第一个字符'3'距离字符串首地址%d个字节", p - str); return 0; } char* my_strchr(char *str, int c) { if(str == NULL){ printf("字符串无效\n"); return NULL; } while(1){ if(*str == '\0'){ return NULL; }else if(*str == c){ return str; } str++; } }
习题11:试着实现strstr的函数功能
- 思路:循环嵌套、goto语句
f1. 封装实现strstr函数功能的API: char* my_strstr(char *str, char *substr); f1.1 判断指针str的值是否是NULL f1.1.1 如果是, 那么,代表函数执行失败,提前结束函数调用,返回NULL f1.2 备份子字符串的首地址给字符指针ptr: char *ptr = substr; //内在逻辑:我们的第二层循环是去让substr与str做匹配,如果中途失败,需要让指针substr复位 f1.3 while循环,控制循环的变量是*str, 当指针str没有偏移到末尾 时,进入循环 f1.3.1 设定一个back标签: back: //内在逻辑:每当第二层循环中substr与str不匹配,我们需要回到此处,继续执行以下相同代码 f1.3.2 判断当前str所指字符是否与当前substr所指字符相等 f1.3.2.1 如果是, //str和substr的头对上了,开始进入二层循环进行匹配 f1.3.2.1.1 定义一个复合语句中的局部字符指针变量p来记住当前str位置: char *p=str; f1.3.2.1.2 while循环,控制循环的变量是*substr, 当指针substr没有偏移到末尾 时,进入循环 f1.3.2.1.2.1 判断当前str所指字符是否与当前substr所指字符相等 f1.3.2.1.2.1.1 如果是, f1.3.2.1.2.1.1.1 修改循环变量str的值,让指针str指向下一个字符: str++; f1.3.2.1.2.1.1.2 修改循环变量substr的值,让指针substr指向下一个字符: substr++; f1.3.2.1.2.1.2 否则,// 意味着不得不退出二层循环,终止匹配 f1.3.2.1.2.1.2.1 让指针substr复位: substr = ptr; //5行 f1.3.2.1.2.1.2.2 用goto语句回跳到一层循环的开始: goto back: //8行 f1.3.2.1.3 提前结束函数调用,返回字符指针p: return p; //12行 //内在逻辑:顺利通过二层循环,说明substr已经和str匹配上了,返回起始匹配位置 f1.3.2.2 否则, f1.3.2.2.1 修改循环变量str的值,让指针str指向下一个字符: str++; f1.4 结束函数调用,返回NULL //内在逻辑:顺利通过一层循环,说明str偏移到末尾还没有和substr匹配上,故返回NULL表示没找到 1. 初始化或者输入两个字符串,可以放在栈区、堆区、常量区,比如: char *str = "abcde1234$#$ abcde5678$#$"; char *substr = "$#$"; 2. 调用API1. 在字符串str中找子串substr,首地址保持在字符指针p中,比如: char *p = my_strstr(str, substr); 3. 紧接着判断查找的子字符串是否存在,如果不存在就结束程序 4. 打印第一个子字符串substr的首地址距离字符串str的首地址多少个字节
- 代码:
#include <stdio.h> char* my_strstr(char *str, char *substr); int main(int argc, char const *argv[]) { char *str = "abcde1234$#$ abcde5678$#$"; char *substr = "$#$"; char *p = my_strstr(str, substr); if(p == NULL){ printf("找不到子字符串\n"); return 0; } printf("第一个子字符串%s距离字符串首地址%d个字节", substr, p-str); return 0; } char* my_strstr(char *str, char *substr) { if(str==NULL || substr==NULL){ printf("无效字符串\n"); return NULL; } char *ptr = substr; while(*str != '\0'){ back: if(*str == *substr){ char *p = str; while(*substr != '\0'){ if(*str == *substr){ str++; substr++; } substr = ptr; goto back; } return p; } str++; } return NULL; }
习题12:从手机短信格式中解析信息 "+CMGR:REC UNREAD,+8613857607035,23/01/03,19:31:24+00,你好吗?",写成一个函数。
- 思路:通过二级指针引用普通指针数组
f1. 封装解析短信信息的API: int msg_deal(char *msg_src, char *delim, char **msg_done); 形参是短信来源字符串、分隔符、调用字符指针数组的二级指针msg_done f1.1 调用strtok函数,用delim分割msg_src,获取首个子字符串首地址,保存在字符指针数组第0个元素: int i = 0; //i是字符指针数组的下标 msg_done[i] = strtok(msg_src, delim); f1.2 while循环,控制循环的变量是分割到的子字符串首地址msg_done[i],地址不为NULL 时,进入循环 f1.2.1 修改代表字符指针数组下标的循环变量i的值: i++; f1.2.2 调用strtok函数,从msg_src中分割子字符串,子串的首地址保存在字符指针数组第i个元素: msg_done[i] = strtok(NULL, delim); f1.3 返回i的值,因为我们顺便用i记录子字符串的个数 1. 初始化或输入一个字符串,代表短信来源字符串,只可以放在栈区、堆区,比如: char msg_src[100] = "+CMGR:REC UNREAD,+8613857607035,23/01/03,19:31:24+00,你好吗?"; 2. 初始化一个字符串,代表分隔符,可以放在栈区、堆区、常量区,这里规定用逗号分割: char *delim = ","; 3. 定义一个字符指针数组,一会用来存放分割来的字符串的首地址: char *p[6]; 4. 调用API1. 将msg_src用delim分割,字符串首地址保存在字符指针数组中: msg_deal(msg_src, delim, p); 5. 打印字符串个数num,以及短信信息内容。
- 代码:
#include <stdio.h> #include <string.h> int msg_deal(char *msg_src, char *delim, char **msg_done); int main(int argc, char const *argv[]) { char msg_src[100] = \ "+CMGR:REC UNREAD,+8613857607035,23/01/03,19:31:24+00,你好吗?"; char *delim = ","; char* p[6]; int num; num = msg_deal(msg_src, delim, p); printf("字符串个数=%d\n", num); printf("状态 :%s\n", p[0]+10); printf("手机号:%s\n", p[1]+3); printf("日期 :%s\n", p[2]); *(p[3] + 8) = '\0'; printf("时间 :%s\n", p[3]); printf("内容 :%s\n", p[4]); return 0; } int msg_deal(char *msg_src, char *delim, char **msg_done) { int i = 0; msg_done[i] = strtok(msg_src, delim); while (msg_done[i] != NULL){ i++; msg_done[i] = strtok(NULL, delim); } return i; }
习题13:试着实现atoi的函数功能
- 思路:
f1. 封装实现atoi功能的函数: int my_atoi(const char *str); f1.1 判断str字符串的首个字符是否是'-', f1.1.1 如果是, f1.1.1.1 令记录数值正负号的变量flag为-1: flag = -1; // 一开始定义成 int flag = 1; f1.1.1.2 让指针str指向下一个字符: str++; f1.2 while循环,控制循环的变量是*str,当指针str没有偏移到末尾 时,进入循环 f1.2.1 判断指针str当前指向的字符是否是'0'或'1'或'2'或'3'或'4'或'5'或'6'或'7'或'8'或'9' f1.2.1.1 如果是, f1.2.1.1.1 修改保存整型数值的变量temp,它等于自身×10,再加上本次读取数字: temp = temp*10 + (*str-'0');//temp一开始初始化成0,万一找不到就返回0 f1.2.1.1.2 让指针str指向下一个字符: str++; f1.2.1.2 否则, 那么说明碰到别的字符,需要用break提前退出循环 f1.3 通过flag判断整型数是否是负数, f1.3.1 如果是, 那么,返回 (-1)*temp; f1.3.2 否则, 返回 temp;
- 代码:
#include <stdio.h> int my_atoi(const char *str); int main(int argc, char const *argv[]) { printf("%d\n", my_atoi("-01234.a")); return 0; } int my_atoi(const char *str) { int flag = 1; int temp = 0; if (*str == '-'){ flag = -1; str++; } while (*str != '\0'){ if(*str == '0' || *str == '1' || \ *str == '2' || *str == '3' || \ *str == '4' || *str == '5' || \ *str == '6' || *str == '7' || \ *str == '8' || *str == '9') { temp = temp*10 + (*str - '0'); str++; }else{ break; } } if (flag == -1){ return (-1)*temp; }else{ return temp; } }
习题14:试着实现atof的函数功能
- 思路:多分支选择控制语句(条件制约)
f1. 封装实现atof功能的函数: double my_atof(const char *str); f1.1 判断str字符串的首个字符是否是'-', f1.1.1 如果是, f1.1.1.1 令记录数值正负号的变量flag为-1: flag = -1; // 一开始定义成 int flag = 1; f1.1.1.2 让指针str指向下一个字符: str++; f1.2 while循环,控制循环的变量是*str,当指针str没有偏移到末尾 时,进入循环 f1.2.1 判断指针str当前指向的字符是否是'0'或'1'或'2'或'3'或'4'或'5'或'6'或'7'或'8'或'9' 并且,小数点符号'.'是否没有出现过,在此之前需要定义: 1、代表表示小数点出现次数的变量: int periodOccur=0; f1.2.1.1 如果是,我们开始处理整数部分 f1.2.1.1.1 修改保存整型部分数值的变量intPart,它等于自身×10,再加上本次读取数字: intPart = intPart*10 + (*str-'0'); //intPart一开始初始化成0 f1.2.1.1.2 让指针str指向下一个字符: str++; f1.2.1.2 否则,判断str当前指向的字符是否是小数点符合'.',并且'.'是否没有出现过 f1.2.1.2.1 如果是, //内在逻辑:我们通过变量periodOccur制约分支条件1.2.1和1.2.1.2,第一个'.'有效 f1.2.1.2.1.1 修改代表小数点'.'出现次数的变量: periodOccur = 1; f1.2.1.2.1.2 让指针str指向下一个字符: str++; f1.2.1.3 否则,判断指针str当前指向的字符是否是'0' ~ '9' f1.2.1.3.1 如果是,我们开始处理小数部分,在此之前,需要定义: 1、代表小数数值的变量: double fracPart; 2、代表当前小数点后位数的变量: int decimalPlace; 3、根据当前位数,使用循环确定读取到的小数数值的循环变量: double currentFrac; f1.2.1.3.1.1 设置循环入口: currentFrac = (*str - '0'); f1.2.1.3.1.2 for循环,代表循环次数的循环变量i从0开始, <decimalPlace 时,进入循环 f1.2.1.3.1.2.1 修改循环变量currentFrac: currentFrac = currentFrc / 10; f1.2.1.3.1.3 修改保存小数部分数值的变量fracPart: fracPart += currentFrac; f1.2.1.3.1.4 修改代表当前小数位数的变量: decimalPlace++; f1.2.1.3.1.5 让指针str指向下一个字符: str++; f1.2.1.4 否则,那么说明碰到别的字符,需要用break提前退出循环 f1.3 通过flag判断结果是否是负数, f1.3.1 如果是, 那么,返回 (-1)*(intPart+fracPart); f1.3.2 否则, 返回 (intPart+fracPart);
- 代码:
#include <stdio.h> double my_atof(const char *str); int main(int argc, char const *argv[]) { printf("%lf\n", my_atof("-01234.24a")); return 0; } double my_atof(const char *str) { int flag = 1; int intPart = 0; double fracPart = 0; int periodOccur = 0; int decimalPlace = 1; double currentFrac = 0; if (*str == '-'){ flag = -1; str++; } while (*str != '\0'){ if( (*str == '0' || *str == '1' || \ *str == '2' || *str == '3' || \ *str == '4' || *str == '5' || \ *str == '6' || *str == '7' || \ *str == '8' || *str == '9') && periodOccur==0 ) //整数部分处理 { intPart = intPart*10 + (*str - '0'); str++; }else if(*str=='.' && periodOccur==0){ //小数部分处理 periodOccur = 1; str++; }else if(*str == '0' || *str == '1' || \ *str == '2' || *str == '3' || \ *str == '4' || *str == '5' || \ *str == '6' || *str == '7' || \ *str == '8' || *str == '9') { currentFrac = (*str - '0'); for(int i=0; i<decimalPlace; i++){ currentFrac = currentFrac / 10; } fracPart = fracPart + currentFrac; decimalPlace++; str++; }else{ break; } } if (flag == -1){ return (-1)*(intPart+fracPart); }else{ return (intPart+fracPart); } }