首页 > 其他分享 >C语言之字符串操作

C语言之字符串操作

时间:2024-08-28 22:17:04浏览次数:10  
标签:src 函数 dest 空字符 C语言 char 字符串 操作

C语言之字符串操作

C 语言提供了丰富的字符串处理相关的库函数,这些函数基本上,都声明在头文件string.h当中,所以使用它们需要包含这个头文件。这里只介绍几种最基本的和最常用的,以及手动实现它们的方式。

字符串长度strlen

strlen

函数全名:string_length

函数声明:

size_t strlen (const char *s);

函数作用:返回当前字符串的长度,也就是字符数组中空字符'\0'前面的字符数量。不包括空字符'\0'!

int len;
char str[] = "abcd";
char str2[10] = "12345";
char str3[5] = { 'a','\0','c' };
len = strlen("abc");    /* len is now 3 */
len = strlen("");       /* len is now 0 */
len = strlen(str);      /* len is now 4 */
len = strlen(str2);     /* len is now 5 */
len = strlen(str3);     /* len is now 1 */

char str4[4] = "1234";
len = strlen(str4);     // str4并不能表示一个字符串,该函数调用会引发未定义行为

手动实现my_strlen

size_t my_strlen1(const char* str) {
    size_t len = 0; 
    while (*str != '\0') {		// 遍历直到遇到空字符
        len++;
        str++;
    }
    return len;
}

// 数组下标形式
size_t my_strlen2(const char* str) {
    size_t len = 0; 
    while (str[len] != '\0') {
        len++;
    }
    return len;
}

size_t my_strlen3(const char* str) {
    const char* p = str;	// 用一个指针记录数组首元素
    while (*str) {		// 惯用法: 遍历直到字符串的末尾,
        str++;
    }   // 循环结束时,str指针指向空字符,p指针指向数组首元素
    
    // 相当于空字符的下标减去首元素下标,结果就是字符串的长度
    return str - p;
}

字符串复制strcpy,strncpy

strcpy

函数全名:string_copy

函数声明:

char *strcpy(char *dest, const char *src);

函数作用:src 中存储的以空字符 '\0' 结束的字符串复制到 dest所指向的数组中。

也就是说,会从首字符开始逐个字符地从 src 复制字符,包括空字符在内,都会从dest首位置开始,复制到dest当中。

这个过程中,src数组是不会被修改的,所以它被const修饰。总之,该函数调用后,dest 将包含 src 的完整副本。

char src[] = "hello";
char dest[10];

// 调用函数后,src不变
// dest数组就会变成{'h','e','l','l','o','\0', ....}
// 其中前6个字符是字符串"hello"
strcpy(dest, src);

函数返回值:

该函数会返回指向目标数组 dest 的指针,一般来说,该函数的返回值没有什么意义,推荐忽略它。

但某些场景下,这个返回值允许strcpy 函数的调用,可以被嵌套使用或者用于更复杂的表达式中。比如:

// strcpy返回复制完成后指向 dest 数组的指针
char src[] = "hello";
char dest[10];
char dest2[10];

// 可以直接利用printf、puts函数打印复制后的目标数组
puts(strcpy(dest, src));

// 更加复杂的函数调用
// 将src复制到dest,再将dest复制给dest2
strcpy(dest2, strcpy(dest, src));

注意:

  1. strcpy函数是不安全的,它并不会检查dest是否真的能够包含src数组。如果dest不够大,就会因数组越界而产生未定义行为。
  2. 为了安全起见,可以考虑使用strncpy函数解决这一问题,虽然它可能效率更低一些。

strncpy

函数声明:

char *strncpy(char *dest, const char *src, size_t n);

函数作用:

该函数会将最多n个字符从src中复制到dest数组中:

  1. 如果n < strlen(src) + 1,也就是 n 小于源字符串的长度 + 1时,此时strncpy函数就无法将整个源字符串数据都复制到 dest 中,并且此时复制完成的 dest 数组也不会以空字符结尾,无法表示一个字符串。不过好在,它不会引起越界问题,是安全的操作。
  2. 如果n = strlen(src) + 1,即 n 恰好是源字符串长度 + 1时,strncpy函数会将src完整复制到dest数组中,包括空字符。
  3. 如果n > strlen(src) + 1,即 n 完全大于源字符串长度 + 1时,strncpy函数不仅会完整复制src字符串到dest中,还会将剩余 (n - strlen(src) - 1) 个字符设置为空字符。

strncpy函数一定会处理n个字符数据,如果n比较大,在复制完源数组后,它会顺道将 dest 数组中的元素置为空字符。

实际开发中,建议将n的值设定为 dest长度-1,并主动将dest的最后一个元素设置为空字符,这样总能安全地得到一个字符串。

也就是以下操作代码:

strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
char src[] = "hello";
char dest[10];
// 这个复制是没有问题的
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';

char dest2[5];
// 这个复制虽然没有真的将"hello"都复制到dest2,但是一个安全的操作。最终dest2存放字符串"hell"
strncpy(dest2, src, sizeof(dest2) - 1);
dest2[sizeof(dest2) - 1] = '\0';

这样的复制行为,若dest足够装下src,会导致dest剩余部分全都被设置为空字符。

手动实现my_strcpy、my_strncpy

my_strcpy

char* my_strcpy1(char *dest, const char *src) {
    char *tmp = dest;   // dest数组的首元素指针要作为返回值,所以要临时保存一下
    while (*src) {
        *dest++ = *src++;
    }   // 这个while循环没有复制空字符,所以需要手动来复制空字符
    // while循环结束时,src指针指向空字符, dest指针也指向对应的位置
    *dest = 0;
    return tmp;
}


// 简化: 复制字符串的惯用法
char *my_strcpy2(char *dest, const char *src) {
    char *tmp = dest;   // dest数组的首元素指针要作为返回值,所以要临时保存一下
    /*
        (*dest++ = *src++)
        主要作用: 返回*src的值
        副作用: src和dest都往后挪动指针

        当主要作用返回src指针指向的元素, 且返回空字符结束这个while循环时
        =的副作用赋值并不会消失
        所以所以这种写法, 空字符也会连带被复制
        不需要考虑空字符的手动复制
    */
    while (*dest++ = *src++)
        ;
    return tmp;
}

my_strncpy

char *my_strncpy1(char *dest, const char *src, size_t n) {
    char *tmp = dest;
    /*
        复制src到dest,直到达到n个字符或遇到src的终结符
        while循环结束条件是:
        1.n足够长时(n >= strlen(src)+1时),会将src中直到空字符的数据复制到dest中,然后结束循环
        2.n不够长时(n < strlen(src)+1时),会将src中的前n个字符复制到dest中,然后结束循环
            此时dest数组不会存储空字符,它不能表示字符串,但不会有越界风险
    */
    while (n > 0 && (*dest++ = *src++)) {
        n--;
    }
    /*
        当n >= strlen(src) + 1时
        上面的while循环依靠(*dest++ = *src++)结束循环
        此时空字符被复制到dest中了
        但循环体没有执行循环就终止了, n--没有执行, 即复制了一个字符但n没有减少1
        所以下面要判断n > 1 而不是n > 0
    */
    while (n > 1) {
        *dest++ = '\0';
        n--;
    }
    return tmp;
}

// 如果不将n设置为无符号类型,而设置为int类型
char *my_strncpy2(char *dest, const char *src, int n) {
    char *tmp = dest;
    // 这个循环不用担心无符号0减1变为最大值的情况发生,可以放心使用
    while (n-- && (*dest++ = *src++))
        ;
    // 由于n--正常执行,所以下面仍然可以判断n>0
    while (n > 0) {
        *dest++ = 0;
        n--;
    }
    return tmp;
}


// 当然为了追求原汁原味, 还是应该保留n的size_t类型
// 同时为了保证n>0的正常使用,我们就需要保证n--正常执行
char *my_strncpy3(char *dest, const char *src, size_t n) {
    char *tmp = dest;
    while (n > 0 && (*dest = *src)) { 
        dest++;
        src++;
        n--;
    }// while循环结束时,src指针指向指向空字符,dest指针也指向空字符
    // 虽然空字符已经复制结束后,但由于指针仍然指向空字符,下面的while循环就可以n>0执行
    while (n > 0) {
        *dest++ = '\0';
        n--;
    }
    return tmp;
}

字符串拼接strcat,strncat

strcat

函数全名: string_concat,顾名思义,它用于将一个字符串拼接到另一个字符串的末尾。

函数声明:

char *strcat(char *dest, const char *src);

函数作用: strcat 函数会将 src(源字符串)中存储的以空字符 '\0' 结束的字符串拼接到 dest(目标字符串)的末尾。

具体来说:

会从 dest 字符串的空字符 '\0' 开始,替换这个空字符,然后将src中表示字符串的字符数据从开头到空字符,全部拼接到dest末尾。

这个过程中,src 字符串不会被修改,所以它被 const 修饰。

总之,该函数调用后,dest 将包含 dest 和 src 的字符串拼接后的副本。

简单的代码示例:

char dest[20] = "Hello, ";
char src[] = "World!";
// 调用函数后,dest数组变为 "Hello, World!\0",其余部分填充为空字符
strcat(dest, src);
// 输出拼接后的字符串
printf("%s", dest); // 输出 "Hello, World!"

函数返回值:

该函数会返回指向目标数组 dest 的指针。一般来说这个返回值可以忽略,但有些场景中可以利用该返回值,对函数调用进行嵌套处理。

例如:

char dest[20] = "Hello, ";
char src[] = "Everyone!";
char src2[] = " Have a nice day!";
// 嵌套,将src和src2都拼接到dest上
strcat(strcat(dest, src), src2);
// 输出最终的拼接字符串
printf("%s", dest); // 输出 "Hello, Everyone! Have a nice day!"

注意:

  1. src 和 dest两个参数都必须是字符串,即以空字符串结尾的字符数组,否则将引发未定义行为。
  2. strcat 函数与 strcpy 一样,不会检查 dest 数组是否能够容纳拼接后的字符串。如果 dest 不够大,就会产生未定义行为,这是潜在的安全隐患。
  3. 可以考虑使用更安全的strncat函数来实现字符串拼接,它允许指定最大拼接的字符数。

strncat

函数声明:

char *strncat(char *dest, const char *src, size_t n);

函数作用:

仍旧是找到dest字符串末尾的空字符,然后从该字符开始,将 src 的首个元素复制到 dest末尾,直到:

  • 已经复制了 n 个字符。

  • 或者复制到达了src字符串的结尾,即遇到了src的空字符串。所以该函数不会把src中的空字符复制到dest中

最后,strncat函数一定会在dest的末尾添加一个空字符,以确保dest能够表示一个字符串。由于这一步操作的存在,该函数仍然具有越界访问的风险,需要程序员自己设定合理的n取值,以规避这种情况。

为了安全的使用此函数,基于函数的特点,我们就可以得出n的计算公式:

int n = sizeof(dest) - strlen(dest) - 1;    // dest数组的长度 - 已存储字符串的长度 - 1(留给存空字符)

表达式中-1是必要的,否则会导致数组越界情况发生。

给strncat函数传入这样的一个n参数,就能够保证一定安全的得到一个拼接后的结果字符串。

当然若dest容量不够大,拼接只会截取src的一部分字符数据。但安全是更重要的事情,这样的结果是我们可以接受的。

实际开发中,建议采取以下方式来调用此函数,避免dest空间不足导致溢出:

char src[] = "world";
char dest[10] = "hello, ";
// 确保dest是一个数组类型,而不是一个指针类型才能这样操作
int n =  sizeof(dest) - strlen(dest) - 1;
strncat(dest, src, n);

dest最多再拼接(自身数组长度 - 字符串长度 - 1)个字符,最后留一个字节,strncat函数会自动拼接一个空字符表示字符串结束。

手动实现my_strcat、my_strncat

my_strcat

char* my_strcat(char* dest, const char* src) {
    char* temp = dest;
    // 找到dest的末尾,也就是让dest指针指向空字符
    while (*dest) {
        dest++;
    }

    // 将src的字符串数据从开头到空字符都拷贝到dest的末尾
    while ((*dest++ = *src++))
        ;
    return temp;  // 返回dest的起始地址
}

my_strncat


char* my_strncat1(char* dest, const char* src, int n) {
    char* temp = dest;
    // 找到dest的末尾,也就是让dest指针指向空字符
    while (*dest) {
        dest++;
    }

    // 拷贝最多n个字符从src到dest的末尾
    while (n-- && (*src)) {
        *dest++ = *src++;
    }

    // 确保dest以空字符结尾,所以一定会将末尾置为空字符。这里没有判断越界,所以strncpy函数实际仍存在越界风险
    // 这就要求我们传入一个正确合理的n以确保安全    
    *dest = '\0';
    return temp;  // 返回dest的起始地址
}

字符串比较大小strcmp

strcmp

函数全名:string_compare,用于比较两个字符串的大小。

函数声明:

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

函数作用: strcmp函数按照字典顺序比较两个字符串的大小。当调用 strcmp函数时,它会逐个字符地比较两个字符串 str1 和 str2:

注意:

  1. 这个函数不会修改任何一个字符串,所以两个参数都被 const 修饰。
  2. 不同编译器和环境可能对strcmp函数返回值的表现有所不同。在某些环境中(比如VS),出于简化的目的,可能会将返回值归一化为 -1、0 或 1。但是标准的 C 语言库中 strcmp 的返回值是根据实际字符的ASCII值差异来确定的,而不是简单的 -1、0 或 1。
  3. 要确保传入的两个参数数组都表示字符串,都以空字符结尾,否则可能会导致比较越界。
  4. strcmp 是区分大小写的比较,如果需要进行不区分大小写的比较,可以使用 strcasecmp 函数。
  5. 由于strcmp是基于ASCII值进行比较的,它在处理非ASCII或多字节字符(如UTF-8编码的文本)时可能不会表现出预期的行为。

手动实现strcmp

int my_strcmp1(const char* str1, const char* str2) {
    while (*str1 && *str2) {
        if (*str1 != *str2)
        {
            return *str1 - *str2;
        }
        str1++;
        str2++;  
    }
    return *str1 - *str2;
}

// 这个实现可读性更差,但简洁很多
int my_strcmp2(const char* s1, const char* s2) {
    while (*s1 && (*s1 == *s2)) {
        // 移动两个指针,比较下一对字符
        s1++;
        s2++;
    }
    /*
    * while循环结束的条件是:
    * 1.s1指针指向了空字符
    * 2.或者s2指针指向了空字符
    * 3.或者s1和s2指针指向的字符不一致
    * 不管是哪一种情况,返回它们的编码值差就是结果
    */
    return *s1 - *s2;
}

字符相关的库函数

在操作字符串的过程中,经常需要对单个字符做出判断,比如:

  1. 判断字符是大写字母还是小写
  2. 判断字符是不是数字
  3. ...

C 标准函数库在头文件 <ctype.h> 中定义了大量此类功能函数。比如:

islower 和 isupper 函数是 C 标准库中声明在 <ctype.h> 头文件中的函数,它们用于检查给定的字符是否为小写字母或大写字母。

除此之外,<ctype.h> 头文件中还包括了以下类似的库函数:

  1. isalpha(int c):检查传入的字符是否为字母(包括大写和小写)。
  2. isdigit(int c):检查传入的字符是否为十进制数字(0到9)。
  3. isalnum(int c):检查传入的字符是否为字母或数字。
  4. isspace(int c):检查传入的字符是否为空白字符,比如空格、制表符、换行符等。
  5. isblank(int c):检查传入的字符是否为空格或制表符。
  6. ...

标签:src,函数,dest,空字符,C语言,char,字符串,操作
From: https://www.cnblogs.com/Invinc-Z/p/18385610

相关文章

  • C语言位运算
    在C语言中,位运算是对二进制位进行的操作。以下是关于C语言位运算的介绍:一、常见的位运算符按位与(&):规则:两个对应的二进制位都为1时,结果位为1,否则为0。例如:5(二进制为0101)与3(二进制为0011)进行按位与运算,结果为0001,即1。按位或(|):规则:两个对应的二进制位只要......
  • 超详细!SQL运算符的操作练习-笔记-04章:运算符
    写在前面的话(•̀.̫•́)✧即将学习数据库系统的课程,此文为预习与自学,自学配套课程链接为:MySQL数据库入门到大牛,mysql安装到优化,百科全书级,全网天花板_哔哩哔哩_bilibili时间不够可以不看,前五章内容都比较简单,纯看笔记也能学会。本文主要内容是学习和练习运算符,这......
  • python基础操作
    python基础仅仅打印输出helloworld是不够的,对吧?你想要做的远不止这些——你想要得到一些输入,操作它并从中得到一些东西。1.注释注释是#符号右侧的任何文本,主要用作程序读者的注释。print('helloworld')#注意,print是一个函数。或者:#注意,print......
  • 第42天:WEB攻防-PHP应用&MYSQL架构&SQL注入&跨库查询&文件读写&权限操作 - 快捷方式
    接受的参数值未进行过滤直接带入SQL查询 MYSQL注入:(目的获取当前web权限)1、判断常见四个信息(系统,用户,数据库名,版本)2、根据四个信息去选择方案root用户:先测试读写,后测试获取数据非root用户:直接测试获取数据  #PHP-MYSQL-SQL常规查询获取相关数据:1、数据库版本-看是否......
  • 算数、赋值、单目操作符介绍
    操作符的介绍目录算数操作符1.1:+和-1.2:*1.3:/1.4:%赋值操作符2.1:连续赋值2.2:复合赋值符单目操作符3.1++和--3.1.1:前置++和前置--3.1.2:后置++和后置--3.2:+和-算数操作符算数操作符包含以下几个符号:1:+(加号)2:-(减号)3:*(乘号)4:/(除号)5:%(求余号)这五种符号在C语言运算中起......
  • 操作指南:在Ubuntu 24.04上安装网易邮箱大师
    网易邮箱大师是一款功能强大的邮箱客户端,支持多种邮箱的管理,提供了便利的邮件收发、日历管理和任务跟踪等功能。对于使用Ubuntu的用户而言,虽然Linux系统上的邮箱客户端有所限制,但我们仍然希望能够在这个开源的环境中实现高效的邮件管理。本文将详细介绍如何在Ubuntu24.04上安......
  • 【爬虫实战】——利用bs4和sqlalchemy操作mysql数据库,实现网站多行数据表格爬取数据
    前言此篇接上一篇的内容,在其基础上爬取网站的多行表格数据,以及把数据写入到mysql数据库中目录一、定位表格查找元素二、提取数据三、写入mysql数据库四、附录一、定位表格查找元素首先打开网站,如图需要爬取多行数据的表格,利用查找元素定位,看图中分析得知我要爬取的是tr......
  • 深入解析Pandas的Series与DataFrame索引和切片操作(三)
    Pandas库是Python中用于数据处理和分析的强大工具,它的核心数据结构包括Series和DataFrame。掌握Pandas的索引与切片操作是数据分析的基础,因为它们允许我们高效地访问、筛选和操作数据。本文将详细介绍Pandas中的Series和DataFrame的索引与切片方法,帮助你更好地理解和应用这......
  • fastadmin 搭建项目,gitignore忽略文件配置,避免在操作git时产生代码冲突
    当多人进行开发fastadmin项目时,在提交代码到git仓库前,有一些文件如果不进行git忽略的话,在开发过程中很容易导致git冲突~以下是我在开发fastadmin项目时常用的gitignore文件的写法,在新项目提交到git仓库前可以进行替换(替换项目根目录的.gitignore文件内容)#BuildandReleaseFo......
  • zabbix监控Tomcat服务器操作指引
    作者:乐维社区(forum.lwops.cn)许远Tomcat是一个流行的JavaServlet容器,用于开发和部署JavaWeb应用程序,广泛应用于中小型系统、开发与测试环境、集成环境等场合。Zabbix是一个开源的监控解决方案,广泛用于监控各种网络参数、服务器健康状态以及应用程序的性能,而JMX(JavaManagement......