1 引言
我发现在开发过程中或者面试过程中经常会遇到这些函数(strcpy,strncpy,strcat,strncat,strcmp,strncmp,strlen,strchrmemset,memcpy),从过往经验来看大部分面试都是不能调用库函数,需要实现函数原型。可见这些函数的底层原理其重要性和作用就不多说了。经过对这些函数的不断研究和验证,下面就一 一详谈这些函数的功能和特性以及函数实现。
2 函数功能及特性
1)strcpy函数
函数原型:char *strcpy(char *dest, const char *src);
函数功能:把从src地址开始且含有NULL结束符的字符串复制到dest开始的地址空间,并且返回指向dest的指针。
函数特性:
(1)、strcpy只能复制字符串,strcpy不需要指定长度,它遇到被复制字符串结束符’\0’才结束。很容易造成缓冲区溢出。
(2)、src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src所指向的字符串。
(3)、src所指向源字符串参数用const修饰,防止修改源字符串。
2)strncpy函数
函数原型:char *strncpy(char *dest, const char *src, size_t n);
函数功能:把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest。
函数特性:
(1)、strncpy只能复制字符串,strncpy需要指定长度,它遇到被复制字符串结束符’\0’才结束。
(2)、如果n<src的长度,只是将src的前n个字符复制到dest的前n个字符,不自动添加’\0’,也就是结果dest不包括’\0’,需要再手动添加一个’\0’。如果n>src的长度,则以NULL填充dest直到复制完n个字节。
(3)、src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符长度+’\0’。(4)、src所指向源字符串参数用const修饰,也是防止修改源字符串。
3)strcat函数
函数原型:char *strcat(char *dest, const char *src);
函数功能:把src所指向的字符串复制到dest所指向的字符串后面。要保证dest所指向字符串空间足够长,以容纳被复制进来的src所指向的字符串。src所指向字符串中原有的字符不变。并且返回指向dest的指针。
函数特性:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src所指向的字符串。
4)strncat函数
函数原型:char *strncat(char *dest, const char *src, size_t n);
函数功能:把src所指字符串的前n个字符添加到dest所指字符串的结尾处,并覆盖dest所指字符串结尾的’\0’。
函数特性:src和dest所指内存区域不可以重叠,并且dest必须有足够的空间来容纳src的字符串。
5)strcmp函数
函数原型:int strcmp(const char *dest, const char *src);
函数功能:比较src和dest所指向的两个字符串并根据比较结果返回整数。
函数特性:
(1)、当dest<src时,返回为负数;当dest=src时,返回值= 0;当dest>src时,返回正数。
(2)、比较规则是两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。
(3)、这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。
6)strncmp函数
函数原型:int strncmp(const char *dest, const char *src, size_t n);
函数功能:把src所指向的前n个字节的字符串与dest所指向的字符串进行比较,若dest与src的前n个字符相同,则返回0;若dest大于src,则返回大于0的值;若dest小于src,则返回小于0的值。
函数特性:
(1)、函数strncmp与函数strcmp极为类似,但功能不完全相同。
(2)、两者不同之处是,strncmp函数是指定比较n个字符,strcmp函数比较整个字符,直到出现不同的字符或遇’\0’为止。
7)strlen函数
函数原型:size_t strlen(const char *src);
函数功能:计算src所指向字符串的(unsigned int型)长度,不包括’\0’在内,并且返回src所指向字符串的长度,不包括结束符NULL。
函数特性:
(1)、size_t代表的是unsigned int型。
(2)、该函数从内存的某个位置可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域开始扫描,直到遇到第一个字符串结束符’\0’为止。
8)strchr函数
函数原型:char *strchr(const char *src, int c);
函数功能:在str所指向的字符串中搜索第一次出现字符c的位置,并且返回一个指向该字符串中第一次出现的字符的指针,如果字符串中不包含该字符则返回NULL空指针。
函数特性:注意strchr函数是区分大小写的。
9)memset函数
函数原型:void* memset(void *src, int c, size_t n);
函数功能:将src中当前位置后面的n个字节用c替换并返回src。
函数特性:
(1)、该函数是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。
(2)、memset函数参数中c实际范围应该在0~255,因为该函数只能取c的后八位赋值给你所输入的范围的每个字节。
10)memcpy函数
函数原型:void* memcpy(void *dest, const void *src, size_t n);
函数功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中,src和dest所指内存区域不能重叠,函数返回指向一个目标存储区dest的指针。
函数特性:
(1)、memcpy可以复制任意内容,例如字符数组、整型、结构体、类等而strcpy只能复制字符串。
(2)、需要复制其他类型数据时则一般用memcpy函数,而复制字符串时则一般用strcpy函数。
(3)、strcpy不需要指定长度,它遇到被复制字符的串结束符’\0’才结束,所以容易溢出。memcpy则是根据第3个参数决定复制的长度。
(4)、src和dest所指的内存区域可能重叠,如果src和dest所指的内存区域重叠,那么memcpy函数并不能够确保src所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向dest的指针。(memcpy函数在下面的功能实现中是考虑了内存覆盖情况的)
3 函数功能实现
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct _OP
{
char* (*p_strcpy)(char*, const char*);
char* (*p_strncpy)(char*, const char*, size_t);
char* (*p_strcat)(char*, const char*);
char* (*p_strncat)(char*, const char*, size_t);
int (*p_strcmp)(const char*, const char*);
int (*p_strncmp)(const char*, const char*, size_t);
size_t (*p_strlen)(const char *);
char* (*p_strchr)(const char *, int);
void* (*p_memset)(void *, int, size_t);
void* (*p_memcpy)(void *, const void *, size_t);
}OP;
char *my_strcpy(char *dest, const char *src)
{
assert((dest != NULL) && (src != NULL));
char *temp = dest;
while((*dest++ = *src++) != '\0');
return temp;
}
char *my_strncpy(char *dest, const char *src, size_t n)
{
assert((dest != NULL) && (src != NULL));
/*不考虑内存重叠情况*/
char *pdest = dest;
while(*src != '\0' && n > 0 )
{
*dest++ = *src++;
--n;
}
if (n > 0)
{
*pdest = '\0';
}
return pdest;
}
char *my_strcat(char *dest, const char *src)
{
assert((dest != NULL) && (src != NULL));
char *pdest = dest;
while(*dest != '\0')
{
++dest;
}
while(*src != '\0')
{
*dest++ = *src++;
}
return pdest;
}
char *my_strncat(char *dest, const char *src, size_t n)
{
assert((dest != NULL) && (src != NULL));
char *pdest = dest;
while(*dest != '\0')
{
++dest;
}
while(*src != '\0' && n--)
{
*dest++ = *src++;
}
*dest = '\0';
return pdest;
}
int my_strcmp(const char *dest, const char *src)
{
assert((dest != NULL) && (src != NULL));
while(*dest && *src && (*dest++ == *src++));
return (*dest - *src);
}
int my_strncmp(const char *dest, const char *src, size_t n)
{
assert((dest != NULL) && (src != NULL));
if (!n)
{
return 0;
}
while(n--)
{
if (*dest == '\0' || *dest != *src)
{
return *dest - *src;
}
dest++;
src++;
}
return 0;
}
size_t my_strlen(const char *src)
{
#if 1
/* 非递归 */
int len = 0;
assert(src != NULL);
while(*src++ != '\0')
{
++len;
}
return len;
#else
/* 递归 */
if (src == NULL || *src == '\0')
{
return 0;
}
return my_strlen(src + 1) + 1;
#endif
}
char *my_strchr(const char *src, int c)
{
assert(src != NULL);
while(*src != '\0' && *src != c)
{
++src;
}
if (*src == c)
{
return (char *)src;
}
else
{
return NULL;
}
}
void* my_memset(void *src, int c, size_t n)
{
assert(src != NULL);
char *psrc = (char *)src;
while((n--) > 0)
{
*psrc++ = n;
}
return psrc;
}
void* my_memcpy(void *dest, const void *src, size_t n)
{
assert(dest != NULL && src != NULL);
#if 0
/* 不考虑内存重叠情况 */
char *pdest = dest;
const char *psrc = src;
if (0 == n)
{
return dest;
}
while((n--) > 0)
{
*pdest++ = *psrc++;
}
*(pdest + n) = '\0';
return dest;
#else
/* 考虑内存重叠情况 */
#if 0
char *pdest;
const char *psrc;
if (0 == n)
{
return dest;
}
if (dest >= (src+n) || dest <= src)
{
pdest = dest;
psrc = src;
while(n--)
{
*pdest++ = *psrc++;
}
}
else
{
pdest = (char *)(dest + n - 1);
psrc = (char *)(src + n - 1);
while(n--)
{
*pdest-- = *psrc--;
}
}
return dest;
#else
char *pdest;
const char *psrc;
if (0 == n)
{
return dest;
}
if (dest > src)
{
pdest = (char *)(dest+n-1);
psrc = (char *)(src+n-1);
while(n >= 4)
{
*pdest-- = *psrc--;
*pdest-- = *psrc--;
*pdest-- = *psrc--;
*pdest-- = *psrc--;
n -= 4;
}
while(n--)
{
*pdest-- = *psrc--;
}
}
else if (dest <= src)
{
pdest = (char *)dest;
psrc = (char *)src;
while(n >= 4)
{
*pdest++ = *psrc++;
*pdest++ = *psrc++;
*pdest++ = *psrc++;
*pdest++ = *psrc++;
n -= 4;
}
while(n--)
{
*pdest++ = *psrc++;
}
}
return dest;
#endif
#endif
}
void init_op(OP *op)
{
op->p_strcpy = my_strcpy;
op->p_strncpy = my_strncpy;
op->p_strcat = my_strcat;
op->p_strncat = my_strncat;
op->p_strcmp = my_strcmp;
op->p_strncmp = my_strncmp;
op->p_strlen = my_strlen;
op->p_strchr = my_strchr;
op->p_memset = my_memset;
op->p_memcpy = my_memcpy;
}
void my_test(void)
{
char *src = "9876543210";
char pdest[11] = {0};
char dest[7] = {"abcdef"};
char qdest[10] = {0};
char *cat = "asdfg";
char pcat[20] = {"10010110"};
char *ncat = "00111100";
char pncat[25] = {"abcdefg"};
char *str1 = "ABCDEF G";
char *str2 = "ABCDHK L";
const char *str = "This is a sample string!";
char mset[] = {"01011100 ABCDEF"};
char mmcpy[] = {"hello,world!"};
OP *op = (OP *)malloc(sizeof(OP));
init_op(op);
(char *)op->p_memcpy(mmcpy+1, mmcpy, my_strlen(mmcpy)+1);
printf("my_strcpy:%s\t\nmy_strncpy:%s\t\n \
\rmy_strcat:%s\t\nmy_strncat:%s\t\n \
\rmy_strcmp:%d\t\nmy_strncmp:%d\t\n \
\rmy_strlen:%ld\t\nmy_strchr:%s\t\n \
\rmy_memset:%s\t\nmy_memcpy:%s\t\n",
op->p_strcpy(pdest, src),
op->p_strncpy(qdest, dest, 3),
op->p_strcat(pcat, cat),
op->p_strncat(pncat, ncat, 6),
op->p_strcmp(str1, str2),
op->p_strncmp(str1, str2, 5),
op->p_strlen(str1),
op->p_strchr(str, 'a'),
(char *)op->p_memset(mset, 0, 3),
mmcpy
);
free(op);
op = NULL;
}
int main(int argc, char const *argv[])
{
my_test();
return 0;
}
4 编译
最后在Ubuntu上进行编译,编译结果如下所示:
5 总结
其实这些比较常见常用的函数原理需要我们逐个去理解,多思考,多写程序,多实验,才能在以后的工作或者学习中运用自如,达到事倍功半的效果,如果有需要的朋友可以进行参考。有不足之处还望指出。