文章目录
前言
emmmmm,写这段前言的目的是想让大家明白该如何独立的寻找各个函数的使用方法和说明,另外库函数的源文件的具体代码该如何检索。
①.函数的使用方法和说明
在前一个博客有说道一个个c/c++的官方网站,里面有各个函数的调用方法与具体介绍,甚至每个函数的example也都会列举出来,是一个在线查阅的好方法。
②.因为本文章出于要编写源代码的目的,那么写出来之后必然需要跟库函数的源代码进行比较,看自己写的代码哪些地方可以更好的优化,在这里给大家推荐一个,比较好的搜索软件:Everything,博主引用一下它的相关介绍。
Everthing 正是当之无愧的最强文件搜索神器!!它可以在闪电般的瞬间从海量的硬盘中找到你需要的文件!速度快到绝对让你难以置信!首次接触到 Everything 可真让我惊讶和兴奋了许久!!
③.Notepad++这个记事本软件非常好用,在查看源代码的时候也需要用到,这里也建议有时间安装一下
因常用字符串的使用方法较为简单,本文章重点介绍源文件的编写思路。
常见字符串函数
复制函数strcpy
char* strcpy(char * destination, const char * source );
注意事项:
· 将源指向的C字符串复制到目标指向的数组中,包括终止空字符(并在该点停止)。
· 源字符串必须以 '\0' 结束。
· 会将源字符串中的 '\0' 拷贝到目标空间。
· 目标空间必须足够大,以确保能存放源字符串。
· 目标空间必须可变。
例子如下,柒柒这里使用的编译器为VS2013。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "QQHSJ";
char arr2[] = "naive_boy";
strcpy(arr1, arr2);
return 0;
}
使用F10(逐过程)调试代码。如下:
调用函数前
调用函数后
显而易见,只要调用函数前的各个参数满足博主刚刚说过的几个条件,“QQHSJ”被“naive_boy”所完全替代,并且以’\0’结尾。
那么该如何实现呢?
既然是改变了原参数里面的内容,那么传址调用使用指针的形式是必须的,另外需要注意的一点是使用指针需要使用assert函数来防止空指针传入函数,以此提高函数的安全与可测试性。
如果source里面储存了7个字符,那么我们就需要循环调用7次指针,并将它地址的内容取出赋值给destination。
这样这个比较简单的自定义函数My_Strcmp就实现了。
代码如下:
//src 加 const 的目的是防止src地址里的内容改变,而 dest 里的值是允许改变的。
char* My_Strcpy(char* dest, const char* src)
{
//断言,增加程序的安全与可测试性,需要引用<assert.h>库函数
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
//循环体,直至 src 里的最后一个字符为'\0'即可结束
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
//之前命名指针 ret 的目的是因为 dest 指针在循环的时候是不断向前的
//那么如果直接返回 dest 指针是无法指向首元素地址的
//所以在此之前定义一个指针,将 dest 的地址传给 ret 就可以防止此情况的发生
return ret;
其实上述代码还可以进一步优化:
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
这一部分其实可以写成这样
while(*dest++ = *src++)
{
;
}
是不是更加简洁,当条件表达式'\0'(0)时为假,即会跳出循环,虽然条件表达式的后置++仍会生效,
但是我们最后返回的是 ret ,所以并不影响整个函数的使用。
追加函数strcat
char * strcat ( char * destination, const char * source );
注意事项:
将源字符串的副本追加到目标字符串。目标中的终止空字符被源的第一个字符覆盖,空字符包含在由目标中的两个字符连接而成的新字符串的末尾。
源字符串必须以 '\0' 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改
另外这个函数不能自己给自己追加字符串。但是自己编写的话是可以解决这个问题的
参考示例如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "QQHSJ";
char arr2[] = "naive_boy";
strcat(arr1, arr2);
return 0;
}
调试结果
可以看到前一个字符串的末尾’\0’开始就被另一字符串的首字符所替代了。直至遇到arr2的’\0’截止。
思路也相对比较简单,只需要在strcpy的基础上修改就ok。如下:
char* My_Strcat(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
//第一次循环的目的是为了找到 dest 的'\0'
while (*dest++ != '\0')
{
;
}
//由于后置++的原因,需要用--抵消最后一次条件表达式的执行。
//记住是条件表达式而不是循环体内部
dest--;
//与strcpy写法相同
while (*dest++ = *src++)
{
;
}
return ret;
}
比较函数strcmp
int strcmp ( const char * str1, const char * str2 );
注意事项:
该函数开始比较每个字符串的第一个字符。如果它们彼此相等,它将继续使用下列字符对,直到字符不同或到达终止的空字符。
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
另外他们的比较,主要是根据字符串所对应的ASCII码表值,即整形值来进行比较
示例如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "QQHSJ";
char arr2[] = "QQCBB";
int ret1 = strcmp(arr1, arr2);
char arr3[] = "naive_boy";
char arr4[] = "naive_girl";
int ret2 = strcmp(arr3, arr4);
char arr5[] = "naive_boy";
char arr6[] = "naive_boy";
int ret3 = strcmp(arr5, arr6);
return 0;
}
调试结果如下:
我把三种可能返回的值调试出来了,供大家参考。
想要实现的思路就是,使用循环将指针所指内容取出并一个一个进行比较直至’\0’结束,如果中途出现不相等的情况,比较两值相减后的值
如下:
int my_strcmp(const char * dest, const char * src)
{
int ret = 0;
assert(src != NULL);
assert(dest != NULL);
//当满足 ret 不为0且*src不为'\0'的条件下执行循环体
while (!(ret = (*dest++) - (*src++)) && *src)
{
;
}
if (ret < 0)
ret = -1;
else if (ret > 0)
ret = 1;
return ret;
}
源文件的查找
打开Everything软件,搜索你想要查看的源文件再使用Notepad++或者VS2013打开即可
以strcat.c为例,如下:
再参考与自己写的代码即可。
可调字符串函数
可调?之前的函数是不能改变长度的要么全部复制,要么全部追加。是不可控的,下面的函数可以选择你想要的复制、追加的字符个数,即为可调。
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
注意事项:
将源的前几个字符复制到目标。如果源C字符串的末尾(由空字符表示)是在复制数字字符之前找到的,目标将被填充零,直到总共有数字字符被写入其中。
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
这里需要考虑的一个问题是,如果source里面的字节个数小于num会怎么样呢?
我们先来看看例子:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "QQHSJ0xxxxxxxxx";
char arr2[] = "QQCBB";
strncpy(arr1, arr2, 8);
return 0;
}
调试结果如下
可得出如果source字符少于num,那么多出来的部分就会由’\0’替代
那么函数代码实现过程如下:
char* My_Strncpy(char* dest, const char* src, int num)
{
char* ret = dest;
//前半循环是普通的复制
while (num-- && (*dest++ = *src++))
{
;
}
//后半循环是补'\0'
if (num)
{
while (num--)
{
*dest++ = '\0';
}
}
return ret;
}
strncat
char * strncat ( char * destination, const char * source, size_t num );
注意事项:
将源的前几个字符追加到目标,加上一个终止的空字符。
如果源中的C字符串长度小于num,则仅复制直到终止空字符的内容。
注意事项中的第二项有说道,与strncpy不同的是它不会补’\0’,
示例如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "QQHSJ";
char arr2[] = "QQCBB";
strncat(arr1, arr2, 8);
return 0;
}
输出结果
可以得出确实在num大于source的字符是并没有补’\0’。
源代码如下:
char* My_Strncat(char* dest, const char* src, int num)
{
char* ret = dest;
while (*dest++ != '\0')
{
;
}
dest--;
while (num-- && (*dest++ = *src++))
{
;
}
*dest = '\0';
return ret;
}
另库函数的具体内容之前有介绍,不赘述了。
strncmp
strncmp与之前两个可调字符串函数相似,在这里也不做过多介绍了。
常见内存操作函数
memcpy
这个与strcpy不同的地方是,memcpy使用范围比strcpy大很多,适用于各种类型(float、int、struct)等等
void * memcpy ( void * destination, const void * source, size_t num );
注意事项:
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。//memmove可以解决重叠问题
示例如下:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[20] = { 1, 2, 3, 4, 5 };
char arr2[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
memcpy(arr1, arr2, 8);
return 0;
}
输出结果如下:
这个的思路也比较简单,唯一需要注意的是destination与source是一个字节一个字节的交换。
如下:
void* My_Memcpy(void* dest, const void* src, size_t num)
{
assert(dest);
assert(src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
return ret;
}
这里有几个需要注意的点
一、void*指针类型是不允许解引用的
二、void*指针类型的好处就是能将各种指针类型存入当中
三、使用运算的时候要注意强制类型转换,例如(char*)dest
四、为什么不使用后置++而是前置++,这是后置++优先级比较高,只是它可能起作用的时间慢,
很有可能没有进行强制类型转换之前就已经实现了后置++,所以此代码使用的是前置++。
代码虽是写好了,但是我们要注意的是,这个函数是否能自己复制自己呢?
举个简单的例子,如下:
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
My_Memcpy(arr+2, arr, 4*sizeof(int));
return 0;
}
调试结果如下:
按理来说这里红色部分应该是1234,而不是1212,原因出在哪里呢?
我们可以画个图简单的分析一下
红色为source,蓝色为destination。当前source两个数字也就是12将destination的前两个数值34替换之后,出现了一个问题,34去哪儿了?,如果按照这样的算法进行下去,那么56也会被替换成12。也就造成了调试结果那样的情况。
那么该如何解决呢?
上图有一个比较简单的办法,如果先向前换字节不行,那么我先向后呢?
如下图所示
这样就解决了刚刚上述的情况,但是我们试想一个问题,都先向后交换字符是否可行呢?
我们将destination与source的位置交换一下,如下图所示:
红色为source,蓝色为destination,虽然是先向后交换字符但是仍然出现刚刚类似的情况,但是这种情况其实可以使用先向前的办法进行交换,那么问题来了。我们需要判断什么时候需要先向前,什么时候先向后。不难发现当指针destination大于source时向后,反之向前。
那么这样代码的实现方法就搞清楚了,如下:
void* My_Memmove(void* dest, const void* src, size_t num)
{
assert(dest);
assert(src);
void* ret = dest;
if (dest < src)
{
//前-->后
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
}
else
{
//后-->前
while (num--)
{
*((char*)dest+num) = *((char*)src+num);
}
}
return ret;
}
memmove
这个函数也就是我刚刚所介绍的优化过的memcpy:
void * memmove ( void * destination, const void * source, size_t num );
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
常用字符串函数在这里也就告一段落了,最近博客写的比较多,梳理起来确实比较难,其实还有许许多多的小心得往这里写。我先歇息一段时间吧,毕竟自己的时间也有限。貌似也快到五一了,提前祝CSDN的小伙伴们节日快乐啦~~~