字符函数和字符串函数

求字符串长度

  • 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
#include <stdio.h>#include <string.h>int main() {    int len = strlen("abcdef");    printf("%d\n",len);    return 0;}

字符串len有6个元素,打印的结果是6。
strlen的原理就是求出一个字符串中\0元素之前的元素个数。

123456789
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <assert.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <assert.h>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
#include <stdio.h>#include <string.h>int main() {    char arr1[30] = "hello";//目的地一定足够大,能放下追加的字符串    char arr2[] = "world";//    strcat(arr1,arr2);    printf("%s\n",arr1);    return 0;}

打印的结果是helloworld

模拟实现strcat:

123456789101112131415161718192021222324
#include <stdio.h>#include <assert.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <assert.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <string.h>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)
查找子字符串。
查找子串是否存在,存在返回第一次出现的起始地址,不存在返回NULL
const char *string是要查找的字符串(在这个字符串里查找)
const char *strCharSet是要找的字符串

12345678910111213141516
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <assert.h>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 中的字符都行。

strtokstr中找到了sep中规定的字符,就把这个字符改成\0,并返回指向这个标记的指针。
例如example@eee.com返回example,再次调用,在之前的前提下,寻找下个标记,找到了.点,并返回下个首字符,即eee

一般使用strtok函数,都要临时拷贝一份,切割拷贝的那份,避免原数据找不到了

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

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

字符串中没有更多标记,返回NULL

1234567891011121314151617181920
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <string.h>int main(){    char* str = strerror(0);    printf("%s\n",str);    return 0;}

打印的结果是:Undefined error: 0

12345678
#include <stdio.h>#include <string.h>int main(){    char* str = strerror(1);    printf("%s\n",str);    return 0;}

打印的结果是:Operation not permitted

errno:是全局错误码的变量,当C语言库函数在执行过程中出现错误,会把错误码给errno中。

1234567891011121314
#include <stdio.h>#include <string.h>#include <errno.h>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
#include <stdio.h>#include <string.h>#include <ctype.h>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
#include <stdio.h>#include <string.h#include <ctype.h>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
#include <stdio.h>#include <string.h>int main(){    int arr1[] = {1,2,3,4,5};    int arr2[10] = {0};    //strcpy(arr2, arr1);//不能把整型地址赋给字符型指针,只能拷贝首元素    return 0;}

使用memcpy:

123456789
#include <stdio.h>#include <string.h>int main(){    int arr1[] = {1,2,3,4,5};    int arr2[10] = {0};    memcpy(arr2, arr1, sizeof(arr1));    return 0;}

arr1的内容被拷贝到了arr2中去

123456789101112131415161718
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <assert.h>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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <assert.h>  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
#include <stdio.h>#include <string.h>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
#include <stdio.h>#include <string.h>int main(){    char arr[10] = "";    memset(arr, '#',3);    return 0;}

下面是一个错误示范:

12345678
#include <stdio.h>#include <string.h>int main(){    int arr[20] = {0};    memset(arr, 1, 10);    return 0;}

memset(arr, 1, 10);不会让数组内的10个整型改为1,而是会让相邻的10个字节的内存改为1

🔍 ×