该文章主要总结C语言的库函数对字符串的初始化,取长度,拷贝,链接,比较,搜索等基本操作。
1、初始化字符串。
#include <string.h>
void *memset(void *s, int c, size_t n);
该函数的返回值:指针s指向哪里,返回值的指针就指向哪里。
memset把s所指的内存的内存地址开始的n个字节都填充为c的值,通常c为0,表示将一段内存清空。
举个例子:char buf[10],如果他是全局变量或静态变量,则自动初始化为0,如果它是函数的局部变量,则初始化的值不确定,可以用memset(buf,0,10)来清零。由malloc分配来的内存初值也是不确定的也可以用memset来清零。
这里有个问题:说全局变量或静态变量初始化后,位于.bss段。何解?
2、取字符串的长度
#include <string.h>
size_t strlen(const char *s)
strlen函数返回s所指字符串的函数,该函数从s所指的第一个字符开始查找‘\0’字符,一旦找到就返回,返回长度不包括'\0'在内。
举例说明:char buf[] = "hello",调用strlen(buf)后返回的值是5,这里有个问题,为什么如果定义char buf[5] = "hello"时,再调用strlen(buf)后,会造成数组越界。
3、拷贝字符串
这里介绍四个函数:
#include <string.h>
char *strcpy(char *dst, const char *src);
char *strncpy(char *restrict dst, const char *restrict src, size_t n);
//从src所指的内存地址拷贝n个字节到dest所指的内存地址
void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
先看两个更基本的字符串拷贝函数,strcpy和strncpy。这两个函数也是位于string.h头文件中。都是实现了把src所指向的字符串拷贝到dst所指向的内存空间中。这里要注意src的参数类型是const的,也就是说src所指向的内存空间是可读不可写的。而dest所指向的内存空间是可读可写的。
再来说下strcpy和strncpy的区别,区别就在n上。
strcpy拷贝字符串时,会一直拷贝,直到遇到字符‘\0’,所以dest所指向的内存空间要足够大,否则可能导致写越界。另外src和dest所指向的内存空间不能有重叠。
而strncpy拷贝字符串时,则会根据n的值,拷贝n个字节到dest中,即,如果拷贝到'\0'就结束,如果拷贝到n各字节,仍然没有碰到'\0',拷贝也会结束,但是为了确保不会出现写越界的情况,需要手动指定第n个字节的值为'\0'。strncpy还有一个特性,如果src所指向的字节完全拷贝了,仍然不足n个字节,那么还差多少个字节就补多少个'\0'。
memcpy函数也是从src所指向的内存地址拷贝n个字节到dest所指向的内存空间中,但是和strncpy不同的是,memcpy并不会遇到'\0'就结束,而是一定要拷贝n个字节。
所以,会有一个很明显的区别,str开头的拷贝字符串函数处理以'\0'结尾的函数,而mem开头的函数则不关心‘\0’字符,或者说mem开头的函数,并不把参数当成字符串看待,从参数类型就可以看出,都是void *,而非str函数中的char *。
memmove也是从src所指内存地址拷贝n个字节到dest所指的内存地址,和memcpy不同的是,src和dest所指的内存地址如果重叠,则无法保证正确拷贝,而memmove却可以正常拷贝。
这里要解释一个关键字:restrict。
restrict是再C99中被引入的,用以告诉编译器可以放心对此函数进行优化,程序猿自己保证这些指针所指的内存空间互不重叠。
(上面的strcpy和strncpy中,strncpy是来自mac本上的man strncpy指令的结果,mac本上已经引入了C99标准。strcpy是拷贝于网页资料,所以参数并没有加入restrict关键字。)
来看一个例子:
void vector_add(const double *x, const double *y, double *result)
{
int i;
for (i = 0; i < 64; ++i)
result[i] = x[i] + y[i];
}
如果这个函数再多处理器的计算机上运行,编译器可以这样优化,把这一个循环拆成两个循环,一个处理器计算i值从0到31,另一个处理器计算i值从32到63。这样两个处理器同时工作,使运算时间缩短一半。但是如果result所指的内存空间和x所指的内存空间重叠的呢?比如result[0]就是x[1],result[1]就是x[2],那么这两个处理器就不能各干各的事了,第二个处理器的工作依赖于第一个处理器最终计算结果,这样处理器并行性就无法利用。为此,C99引入了restrict关键字来解决上述问题。
从连接字符串开始,所使用的函数原型都是引自mac本上man命令,也就是说C99标准,有些参数会有restrict关键字
4、连接字符串
#include <string.h>
char *strcat(char *restrict s1, const char *restrict s2);
char *strncat(char *restrict s1, const char *restrict s2, size_t n);
strcat(strncat)把s2指向的字符串连接到s1指向的字符串后面。在连接过程中,s1指向的字符串后面的'\0'会被覆盖掉。
另外,使用strcat过程中,调用者也必须要考虑s1缓冲区足够大的问题。而strncat通过参数n来指定连接长度,避免溢出问题。
这里要注意strncat和strncpy的区别。strncat是从s2中取n个字符,连接到s1后,如果前n个字符不包含'\0'便会在最后添加一个'\0',所以实际上s1应该提供strlen(s1)+n+1的长度。而strncpy并不理会拷贝完成后,最后的字符是不是'\0',这个需要程序猿自己添加。
5、比较字符串
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
memcmp比较缓冲区s1和s2的前n个字节,如果全部一样,返回0,如果遇到不一样的字节,s1比s2小返回负值,s1比s2大,返回正值
strcmp比较字符串,如果碰到'\0'则结束比较。
strncmp比较字符串,要么遇到'\0'结束,否则会比较全部n个字符。
6、搜索字符串
#include <string.h>
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
strchr在字符串s中从前到后寻找字符c,如果找到字符c第一次出现的位置,就返回,返回值指向这个位置。如果找不到字符c就返回NULL。而strrchr感觉和strchr用法类似。
#include <string.h>
char *strstr(const char *s1, const char *s2);
在长字符串s1中寻找子字符串s2,找到则返回第一次出现的位置,否则返回NULL。
7、分割字符串
#include <string.h>
char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
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;
}
看一个例子,strtok第一次调用的时候,需要把待分割的字符串首地址传给第一个参数,之后再调用只需要穿NULL,strtok会自动记住上次处理到字符串什么位置。