C字符串 | 字符串处理函数 | 使用 | 原理 | 实现

news2024/11/8 9:54:50

文章目录

        • 1.字符串的定义
        • 2.函数的使用
        • 3.strlen使用与实现
        • 4.strcpy使用与实现
        • 5.strcat的使用与实现
        • 6.strcmp的使用与实现
        • 7.strstr的使用与实现
        • 8.memcpy的使用和实现
        • 9.memmove的使用和实现

1.字符串的定义

字符串是一系列字符组成的序列,C语言中字符串以\0结尾。由""引起的的字符串常量系统默认会添加\0,区分而由''引起的是字符常量。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    // 方式1:
    char* str1 = "";
    // 方式2:
    char arr1[] = "hello"; // 系统会默认添加一个\0,注意比较arr1和arr2
    char arr2[] = {'h','e','l','l','o'};
    char arr3[] = ""; // 默认一个\0
    // 方式3:
    char* str2 = (char*)malloc(128 * sizeof(char));
    strcpy(str2,"hello world");

    // 注意比较:strlen 和 sizeof的不同
    printf("use sizeof to calculate : char arr1[] = %ld\n",sizeof(arr1));
    printf("use strlen to calculate : char arr1[] = %ld\n",strlen(arr1));

    printf("use sizeof to calculate : char arr2[] = %ld\n",sizeof(arr2));
    printf("use sizeof to calculate : char arr3[] = %ld\n",sizeof(arr3));

    printf("%s\n",str2);
    
    return 0;
}
2.函数的使用

字符串函数传入参数的一个特点类似A of B 的格式,A of B表示BA。以strcpy为例,传入的参数第一个是destination,第二个是source。是将source拷贝到destination中。

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

这里和Linux命令是相反的,例如,cp命令:前面的是原文件(source_file),后面的是目标文件(destination_directory)

cp [OPTION]... source... directory
3.strlen使用与实现

strlen的使用很简单,只需要传入一个char*的指针即可。

size_t strlen ( const char * str )

示例代码:

int main()
{
    const char* str1 = "hello world";
    char str2[] = "hello";

    // %u 是用于格式化输出无符号整数,%zu 格式化输出size_t
    printf("%u %u\n",strlen(str1),strlen(str2));

    return 0;
}

其实strlen的原理也比较简单,字符串是一个字符序列,strlen的工作就是统计一个一个字符,直到遇到\0。实现strlen有三种方式:
方式1:可以使用一个count计数器来统计。一层循环直到遇到\0结束。

size_t my_strlen1(const char* str)
{
    size_t count = 0;

    while(*str != '\0')
    {
        count++;	// 计数器++
        str++;		// 指针++
    }
    return count;
}

方式2:利用指针的加减特性,记录当前位置的地址,然后将指针指到\0位置,减去起始位置。

size_t my_strlen2(const char* str)
{
    const char* start = str; // 记录起始位置
	// 求'\0'的位置
    while (*str != '\0')
    {
        str++;
    }
    return str - start;  // 指针相减
}

方式3:使用函数递归,根据递归的三部曲:1.确定递归函数的参数和返回值 2.确定终止条件 3.确定单层递归的逻辑。这里可以简单的思考,让my_strlen3是一个黑盒,给一个char*的指针就能帮我求出字符串的长度。比如求:求"abc"的长度就要,求"bc"长度 + 1,如果求"bc"就可以又交给my_strlen3函数。

size_t my_strlen3(const char* str)
{
    if(*str == '\0') return 0;
    return 1 + my_strlen3(str + 1);
}
4.strcpy使用与实现

strcpy需要注意的点在于,确保destination的空间足够大,并且可以被修改。当然既然是指针就要保证不能使用空指针。

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

函数的实现:
实现函数困惑的点,soure\0标志结尾,destination又要把\0拷贝,怎么实现。

void my_strcpy(char* distination,const char* sourse)
{
    while(sourse != '\0')
    {
        *(distination++) = *(sourse++);
    }
}

如果像上面这样实现程序,并没有把结束标志的\0拷贝到distination,如果打印distination会出现Segmentation fault (core dumped)

 printf("%s\n",distination);

让sourse赋值给distination然后再去判断\0。其实\0、0、NULL、false本质都是0,0值会判断为假,就退出循环。这里就很巧妙。

void my_strcpy(char* distination,const char* sourse)
{
    while(*(distination++) = *(sourse++))
    {
        ; 
    }
}

其实上面的版本就能实现字符串的拷贝,但如果传入一个NULL,就发生空指针解引用。不能指望程序员来严格遵守规则,需要在程序中避免错误。改进版本:

void my_strcpy(char* distination,const char* sourse)
{
    const char* ret = sourse;   	// 好习惯
    assert(distination != NULL);    // 断言,false就进入函数就报错,错就是错,对就不报错
    assert(sourse != NULL);

    while(*(distination++) = *(ret++))
    {
        ; 
    }
}
5.strcat的使用与实现

strcat是一个字符串处理函数,当然要遵守C字符串的风格的特点,如:字符串的结尾是\0。使用strcat和strcpy的使用特点基本一样,要求destination可以修改,并且要求空间足够大。当然如果详细的用法还是需要查看文档!

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

strcat函数的使用:

int main ()
{
    char arr[128] = "hello";
    strcat(arr," world");

    printf("%s\n",arr);
    return 0;
}

strcat函数的实现

char *my_strcat(char* destination,const char* source)
{
	// 记录destination其实位置,最后返回。
    char * ret = destination;

    assert(destination != NULL);
    assert(source != NULL);
	
	// 将destination移动到\0处
    while (*destination != '\0')
    {
        destination++;
    }
    
	// strcpy拷贝的逻辑
    while(*(destination++) = *(source++)){}
    
    return ret;
}
6.strcmp的使用与实现

strcmp用来比较字符串的大小,标准规定,标准是这样规定,但编译器尊不遵守那就不一定了!

  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字
int strcmp ( const char * str1, const char * str2 )

模拟实现:

int my_strcmp(const char* str1,const char* str2)
{
    assert(str1 != NULL);
    assert(str2 != NULL);

    while(* str1 != '\0' && *str2 != '\0')
    {
        if(*str1 > * str2) {return 1;}
        else if(*str1 < *str2) {return -1;}
        else 
        {
            str1++;
            str2++;
        }
    }
    if(str1 == '\0' && str2 =='\0') return 0;
    else if(str1 == '\0' && str2 !='\0') return -1;
    else return 1;
}

上面一看就是我写的,而下面的版本更加的巧妙

int my_strcmp(const char *src, const char *dst)
{
    int ret = 0;
    assert(src != NULL);
    assert(dst != NULL);

    
    while (!(ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
        ++src, ++dst;
    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;
    return ret;
}
7.strstr的使用与实现
const char * strstr ( const char * str1, const char * str2 );
      char * strstr (       char * str1, const char * str2 );

简单使用:

int main()
{
    const char* str1 = "take it easy,the all things will be ok!";
    const char* str2 = "easy";

    if(strstr(str1,str2) != NULL)
    {
        printf("存在\n");
    } 
    else 
    {
        printf("不存在\n");
    }
    return 0;
}

模拟实现:

char* my_strstr(const char* str1,const char* str2)
{
    char* ret = (char*)str1;
    if(*str2 == '\0') return ret;
    char *s1,*s2; 

    while (* ret != '\0')
    {        
        // 给第二层循环使用
        s1 = ret;
        s2 = (char*)str2; 

        while(*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
        {
            s1++;
            s2++;
        }

        // s2到结尾了,说明已经找到了
        if(*s2 == '\0'){return ret;}	// ret表示返回匹配的第一个字符的地址
        ret++;
    }
    
    return NULL;
}
8.memcpy的使用和实现

memcpy函数是按照字节的方式来拷贝的,遇到 \0的时候并不会停下来。如果source和destination有任何的重叠,复制的结果都是未定义的。
函数的使用:

struct person
{
    char name[128];
    int age;
}person;

int main()
{
    const char* name = "i am lihua";
    memcpy(&person.name,name,strlen(name) + 1);

    int age = 0;
    memcpy(&person.age,&age,sizeof(int));

    printf("name = %s : age = %d\n",person.name,person.age);
    return 0;
}

模拟实现:
实现的思路也比较简单,source按照一个字节一个字节拷贝到destination,需要注意一点是要将void*强转成char*char就是一个字节。

void *my_memcpy(void* destination,const void* source,size_t num)
{
    void* ret = destination;

    while(num--)
    {
        *(char*)destination = *(char*)source;
        destination++;
        source++;
    }
    return ret;
}
9.memmove的使用和实现
int main()
{
    char str[] = "memmove can allow memory cover";

    char *ret = (char *)my_memmove(str + 20, str + 15, 10);

    puts(ret);

    return 0;
}

模拟实现

void *my_memmove(void *destination, void *sourse, size_t num)
{
    void *ret = destination;

    if (destination <= sourse || destination >= sourse + num)
    {
        while (num--)
        {
            *(char *)destination = *(char *)sourse;
            destination++;
            sourse++;
        }
    }
    else
    {
        destination = destination + num - 1;
        sourse = sourse + num - 1;

        while (num--)
        {
            *(char *)destination = *(char *)sourse;
            destination--;
            sourse--;
        }
    }
    
    return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2235733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wxWidgets布局管理及XRC界面使用解惑

wxWidgets布局&#xff0c;通常就是wxBoxSizer&#xff0c;当然还有别的Sizer&#xff0c;就像Qt中的Layout一样&#xff0c;各种布局管理类。只是如今的wxWidgets尚未发展起来&#xff0c;一般其他的你用的比较少&#xff0c;因为这个最简单&#xff0c;最容易布局&#xff0c…

【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 062 &#xff0c;文末自助获取源码 \color{red}{T062&#xff0c;文末自助获取源码} T062&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

【循环引用及格式化输出】

垃圾回收机制 当一个值在内存中直接引用跟间接引用的量为0时&#xff0c;&#xff08;即这个值没有任何入口可以找到它&#xff09;那么这个值就会被清空回收♻️&#xff0c;释放内存空间&#xff1b; 列表在内存中的存储方式 1&#xff09;引用计数的两种方式 x "ea…

31.7K+ Star!AgentGPT:一个在浏览器中运行的Agent

AgentGPT 简介 AgentGPT[1] 是一个可以让你在浏览器中组装、配置和部署自主AI代理的项目。你可以为你的自定义AI命名,并让它去实现任何你想象中目标。它将尝试通过思考要执行的任务、执行它们并从结果中学习来达成目标。 项目特点 主要特点 自主AI代理:用户可以自定义AI并赋…

【大模型】通过Crew AI 公司的崛起之路学习 AI Agents 的用法

AI 技术的迅猛发展正以前所未有的速度重塑商业格局&#xff0c;而 AI Agents&#xff0c;作为新一代的智能自动化工具&#xff0c;正逐步成为创新型公司的核心力量。在本文中&#xff0c;我们将探讨如何利用 AI Agents 构建一家 AI 驱动的公司&#xff0c;并详细了解 Crew AI 创…

【Uniapp】Uniapp Android原生插件开发指北

前言 在uniapp开发中当HBuilderX中提供的能力无法满足App功能需求&#xff0c;需要通过使用Andorid/iOS原生开发实现时&#xff0c;或者是第三方公司提供的是Android的库&#xff0c;这时候可使用App离线SDK开发原生插件来扩展原生能力。 插件类型有两种&#xff0c;Module模…

网页版五子棋——用户模块(服务器开发)

前一篇文章&#xff1a;网页版五子棋—— WebSocket 协议-CSDN博客 目录 前言 一、编写数据库代码 1.数据库设计 2.配置 MyBatis 3.创建实体类 4.创建 UserMapper 二、前后端交互接口 1.登录接口 2.注册接口 3.获取用户信息 三、服务器开发 1.代码编写 2.测试后端…

Jenkins声明式Pipeline流水线语法示例

系列文章目录 docker搭建Jenkins2.346.3版本及常用工具集成配置(ldap、maven、ansible、npm等) docker安装低版本的jenkins-2.346.3,在线安装对应版本插件失败的解决方法 文章目录 系列文章目录jenkins流水线基础1、pipeline1.1、什么是pipeline&#xff1f;1.2、为什么使用pi…

【NLP】使用 SpaCy、ollama 创建用于命名实体识别的合成数据集

命名实体识别 (NER) 是自然语言处理 (NLP) 中的一项重要任务&#xff0c;用于自动识别和分类文本中的实体&#xff0c;例如人物、位置、组织等。尽管它很重要&#xff0c;但手动注释大型数据集以进行 NER 既耗时又费钱。受本文 ( https://huggingface.co/blog/synthetic-data-s…

【数据集】【YOLO】【目标检测】道路裂缝数据集 5466 张,YOLO/VOC格式标注!

数据集介绍 【数据集】道路裂缝数据集 5466 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含一种分类&#xff0c;检测范围城市道路裂缝、高速道路裂缝、乡村道路裂缝。 戳我头像获取数据&#xff0c;或者主页私聊博主哈~ 一、数据概述 道路裂缝检测…

C++用string实现字符串相加

. - 力扣&#xff08;LeetCode&#xff09; -》》》》》题目链接 实现思路&#xff1a;计算数字符串长度并用数组的方式计算出字符位置&#xff0c;用字符的ask码‘0’计算出字符本身。 class Solution { public:string addStrings(string num1, string num2) {string str;int…

easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头

easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头 原版表头和表体字体美化自动拼接错误提示列自适应宽度自动合并单元格使用Easyexcel使用poi导出 在后台管理开发的工作中,离不开的就是导出excel了. 如果是简单的导出, 直接easyexce…

brainpy 动力学编程基础

文章参考&#xff1a; 《神经计算建模实战——基于brainpy》 吴思 【brainpy学习笔记】基础知识2(动力学模型的编程基础)-CSDN博客 Brainpy手册 文章目录 积分器&#xff1a;定义ODE函数数值积分方法 更新函数和动力系统计算介绍什么是brainpy.DynamicalSystem&#xff1f;如…

高级图像处理工具

图像处理-高级 1、功能概览 随着社交媒体的普及和个人创作需求的增长&#xff0c;图像处理成为了日常生活中不可或缺的一部分。无论是专业的设计师还是爱好者&#xff0c;都需要一款强大的工具来帮助他们完成各种任务。今天&#xff0c;我们将介绍一款基于Python开发的高级图…

【Zookeeper集群搭建】安装zookeeper、zookeeper集群配置、zookeeper启动与关闭、zookeeper的shell命令操作

目录 一、安装Zookeeper 二、配置Zookeeper集群 三、Zookeeper服务的启动与关闭 四、Zookeeper的shell操作 前情提要&#xff1a;延续上篇【Hadoop和Hbase集群配置】继续配置Zookeeper&#xff0c;开启三台虚拟机Hadoop1、Hadoop2、Hadoop3&#xff0c;进入终端&#xff0c…

Transformer和BERT的区别

Transformer和BERT的区别比较表&#xff1a; 两者的位置编码&#xff1a; 为什么要对位置进行编码&#xff1f; Attention提取特征的时候&#xff0c;可以获取全局每个词对之间的关系&#xff0c;但是并没有显式保留时序信息&#xff0c;或者说位置信息。就算打乱序列中token…

Python爬虫如何处理验证码与登录

Python爬虫如何处理验证码与登录 Python 爬虫在抓取需要登录的网站数据时&#xff0c;通常会遇到两个主要问题&#xff1a;登录验证和验证码处理。这些机制是网站用来防止自动化程序过度抓取数据的主要手段。本文将详细讲解如何使用 Python 处理登录与验证码&#xff0c;以便进…

《深入浅出Apache Spark》系列②:Spark SQL原理精髓全解析

导读&#xff1a;SQL 诞生于 20 世纪 70 年代&#xff0c;至今已有半个世纪。SQL 语言具有语法简单&#xff0c;低学习门槛等特点&#xff0c;诞生之后迅速普及与流行开来。由于 SQL 具有易学易用的特点&#xff0c;使得开发人员容易掌握&#xff0c;企业若能在其计算机软件中支…

JS实现,防抖节流 + 闭包

防抖&#xff08;Debounce&#xff09; 防抖是指短时间内大量触发同一事件&#xff0c;只会在最后一次事件完成后延迟执行一次函数。 防抖的典型应用场景是输入框的搜索建议功能&#xff0c;用户输入时不需要每次输入都去查询&#xff0c;而是在用户停止输入一段时间后才进行…

安卓编程最方便的读写资料类SharedPreferences,多个APP共享

本文介绍Android平台进行数据存储的五大方式,分别如下: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解这五种方式的特点 第一种&#xff1a; 使用SharedPreferences存储数据 …