目录
- 前言
- 常见库函数
- 字符串函数
- strlen
- strcmp
- strcat
- strcpy
- strstr
- 内存操作函数
- memcpy函数
- memmove
前言
我们知道C语言中有很多库函数,它们在我们的编程中起到非常大的作用,下面就让我们写出常见的几个库函数,从而进一步了解它们的内部工作原理。
常见库函数
字符串函数
strlen
求字符串长度函数是我们经常使用的函数,这里不再赘述,下面提供三种方法以供参考
size_t my_strlen_1(char* s) {
size_t count = 0;
while (*s++) {
++count;
}
return count;
}
size_t my_strlen_2(char*s){
if (*s == 0)
return 0;
else
return 1 + my_strlen_2(++s);
}
size_t my_strlen_3(char* s) {
char* p = s;
while (*p++) {
}
return p - s-1;
}
- 常见的有一个非’\0’加一法,最后返回一个值
- 不用创建临时变量的函数迭代法
- 指针相减法
strcmp
strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。
基本形式为strcmp(str1,str2)
若str1=str2,则返回零;
若str1<str2,则返回负数;
若str1>str2,则返回正数。
int my_strcmp(const char* s1, const char* s2) {
assert(s1 && s2);
while (*s1 == *s2) {
if (*s1 == 0)
return 0;
++s1;
++s2;
}
return *s1 - *s2;
}
strcat
字符串追加函数
- 把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除* dest原来末尾的“\0”)。要保证* dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针。
- 需要注意的是,dest数组要有足够长来存放追加内容。
char* my_strcat(char* dest, const char* src) {
assert(dest && src);
char* p = dest;
while (*dest) {
++dest;
}
while ((*dest++ = *src++)) {
;
}
return p;
}
strcpy
strcpy把含有’\0’结束符的字符串复制到另一个地址空间,返回值的类型为char*。
char* my_strcpy(char* dest, const char* src) {
assert(dest&&src);
char* p = dest;
while (*dest++ = *src++) {
}
return p;
}
strstr
strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串;否则,返回NULL。
在我们的设定中如果不是字串就返回-1;
int strStr(char* haystack, char* needle) {
int n = strlen(haystack);
int m = strlen(needle);
for (int i = 0; i + m <= n; i++) {
int flag = 1;
for (int j = 0; j < m; j++) {
if (haystack[i + j] != needle[j]) {
flag = 0;
break;
}
}
if (flag) {
return i;
}
}
return -1;
}
力扣—模拟strStr
内存操作函数
memcpy函数
memcpy指的是C和C++使用的内存拷贝函数,函数原型为
void *memcpy(void *dest, void *src, size_t count);
函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源src中拷贝n个字节到目标dest中。
我们需要注意的是:
- src和dest所指的内存区域可能重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。也就是说需要拷贝与粘贴的内存空间不能重合
- memcpy函数会覆盖原先内存内容
- src和dest都不一定是数组,任意的可读写的空间均可。
有了上面的信息,我们可以实现memcpy函数
void* my_memcpy(void* dest, const void* src, size_t n) {
while (n--) {
*((char*)dest + n) = *((char*)src + n);
}
return dest;// (1)
}
代码解释:
(1)采用了从后向前拷贝,并没有改变dest指针,所以可以直接返回dest指针;如果从前向后拷贝,可能需要改变dest指针,这时候我们需要提前创建一个变量存储dest然后用来返回
上文介绍了字符串拷贝函数strcpy好像与memcpy很相似,下面分它们的区别
strcpy和memcpy主要有以下3方面的区别。
- 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
- 复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
- 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
memmove
上面函数我们知道了memcpy的拷贝不能用于重叠空间,原因是覆盖后内容消失,memmove就是来解决这个问题的,那要怎么解决呢?
很简单,判断重叠情况,讨论拷贝方式从前往后还是从后往前,从何规避从重叠内存造成的影响
简而言之,重叠空间先使用后覆盖
下面我们将要对情况进行讨论
情况1
我们看到两者没有任何重叠空间,所以从前拷贝还是从后拷贝都可以
情况2
我们看到src前部分被覆盖,若是从前拷贝的话即使被覆盖也不会有影响,所以它只能从前拷贝
情况3
根据情况2分析,我们只能从后拷贝
情况4
可以从前或者从后拷贝
我们可以将情况2或者情况3归为一类,其他一类
我选择将情况3归为一类,它对应的条件是
dest》=src&&(char*)dest<(char*)src+count;
函数内部操作
void* my_memmove(void* dest, const void* src, size_t count) {
assert(dest && src);
char* p = dest;
if (dest >= src && (char*)dest <= (char*)src + count) {
while (count--) {
*((char*)dest + count) = *((char*)src + count); //(1)
}
}
else {
while (count--) {
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}//(2)
return p;
}
代码解释
(1) 从后向前拷贝
(2) 从前向后拷贝