C语言库函数探究

1、strlen()求字符串长度

 //模拟实现strlen函数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
int my_strlen1(const char* str) //借助临时变量实现
{
int count = ;
while (*str++)
{
count++;
}
return count;
}
int my_strlen2(char* s)
{
char* p = s;
/*while (*p != '\0')
p++;*/
while (*p++);
return p - s - ;
}
int my_strlen3(char* str) //递归法
{
if (!*str)
return ;
else
return + my_strlen3(str + );
}
int main()
{
char src[] = "qwertyuiop";
int num1 = my_strlen1(src);
int num2 = my_strlen2(src);
int num3 = my_strlen3(src);
printf("%d\n", num1);
printf("%d\n", num2);
printf("%d\n", num3);
getchar();
return ;
}

2、strcpy()字符串拷贝函数

 char* my_strcopy1(char* dest, const char* str)
{
assert(dest != NULL);
assert(str != NULL);
char* ret = dest;
while (*dest++ = *str++)
{
;
}
return ret;
}
char* my_strcopy2(char* dest, const char* str)
{
assert(dest != NULL);
assert(str != NULL);
char* ret = dest;
while (*str != '\0')
{
*dest = *str;
dest++;
str++;
}
*dest = *str;
return ret;
}
int main()
{
char str[] = "abcdefg";
char dest1[];
char dest2[];
my_strcopy1(dest1, str);
my_strcopy2(dest2, str);
printf("%s\n", dest1);
printf("%s", dest2);
getchar();
return ;
}

(1)字符数组dest1和dest2必须定义得足够长,以容纳复制进去的字符串;

(2)与其相关的还有strncpy()函数定义拷贝固定长度的字符

 strncpy(str1, str2);

作用是将str2中前2个字符复制到str1中,取代str1中原有的前两个字符,N应不大于str1原字符长度(不包括‘\0’)。

3、strcmp()字符串比较函数

     int my_strcmp(const char *str1, const char *str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0')
return ;
else
{
str1++;
str2++;
}
}
return (*str1 - *str2);
}
int main()
{ printf("%d\n", my_strcmp("awc", "aaaa")); //输出大于0的数
printf("%d\n", my_strcmp("ac", "aw")); //输出小于于0的数
printf("%d\n", my_strcmp("awc", "awc")); //输出0
printf("%d\n", strcmp("awc", "aaaa")); //输出1
printf("%d\n", strcmp("ac", "aw")); //输出-1
printf("%d\n", strcmp("awc", "awc")); //输出0
getchar();
return ;
}

4、strcat()字符串连接函数

 char *my_strcat(char *dest, const char *str)
{
char *ret = dest;
assert(dest != NULL);
assert(str != NULL);
while (*dest != '\0')
{
dest++;
}
while (*dest++ = *str++)
{
;
}
return ret;
}
int main()
{
char arr[] = "abcdefg";
my_strcat(arr, "hit");
printf("%s\n", arr);
getchar();
return ;
}

5、strstr()找子串(模式匹配)

原型可以写成:char *my_strstr(const char *str, const char *substr)

(1)模式匹配是数据结构中字符串的一种基本运算,给定一个子串,要求在某个字符串中找出与该子串相同的所有子串,这就是模式匹配。
假设str是给定的子串,substr是待查找的字符串,要求从str中找出与substr相同的所有子串,这个问题成为模式匹配问题。str称为模式,substr称为目标。如果str中存在一个或多个模式为substr的子串,就给出该子串在str中的位置,称为匹配成功;否则匹配失败。
这种匹配方法可以解决问题,但是并不高效,最坏的情况下要匹配M*(N-M+1)次,时间复杂度为O(M*N)。比如下面的例子:
char str = "ababcababa";
char substr = "ababa";
C语言库函数探究

C语言库函数探究

C语言库函数探究

代码如下:

 char *my_strstr(const char *str, const char *dest)
{
const char *str1 = NULL;
const char *str2 = NULL;
const char *start = str;
assert(str);
assert(dest);
if (*dest == NULL)
{
return (char *)str;
}
while (*start)
{
str1 = start;
str2 = dest;
while ((*str1) && (*str2) && (*str1 == *str2))
{
str1++;
str2++;
}
if (*str2 == '\0')
{
return (char *)start;
}
start++;
}
return NULL;
}
int main()
{
char str1[] = "ababcababa";
char str2[] = "ababa";
char *ret = my_strstr(str1, str2);
if (ret != NULL)
printf("%s\n", ret);
else
printf("not exit!\n");
getchar();
return ;
}

(2)介绍另外一种方法,KMP算法

C语言库函数探究

这时,按第一种方法是,将目标串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"查找位置"移到已经比较过的位置,重比一遍。一个基本事实是,当 i=4,j=4 c与a不匹配时,你其实知道前面四个字符是"abab"。KMP算法的想法是,设法利用这个已知信息,不要把"查找位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

C语言库函数探究

我们可以针对搜索词,算出一张《部分匹配表》如上表。这张表是如何产生的,后面会介绍,这里先看它的用法。

C语言库函数探究

已知c与a不匹配时,前面四个字符"abab"是匹配的。查表可知,最后一个匹配字符b对应的"部分匹配值"为0,因此按照下面的公式算出向后移动的位数:

 移动位数 = 已匹配的字符数 - 对应的部分匹配值

 4 - 0 等于4,所以将搜索词向后移动4位。

C语言库函数探究

因为a与c不匹配,搜索词还要继续往后移。这时,已匹配的字符数为0,对应的"部分匹配值"为0。所以后移一位

C语言库函数探究

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果目标字符串后面还有,还要继续搜索的话(即找出全部匹配),移动位数 = 5 - 3,再将搜索词向后移动2位,这里就不再重复了。

下面介绍部分匹配值的算法:

C语言库函数探究

上图中后缀是从后往前看的,写成正序应为: a, ba, aba, baba 最后一个a不算在内。

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ababa"为例:

- "a"的前缀和后缀都为空集,共有元素的长度为0;

- "ab"的前缀为[a],后缀为[b],共有元素的长度为0;

- "aba"的前缀为[a, ab],后缀为[ba, a],共有元素的长度1;

- "abab"的前缀为[a, ab, aba],后缀为[bab, ab, b],共有元素的长度为2;

- "ababa"的前缀为[a, ab, aba, abab],后缀为[baba, aba, ba, a],共有元素为"aba",长度为3;

next数组的求解思路(即部分匹配值)

通过上文完全可以对kmp算法的原理有个清晰的了解,那么下一步就是编程实现了,其中最重要的就是如何根据待匹配的模版字符串求出对应每一位的最大相同前后缀的长度。

 void makeNext(const char* str,int next[])
{
int num,max;//num:模版字符串下标;max:最大前后缀长度
int len = strlen(str);//模版字符串长度
next[] = ;//模版字符串的第一个字符的最大前后缀长度为0
for (num = ,max = ; num < len; ++num)//for循环,从第二个字符开始,依次计算每一个字符对应的next值
{
while(max > && str[num] != str[max])//递归的求出dest[0]···dest[n]的最大的相同的前后缀长度max
max = next[max - ];
if (str[num] == str[max])//如果相等,那么最大相同前后缀长度加1
{
max++;
}
next[num] = max;
}
}
 int kmp(const char str[],const char dest[],int next[])
{
int slen, dlen;
int i,n;
slen = strlen(str); //长字符串的长度
dlen = strlen(dest); //目标字符串长度
makeNext(dest,next);
for (i = ,n = ; i < slen; ++i)
{
while(n > && dest[n] != str[i])
n = next[n-]; //目标字符串下标n回退
if (dest[n] == str[i])
{
n++;
}
if (n == dlen) //找到目标串,返回目标串起始下标
{
printf("Pattern occurs with shift:%d\n",(i - dlen + ));
}
}
} int main()
{
int i;
int next[]={};
char str[] = "ababxbababcadfdsss";
char dest[] = "abcdabd";
printf("%s\n",str);
printf("%s\n",dest);
// makeNext(dest, next);
kmp(str, dest, next);
for (i = ; i < strlen(dest); ++i)
{
printf("%d ",next[i]);
}
printf("\n"); return ;
}

KMP算法的时间复杂度为O(m+n);空间复杂度为O(n)。

6、strlwr将字符串中的字符转换为小写

原型为:char *strlwr(char *str);

参数说明:str为要转换的字符串。
返回值:返回转换后的小写字符串,其实就是将str返回。
 strlwr() 不会创建一个新字符串返回,而是改变原有字符串。所以strlwr()只能操作字符数组,而不能操作指针字符串,因为指针指向的字符串是作为常量保存在静态存储区的,常量不能被修改
注意:strlwr()和不是标准库函数,只能在windows下(VC、MinGW等)使用,Linux
GCC中需要自己定义。

  char* my_strlwr(char *str)
{
assert(str);
char* start = str;
while (*start)
{
if (*start >= 'A' && *start <= 'Z')
*start += ;
start++;
}
return str;
}
char* my_strupr(char *str)
{
assert(str);
char* start = str;
while (*start)
{
if (*start >= 'a' && *start <= 'z')
*start -= ;
start++;
}
return str;
}
int main()
{
char str1[] = { "ABCDCFbbbdgeJhssW" }; printf("%s\n", my_strlwr(str1)); //调用该函数,并且输出新的字符串
printf("%s\n", my_strupr(str1)); //调用该函数,并且输出新的字符串
getchar();
return ;
}

7、memcpy内存拷贝函数

函数原型:void *memcpy(void *dest, const void *src, size_t n);

功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。

返回值:函数返回指向dest的指针。

说明:1.source和destin所指的内存区域可能重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向destin的指针。

2.如果目标数组destin本身已有数据,执行memcpy()后,将覆盖原有数据(最多覆盖n)。如果要追加数据,则每次执行memcpy后,要将目标数组地址增加到你要追加数据的地址。
注意:source和destin都不一定是数组,任意的可读写的空间均可。
 void *my_memcpy(void *dest, const void *str, int sz)
{
int i = ;
char *pdest = (char *)dest;
char *pstr = (char *)str;
assert(dest);
assert(str);
for (i = ; i < sz; i++)
{
*pdest = *pstr;
pdest++;
pstr++;
}
return dest;
}
int main()
{
int i = ;
int arr1[] = { , , , , , , , };
int arr2[];
my_memcpy(arr2,arr1,*sizeof(int));
for (i = ; i < sizeof(arr2) / sizeof(arr2[]); i++)
{
printf("%d\n", arr2[i]);
}
getchar();
return ;
}

8、memmove内存拷贝函数

函数原型:void *memmove(void *dest, const void *src, size_t n);

描述:memmove() 函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存可以重叠。
返回值:memmove函数返回一个指向dest的指针。
它和memcpy唯一区别就是在对待重叠区域的时候,memmove可以正确的完成对应的拷贝,而memcpy不能。

内存覆盖的情形主要有以下两种:
C语言库函数探究
 #include<stdio.h>
#include<string.h>
#include<assert.h>
void *my_memmove(void *dest, const void *str, int count)
{
char *pdest = (char *)dest;
const char *pstr = (char *)str;
assert(pdest);
assert(pstr);
if (pdest > pstr)
{
while (count--)
{
*(pdest + count) = *(pstr + count);
}
}
else
{
while (count--)
{
*pdest = *pstr;
pdest++;
pstr++;
}
}
return dest;
} int main()
{
int i = ;
int arr[] = { , , , , , , , , , };
my_memmove(arr + , arr + , * sizeof(int));
for (i = ; i < sizeof(arr) / sizeof(arr[]); i++)
{
printf("%d\n", arr[i]);
}
getchar();
return ;
}

赐教!

上一篇:Spring Data JPA、MyBatis还有Hibernate有什么区别


下一篇:POJ 2752 Seek the Name, Seek the Fame(next数组的理解)