0%

字符函数和字符串函数

文章时效性提示

本文发布于 507 天前,部分信息可能已经改变,请注意甄别。

求字符串长度

  • 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的返回值相减,这样得到的结果不是想要的。

1
2
3
4
5
6
7
#include <stdio.h>
#include <string.h>
int main() {
    int len = strlen("abcdef");
    printf("%d\n",len);
    return 0;
}

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

1
2
3
4
5
6
7
8
9
#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、指针-指针的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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结束。

1
2
3
4
5
6
7
8
9
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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)

1
2
3
4
5
6
7
8
9
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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
返回值根据不同编译器可能会不同,但符合返回值规律。
1
2
3
4
5
6
7
8
9
#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的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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个。

1
2
3
4
5
6
7
8
9
#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后面)

1
2
3
4
5
6
7
8
9
#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
1
2
3
4
5
6
7
8
9
#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是要找的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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;
}

打印的结果是:

1
2
3
example
eee
com

上面代码的改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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);

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <string.h>
int main()
{
    char* str = strerror(0);
    printf("%s\n",str);
    return 0;
}

打印的结果是:Undefined error: 0

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <string.h>
int main()
{
    char* str = strerror(1);
    printf("%s\n",str);
    return 0;
}

打印的结果是:Operation not permitted

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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。
1
2
3
4
5
6
7
8
9
#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>

1
2
int tolower(int c);//转小写
int toupper(int c);//转大写
1
2
3
4
5
6
7
8
#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的局限性:

1
2
3
4
5
6
7
8
9
#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:

1
2
3
4
5
6
7
8
9
#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中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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不能处理的重叠拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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
1
2
3
4
5
6
7
8
9
10
#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);

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <string.h>
int main()
{
    char arr[10] = "";
    memset(arr, '#',3);
    return 0;
}

下面是一个错误示范:

1
2
3
4
5
6
7
8
#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