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

文章目录

        • 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

景联文科技专业数据标注公司:高质量数据标注推动AI产业发展

在当今数据驱动的时代&#xff0c;高质量的数据标注对于机器学习、自然语言处理&#xff08;NLP&#xff09;和计算机视觉等技术领域的发展起着至关重要的作用。 数据标注是指对原始数据进行处理&#xff0c;标记对象的特征&#xff0c;生成满足机器学习训练要求的可读数据编码…

yelp数据集上识别潜在的热门商家

yelp数据集是研究B2C业态的一个很好的数据集&#xff0c;要识别潜在的热门商家是一个多维度的分析过程&#xff0c;涉及用户行为、商家特征和社区结构等多个因素。从yelp数据集里我们可以挖掘到下面信息有助于识别热门商家 用户评分和评论分析 评分均值: 商家的平均评分是反映其…

YOLO11改进 | 融合改进 | C3k2融合ContextGuided 【独家改进, 两种方式】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文给大家带来的教程是将YOLO11的C3k2替…

【harbor】离线安装2.9.0-arm64架构服务制作和升级部署

harbor官网地址&#xff1a;Harbor 参考文档可以看这里&#xff1a;部署 harbor 2.10.1 arm64 - 简书。 前提环境准备&#xff1a; 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…

InfluxDB 2 关闭pprof

背景&#xff1a; Go 语言的 net/http/pprgf包如未配置正确暴露在公网容易引起敏感信息泄漏问题&#xff0c;导致源码等信息泄漏。 influxdb 2 默认是开启pprof的 使用 localhost:8086/debug/pprof/goroutine?debug1 可以看到接口暴露的信息 如何关闭pprof 官方文档&…

CJ/T188-2004 详细介绍

REDISANT 提供互联网与物联网开发测试套件 # 互联网与中间件&#xff1a; Redis AssistantZooKeeper AssistantKafka AssistantRocketMQ AssistantRabbitMQ AssistantPulsar AssistantHBase AssistantNoSql AssistantEtcd AssistantGarnet Assistant 工业与物联网&#xff1…

阿里云k8s-master部署CNI网络插件遇到的问题

问题 按照网络上的部署方法 cd /opt/k8s # 下载 calico-kube-controllers配置文件&#xff0c;可能会网络超时 curl https://docs.projectcalico.org/manifests/calico.yaml -O kubectl apply -f calico.yaml 试了很多次都不行&#xff0c;k8s-master都是Not ready的状态 ca…

Netty篇(学习前言)

目录 一、为什么使用Netty 1. Netty编程相比NIO编程的优势 2. Netty 相比其它网络应用框架的优势 二、让我们走进Netty 1. 简介 2. 设计目标 3. 主要特点 4. Netty的作者 5. Netty 的地位 6. Netty 的优势 五、Netty版本说明 六、Netty架构设计 1. 线程模型基本介绍…

C/C++使用AddressSanitizer检测内存错误

AddressSanitizer 是一种内存错误检测工具&#xff0c;编译时添加 -fsanitizeaddress 选项可以在运行时检测出非法内存访问&#xff0c;当发生段错误时&#xff0c;AddressSanitizer 会输出详细的错误报告&#xff0c;包括出错位置的代码行号和调用栈&#xff0c;有助于快速定位…

JavaScript基础语法部分-黑马跟课笔记

一、Javascript介绍 1.JavaScript是什么&#xff1f; 1.是什么&#xff1f; 是一种运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;实现人机交互效果 2.作用&#xff08;做什么&#xff1f;&#xff09; 网页特效&#xff08;监听用户的一些行为让网页做…

【MongoDB】MongoDB的Java API及Spring集成(Spring Data)

文章目录 Java APISpring 集成1. 添加依赖2. 配置 MongoDB3. 创建实体类4. 创建 Repository 接口5. 创建 Service 类6. 创建 Controller 类7. 启动 Spring Boot 应用8. 测试你的 API 更多相关内容可查看 Java API maven <dependency><groupId>org.mongodb</gr…

非线性关卡设计

【GDC】如何设计完全非线性的单人关卡_DOOM (bilibili.com) 本文章算是此视频的简单笔记&#xff0c;更详细还请看视频 设计完全非线性关卡强调自由移动和沙盒式玩法&#xff0c;鼓励玩家进行不可预测的移动和空间探索。讲解者分享了设计此类关卡的具体步骤&#xff0c;包括明…

(蓝桥杯C/C++)——基础算法(下)

目录 一、时空复杂度 1.时间复杂度 2.空间复杂度 3.分析技巧 4.代码示例 二、递归 1.递归的介绍 2.递归如何实现 3.递归和循环的比较 4.代码示例 三、差分 1.差分的原理和特点 2.差分的实现 3.例题讲解 四、枚举 1.枚举算法介绍 2.解空间的类型 3. 循环枚举解…

神经网络基础--什么是正向传播??什么是方向传播??

前言 本专栏更新神经网络的一些基础知识&#xff1b;这个是本人初学神经网络做的笔记&#xff0c;仅仅堆正向传播、方向传播就行了了一个讲解&#xff0c;更加系统的讲解&#xff0c;本人后面会更新《李沐动手学习深度学习》&#xff0c;会更有详细讲解;案例代码基于pytorch&a…

代码随想录算法训练营第三十七天 | 完全背包 518.零钱兑换 Ⅱ 377.组合总和Ⅳ 70.爬楼梯(进阶版)

完全背包&#xff1a; 文章链接 题目链接&#xff1a;卡码网 52.携带研究材料 与01背包的区别在于物品数量无限&#xff0c;因此同一种物品可以取多次。 递推式如下&#xff1a; 二维&#xff1a;dp[i][j] max(dp[i - 1][j], dp[i][j - weights[i]] value[i])&#xff0c;因…

C语言心型代码解析

方法一 心型极坐标方程 爱心代码你真的理解吗 笛卡尔的心型公式&#xff1a; for (y 1.5; y > -1.5; y - 0.1) for (x -1.5; x < 1.5; x 0.05) 代码里面用了二个for循环&#xff0c;第一个代表y轴&#xff0c;第二个代表x轴 二个增加的单位不同&#xff0c;能使得…

C语言网络编程 -- TCP/iP协议

一、Socket简介 1.1 什么是socket socket通常也称作"套接字"&#xff0c;⽤于描述IP地址和端⼝&#xff0c;是⼀个通信链的句柄&#xff0c;应⽤ 程序通常通过"套接字"向⽹络发出请求或者应答⽹络请求。⽹络通信就是两个进程 间的通信&#xff0c;这两个进…

字符串接龙 /单词接龙 (BFs C#

卡码网 110和 力扣127 和LCq 108题都是一个解法 这两道题乍一看在结果处可能不一样 力扣要求 字符串里边必须包含对应的最后一个字符 而110不需要最后一个字符 但是在实验逻辑上是一致的 只是110需要把如果在set中找不到最后一个字符就直接返回0的逻辑删去 就可以了 这就是…

Transformer和BERT的区别

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

python操作MySQL以及SQL综合案例

1.基础使用 学习目标&#xff1a;掌握python执行SQL语句操作MySQL数据库软件 打开cmd下载安装 安装成功 connection就是一个类&#xff0c;conn类对象。 因为位置不知道&#xff0c;所以使用关键字传参。 表明我们可以正常连接到MySQL 演示、执行非查询性质的SQL语句 pytho…