目录
一、前言
这篇博客是作者知识的阶段性总结,较为基础也较为全面,梳理字符函数和字符串函数。内容形式上有些重复,看起来枯燥,所以选取你需要的学习吧。如果对你起到一点点的帮助,博主的文章写的也是很值得的。
二、通性易错点
以下几个字符串函数共同的易错点,写在这里,就不在后面重复赘述。
①C语言中用字符数组和常量字符串储存字符串。区别在于,常量字符串不可以修改(所以指向常量字符串的指针最好加const修饰,限制对它修改的错误行为),而字符数组可以,所以我们应该选用字符数组储存字符串
②如果字符串函数依托\0作为函数结束标志,那么字符串中必须以\0结尾,以下形式则不可以
char str[]={'a','b','c'};
③长度不受限制的字符串函数(strcpy,strcat,strcmp),遇到\0函数才会停止,如果目标数组空间不足容纳源数组,程序会崩溃
(以上演示将str2拷贝到str1的过程,其他函数类似)
二、strlen
1.知识点
①strlen的返回值为size_t类型,size_t是 C语言定义的一种非负整数,所以判断下方代码结果
int main()
{
char str1[] = "abcd";
char str2[] = "abcde";
if ((sizeof(str1)-sizeof(str2)) > 0)
printf("hello");
else
printf("hi");
}
非负数相减结果不为负,所以为hello
②字符串以\0结束,strlen返回输入地址到\0之间的字符个数
字符串本质是一个数组,里面存入各个元素和自动加入的‘\0’。而单个字符的存入则不会自动加入'\0',如上图的情况,strlen求出的长度就是随机值。
2.自定义实现strlen
//my_strlen
//方法一:count++
int my_strlen_1(char*str)
{
int count = 0;
while (*str != '\0')
{
str++;
count++;
}
return count;
}
//
//方法二:递归
int my_strlen_2(char*str)
{
if (*str != '\0')
{
return (1 + my_strlen_2(++str));//注意这里应该是前置加加,否则会死循环。
}
else
return 0;
}
//方法三:指针相减
int my_strlen_3(char*str)
{
char*start = str;
char*end = str;
while (*end != '\0')
{
end++;
}
return end - start;
}
int main()
{
char arr[] = "abcd";
int len = my_strlen_3(arr);
printf("%d\n", len);
return 0;
}
三、strcpy
1.知识点
①功能:将src的内容拷贝到des内(会拷入src的‘\0’)
②因为会拷贝入\0,所以打印时显示出类似覆盖的效果,但实际不是覆盖
调试一下就可以知道具体情况
3.自定义实现
char* my_strcpy(char*des, const char*src)//src不需要修改,const修饰更加安全规范
{
assert(des);//断言更加规范,避免传入空指针
assert(src);
char*start = des;
while (*des++ = *src++);//巧妙的在解决赋值的问题下又能满足我们判断的要求
return start;
}
int main()
{
char str1[10] = "abcdefg";
char str2[] = "xxx";
printf("%s\n",my_strcpy(str1,str2));
}
四、strncpy
1.知识点
①功能:解决strcpy长度不受限制的情况,由count指定拷贝的元素个数
②特殊:count大于src长度时,多余的count打印\0
2.自定义函数实现
//my_strncpy
char* my_strncpy(char*str1,const char*str2, size_t count)
{
assert(str1 && str2);//防止输入空指针
char*start = str1;
while (count-- && (*str1++ = *str2++))//右边的括号不可以省,因为=的优先级低于&&
{
;
}
while (count--)//前后置还是要想清楚
{
*str1++ = '\0';
}
return start;
}
int main()
{
char str1[30] = "abcdefg66666666666";
char str2[] = "xyz666";
char*ret=my_strncpy(str1,str2,8);
printf("%s\n",ret);
return 0;
}
五、memcpy
1.知识点
①功能:strcpy和strncpy都依托于\0作为结束标志,仅限于字符类型,较为局限。而memcpy适用与任何类型的数据拷贝,他依赖于我们输入的count(拷贝的字节数,单位字节)作为限制条件
②易错:count表示的是字节数,而不是拷贝的元素数。所以如果处理int类型的数据,4个一组才能拷贝完整的一个元素(考虑数据的储存注意大小端问题,例如1储存为 0x 01 00 00 00 )。
3.自定义实现
若对void*这类写法不熟悉,可参考博主之前的文章,里面有详细描述,本文篇幅较长不再赘述。c学习笔记——自定义qsort函数_罅隙的博客-CSDN博客
若对优先级和结合性搞不清,博主之前做过简单明了的总结,可以参考一下
由于char*的步长为1Byte,满足我们一个字节一个字节拷贝的需求,所以我们选择强制类型转化为char*类型
//my_memcpy
void*my_memcpy(void*des,const void*src,size_t count)
{
assert(des && src);
char*pc = des;
while (count--)
{
*(char*)des = *(char*)src;
//注意 (char*)des++写法错误,运行后置加加时,此时des已经变回了(void*)类型
++(char*)des;
++(char*)src;//(char*)++src错误,因为单目操作符的结合性自右向左
}
return pc;
}
int main()
{
char str1[] = "abcdef";
char str2[] = "xyz";
char*ret=my_memcpy(str1,str2,2);
printf("%s\n",ret);
return 0;
}
六、memmove
1.知识点
①memmove的出现解决memcpy不能处理源字符串与目标字符串存在重叠的情况
②分析:拷贝过程出现问题的部分在于重叠部分。若从前往后拷贝,src的内容拷贝到des内时,会将位于des内的src数据覆盖造成数据缺失。所以这种情况下我们从src的后往前拷贝
总结:若 src<des,从后往前拷贝;若des<src,从前往后拷贝;若两者没有交集,随意
2、自定义实现
//my_memmove
void*my_memmove(void*des,const void*src,size_t count)
{
assert(des && src);
char*ret = des;
if (des > src)//后->前
{
while (count--)
{
*((char*)des + count) = *((char*)src + count);//不能写成+=,这里des和src的位置不能变
}
}
else//前->后
{
while (count--)
{
*((char*)des) = *((char*)src);
++(char*)des;
++(char*)src;
}
}
return ret;
}
int main()
{
char str1[] = "abcdefghi";
printf("%s\n",my_memmove(str1+2,str1,4));
return 0;
}
七、strcat
1.知识点
①功能:在des后面追加字符串src。des的第一个\0被scr的首元素覆盖,以此往后拷贝。
②特殊:不可以给自己追加
(原因分析:给自己追加时,\0首先被首元素覆盖,该字符串\0丢失,函数无法正常停止,一直往后追加,出现非法访问错误)
2.自定义实现
//my_strcat
char*my_strcat(char*des,const char*src)
{
assert(des && src);
char*start = des;
while (*++des);//这里后置加加会指向\0下一个的元素
while (*des++ = *src++);
return start;
}
int main()
{
char str1[30] = "abcde";//注意str1数组要足够大,不然strcat会强推
char str2[] = "xyz666666";
printf("%s",my_strcat(str1,str2));
return 0;
}
八、strncat
1.知识点
①功能:将src内count个字符追加到des后
②特殊:若count个数大于src,则追加到src的‘\0’就停止追加了,不同于strncpy会补\0。但不管怎么样,最后都会额外加上一个‘\0’
2.自定义实现
//my_strncat
char*my_strcat(char*des,const char*src,int n)
{
assert(des && src);
char*pc = des;
while (*++des);
while (n-- && (*des++ = *src++));
*des = '\0';
return pc;
}
int main()
{
char str1[30] = "abcde";
char str2[] = "xyz666666";
char*ret=my_strcat(str1,str2,3);
printf("%s",ret);
return 0;
}
九、strcmp
1.知识点
①功能:比较两个字符串的大小(一位一位的比较字符大小,直到比出大小。比较字符大小实际比的是ASCLL码的大小。如abcdkkk<abcedaaa,因为abc一一比较都相同,而e>d,所以后者更大,后面也不再继续比较了)
②str1 > str2 返回值大于0
str1 = str2 返回值等于0
str1 <str2 返回值小于0
(特殊:在vs编译器下,大于0用1表示,小于0用-1表示。其他编译器则不一定)
2.自定义实现
//my_strcmp
int my_strcmp(const char*str1,const char*str2)
{
assert(str1 && str2);
while (*str1 && *str1++ == *str2++);//直到找到不同的才有比较的必要并保证找到\0就退出
if (*str1 == *str2)
return 0;
else if (*str1 > *str2)
return 1;
else
return -1;
}
十、strncmp
1.知识点
①功能与strcmp相似,只不过是比较我们指定的前count个字符
2.自定义实现
//my_strncmp
int my_strncmp(const char* str1, const char*str2, size_t count)
{
assert(str1 && str2);
if (!count)//小心开始输入的n为0
{
return 0;
}
while (count-- && *str1++ == *str2++)//找到不同为止
{
;
}
return *str1 - *str2;
}
十一、strstr
1.知识点
①功能:判断一个源字符串是否是另一个目标字符串的子串。找到并返回目标字符串中子串的地址
2.自定义实现
代码的重点在于需要引入cur和str1两个指针,cur就像是一根桩,str1则是从此出发的航船与str2进行匹配。不同于str1的自由,cur每次只能移动一次,否则就会有序列被错过,
//my_strstr
char*my_strstr(const char*p1,const char*p2)
{
assert(p1 && p2);
if (!*p2)//若p2为空的特殊情况
{
return p1;
}
char*cur = (char*)p1;//强制类型转化警告
char*str1 = p1;
char*str2 = p2;
while (*cur)//确认p1已经检查完
{
str1 = cur;
str2 = p2;
while (*str1 && *str2 && !(*str1-*str2))//str1==str2的另一种写法
{
str1++;
str2++;
}
if (!*str2)
{
return cur;
}
cur++;
}
return NULL;//找不到则返回空指针
}
int main()
{
char str1[] = "abcdefgh";
char str2[20] = { 0 };
printf("请输入待查找字符串:");
scanf("%s",&str2);
if (my_strstr(str1, str2) != NULL)
printf("是子串,打印结果为:%s", my_strstr(str1, str2));
else
printf("不是子串");
return 0;
十二、字符串分类函数
一张图总结一下(网图,侵删)
注: 头文件为 <ctype.h> |
十三、字符转换函数
注:头文件为 <stdlib.h> and <ctype.h> |
①int tolower(int c)大写转小写
②int toupper(int c)小写转大写