目录

0.前言

1. 函数介绍

1.1 strlen

1.2 strcpy

1.3 strcat

1.4 strcmp

1.5 strncpy

1.6 strncat

1.7 strncmp

1.8 strstr

1.9 strtok

1.10 strerror

1.11 字符分类函数

1.12memcpy

1.13memmove

1.14memcmp

1.15 memset


0.前言

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

字符串常量适用于那些对它不做修改的字符串函数。

1. 函数介绍

1.1 strlen

求字符串长度

size_t strlen ( const char * str );

基本使用方式:

#include #include int main(){char arr[] = "hello bit";int len = strlen(arr); //string lengthprintf("len = %d", len); //len = 9return 0;}

注:

① 字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )

② 参数指向的字符串必须要以 ‘\0’ 结束 (不然得到的是随机值)

③ 注意函数的返回值为size_t (unsigned int) ,是无符号的,即字符串长度不可能为负( 易错点 )表达式中包含无符号整数时,表达式的结果也是无符号整数,这样就可能出现问题

看这样一段代码:

//达不到预期效果的代码int main(){char* p1 = "abc";char* p2 = "abcdef";if (strlen(p1) - strlen(p2) > 0){printf("hehe\n");}else{printf("haha\n");}return 0;//执行结果为 hehe}//修改后的代码int main(){char* p1 = "abc";char* p2 = "abcdef";if (strlen(p1) > strlen(p2) ){printf("hehe\n");}else{printf("haha\n");}return 0;//执行结果为 haha }

strlen(p1) – strlen(p2) 理论上讲应该为 -3,而当看作无符号数时,-3则成了一个很大的正数

解决办法:1.在用strlen计算之前先强制转换成 int 类型;2.不用加减,直接用> <比较大小

④ 模拟实现:

#include #include #include //方法一://size_t 就相当于unsigned int,定义无符号整型时可以直接使用size_t my_strlen1(const char* str) //用const修饰*str, 使该指针指向的内容不能被修改了{assert(str != NULL);int count = 0;//计数器while (*str != '\0'){count++;str++;}return count;}//方法二://不能使用计数器的版本(递归)//my_strlen("hello");//1 + my_strlen(ello);//1+1+my_strlen(llo);//1+1+1+my_strlen(lo);//1+1+1+1+my_strlen(o);//5+my_strlen('\0');//5 + 0 = 5size_t my_strlen2(const char* str) //用const修饰*str, 使该指针指向的内容不能被修改了{assert(str != NULL);if (*str != '\0'){return 1 + my_strlen2(str + 1);}else{return 0;}}//方法三://指针减指针的方法size_t my_strlen3(const char* str){assert(str != NULL);const char* start = str; //const的目的:把受保护的数据,交给一个"安全的"指针while (*str != '\0'){str++;}return str - start;}int main(){char arr[] = "hello";int len = my_strlen3(arr);printf("len = %d", len);return 0;}

1.2 strcpy

字符串拷贝

char* strcpy(char * destination, const char * source );

基本使用方式:

int main(){char arr1[20] = { 0 };char arr2[] = "hello bit";strcpy(arr1, arr2); //arr1为目标字符串,arr2为源字符串return 0;}

① 源字符串必须以 ‘\0’ 结束。

如,上述示例中如果这样定义arr2 :

char arr2[] = {'b','i','t'};

则会出现问题,因为strcpy函数会把源字符串的 ‘\0′ 也拷贝进入目标字符数组,如果定义时不加’\0’

系统会继续往后拷贝直到找到’\0’为止,此时不仅达不到目标效果,且还有越界访问的风险

②会将源字符串中的 ‘\0’ 拷贝到目标空间。

③目标空间必须足够大,以确保能存放源字符串。

④目标空间必须可变。

如果目标空间不能修改,则达不到目的,如:

char* arr1 = "xxxxxxxxxx"; //常量字符串,不能被修改

⑤模拟实现 :

//模拟实现strcpy//设置char*为返回类型,便于函数的链式访问(嵌套调用)char* my_strcpy(char* dest,const char* src){char* ret = dest;assert(dest != NULL);assert(src != NULL);while (*dest++ = *src++){;}return ret;}int main(){char arr1[20] = { 0 };char arr2[] = "hello bit";my_strcpy(arr1, arr2);printf("%s\n", my_strcpy(arr1, arr2));//链式访问return 0;}

1.3 strcat

字符串追加/连接

char * strcat ( char * destination, const char * source );

基本使用方式:

int main(){char arr[20] = "hello ";strcat(arr, "world");printf("%s\n", arr); //hello worldreturn 0;}

① 源字符串必须以 ‘\0’ 结束。(因为strcat是从源字符串的 ‘\0’ 处开始向后追加)

② 目标空间必须有足够的大,能容纳下源字符串的内容。

③ 目标空间必须可修改。

④ 字符串自己给自己追加,会怎么样?

追加时,追加字符串的第一个字符会取代源字符串的’\0’,也就是说,在进行拷贝时,本该作为结束标志的’\0’丢失了,代码会一直循环追加操作,直到超出字符数组的范围,越界访问,程序会崩溃。

⑤ 模拟实现:

//模拟实现strcatchar* my_strcat(char* dest, const char* src){char* ret = dest;assert(*dest && *src);//1.找目的地空间的'\0'while (*dest) //当*dest != '\0'为真{dest++;}//2.拷贝数据while (*dest++ = *src++) //由'w'替代arr中'\0'的位置,依次向后拷贝{;}return ret;}int main(){char arr[20] = "hello ";printf("%s\n", my_strcat(arr, "world")); //hello worldreturn 0;}

1.4 strcmp

字符串比较

int strcmp ( const char * str1, const char * str2 );

该函数如何评判两字符串的大小?

从首字符开始比较两者对应字符ASCII码值,若不相等,则ASCII码值大的字符所在字符串大于另一字符串,若相等则比较下一个字符,若所有字符串均相等,则两个字符串相等

② 判断后的返回值是什么?

第一个字符串大于第二个字符串,则返回大于0的数字

第一个字符串等于第二个字符串,则返回0

第一个字符串小于第二个字符串,则返回小于0的数字

基本使用方式:

int main(){int ret1 = strcmp("abc", "awe");int ret2 = strcmp("abc", "abc");int ret3 = strcmp("abc", "abb");printf("%d %d %d\n", ret1, ret2, ret3); //-1 0 1return 0;}

③ 模拟实现:

int my_strcmp(const char* s1, const char* s2){assert(s1 && s2);while (*s1 == *s2){if (*s1 == '\0')return 0;s1++;s2++;}return *s1 - *s2;/*if (*s1 > *s2) //vs的实现方式{return 1;}else{return -1;}*/}int main(){printf("%d\n", my_strcmp("abc", "abb"));return 0;}

以上strcpy、strcat、strcmp函数均为长度不受限制的字符串操作函数,即该函数在使用的时候不会考虑给定空间是否够大,它们只会找’\0’,所以在使用时可能经常会出现警告或者错误

所以接下来介绍的是长度受限制的字符串操作函数:strncpy、strncat、strncmp

1.5 strncpy

字符串拷贝

char * strncpy ( char * destination, const char * source, size_t num );

可见,相比strcpy,strncpy多了一个参数 : size_t num 用于对拷贝字符的数量进行限制:

①拷贝num个字符从源字符串到目标空间。

②如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

例如:

int main(){char arr[10] = "aaaaaaaaa";strncpy(arr, "hello", 7);return 0;}

调试窗口可以看到:

基本使用方式:

int main(){char arr1[20] = { 0 };char arr2[20] = { 0 };char arr3[] = "hello";strncpy(arr1, arr3, 3);//3 表示只拷贝3个字符进arr1strncpy(arr2, arr3, 7);//7 超出了arr3的字符串长度,则会在后面补'\0'printf("%s\n", arr1); //helprintf("%s\n", arr2); //helloreturn 0;}

1.6 strncat

字符串追加/连接

char * strncat ( char * destination, const char * source, size_t num );

num表示追加几个字符

① 从目标字符串的第一个’\0’处开始追加,追加num个字符

② 追加结束后会自动补’\0′

基本使用方式:

int main(){char arr1[20] = "abcdef";char arr2[] = "qwer";strncat(arr1, arr2, 4);printf("%s\n", arr1);return 0;}

练习:判断一个字符串是否可由另一个字符串左旋得到

//字符串左旋操作void left_move(char* str, int n){int i = 0;int len = strlen(str);char tmp = *str;int j = 0;for (i = 0; i < n; i++){//旋转一次操作:for (j = 0; j < len - 1; j++){*(str + j) = *(str + j + 1);//从第二个字符开始前移}*(str + len - 1) = tmp; //第一个字符放到最后去}}暴力方式 : 每一次旋转后都比较一次//int main()//{//char arr1[] = "abcdef";//char arr2[] = "cdefab";//int i = 0;//int len = strlen(arr1);////for (i = 0; i < len; i++)//{//left_move(arr1, 1);//if (strcmp(arr1, arr2) == 0)//{//printf("YES\n");//break;//}//}//if (i == len) //每一趟比较均不相等,则说明不是旋转得到的//{//printf("NO\n");//}////return 0;//}//方法二 : //在arr1后再追加一次arr1 : abcdefabcdef//那么所有左旋转的结果,都被包含在上述字符串的子串中int main(){char arr1[20] = "abcdef";char arr2[] = "cdefab"; if (strlen(arr1) != strlen(arr2)) //如果两字符串长度都不等,则肯定无法通过旋转得到{printf("NO\n");return 0;}strncat(arr1, arr1, 6);//arr1追加自己char* ret = strstr(arr1, arr2); //strstr : 寻找子字符串,若找到子串则返回子串的起始位置,若不是子串则返回空指针if (ret == NULL){printf("NO\n");}else{printf("YES\n");}return 0;}

1.7 strncmp

字符串比较

int strncmp ( const char * str1, const char * str2, size_t num );

比较两字符串的前num个字符

比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完

基本使用方式:

int main(){char arr1[] = "abcdef";char arr2[] = "abcdef";int ret = strncmp(arr1, arr2, 5);//比较前5个字符printf("%d\n", ret); //0return 0;}

1.8 strstr

寻找字符串的子串

char * strstr ( const char *str1, const char * str2);

寻找子字符串,若找到子串则返回子串的起始位置,若不是子串则返回空指针

基本使用方式:

int main(){char arr[] = "abcdefabcdef";char* ret = strstr(arr, "cd");if (ret != NULL){printf("%s\n", ret); //cdefabcdef}return 0;}

模拟实现:

//找到子串返回字串起始位置, 找不到返回空指针char* my_strstr(const char* str1, const char* str2){assert(str1 && str2); //保证str1和str2不为空指针//建两个指针,分别指向起始位置,用于保存 下一次尝试匹配的位置const char* s1 = str1;const char* s2 = str2;//用于保存 : 下一次开始匹配的位置const char* cp = str1;if (*s2 == '\0') //strstr函数规定如果str2为空字符串,则返回str1的地址{return (char*) str1;}while (*cp) //*cp != '\0'说明字符串中还有字符没进行查找{//匹配查找一次的操作s1 = cp;s2 = str2;while (*s1 && *s2 &&  *s1 == *s2) //*s1 != '\0',*s2 != '\0'且s1 == s2时向后判断{s1++;s2++;}//之后分别处理三种跳出循环的情况if (*s2 == '\0') //说明匹配成功了//返回此时的cp地址{return (char*) cp; //co是由const char*修饰的,而该函数返回类型又是char*,即把安全的指针赋给了不安全的,会报警告。所以此时强制类型转换成char*}cp++; //s1 != s2时的处理}return NULL; //s1 == '\0'说明没找到子串返回空指针}int main(){char* str1 = "abcdef";char* str2 = "cde";char* ret = my_strstr(str1, str2);printf("%s\n", ret);return 0;}

1.9 strtok

分解字符串

char * strtok ( char * str, const char * sep );

①sep参数是个字符串,定义了用作分隔符的字符集合。

②第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

③ strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。

(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)

④ strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。

⑤strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

⑥ 如果字符串中不存在更多的标记,则返回NULL 指针

int main(){char arr1[] = "zqf@XJTU.com";char arr2[] = "@."; char tmp[30] = { 0 }; //由于strtok会修改原字符串,所以我们先拷贝,只对拷贝的内容进行修改strcpy(tmp, arr1); char* a = strtok(tmp, arr2);//第一次调用//由于tmp不为空指针, 则会开始向后找第一个标记'@',并将其修改为'\0'//同时: 1. 会返回: 首字符'z'的地址 2.会记住@的位置(作为下次调用的起始位置)printf("%s\n", a); //zqfchar* b = strtok(NULL, arr2);//第二次调用//同一个字符串中被保存的位置开始,查找下一个标记//返回q的地址,记住'.'的位置printf("%s\n", b);//XJTUchar* c = strtok(NULL, arr2);printf("%s\n", c); //com//第三次调用//从被保存的位置开始,查找下一个标记,找到'\0'//返回c的地址//如果再调用也只能返回空指针了//注 : 如果这样定义char arr1[] = "zqf@\0qq.com";//找到第一个'\0'后,如果再调用strtok函数,该函数也不会继续访问内存中的剩余字符,而是返回空指针return 0;}

基本使用方式:

int main(){char arr1[] = "zqf@XJTU.com";char tmp[30] = { 0 };strcpy(tmp, arr1);char arr2[] = "@.";char* p = NULL;for (p = strtok(tmp, arr2); p != NULL; p = strtok(NULL, arr2)){printf("%s\n", p);}return 0;}

1.10 strerror

获得错误的描述字符串

char * strerror ( int errnum );

基本使用方法:

int main(){//当调用库函数,发生错误的时候,就会有些错误码//错误码会放在errno这个全局变量中(如果没有错误则errno默认为0)//需要头文件 #include //正确的情况: printf("%d\n", errno);           // errno   没有错误默认为0printf("%s\n", strerror(errno)); //No error 没有错误//错误的情况: //文件操作//打开文件FILE* pf = fopen("test.tst", "r"); //打开一个文件, 传参为1.文件名, 2.打开形式// (此处为"读"的方式打开,如果文件不存在则会打开失败,如果是以"写"的方式打开,文件不存在时会创建一个该文件)// 当打开成功,则会返回一个有效的指针,打开失败则返回空指针//并且用FILE定义一个结构体变量接受fopen的返回值if (pf == NULL){printf("%s\n", strerror(errno)); //No such file or directoryperror("91班打印错误信息"); //}else{printf("打开成功\n");fclose(pf); //关掉打开的文件pf = NULL;}return 0;}

1.11 字符分类函数

函数如果他的参数符合下列条件就返回真
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任何可打印字符,包括图形字符和空白字符

以tolower函数为例:

int main(){char ch = 'A';//65 'a'为97//printf("%c\n", ch + 32);//printf("%c\n", tolower(ch));//注意 : tolower(ch)返回值是小写字母的ASCII码值,而不是把ch从大写改变成了小写//需要头文件 #include //把一个字符串改成全小写char str[] = "Test String.\n";char c;int i = 0;while (str[i]) //没遇到'\0'就转换并打印{c = str[i];if (isupper(c)){c = tolower(c);}putchar(c);i++;}return 0;}

1.12memcpy

内存拷贝(内存不重叠部分)

void * memcpy ( void * destination, const void * source, size_t num );

num表示需要拷贝的字节

① 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。(一个字节一个字节的拷贝过去)

目标空间必须足够大且可修改。

源字符串不需要有 ‘\0’ 且该函数在遇到 ‘\0’ 的时候并不会停下来。

模拟实现:

void* my_memcpy(void* dest, const void* src, size_t count){void* ret = dest;while (count--){//拷贝一个字节*(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 };//对于整型数组,肯定不能用strcpy进行拷贝 : //01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 小端在内存中的存储//strcpy遇到00就停止了//此时则需要内存拷贝my_memcpy(arr2, arr1, sizeof(arr1));return 0;}

1.13memmove

内存拷贝(两内存块间可重叠)

void * memmove ( void*  destination, const void * source, size_t num );

①和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的,如果源空间和目标空间出现重叠,就得使用memmove函数处理。

② 模拟实现:

void* my_memmove(void* dest, const void* src, size_t count){void* ret = dest;assert(dest && src);//当两块空间未重叠的时候,怎样拷贝都无所谓//只有空间重叠时需要考虑拷贝的方式:if (dest < src)//当dest在src左边的时候,src应该从前向后开始拷贝{while (count--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else//反之,src从后向前拷贝{while (count--){*((char*)dest + count) = *((char*)src + count);}}return ret;}int main(){int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };//此时我想把1 2 3 4 拷贝到3 4 5 6的位置上去my_memmove(arr1 + 2, arr1, 16);return 0;}

dest 在 src 左边时:从前向后拷贝

dest 在 src 右边时:从后向前拷贝

基本使用方式:

int main(){int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };//此时我想把1 2 3 4 拷贝到3 4 5 6的位置上去memmove(arr1 + 2, arr1, 16);return 0;}

1.14memcmp

内存比较

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

比较从 ptr1 和 ptr2 指针开始的num个字节 ,返回类型和strcmp一个道理

基本使用方法:

int main(){int arr1[] = { 1, 2, 3, 4, 5};int arr2[] = { 1, 2, 3, 3, 3};int ret = memcmp(arr1, arr2, 20);printf("%d\n", ret); // 1return 0;}

1.15 memset

初始化函数

void * memset ( void *dest, int value, size_t num );

将某一块内存中的内容(从dest开始到dest+num的全部字节)全部设置为指定的值, 该函数通常为新申请的内存做初始化工作。

基本使用方式:

int main(){int arr[] = { 1,2,3,4,5,6,7,8,9,10 };memset(arr, 0, sizeof(arr)); return 0;}

注意,该函数是按照给定字节进行修改,如果上述函数这样使用:

memset(arr, 1, sizeof(arr)); 

并不是把数组的每一个元素都置为1,而是把每一个字节修改成了 01


如果对本文涉及的代码有需要,可进入我的gitee主页进行下载学习