本篇重点介绍处理字符和字符串的库函数的使用和注意事项
本篇重点
前言:
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常要放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数
求字符串长度
strlen
注意点:
1. 字符串将’\0’作为结束标志,strlen函数返回的是在’\0’之前出现的字符个数但不包含‘\0’。
2. 参数指向的字符串必须要有’\0’,以’\0’为结束标志
3. 注意函数的返回值是size_t ,是无符号整数
4. 最好理解strlen函数的模拟实现
在这里我会展示3种方法来模拟实现strlen函数。
strlen函数模拟实现:
第一种:暴力法死算
#include <stdio.h> #include <assert.h> size_t my_strlen(const char* str) { assert(str); int count = 0; while (*str!='\0') { count++; str++; } return count; } int main() { char arr[] = "abcdef"; int ret = my_strlen(arr); printf("%d", ret); return 0; }
第二种:指针-指针=指针之间元素的个数
size_t my_strlen(const char* str) { assert(str); char* start = str; while (*str != 0) { str++; } return str - start; } int main() { char arr[] = "abcdef"; int ret = my_strlen(arr); printf("%d", ret); return 0; }
第三种:递归法
size_t my_strlen(const char* str) { assert(str); if (*str != '\0') { return 1 + my_strlen(str + 1); } else return 0; } int main() { char arr[] = "abcdef"; int ret = my_strlen(arr); printf("%d", ret); return 0; }
还有一个题目来考考你:涉及上面的注意点喔
#include <string.h> int main() { const char* str1 = "abcdef"; const char* str2 = "bbb"; if (strlen(str2) - strlen(str1) > 0) { printf("str2>str1\n"); } else { printf("srt1>str2\n"); } return 0; }
结果:
为什么呢?
计算str1的字符串大小应该为6,计算str2的字符串大小应该是3,3-6应该小于0,应该打印str1>str2才对呢。
是不是忘记了什么?strlen函数的返回值是什么呢?是size_t类型的,两个无符号数相减,得到的也是无符号数,所以-3就被当作无符号数来看啦,这将是一个非常大的数。所以肯定大于0。
拷贝字符串函数
strcpy
注意点:
1. 将源指向的C字符串复制到目标指向的数组中,包括
终止空字符(并在该点停止)
2. 源字符串必须以’\0’结束
3. 会将源字符串的’\0’拷贝到目的空间
4. 目标空间要足够大,以确保源字符串能存放进去
5. 目标空间必须可变,不能是常量
6. 要理解strcpy模拟实现
strcpy函数模拟实现
#include <stdio.h> #include <assert.h> char* my_strcpy(char* dest, const char* src) { assert(dest && src);//断言判断dest和src指针不为空指针 char* start = dest;//记录dest的起始位置,因为最后还要将目标空间的起始地址返回 while (*dest++ = *src++) { ;//将源字符串全部拷贝到目的空间里,当*src为'\0'时,赋值给dest后该表达式为假,跳出循环,全部拷贝成功 } return start;//将起始地址传回 } int main() { char arr1[20] = { 0 };//目标空间要足够大 char arr2[] = "abcdef"; char *ret=my_strcpy(arr1, arr2); printf("%s", ret); return 0; }
结果:
(追加)连接字符串函数
strcat
注意点:
1. 将源字符串的副本追加到目标字符串。终止的空字符
in destination被源的第一个字符覆盖,并且包含一个空字符
在由目的地中的二者串联形成的新字符串的末尾。
2. 源字符串必须以’\0’结束
3. 目标空间必须要足够大,能容纳源字符串
4. 目的空间要能修改
5. 该函数不能用于字符串给自己追加
strcat函数模拟实现:
#include <stdio.h> #include <assert.h> char* my_strcat(char* dest, const char* src) { assert(dest && src);//断言判断 char* start = dest; //首先要找到目的空间的'\0' while (*dest!='\0') { dest++; } //再将字符串追加到'\0'的后面,包括'\0' while (*dest++ == *src++) { ; } return start;//最后返回目标空间的起始地址 } int main() { char arr1[20] = "hello "; char arr2[] = "world"; char* ret =my_strcat(arr1, arr2); printf("%s",ret ); return 0; }
结果:
比较两个字符串函数
strcmp
注意点:
1. 此函数开始比较每个字符串的第一个字符。如果它们相等,它继续使用,直到字符不同或终止达到空字符。
2. 标志规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数
3. 比较的是相同位上字符的ASCII 码值
模拟实现strcmp函数
int my_strcmp(const char* str1, const char* str2) { assert(str1 && str2); //先比较第一个字符,如果相同则比下一个,后面的类似。 //直到相同位的字符不一样比较字符ASCII码值 while (*str1 == *str2) { if (*str1 == '\0')//在这个循环里,如果str1和str2中有一个等于0则表示两个字符串是相等的,因为在str1==str2的条件下,又等于0 {//肯定是相等的 return 0; } str1++; str2++; } return *str1 - *str2; } int main() { char arr1[] = "abc"; char arr2[] = "abd"; int ret=my_strcmp(arr1, arr2); if (ret > 0) { printf("arr1>arr2"); } else if (ret < 0) { printf("arr1<arr2"); } else printf("arr1=arr2"); return 0; }
结果:
还有一个注意点:
4.在VS编译器环境下strcmp比较两个字符串时,第一个字符串大于第二个字符串返回的是1,第一个字符串小于第二字符串返回的是-1,相同时返回是0,但在不同的环境下,strcmp函数的返回的值可能不是1,-1,所以不能一概而论。
比如这题这样写你觉得合适吗?
int main() { char arr1[] = "abc"; char arr2[] = "abd"; int ret=my_strcmp(arr1, arr2); if (ret==1) { printf("arr1>arr2"); } else if (ret==-1) { printf("arr1<arr2"); } else printf("arr1=arr2"); return 0; }
虽然也能算出来,但在不同的环境下,就不一定能算出来了所以最好还是写成大于0,小于0的形式比较保险。
对上面改进字符串函数介绍
strncpy
是对strcpy函数的改进版,增加了可以拷贝几个字符的功能,其他都一样。
注意点:
1. 将源的前num个字符复制到目标。如果源C字符串的结尾
(由空字符发出信号)在num个字符被复制之前被发现,
目标用零填充,直到总共写入num个字符。
2. 拷贝num个字符从源字符串到目的空间
3. 如果源字符串的长度小于num,则拷贝源字符串后,在目标的后面追加0,直到num为止。
strncat
是对strcat函数的改进版,增加了可以追加几个字符的功能,其他的都一样。
注意点:
将源的前num个字符追加到目标,加上终止的空字符。
如果源代码中的C字符串的长度小于num,则仅复制终止空字符之前的内容。。
strncmp
是strcmp函数的改进版,可以选择比较前num个字符的大小
注意点:
比较出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
查找子字符串函数
strstr
注意点:
1. 返回值是指向str1中首次出现str2所指向的代表的字符的地址,如果序列在str1中不存在,则返回NULL指针
2. 是查找str1中是否有str2这个字符子串。
例子:
int main() { char str[] = "This is a simple string"; char* ret; ret = strstr(str, "simple"); //strstr返回的是在str数组中首次出现"simple"的指针,如果没有出现就返回NULL; printf("%s", ret); return 0; }
结果:
模拟实现strstr函数:
char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); if (*str2 == '\0') { return str1; } char* s1 = NULL; char* s2 = NULL; char* cp = str1; // while (*cp) { s1 = cp; s2 = str2; while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) { s1++; s2++; } if (*s2 == '\0') { return cp; } cp++; } return NULL; } int main() { char arr1[] = "abbbcd"; char arr2[] = "bc"; char* ret = my_strstr(arr1, arr2); printf("%s", ret); return 0; }
结果:
将字符串拆分为标记的函数
strtok
注意点:
1.参数 deli是个字符串,定义了用作分隔符的字符集合
2.第一个参数指定了要一个字符串,它必须包含0个或多个由deli字符串中一个或者多个分隔符分割的标志
3.strtok函数找到str中的一个标记,会将其用’\0’替换,返回一个指向这个标记的指针。(strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容)
4.strtok函数的第一个参数不为NULL,函数将会找到str中的第一个标记,strtok函数将保存它在字符串中的位置
5.strtok的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
6.如果字符串中不存在更多的标记,则返回NULL指针
例子:
#include <string.h> int main() { char arr1[] = "xiao tao lailo@qq.com";//要分割的字符串C char arr2[] = "@.";//分割符形成的集合分割符@ 和 分割符 . char arr3[100] = { 0 }; strcpy(arr3, arr1);//将数据临时拷贝一份,处理arr1中的内容 char*ret=strtok(arr3, arr2);//第一次要将完整的要分割的字符串作为参数 printf("%s\n", ret); ret = strtok(NULL, arr2);//第二次就不需要字符串了,只需要NULL作为参数就可以 printf("%s\n", ret); ret = strtok(NULL, arr2);//第三次也是一样 printf("%s\n", ret); return 0; }
也可以把分割的过程写成一个循环,因为就第一次不同,后面的都是一样的可以写成下面这样:
#include <string.h> int main() { char arr1[] = "xiao tao lailo@qq.com";//要分割的字符串C char arr2[] = "@.";//分割符形成的集合分割符@ 和 分割符 . char arr3[100] = { 0 }; strcpy(arr3, arr1);//将数据临时拷贝一份,处理arr1中的内容 //char*ret=strtok(arr3, arr2);//第一次要将完整的要分割的字符串作为参数 //printf("%s\n", ret); //ret = strtok(NULL, arr2);//第二次就不需要字符串了,只需要NULL作为参数就可以 //printf("%s\n", ret); //ret = strtok(NULL, arr2);//第三次也是一样 //printf("%s\n", ret); char* ret = NULL; for (ret = strtok(arr3, arr2); ret != NULL; ret = strtok(NULL, arr2)) { printf("%s\n", ret); } return 0; }
返回错误信息的函数
strerror
注意点:这个函数的功能是,返回错误码,所对应的错误信息
例子:
#include <errno.h>//使用该库函数需要引用头文件 #include <string.h> int main() { FILE* pFile = fopen("unexist.txt", "r");//打开一个不存在的文件 if (pFile == NULL)//打开失败会进行报错 { printf("%s", strerror(errno));//错误信息打印 } else fclose(pFile); return 0; }
结果:
perror
这个相比较上面的更方便些,因为这个直接可以打印报错
例子:
#include <string.h> int main() { FILE* pFile = fopen("unexist.txt", "r");//打开一个不存在的文件 if (pFile == NULL)//打开失败会进行报错 { perror("fopen");//参数是在哪个过程会报错呢就写哪个过程。 } else fclose(pFile); return 0; }
结果:
字符分类函数
islower
判断是否是小写字母函数
返回值:
是—返回非0,不是返回0.
例子:
#include <stdio.h> #include <ctype.h> int main() { int ret1 = islower('A'); int ret2 = islower('a'); printf("%d\n", ret1);//不是小写字母返回0 printf("%d\n", ret2);//是小写字母返回非0 return 0; }
isupper
1.检查字符是否为大写字母
2.返回值:
是大写字母就返回非0,不是就返回0.
3.例子:
#include <stdio.h> #include <ctype.h> int main() { int ret1 = isupper('A');//是大写字母返回非0 int ret2 = isupper('a');//不是大写字母返回0 printf("%d\n", ret1); printf("%d\n", ret2); return 0; }
归纳总结
以上字符分类函数的结构都是一样的,返回值也是差不多的,如果是真就返回非0的数,如果是假就返回0。
都是只有一个参数,一个整数返回值。参数取决于是分类什么。
字符转换函数:
tolower
例子1:
#include <ctype.h> int main() { int ret=tolower('A');//将大写转换为小写 printf("%c", ret); return 0; }
例子2:
#include <ctype.h> int main() { int i = 0; char arr[] = "Xiao Tao Lai Lo"; char c; while (arr[i]) { c = arr[i]; if (isupper(c))//如果是大写 { c = tolower(c);//转换成小写 } putchar(c);//输出 i++; } return 0; }
结果:
toupper
将小写字母转换为大写字母
例子:
#include <ctype.h> int main() { int ret=toupper('a');//将小写转换为大写 printf("%c", ret); return 0; }
以上函数都是针对字符或者字符串的,下面将介绍可以改变各种类型的函数————内存函数
针对内存的函数
memset
注意点:
1. 这是内存设置函数
2. 以字节为单位来设置内存中的数据
例子:
#include <string.h> int main() { char arr[] = "xiao tao lai lo"; memset(arr, 'x', 4);//第一个参数是要指向设置的内存块的指针 //第二个参数是要设置的值 //第三个参数是要设置多少字节数 printf("%s\n", arr); memset(arr + 5, 'y', 3); printf("%s\n", arr); return 0; }
注意:这个是以字节为单位改变内存的
int main() { int arr[10] = { 0 }; memset(arr, '1', 40);//要将arr这个内存块40个字节全部设置成1可以吗? return 0; }
可是通过监测分析arr中每个数都是一个很大的数字这是为什么呢?
这是因为memset是以字节为单位来改变内存的,它每次修改一个字节,也就是每次将一个字节修改为1,所以一个int类型4个字节,就改成了4个1了。
一开始内存中都是0,使用memset函数后看内存中是什么样子呢?
所以在使用memset函数时要注意这点,不然容易出错。一般memset函数用于将一个变量初始化为0,比较容易。
memcpy
例子:
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 0 }; memcpy(arr2, arr1, 40);//参数1是要拷贝的目标空间,参数2是拷贝的源数据,参数3是拷贝的数量 return 0; }
我们可以来深入的理解这个函数,来模拟实现下
memcpy函数模拟实现:
void* my_memcpy(void* dest, const void* src, size_t num) { void* ret = dest;//记录一下目标空间的起始地址,最后返回要用 //由于void*类型无法使用我们需要把它强制类型转换为char* while (num--)//循环num次 { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; //注意这里不能写成(char*)dest++这种形式,因为强制类型转换的只是暂时的,++后还是void*类型的 //但是可以++(char*)dest这样写 } return ret;//返回目标空间的起始地址 } int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 0 }; my_memcpy(arr2, arr1, 40); return 0; }
注意点2:
该函数要求要拷贝的空间与源数据的空间不能由重叠
举个例子:
它要求使用memmove这个函数来处理有重叠部分。其实在VS环境下,memove的功能与memcpy功能是差不多的都可以处理
但有的环境不可以,所以保险起见,当处理有重叠部分的需要使用memove函数,接下来就介绍memove函数
memmove
这个功能结构都是与memcpy是一样的,只不过更全面可以用于重叠部分的拷贝
int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; memmove(arr, arr + 2, 20); //要求将 3 4 5 6 7拷贝到 1 2 3 4 5内容去, return 0; }
那怎么实现重叠部分的拷贝的呢?
分情况讨论下,1.当目标空间在源数据空间的左边时
2.当目标空间在源数据的右边时
所以总结下,当目的空间地址小于源数据空间时,从前往后交换
当目的空间地址大于源数据空间时,从后往前交换
模拟实现下这个memmove函数吧
void* my_memmove(void* dest, void* src, size_t num) { void* ret = dest;//记录一下目标空间的起始地址,最后返回要用 if (dest < src) { //从前往后交换,跟memcpy一样直接复制过来就可以 while (num--)//循环num次 { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else { //从后完前交换 while (num--) { *((char*)dest + num) = *((char*)src + num); } } return ret; } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memmove(arr+2, arr, 20); //要求将 1 2 3 4 5拷贝到 3 4 5 6 7内容去, return 0; }
memcmp
返回值:
例子:
int main() { int arr1[] = { 1,2,3 }; int arr2[] = { 1,2,4 }; int ret=memcmp(arr1, arr2, 8);//前两个都一样所以最后结果应该为0 printf("%d", ret); return 0; }
int main() { int arr1[] = { 1,2,3 }; int arr2[] = { 1,2,4 }; int ret=memcmp(arr1, arr2, 12);//前两个都一样,但第三个不同进行比较,3小于4,所以最好结果为<0 printf("%d", ret); return 0; }

















































