在C语言里有string.h这个头文件,但是C语言里没有string这个类型。字符串通常放在常量字符串中或者字符数组中,字符串常量适用于那些对她不做修改的字符串函数。string.h这个头文件里声明的函数原型也全是针对char数组的种种操作。直到C++中才出现了string这个类。这篇文章就简单整理一下C语言中处理字符、字符串的库函数,以及一些函数的简单实现。
1.strlen
size_t strlen ( const char * str );
功能:计算字符串长度,不包含’\0’
返回值:返回字符串的字符数
说明:
(1)字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
(2)参数指向的字符串必须要以 '\0' 结束。
(3)注意函数的返回值为size_t,是无符号的( 易错 )
注:size_t 是一些C/C++标准在stddef.h中定义的,size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数。
strlen的实现方式也不难,如下所示:
//方法一:
int my_strlen(const char * str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
//方法二:(利用递归)
int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
//方法三:
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
2.strcpy
char* strcpy(char* dest,char* src)
功 能: 将参数src字符串拷贝至参数dest所指的地址
返回值: 返回参数dest的字符串起始地址
说明:
(1)Copies the C string pointed by source into the array pointed by destination, including the
terminating null character (and stopping at that point).
(2)源字符串必须以 '\0' 结束。
(3)会将源字符串中的 '\0' 拷贝到目标空间。
(4)目标空间必须足够大,以确保能存放源字符串。
(5)目标空间必须可变。
模拟实现:
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
3.stract
char* strncat (char* dest,const char* src,size_t num)
功能: 字符串拼接
返回值:返回dest字符串起始地址
说明:
(1)strncat将会从字符串src的开头拷贝n个字符到dest字符串尾部
(2)dest要有足够的空间来容纳要拷贝的字符串
(3)如果n大于字符串src的长度,那么仅将src全部追加到dest的尾部
(4)strncat会将dest字符串最后的’\0’覆盖掉,字符追加完成后,再追加’\0’
模拟实现:
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}
4.strcmp
int strcmp (const char* str1,const char* str2)
功能:字符串比较
返回值:若参数s1和s2字符串相同则返回0,s1若大于s2则返回大于0的值,s1若小于s2则返回小于0的值
模拟实现:
int my_strcmp(const char* str1, const char* str2)//加上const是为了保证函数过程中*str的值不被其他语句修改
{
assert(str1 && str2);//此处是为了保证这两个指针均不为空指针(同为空指针时系统会崩掉)
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 -*str2;
}
5.strncpy、strncat、strncmp
1)strncpy
char* strncpy(char* dest,const char* src,size_t num)
功能:拷贝src字符串的前num个字符至dest
返回值:dest字符串起始地址
说明:
(1)如果src字符串长度小于num,则拷贝完字符串后,在目标后追加0,直到num个
(2)strncpy不会向dest追加’\0’
(3)src和dest所指的内存区域不能重叠,且dest必须有足够的空间放置n个字符
2)strncat
char* strncat (char* dest,const char* src,size_t num)
功能:将n个字符追加到字符串结尾
返回值:返回dest字符串的起始地址
说明:
(1)strncat将会从字符串src的开头拷贝n个字符到dest字符串尾部
(2)dest要有足够的空间来容纳要拷贝的字符串
(3)如果n大于字符串src的长度,那么仅将src全部追加到dest的尾部
(4)strncat会将dest字符串最后的’\0’覆盖掉,字符追加完成后,再追加’\0’
3)strncmp
int strncmp(const char* str1,const char* str2,size_t num)
功能:指定长度大小比较
返回值:同strcmp
6.strstr
前面提到的字符串函数在没学习这一块之前或多或少接触过,后面这一块可能有点陌生了,但是也很重要。
char* strstr(const char* str,const char* substr)
功能:检索子串在字符串中首次出现的位置
返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回NULL
对于这个函数可以简单实现,方法就是暴力查找,当然也有更好的方法,这里先不提及。
char *my_strstr(const char *str1,const char *str2)
{
assert(str1&&str2);
if(*str2=='\0')
return str1;
const char *s1=NULL;
const char *s2=NULL;
const char *cp=str1;
while(*cp)
{
s1=cp;
s2=str2;
while(*s1!='\0'&&*s2!='\0'&&*s1==*s2)
{
s1++;
s2++;
}
if(*s2=='\0')
return (char*)cp;
cp++;
}
return NULL;
}
7.strtok
char* strtok(char* str,const char* sep)
功能:根据分隔符将字符串分隔成一个个片段
返回值:返回下一个分割后的字符串指针,如果已无从分割则返回NULL
说明:
(1)sep参数是个字符串,定义了用作分隔符的字符集合
(2)第一个参数指定一个字符串,它包含了一个或者多个由sqp字符串中一个或者多个字符分割的标记
(3)第一次调用时将字符串首地址传进去,之后调用不用传地址,内部会有static函数保存指向地址
(4)分隔符不作为输出内容,只做分界符
(5)当strtok在参数s的字符串中发现到参数sep的分割字符时则会将该字符改为’\0’字符
(6)在第一次调用时,strtok必须赋予参数str字符串,往后的调用则将参数s设置成NULL
(7)strtok会修改原字符串,所以必须放至栈上
使用示例:
int main()
{
char s1[] ="yxlm@666.com";
char sep[]="@.";
printf("%s\n", strtok(s1,sep));
printf("%s",strtok(NULL,sep));
return 0;
}
8.strerror
char * strerror ( int errnum );
功能:返回指向错误信息字符串的指针
说明:
必须包含头文件errno.h
该函数会有不同的错误提示。
接下来再介绍一个与之相关的函数:perror
该函数集转换和打印功能于一身,它会自动读取之前产生的错误码,将其转换成错误信息并且将它打印出来。
示例如下:
#include <stdio.h>
int main ()
{
FILE *fp;
/* 首先重命名文件 */
rename("file.txt", "newfile.txt");
/* 现在让我们尝试打开相同的文件 */
fp = fopen("file.txt", "r");
if( fp == NULL )
{
perror("Error: ");
return(-1);
}
fclose(fp);
return(0);
}
9.memcpy
void * memcpy ( void * destination, const void * source, size_t num );
返回值:函数返回一个指向dest的指针。
说明:
1)source和destin所指内存区域不能重叠,函数返回指向destin的指针。
2)与strcpy相比,memcpy并不是遇到'\0'就结束,而是一定会拷贝完n个字节。
我们可以模拟实现这个函数:
通过从低地址到高地址
void my_memcpy(void *dest,void *src,size_t num)
{
void *ret=dest;
assert(dest&&src);
while(num--)
{
*(char*)dest=*(char*)src;
dest=(char*)dest+1;
src=(char*)src+1;
}
return ret;
}
10.memmove
void * memmove ( void * destination, const void * source, size_t num );
作用:
1)和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2)如果源空间和目标空间出现重叠,就得使用memmove函数处理
考虑这种情况,如果dst和src内存地址有重叠,且dst位于src之后,如果从前往后拷贝,那么src中和dst重叠的部分就会先被覆盖,造成拷贝错误,因此需要考虑拷贝顺序的问题:
1)如果dst在src之后,从后往前拷贝
2)如果dst在src之前,从前往后拷贝
这样便可以避免发生拷贝错误。
可以这样实现:
void *my_memmove(void *dest,void *src,size_t num)
{
void *ret=dest;//标记首地址
if(dest<src)
{
while(num--)
{
*(char*)dest=*(char*)src;
dest=(char*)dest+1;
src=(char*)src+1;
}
}
else
{
while(num--)
{
*((char*)dest+num)=*((char*)src+num);
}
}
}