C 字符串函数
字符串初始化函数
memset
void *memset(void *s, int c, size_t n);
将s
中前n
个字节用c
替换并返回s
, 是对较大的结构体或数组进行清零操作的最快方法
实现
void *my_memset(void *s, int c, size_t n)
{
assert(NULL != s);
void *temp = s;
while(n--)
{
*((char *)temp) = (char)c;
temp = (char *)temp + 1;
}
return s;
}
Example
int buf[6] = {0};
memset(buf, 1, sizeof(buf)); // Tip1: 参数三使用 sizeof 运算符 Right
memset(buf, 1, 6); // Tip2: buf 为 [16843009 257 0 0 0 0] Wrong
Tip1
尽量使用 sizeof
运算符,能够确保初始化的字节数目,因为不同系统中的数据类型所占字节数目不同。
Tip2
memset
函数是按照字节对初始化空间进行初始化的,即参数二是按照单字节往参数一所指区域赋值的,即对于单字节类型(char)可以初始化为任意支持的值,而多多字节类型(int、long等)只能初始化为0
,因为对多字节类型的所有字节赋值0
的结果都是0
,而如果初始化为其他值,就不一定正确,比如示例中的对int
数据类型赋值为1
,第二个参数会先转换为char
类型进行赋值,前8位的二进制为000000001
,而buf
数组元素类型为int
类型,占四个字节,故第一个元素为00000001 00000001 00000001 00000001
即16843009
,第二个元素的前两个字节为00000001 00000001
,即257
Tip3
memset
相信传入的字节数目限制,并且memset
需要工作在一块内存中,而不是一个以0
结束的字符串,所以不需要进行字符串结束的检查
字符串长度函数
strlen
size_t strlen(const char *s);
strlen
函数返回s
所指的字符串的长度。该函数从s
所指的第一个字符开始找'\0'
字符,一旦找到就返回,返回的长度不包括'\0'
字符在内。例如定义char buf[] = "hello";
,则strlen(buf)
的值是5,但要注意,如果定义char buf[5] = "hello";
,则调用strlen(buf)
是危险的,会造成数组访问越界。
实现
size_t my_strlen(const char *s)
{
assert(NULL != s);
const char *temp;
for(temp = s; *temp; ++temp);
return temp - s;
}
问题
sizeof
运算符和strlen
函数的区别?- sizeof是运算符,strlen是库函数。
- sizeof可以用类型、变量做参数,而strlen只能用 char* 变量做参数,且必须以
\0
结尾。 - sizeof是在编译的时候计算类型或变量所占内存的大小,而strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度。
- 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
参考
字符串拷贝函数
strcpy
char *strcpy(char *dest, const char *src);
把从src
地址开始且含有NULL结束符的字符串复制到以dest
开始的地址空间, src
和dest
所指内存区域不可以重叠且dest
必须有足够的空间来容纳src
的字符串。
实现
char *strcpy(char *dest, const char *src)
{
assert(NULL != dest && NULL != src);
char *s = dest;
while((*s++ = *src++) != 0)
;
return (dest);
}
strncpy
char *strncpy(char *dest, const char *src, size_t n);
strncpy
函数与strcpy
函数类似,除了指定最多拷贝src
中的n
个字节,如果前n
个字节中没有结束字符\0
, 则拷贝后的dest
字符串没有结束字符。如果src
字符串长度小于n
,则strncpy
会添加结束符\0
到dest
剩余位置。
实现
char *my_strncpy(char *dest, const char *src, size_t n)
{
assert(NULL != dest && NULL != src);
char *s = dest;
while(n > 0 && (*s++ = *src++) != '\0')
n--;
while(n-- > 0)
*s++ = '\0';
return dest;
}
memcpy
void *memcpy(void *dest, const void *src, size_t n);
memcpy
函数从src
所指的内存地址拷贝n
个字节到dest
所指的内存地址,和strncpy
不同,memcpy
并不是遇到'\0'
就结束,而是一定会拷贝完n
个字节。这里的命名规律是,以str
开头的函数处理以'\0'
结尾的字符串,而以mem
开头的函数则不关心'\0'
字符,或者说这些函数并不把参数当字符串看待,因此参数的指针类型是void *
而非char *
实现
void *memcpy(void *dest, const void *src, size_t n)
{
assert(NULL != dest && NULL != src);
char *d = dest;
const char *s = src;
while(n--)
*d++ = *s++;
return dest;
}
memmove
void *memmove(void *dest, const void *src, size_t n);
memmove
也是从src
所指的内存地址拷贝n
个字节到dest
所指的内存地址,虽然叫move但其实也是拷贝而非移动。但是和memcpy
有一点不同,memcpy
的两个参数src
和dest
所指的内存区间如果重叠则无法保证正确拷贝,而memmove
却可以正确拷贝, 是因为首先将src
拷贝到一个临时数组中,然后再从临时数组中拷贝到dest
。
实现
void *memmove(void *dest, const char *src, size_t n)
{
char *d = dest;
const char *s = src;
if(d < s)
{
while(n--)
*d++ = *s++;
}
else
{
d = d + n -1;
s = s + n -1;
while(n--)
*d-- = *s--;
}
return dest;
}
strcpy 与 memcpy区别
参考
字符串连接函数
strcat
char *strcat(char *dest, const char *src);
把src所指字符串添加到dest结尾处(覆盖dest结尾处的’\0’), desc
和src
所指内存不可以重叠切dest
必须有足够的空间容纳src
字符串
实现
char *strcat(char *dest, const char *src)
{
assert(NULL != dest && NULL != src);
char *d = dest;
while(*d != '\0')
++d;
while((*d++ = *src++) != '\0');
return dest;
}
strncat
char *strncat(char *dest, const char *src, size_t n);
把src
所指字符串的前n
个字符添加到dest
结尾处,覆盖dest
结尾处的'/0'
,实现字符串连接。
实现
char *strncat(char *dest, const char *src, size_t n)
{
if(n != 0)
{
char *d = dest;
const char *s = src;
while(*d != 0)
d++;
do{
if((*d = *s++) == 0)
break;
d++;
}while(--n != 0);
*d = 0;
}
return dest;
}
参考
字符串比较函数
memcmp
int memcmp(const void *s1, const void *s2, size_t n);
memcmp
从前到后逐个比较缓冲区s1
和s2
的前n
个字节(不管里面有没有'\0'
),如果s1
和s2
的前n
个字节全都一样就返回0,如果遇到不一样的字节,s1
的字节比s2
小就返回负值,s1
的字节比s2
大就返回正值。
实现
int memcmp(const void *s1, const void *s2, size_t n)
{
assert(NULL != s1 && NULL != s2);
unsigned char u1, u2;
for(; n--; s1++, s2++)
{
u1 = *(unsigned char *)s1;
u2 = *(unsigned char *)s2;
if(u1 != u2)
return u1 - u2;
}
return 0;
}
strcmp
int strcmp(const char *s1, const char *s2);
strcmp
把s1
和s2
当字符串比较,在其中一个字符串中遇到'\0'
时结束,按照上面的比较准则,"ABC"
比"abc"
小,"ABCD"
比"ABC"
大,"123A9"
比"123B2"
小。
实现
int strcmp(const char *s1, const char *s2)
{
assert(NULL != s1 && NULL != s2);
for(; *s1 == *s2; s1++, s2++)
if(*s1 == '\0')
return 0;
return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1: 1);
}
strncmp
int strncmp(const char *s1, const char *s2, size_t n);
strncmp
的比较结束条件是:要么在其中一个字符串中遇到'\0'
结束(类似于strcmp
),要么比较完n
个字符结束(类似于memcmp
)。例如,strncmp("ABCD", "ABC", 3)
的返回值是0,strncmp("ABCD", "ABC", 4)
的返回值是正值。
实现
int strncmp(const char *s1, const char *s2, size_t n)
{
assert(NULL != s1 && NULL != s2);
for(; n > 0; s1++, s2++, --n)
{
if(*s1 != *s2)
return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1: 1);
else if(*s1 == '\0')
return 0;
}
return 0;
}
memcmp 与 strcmp 区别
参考
字符串搜索函数
strchr
char *strchr(const char *s, int c);
strchr
在字符串s
中从前到后查找字符c
,找到字符c
第一次出现的位置时就返回,返回值指向这个位置,如果找不到字符c
就返回NULL
实现
char *strchr(const char *s, int c)
{
assert(NULL != s);
const char ch = c;
for(; *s != ch; s++)
if(*s == '\0')
return 0;
return (char *)s;
}
strrchr
char *strrchr(const char *s, int c);
strrchr
和strchr
类似,从前到后查找字符c
,找到字符c
最后一次出现的位置时返回,返回值指向这个位置,如果找不到字符c
就返回NULL
也可以理解为从右向左找字符c
,找到字符c
第一次出现的位置就返回,函数名中间多了一个字母r可以理解为Right-to-left
实现
char *strrchr(const char *s, int c)
{
assert(NULL != s);
char *ret = 0;
do{
if(*s == (char)c)
ret = s;
}while(*s++);
return ret;
}
strstr
char *strstr(const char *haystack, const char *needle);
strstr
在一个长字符串中从前到后找一个子串(Substring),找到子串第一次出现的位置就返回,返回值指向子串的开头,如果找不到就返回NULL。这两个参数名很形象,在干草堆haystack
中找一根针needle
,按中文的说法叫大海捞针,显然haystack
是长字符串,needle
是要找的子串。
实现
char *strstr(const char *haystack, const char *needle)
{
assert(NULL != haystack && NULL != needle);
const char *p = haystack;
const size_t len = strlen(needle);
for(; (p = strchr(p, *needle)) != 0; p++)
{
if(strncmp(p, s2, len) == 0)
return (char *)p;
}
return 0;
}
参考
- strchr Apple Open Source
- [strrchr clc-wiki](http://clc-i source codewiki.net/wiki/C_standard_library:string.h:strrchr)
- strstr gcc
字符串分割函数
strtok
char *strtok(char *str, const char *delim);
strtok
通过分隔符delim
将字符串str
分割成多个tokens
, 其中delim
可以为多个分隔符,strtok
遇到其中任何一个分隔符就会分割字符串。
示例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "root:x::0:root:/root:/bin/bash:";
char *token;
token = strtok(str, ":");
printf("%s\n", token);
while ( (token = strtok(NULL, ":")) != NULL)
printf("%s\n", token);
return 0;
}
// Output
/*
* $ ./a.out
* root
* x
* 0
* root
* /root
* /bin/bash
*/
实现
char *strtok(char *str, const char *delim)
{
static char *p = 0;
if(str)
p = str;
else if (!p)
return 0;
str = p + strspn(p, delim);
p = str + strcspn(str, delim);
if(p == str)
return p = 0;
p = *p ? *p = 0, p + 1 : 0;
return str;
}
参考
字符串数值转换函数
atoi
int atoi(const char *nptr);
atoi
把一个字符串开头可以识别成十进制整数的部分转换成int
型,相当于下面要讲的strtol(nptr, (char **) NULL, 10);
,除了 atoi
不能检查出错。
实现
int atoi2(char *str)
{
// Handle error if str is NULL
if (*str == NULL)
return 0;
// Handle error if spaces in str head
while(isspace(*str))
str++;
int res = 0; // Initialize result
int sign = 1; // Initialize sign as positive
int i = 0;
// If number is negative , then update sign
if(str[0] == '-')
{
sign = -1;
i++;
}
// Interate through all digits and updates the result
for(; str[i] != '\0'; ++i)
{
// Handle error if str contains non-numeric characters
if (isNumberorChar(str[i]) == false)
break;
res = res * 10 + str[i] - '0';
}
// Return result with sign
return sign * res;
}