前言
在C语言中,我们经常会遇到字符串的处理,我们要是每当用到字符串总要自己写函数去解决问题的时候,未免有些麻烦,所以在这里我们可以调用库中的 “string.h" 的头函数来解决问题;下面就对一些经常使用的字符函数和字符串函数进行使用讲解和函数解析;
strlen()
size_t strlen ( const char * str );
获取字符串的长度
对于一个字符串,如”abc“,会通过字符串的首个字符开始计算,直至遇到终止字符‘\0’开始结束计算,终止字符’\0’ 不会算进去,所以”abc"长度为3;
当我们要使用的时候,输入一个字符指针或者字符数组即可;
char arr1[]=“abc”;
char* arr2=“bcd”
strlen(arr1);
strlen(arr2);
先看下面例子:
#include <string.h> #include<stdio.h> int main() { if (strlen("abc") - strlen("abcdef") > 0) { printf("大于\n"); } else { printf("小于等于\n"); } return 0; }
大于
我们可以很明显看出,"abc"是比“abcdef”字符串长度短的,但程序的结果却是大于,这是因为对于strlen()函数来说,它的返回类型是size_t,这个类型相当于unsigned char,是一个无符号的数据类型;所以对于使用strlen()来比较字符串长短,要用大于小于,像程序中的操作,只会显示错误;
正确方式:
#include <string.h> #include<stdio.h> int main() { if (strlen("abc") >strlen("abcdef") ) { printf("大于\n"); } else { printf("小于等于\n"); } return 0; }
实现strlen()
代码:
size_t my_strlen(const char* str) { int count = 0; while (*str != '\0') { count++; str++; } return count; }
对于一个字符串来说,我们用指针传参,那么传过去的是字符串的首个字符的地址,那么就可以通过指针的移动来统计字符串的长短;这里要注意:由于count类型和函数的类型不统一,一般以函数类型为主。
strcpy()
char * strcpy ( char * destination, const char * source );
复制字符串
将源指向的 C 字符串复制到目标指向的数组中,包括终止的 null 字符(并在该点停止)。
为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的 C 字符串(包括终止空字符),并且不应在内存中与源重叠。
所以strcpy函数一般用于字符数组;
使用例子:
int main() { char arr1[11] = ""; char arr2[6] = "abcdef"; strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }
abcdef
模拟实现
代码:
#include<assert.h> char* my_strcpy(char* dest, const char* src) { char* ret = dest; assert(dest != NULL); assert(src != NULL); while (*dest++ = *src++) { ; } return ret; }
对于源字符串src是不可变的,所以用const来修饰;首先先用ret指针来记住arr1的首元素位置,然后对传参进行断言判断,如果为空那么将会在报出在哪行代码发生错误,可以快速的找到错误源头;这里的循环用到星号和++的优先级关系;这里是先解引用dest和src,然后将src赋值给dest,执行完之后dest和src才++,当src走到终止字符时,就会将终止字符赋给*dest当while循环条件判断时,将会终止该循环,最后返回ret指针。
strcat()
char * strcat ( char * destination, const char * source );
连接字符串
将源字符串的副本追加到目标字符串。目标中的终止空字符被源的第一个字符覆盖,并且在目标中由两者串联形成的新字符串的末尾包含一个空字符。这里不用在意源字符串的大小
目的地和来源不得重叠。
对于字符串来说都有终止字符,那么说明可以在目标字符串终止字符位置上开始追加源字符串;
使用例子:
int main() { char arr1[20] = "hello "; char arr2[] = "world"; strcat(arr1, arr2); printf("%s\n", arr1); return 0; }
hello world
strcat()模拟实现
代码:
char* my_strcat(char*dest, const char *src) { assert(dest && src); char* ret = dest; //1. 找目标空间中的\0 while (*dest) { dest++; } while (*dest++ = *src++) { ; } return ret; }
首先先对dest和src进行断言;然后用ret记住dest的地址,然后对dest移动,当移动到终止符号时停下,然后开始src对dest赋值;最后返回ret。
这里记住不能这样写:
这是因为当我们指针指向终止字符’\0’时,本来是判断结束了,但由于后置加加,所以加速之后指针还会向后走一步;
strcmp()
int strcmp ( const char * str1, const char * str2 );
比较两个字符串
将 C 字符串 str1 与 C 字符串 str2 进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续以下对,直到字符不同或达到终止空字符.
这里的字符比较,将会根据字符对应的ACLII码字表对应的十进制数字进行比较大小,在VScode的gcc编译环境下,大于则返回1,小于则返回-1,等于则返回0;
int main() { char* arr1="abc"; char* arr2="ABC"; printf("%d\n",strcmp(arr1,arr2)); printf("%d\n",strcmp(arr2,arr1)); }
1
-1
模拟实现strcmp()
代码:
int my_strcmp(const char* str1, const char* str2) { assert(str1 && str2); while (*str1 == *str2) { if (*str1 == '\0') return 0; str1++; str2++; } return (*str1 - *str2); }
先对str1,str2进行断言判断;当两个字符串的字符都相等时,则指针先后移动,移动到终止字符时就返回0,当两个字符串的字符不一样时,就返回它们的差值。
strstr()
const char * strstr ( const char * str1, const char * str2 );
char * strstr ( char * str1, const char * str2 );
查找子字符串
返回指向 str2 中第一次出现的 str1 的指针,如果 str2 不是 str1 的一部分,则返回一个空指针。
匹配过程不包括终止空字符,但它到此为止。
例子:
#include <stdio.h> #include <string.h> int main () { char str[] ="This is a simple string"; char * pch; pch = strstr (str,"simple"); printf("%s\n",pch); return 0; }
simple string
模拟实现strstr()
代码:
char* my_strstr(char *str1, char* str2) { char* cp = str1; char* s1 = cp; char* s2 = str2; //str2为空直接返回str1首元素 if (*str2 == '\0') return str1; while (*cp) { //开始匹配 s1 = cp; s2 = str2; while (*s1 && *s2 && *s1 == *s2) { s1++; s2++; } if (*s2 == '\0') return cp; cp++; } return NULL; }
由于我们要多次循环匹配str1是否有str2字符串,所以利用循环嵌套的方法,外面的循环的对str1中每条子字符串进行匹配判断,指针逐渐先后移动;内层循环是判断str1中的子字符串中的字符是否与str2中的字符一致;
这里用cp记住str1的每个子字符串的首元素,s1是用来遍历str1每个子字符串中的字符是否与str2匹配;倘若内层遍历完到s2走到str2的终止字符,那么直接返回cp。外层循环一直没有结果就返回空指针;
strncpy(),strncmp(),strncat()
这类函数可以说是 strcpy(),strcmp(),strcat()函数的进阶版;它们的函数意义都没有改变,只是可以具体到了某个字节位置,不再是只到字符串的终止字符才停止;
char * strncpy ( char * destination, const char * source, size_t num );
从字符串中复制字符
将源的第一个字符数复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到总共写入 num 个字符为止。
如果源长度超过 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,不应将目标视为以空结尾的 C 字符串(这样读取它会溢出)。
目的地和来源不得重叠
int strncmp ( const char * str1, const char * str2, size_t num );
比较两个字符串的字符
将 C 字符串 str1 的字符数与 C 字符串 str2 的字符数进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用以下对,直到字符不同,直到达到终止的空字符,或者直到两个字符串中的 num 字符匹配,以先发生者为准。
char * strncat ( char * destination, const char * source, size_t num );
从字符串追加字符
将源的第一个数字字符追加到目标,外加一个终止空字符。
如果源中 C 字符串的长度小于 num,则仅复制终止空字符之前的内容。
strtok()
char * strtok ( char * str, const char * delimiters );
将字符串拆分为标记
对此函数的一系列调用将 str 拆分为标记,这些标记是由分隔符中的任何字符分隔的连续字符序列。
在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描令牌的起始位置。在后续调用中,该函数需要一个空指针,并使用最后一个令牌末尾之后的位置作为扫描的新起始位置。
为了确定标记的开头和结尾,该函数首先从起始位置扫描分隔符中未包含的第一个字符(该字符将成为标记的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,该字符将成为令牌的末尾。如果找到终止空字符,扫描也会停止。
令牌的此结尾将自动替换为空字符,并且令牌的开头由函数返回。
一旦在对 strtok 的调用中找到 str 的终止空字符,则对此函数的所有后续调用(以空指针作为第一个参数)都将返回空指针。
找到最后一个令牌的点由要在下一次调用中使用的函数在内部保留(不需要特定的库实现来避免数据争用)。
具体使用请看下面代码:
int main() { char arr[] = "zpengwei@yeah.net@666#777"; char copy[30]; strcpy(copy, arr); char sep[] = ".@#"; char* ret = NULL; for (ret = strtok(copy, sep); ret != NULL; ret=strtok(NULL, sep)) { printf("%s\n", ret); } return 0; }
在第一次调用的时候,需要对需要拆分的字符串写进去(程序中的copy),当使用strtok函数对copy拆分之后,该函数可以想象为有记忆功能,相当于该函数有某个指针,在第一次调用后,指针会指向”拆分的字符串“拆分标志字符之后的一个位置(@后的y),对该字符串拆分第一个参数用空指针即可,函数会根据你给的字符串(sep)中的字符进行匹配对比,只要”拆分的字符串“中有sep中的字符,那么将会对字符串进行拆分。直至走到”拆分的字符串“的终止字符就停下来。
memcpy()
void * memcpy ( void * destination, const void * source, size_t num );
复制内存块
将字节数的值从源指向的位置直接复制到目标指向的内存块。
源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。
该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。
为避免溢出,目标和源参数指向的数组大小应至少为字节数,并且不应重叠
相比于strcpy()不再只是局限于字符串,它是可以任意类型的。
如,可以将整型数组中的元素进行复制:
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[20] = { 0 }; //将arr1中的内容,拷贝到arr2中 memcpy(arr2, arr1, 40); int i = 0; for (i = 0; i < 20; i++) { printf("%d ", arr2[i]); } return 0; }
1 2 3 4 5 6 7 8 9 10 0 0 0 0 0 0 0 0 0 0
要记住memcpy是根据字节进行复制的,如将上面的memcpy改为:
memcpy(arr2, arr1, 37);
由于小端的存储方式所以打印出来和上面结果一致;
memcpy()的模拟实现
代码:
void* my_strcpy(void* dest,const void* src,size_t num) { void* ret=dest; assert(src&& dest); while(num--) { *(char*)dest=*(char*)src; dest=(char*)dest+1; src=(char*)src+1; } return ret; }
思路大体和strcpy()一致,主要当指针在移动的时候,由于参数类型是void*,且 num是size_t类型的,所以在移动指针的时候需要对指针先强制转换,赋值也一样,这样就可以在num的限制下完成操作;
在上面的介绍函数中说过,memcpy()是不可重叠的,具体如下:
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memcpy(arr1+2, arr1, 20); int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
当我们想从数组中复制该数组成 1 2 1 2 3 4 5 8 9 10的时候,我们试着运行代码,结果是:1 2 1 2 1 2 1 8 9 10,发现答案与预想情况不一致,这是因为当要将3复制过去给5之前,3由于1的复制已经变为1了,所以无法达到效果;
在这里,如果我们要实现这种可重叠的效果,就可用到memmove()函数来解决。
memmove()
void * memmove ( void * destination, const void * source, size_t num );
移动内存块
将字节数的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样,允许目标和源重叠。
源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。
该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。
为避免溢出,目标参数和源参数指向的数组的大小应至少为字节数。
从描述介绍中知道,该函数功能与memcpy函数几乎一致,唯一不同的就是可重叠;
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; memmove(arr1+2, arr1, 20); int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
1 2 1 2 3 4 5 8 9 10
memmove()的模拟实现
代码:
void* my_memmove(void* dest, const void* src, size_t num) { void* ret = dest; assert(dest && src); if (dest < src) { //前->后 while (num--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else { //后->前 while (num--)//20 { *((char*)dest + num) = *((char*)src + num); } } return ret; }
在这里,需要判断dest与src的位置,
主要看重叠的位置,清楚谁给谁赋值,赋值不能改变源数组中的内容即可;
memset()
void * memset ( void * ptr, int value, size_t num );
填充内存块
将 ptr 指向的内存块的第一个字节数设置为指定值(解释为无符号字符)。
该函数相当于对数组进行初始化,可以设置指定值,当然也是以字节为准;
所以不要出现以下这种错误:
例如说想要给整型数组初始化为1,那么这样结果将会不符合预期。
int main() { int arr1[]={1,2,3}; memset(arr1,1,12); printf("%d",arr1[0]); return 0; }
答案:16843009 相当于0x01010101;
所以该函数经常使用在对字符数组的初始化;