求字符串长度
- strlen
长度不受限制的字符串函数
- strcpy
- strcat
- strcmp
长度受限制的字符串函数
- strncpy
- strncat
- strncmp
字符串查找
- strstr
- strtok
错误信息报告
- strerror
字符操作
内存操作函数
- memcpy
- memmove
- memset
- memcmp
C语言字符串通常放在常量字符串和字符数组中。
strlen
strlen函数是用来求字符串长度的size_t strlen(const char *string)
返回类型是size_t就是unsigned int,即无符号整型,因为返回的长度不会是负数。根据这个特性,不能用两个strlen的返回值相减,这样得到的结果不是想要的。
1234567 | int main() { int len = strlen("abcdef"); printf("%d\n",len); return 0;} |
字符串len有6个元素,打印的结果是6。
strlen的原理就是求出一个字符串中\0元素之前的元素个数。
123456789 | int main() { //错误示范! char arr[] = {'a','b','c','d','e'}; int len = strlen(arr); printf("%d\n",len); return 0;} |
上面的打印结果是随机值,因为arr这个字符串数组中没有\0,strlen会一直增加,直到它找到了下一个\0。
模拟实现strlen:
1、计数器方法
2、递归方法
3、指针-指针的方法
123456789101112131415161718 | int my_strlen(const char* str){ int count = 0; assert(str != NULL); while (*str != '\0') { count++; str++; } return count;} int main() { int len = my_strlen("abcdef"); printf("%d\n",len); return 0;} |
strcpy
char* strcpy(char * destination,const char * source);
字符串拷贝,从源头拷贝到目的地。
注意⚠️:
原字符串必须以\0结束。
123456789 | int main() { char arr1[] = "abcdefghijkl"; char arr2[] = "hello"; strcpy(arr1,arr2); printf("%s\n%s",arr1,arr2); return 0;} |
打印的结果是hello和hello。
拷贝时把arr2中的hello和\0一并拷贝到了arr1中
模拟实现strcpy:
123456789101112131415161718192021 | char* my_strcpy(char* dest,const char* src){ assert(dest != NULL); assert(src != NULL);//验证来源和目的地的指针不是空指针 char* ret = dest;//拷贝src指向的字符串到dest指向的空间,包含\0 while (*dest++ = *src++) { ; } return ret;//返回目的空间的起始位置} int main() { char arr1[] = "abcdefghijkl";//dest char arr2[] = "hello";//src my_strcpy(arr1,arr2); printf("%s\n%s",arr1,arr2); return 0;} |
strcat
字符串追加。char * strcat (char * destination, const char * source);
注意⚠️:目的地一定足够大,能放下追加的字符串
源头一定要以\0结尾
使用strcat,不能自己给自己追加(即arr不能又追加arr)
123456789 | int main() { char arr1[30] = "hello";//目的地一定足够大,能放下追加的字符串 char arr2[] = "world";// strcat(arr1,arr2); printf("%s\n",arr1); return 0;} |
打印的结果是helloworld
模拟实现strcat:
123456789101112131415161718192021222324 | char* my_strcat(char* dest ,const char* src){ char* ret = dest; assert(dest != NULL); assert(src != NULL); //1、找到目的字符串的\0 while (*dest != '\0') { dest++; } //2、追加 while (*dest++ = *src++) { ; } return ret;}int main() { char arr1[30] = "hello"; char arr2[] = "world"; my_strcat(arr1,arr2); printf("%s\n",arr1); return 0;} |
strcmp
int strcmp (const char * str1,const char * str2);
字符串比较。对应字符比较,即先比较首字符,相等就往后比。
| 返回值 | |
|---|---|
| str1 < str2 | <0 |
| str1 = str2 | 0 |
| str1 > str2 | >0 |
| 返回值根据不同编译器可能会不同,但符合返回值规律。 |
123456789 | int main() { char *p1 = "abcdef"; char *p2 = "ghijk"; int ret = strcmp(p1,p2); printf("%d\n",ret); return 0;} |
返回的结果是-6(或任何一个负数),原因是a的ASCII值比g小,返回小于0的值。
12345678910111213141516 | int main() { char *p1 = "kbcdef"; char *p2 = "ghijk"; if (strcmp(p1,p2) > 0) { printf("p1 > p2\n"); }else if(strcmp(p1,p2) < 0) { printf("p1 = p2\n"); }else { printf("p1 < p2\n"); } return 0;} |
模拟实现strcmp:
123456789101112131415161718192021222324252627282930 | int my_strcmp(const char* str1, const char* str2){ assert(str1 != NULL); assert(str2 != NULL); while (*str1 == *str2) { if (*str1 == '\0') { return 0; } str1++; str2++; } if (*str1 > *str2) { return 1; }else { return -1; }} int main() { char *p1 = "dbcdef"; char *p2 = "abcdef"; int ret = my_strcmp(p1, p2); printf("ret = %d\n",ret); return 0;} |
strncpy
char *strncpy(char *strDest,const cahr *strSource,size_t count);size_t count的单位是字节
拷贝num个字符从原字符串到目标空间。
如果原字符串的长度小于num,则拷贝完原字符串后,在后边追加\0,直到第num个。
123456789 | int main() { char arr1[5] = "abc"; char arr2[] = "hello"; strncpy(arr1,arr2, 4); printf("%s\n",arr1); return 0;} |
打印的结果是hell
strncat
char *strncat(char *strDest,const char *strSource,size_t count);
追加num个字符从Source到Dest(拷贝到Dest后面)
123456789 | int main() { char arr1[30] = "abcdef"; char arr2[] = "hello"; strncat(arr1,arr2, 4); printf("%s\n",arr1); return 0;} |
打印的结果是abcdefhell
strncmp
int strncmp(const char *string 1,const char *string 2,size_t count);
字符串比较。size_t count是比较的字符数
| 返回值 | |
|---|---|
| string1 < string2 | <0 |
| string1 = string2 | 0 |
| string1 > string2 | >0 |
123456789 | int main() { const char *p1 = "abcdef"; const char *p2 = "abcdaaa"; int ret = strncmp(p1,p2, 5); printf("%d\n",ret); return 0;} |
打印的结果是4(不同编译器可能打印结果不同,但都大于0)
strstr
char *strstr(const char *string,const char *strCharSet)
查找子字符串。
查找子串是否存在,存在返回第一次出现的起始地址,不存在返回NULLconst char *string是要查找的字符串(在这个字符串里查找)const char *strCharSet是要找的字符串
12345678910111213141516 | int main() { const char *p1 = "abcdefghijk"; const char *p2 = "defa"; char* ret = strstr(p1, p2);//在p1里找p2存在否,存在返回p2的起始位置,不存在返回空指针(NULL) if (ret == NULL) { printf("没找到\n"); } else { printf("子串存在,是:%s\n",ret); } return 0;} |
查找子串是否存在,存在返回第一次出现的起始地址,不存在返回NULL
模拟实现strstr:
123456789101112131415161718192021222324252627282930313233343536373839404142434445 | char* my_strstr(char* p1,const char* p2){ assert(p1 != NULL); assert(p2 != NULL); char *s1 = p1; char *s2 = p2; char *current = (char*)p1; if (*p2 == '\0') //如果p2是空指针,则返回p1的地址 { return (char*)p1; } //开始查找 while (*current) { s1 = current; s2 = (char*)p2; while ((*s1 != '\0') && (*s2 != '\0') && (*s1 == *s2)) { s1++; s2++; } if (*s2 == '\0') { return current; } current++; } return NULL;//找不到了}int main() { const char *p1 = "abcdef"; const char *p2 = "def"; char* ret = my_strstr(p1, p2);//在p1里找p2存在否,存在返回p2的起始位置,不存在返回空指针(NULL) if (ret == NULL) { printf("没找到\n"); } else { printf("子串存在,是:%s\n",ret); } return 0;} |
strtok
char * strtok (char *str, const char * sep);sep参数是个字符串,定义了作为分隔的字符集合
例如一个ip地址,192.168.123.234,它就是由点.分隔的
或者一个邮箱example@eee.com,它就是由@和.分隔的str就是要被分隔的字符串,其中有或者没有 sep 中的字符都行。
strtok在str中找到了sep中规定的字符,就把这个字符改成\0,并返回指向这个标记的指针。
例如example@eee.com返回example,再次调用,在之前的前提下,寻找下个标记,找到了.点,并返回下个首字符,即eee
一般使用strtok函数,都要临时拷贝一份,切割拷贝的那份,避免原数据找不到了
strtok函数的第一个参数不是NULL,函数找到str中第一个标记,strtok函数保存它在字符串中的位置。
strtok函数的第一个参数是NULL,函数在同一个字符串中被保存的位置开始,查找下一个标记。
字符串中没有更多标记,返回NULL
1234567891011121314151617181920 | int main(){ char arr[] = "example@eee.com"; char *p = "@."; char buf[1024] = {0}; //先把arr拷贝一份,切割拷贝的字符串 strcpy(buf, arr); //切割buf中的字符串 char* ret = strtok(buf, p); printf("%s\n",ret); //再次切割,找到第二个 ret = strtok(NULL, p); printf("%s\n",ret); //第三次切割 ret = strtok(NULL, p); printf("%s\n",ret); return 0;} |
打印的结果是:
123 | exampleeeecom |
上面代码的改进:
123456789101112131415 | int main(){ char arr[] = "example@eee.com"; char *p = "@."; char buf[1024] = {0}; //先把arr拷贝一份,切割拷贝的字符串 strcpy(buf, arr); char *ret = NULL; for (ret = strtok(arr,p); ret != NULL; ret = strtok(NULL, p)) { printf("%s\n",ret); }} |
上面函数的for循环初始化部分ret = strtok(arr,p)只会运行一次,后面都会运行ret = strtok(NULL, p)。
strerror
把错误码转换成所对应的错误信息char * strerror(int errnum);
12345678 | int main(){ char* str = strerror(0); printf("%s\n",str); return 0;} |
打印的结果是:Undefined error: 0
12345678 | int main(){ char* str = strerror(1); printf("%s\n",str); return 0;} |
打印的结果是:Operation not permitted
errno:是全局错误码的变量,当C语言库函数在执行过程中出现错误,会把错误码给errno中。
1234567891011121314 | int main(){ FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { printf("%s\n",strerror(errno)); } else { printf("打开成功\n"); }} |
打印的结果是:No such file or directory
字符分类函数
使用需要引用头文件#include <ctype.h>
| 函数 | 如果它的参数符合下列条件就返回真 |
|---|---|
| iscntrl | 任何控制字符 |
| isspace | 空白字符:空格’ ‘,换页’\f’,换行’\n’,回车’\r’,制表符’\t’或垂直制表符’\v’ |
| isdigit | 十进制数字 0-9 |
| isxdigit | 十六进制数字,包括所有十进制数字,小写字母a-f,大写字母A-F |
| islower | 小写字母a-z |
| isupper | 大写字母A-Z |
| isalpha | 字母a-z或A-Z |
| isalnum | 字母或着数字a-z,A-Z,0-9 |
| ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
| isgraph | 任何图形字符 |
| isprint | 任何可打印字符,包括图形字符和空白字符 |
| 是真就返回非0,假就返回0。 |
123456789 | int main(){ char ch = 'w'; int ret = islower(ch);//判断ch是不是小写字符 printf("%d\n",ret);} |
ch是小写字符,返回1
字符转换
使用需要引用头文件#include <ctype.h>
12 | int tolower(int c);//转小写int toupper(int c);//转大写 |
12345678 | int main(){ char ch = tolower('Q'); putchar(ch);} |
Q被tolower函数转换成小写q,打印的结果是小写q
memcpy
void* mencpy (void * destination , const void * source ,size_t num);
内存拷贝,可以拷贝任意类型的变量。
C语言标准规定,memcpy只处理不重叠的拷贝,重叠的拷贝要使用memmove。(但有些编译器的memcpy也可以处理重叠拷贝)
strlen的局限性:
123456789 | int main(){ int arr1[] = {1,2,3,4,5}; int arr2[10] = {0}; //strcpy(arr2, arr1);//不能把整型地址赋给字符型指针,只能拷贝首元素 return 0;} |
使用memcpy:
123456789 | int main(){ int arr1[] = {1,2,3,4,5}; int arr2[10] = {0}; memcpy(arr2, arr1, sizeof(arr1)); return 0;} |
arr1的内容被拷贝到了arr2中去
123456789101112131415161718 | struct S{ char name[20]; int age;}; int main(){ int arr1[] = {1,2,3,4,5}; int arr2[10] = {0}; struct S arr3[] = {{"张三",20},{"李四",22}}; struct S arr4[3] = {0}; memcpy(arr2, arr1, sizeof(arr1)); memcpy(arr4, arr3, sizeof(arr3)); return 0;} |
模拟实现memcpy:
12345678910111213141516171819202122232425262728293031 | struct S{ char name[20]; int age;};void* my_memcpy(void* dest,const void* src,size_t num){ void* ret = dest; assert(dest != NULL); assert(src != NULL); while (num--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } return ret;} int main(){ int arr1[] = {1,2,3,4,5}; int arr2[10] = {0}; struct S arr3[] = {{"张三",20},{"李四",22}}; struct S arr4[3] = {0}; my_memcpy(arr2, arr1, sizeof(arr1)); my_memcpy(arr4, arr3, sizeof(arr3)); return 0;} |
memmove
void *memmove (void *dest,const void *src,size_t count);
用于处理mencpy不能处理的重叠拷贝。
123456789101112 | int main(){ int arr[] = {1,2,3,4,5,6,7,8,9,10}; int i = 0; memmove(arr+2, arr, 20); for (i=0 ;i<10; i++) { printf("%d ",arr[i]); } return 0;} |
打印的结果是:1 2 1 2 3 4 5 8 9 10
模拟实现memmove:
123456789101112131415161718192021222324252627282930 | void* my_memmove(void* dest ,const void* src,size_t count){ void* ret = dest; assert(dest != NULL); assert(src != NULL); if (dest < src) { //从前往后拷贝 while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } }else { //从后往前拷贝 while (count--) { *((char*)dest+count) = *((char*)src+count); } } return ret;}int main(){ int arr[] = {1,2,3,4,5,6,7,8,9,10}; my_memmove(arr,arr+2,20); return 0;} |
memcmp
内存比较。int memcmp (const void * ptr1 ,const void * ptr2 ,size_t num);size_t num是比较字节的个数。
| 返回值 | |
|---|---|
| stri1 < str2 | <0 |
| str1 = str2 | 0 |
| str1 > str2 | >0 |
12345678910 | int main(){ int arr1[] = {1,2,3,4,5}; int arr2[] = {1,2,5,4,3}; int ret = memcmp(arr1, arr2, 8); printf("%d\n",ret); return 0;} |
比较前8字节,即1,2这两个整型。打印的结果是0
memset
修改字节。void *memset(void *dest,int c,size_t count);
12345678 | int main(){ char arr[10] = ""; memset(arr, '#',3); return 0;} |
下面是一个错误示范:
12345678 | int main(){ int arr[20] = {0}; memset(arr, 1, 10); return 0;} |
memset(arr, 1, 10);不会让数组内的10个整型改为1,而是会让相邻的10个字节的内存改为1