0%

git 管理

git有啥东西?不就是add、commit、push 、 pull 、reset 、log 、reflog 、status 、tag、 branch 、checkout….
常用也就是这些了,但是,还有很多其他的操作,这些操作是更加面向团队和管理的首先,pr,还有分支管理和分支保护,管理员,fork,ssh

git ssh

ssh就是建立一个安全的下载链接罢了,但是如果是下载私有仓库,那还是得进
行github登录才能实现下载,不是说有ssh就可以直接下载了

git fork

我们经常可以看到github上面好多代码贡献的,比如linux,这个提交量
非常大,而且fork数量也非常多。
fork不等于clone,更不等于zip下载。

zip下载就是下载一个版本或者当前版本,常见就是Tag进行版本管理,这些是没有.git。

clone和ssh就是把整个开发的过程的git都下载过来,这个,就是直接把开源库复制一份到自己的分布式主机上面。此时是无法通过push提交到原仓库的。那为什么gitee那些又可以拉人,又可以下载和提交呢,那是因为所有clone都是默认为不可push,只有管理员去修改我的访问和提交权限,我才能提交和修改。

fork 这个就是不一样了,是自己生成一个仓库副本,就是在github上面直接生成这个这个仓库,此时,就要切换为一个新分支,然后,正常提交,然后push给新分支,然后选择pr就可以提交到原仓库中,然后等待原仓库管理员去解决冲突和选择、审批等。如下操作:
1.首先打开一个项目,然后直接fork,点击创造fork。这样就会在我的账户下面生成一个fork副本。
2.此时可以使用git clone 或者ssh来下载。
3.git remote add upstream https://github.com/original-author/project-name.git
4.新建分支 git checkout -b feature-or-bugfix
5.修改和提交代码
6.推送分支 git push -u origin feature-or-bugfix
7.创建Pull Request (PR) 跟提交分支一样的操作是
8.等待原仓库管理人进行处理

如果不想要fork,就进入到fork仓库,点击settings中的delete仓库,对,就是删库就好了。

git branch保护

为什么要分支保护啊?我们在看gitee一些项目,可以看到main分支上面有一把锁,这个时候就是管理员把main分支进行上锁,就是变为可读模式,此时就是无法修改。使用的是Lock branch这个一般是短期无需修改才会选择这个状态,这个是管理员随时可以修改得状态。

一般常用就是Require a pull request before merging,就是只能通过pr合并来实现main分支的更新,不允许直接push提交,这样就可以让整个结构和流程更加简洁和方便管理,防止胡乱提交导致版本错误等诸多问题。

此时还可以Require approvals,这个就是添加审核人,审核人必须发言和评价才能合并。

使用Do not allow bypassing the above settings就是管理员和用户一视同仁,这个不建议勾选,因为管理员就是为了快速方便管理分支和仓库,只要管理员认真点,仔细点就行了,只要分支保护做的好,都是ok的

git 令牌 & git 小组

git 令牌
这个就是把我的私有仓库通过令牌的方式,给与他人使用权限。这个就是在个人然后点击settings,然后点击Developer settings,
然后选择token,两个都可以new和classic,无脑选择new就行了,然后就是权限限制读写操作,然后就仓库限制,时间天数限制。然后点击确认就是生成令牌。使用git 令牌下载只能使用https下载不能使用ssh下载。下载的时候,直接git clone 仓库,然后写上我的名字,然后就输入这个生成的token就可以下载,分享啦。

git 小组
就是拉人到自己的仓库,一起创作进入仓库,点击settings,然后点击collaborators,添加用户的邮箱,然后进入即可,然后就是小组啦自然还有那些企业版啥的,我目前还没用过,我感觉大差不差应该都是这个原理,企业版就是,可以把所有人拉到一个开发组,然后方便拉近仓库和管理。

git 小组踢人
就是在collaborators中,点击删除就好了

git 小组主动退出
点击右上角的头像,点击setting,点击仓库,就可以看到共同工作的仓库,里面有leave按键,然后确认就好了

git总结

git一句话分布式管理,这个本质就是为了团队,大家一起工作,小公司可能用不到git,但是为了项目管理,为了整体发展git的重要性非常高。git只是自己一个人用,那么就是失去了分布式的意义了,这不就是变成了一个同步了吗?没用的!!!

起因

我本人是不喜欢用这些什么库的,代码就是要开源才能大家一起进步,但是,这些终究只是极客幻想。现在还是打工仔呢,等日后有机会开工作室、独立研发或者商单的时候,在考虑这种事情吧。那么公司的代码,肯定就是要保密啦。保密的办法就是转为库

a静态库

静态库windows是.lib linux是.a文件,这两种静态库的二进制格式不一样,所以不能相互使用。

a静态库生成
1
2
3
gcc –o mylib.o –c mylib.c
ar rcs libmylib.a mylib.o
#此时生成了libmylib.a
a静态库使用

这个就非常简单了,就是把头文件添加到项目中,然后添加库文件到项目中,然后#include “mylib.h”即可。然后根据头文件中的数据和函数进行调用即可

so动态库

linux和UNIX的动态库一般是.so,这个好处就是,不用编进代码之中,缺点就是想要终端或者平台提前准备好动态库文件哦。#动态库库生成 #可以考虑写成makefile文件或者shell文件
windows的动态库一般是dll。

so动态库生成
1
2
3
4
gcc –fPIC -o mylib.o -c mylib.c		 	#(生成符号表)
gcc –shared -o libmylibs.so mylib.o #(防止重复加载)
#或者
gcc –fPIC –shared –o libmylibs.so mylib.c (可以省略 -c 和 .o后缀文件名)
so动态库使用
1
2
#方法一
gcc –o test test.c -lmylibs #后面就是接上动态库所在文件夹即可
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 <stdioh>
#include <dlfcn.h>
int main (void)
{
void *handle;
char *error;
void (*testFunction)(); //要变成指针函数来用(由符号表引出的指针)
if ((handle = dlopen(“/usr/lib/libmylibs.so”, RTLD_LAZY) == NULL)
{
printf (“dlopen error\n”);
exit(1);
}
testFunction = dlsym(handle, "testFunction");
if ((error = dlerror()) != NULL)
{
printf (“dlsym error \n”);
exit(1);
}
testFunction ();
dlclose (handle);
exit(0);
}
lib静态库
1
2
3
4
5
6
7
8
9
#方法一 打开cmd
gcc -c test.c
ar -cr libadd.a add.o
#编译
gcc main.c libadd.a -o test.exe
#或者
gcc main.c -ladd -L./ -o test.exe
#进行测试
test.exe

#方法二 打开keil 直接勾选output中的生成lib文件,生成库的时候不要把main和h文件添加进来,main是最好不要,因为可能冲突。h文件可有可无。然后点击编译即可。
使用方法和.a文件是一样的

dll动态库

我们一般使用vs或者devC++那些ide就可以生成了,虽然使用gcc命令行也可以实现编译,但是毕竟别人ide做了适配啥的,还是一键搞定方便。用vs举例子,点击生成新的动态库项目,然后在解决方案中的源文件和头文件目录中,添加需要的代码。然后选择X64模式,release版本,编译就可以了。然后就生成了dll文件lib文件,把这两个+代码头文件复制到新的项目所在的目录中。新的项目可以使用vs的控制台模式项目,然后在解决方案的资源文件中添加lib文件,不是dll文件哦,再把头文件添加到头文件目录中。然后声明头文件,调用即可。

cpp文件编写

1
2
3
4
5
6
7
8
#include "pch.h"    //必要的头文件
#include "myDll1.h" //自己的头文件

//一个简单的函数
int funTest(int a, int b)
{
//....
}

h文件编写

1
2
3
4
5
6
7
8
#pragma once  //规定只编译一次

//宏定义,增加可读性
#define DATAEXCHANGEDLL_API __declspec(dllexport)

//按C进行编译,声明.cpp中的函数
extern "C" DATAEXCHANGEDLL_API int funTest(int a, int b);
//为了兼容c,如果全是cpp文件的话,那么就无所谓了

对了vs还可以选择静态库项目,也是生成lib文件,使用的话,就是在解决方案的资源文件
中添加lib文件就可以了。自然也是可以gcc生成了,谁没事找事呢(doge)

结语

有些公司会选择visual GDB来进行开发stm32,这个时候就是linux的静态库,自然keil才是开发stm32的主流,所以还是使用的.lib文件,自然两者都会终究是好的

本人菜鸡,没想到因为代码写的太乱了,导致出现头文件内容错误bug

起因

因为我在bsp代码中使用了,一个其他h文件中的枚举作为参数。一开始枚举定义在bsp,所以,所有都正常,但是,我想集中和降低耦合,然后就出现问题了,bsp.h中的函数和一些变量显示没定义。

原因

因为存在依赖,因为c和cpp很多语言都是顺序编译的,很少有那种就是先不报错,等待延后依赖的。所以,一旦发现没定义就会报错,停止编译了,这个其实也是很合理的,如果你是linux的话?编译了几小时,然后依赖真的没有,那完蛋了,真的浪费时间了,所以,就需要人为控制和人为降低耦合来实现

例子

这是一个非常经典的错误,就是后面头文件对前面的头文件存在非常明显的依赖。如果日后,遇到这种问题就说明你写的和我一样是一坨,哈哈哈。就是耦合度太高了,还是建议降低耦合,尽量模块化,独立接口啥的

结语

低耦合高内聚模块化,天天喊,天天叫。哎~~ 如果真的非常依赖bsp的话,可以考虑这样写哦。因为#ifndef #define #endif和 #pragma one的原因,那些头文件只能加载一次,但是如果内部又加载其他的头文件就可以导致,下面的没执行完毕,导致很多数据丢失,编译失败。

错误原因:因为调用头文件,成环了,所以,导致缺失。 头文件有先后顺序才能执行。

这些本质都是因为这些文件关联了其他文件,没实现线性树状的结构导致的高耦合错误。

开始填坑

这里就直接使用arduion ide进行开发,在芯片库中搜索esp32就可以找到esp32的所有资源和下载。我们要知道ESP32是搞物联网的,如果你说你要用esp32来当纯主控,那确实不太好的,因为引脚少,功能有限制。比如有一些引脚,是只有only read pin,因为他和arm的架构不一样,而且是双核,所以使用的时候,需要注意的。甚至有一些同时支持wifi和蓝牙。

如果是用PlatformIO或者espressif idf的话,自己去vscode上面下载这个插件就可以了,其实也是大差不差的。但是arduion ide一键操作,无脑啊,还是非常方便就是界面有点老。其实都是一样的啊

你甚至可以espressif idf直接安装本地,然后使用命令行编译,所以他们其实都是调用这个来作为编译链的,不同的ide,不同界面,本质还是选板子,选串口,选择实例,烧录下载仿真。

使用microPython的话,可以查考如下的博客。

MicroPython开发ESP32

arduion ide

使用arduion,有一个很明显的特点,第一就是cpp,第二两个函数 setup和loop,有点hal的意思了我们使用esp32的目的,肯定是为了一件事,就是联网和io控制。所以点击实例中的esp32实例,修改一下连接的wifi密码和名称,设模式为用户找到一个联网的模板进行修改即可,修改添加一个mqtt模块,添加一个url功能,就能搞定大部分工作了。剩下的都是主控的操作了,什么io输出啊,iic那些

代码编译和烧录

编译就是找到上面的按键进行编译就是了。烧录的话,是通过usb口的iap下载烧录的,所以你得选择开发板和串口,编译成功就是烧录下载了此时还可以进入调试界面来看出现运行状态

总结

开发esp32非常简单的,因为很多东西都是模块化好的库,使用就好了,因为物联网发展的非常好,而且使用的是cpp开发,而且使用的cpp语法也是比较简单的,所以,一般都是找一个模板就可以开始操作了,联网成功之后,剩下就是本地主控操作了。就是一个mcu主控+wifi模块罢了。只不过开发arm mcu的时候,是通过at指令来控制wifi的连接和收发,但是本质还是通过主控去对数据进行处理。此时就是把wifi和主控合二而一,但是放心。厂商已经写好硬件驱动,直接调用api就可以连接和收发了。还可以进行分频和扩频,蓝牙和wifi复用功能,这些都是调用厂商的api,找一个demo就好了

对了,还有一件事,esp32是可以上rtos的哦,rtt和freertos那些都是可以的。

起因

为什么我要说使用这个来开发,因为我们都知道python开发速度快,这一切都是因为有人写好了底层和模块,所以开发速度非常惊人。缺点就是非常明显的消耗资源和低效率。那我为什么还要说这个呢?因为环境变了,大家开始卷嵌入式了,现在已经可以使用高级
语言来开发硬件编码了,那么随着时间的发展,这方面就会边卷,我也是出于学习的态度来了解它,其实大部分情况下,资源还是够用的,除非真的非常稀缺

mricopython

这个可以开发什么呢?
第一个就是ESP32
第二个就是arduion
第三个就是openmv的产品等

编译和烧写

就那arduion和esp32来分析吧,两者在选择好设备和串口之后,就可以直接一键编译,然后一键下载并debug了,它们是通过usb来进行烧写,使用iap来处理的。

代码编写

既然有了python的名字,那么肯定就是使用python的语法来编译的,所以,就是调用模块生成和调用实例,通过实例的成员方法来进行io的控制,中断的处理,外设的功能选择等。然后就是同mcu一样的操作方法了,去做主控该做的事情。

总结

mricoPython可以让所有程序员都可以生成产品,这个目标是远大的,也必然导致内卷。而且它本身的效率也是有问题,虽然python确实是基于c的,但是泛型和容器,那些没有初始化,那些动态的资源,必然导致会消耗更多资源。作为资深的嵌入式开发者,我们对于资源的处理和管理应该非常敏感和仔细才行。

起因

学习mcu的时候,就在想这个板子是怎么联网的,但是当时老师没教,而且我当时学的东西太多了,确实没那么精力去深究,毕竟还没用到

MAC地址

MAC,Media Access Control Address的全称叫做媒体访问控制地址。你可以认为就是硬件地址,这个一般都是集成在cpu中的,少部分是外接的。我们都知道要联网肯定要ip,其实关于MAC并没有强制性要求的。但是mcu中有要求的。

联网流程

首先是CPU,然后就是得到MAC地址,然后通过PHY相当于是芯片外设,然后才可以接上RJ。RJ有RJ11和RJ45,这两个主要是线的数量和速率不同。

配置方法

打开cubemx,点击ETH,然后设置网口即可,其他的不用配置了。可以设置接收中断。反正就和串口一样使用就好了,那些电平变化,数据处理都是硬件搞定了,不需要我去管理了,调用给的回调和API即可完成操作

其他方法

还可以使用qspi来实现联网,这个是什么操作呢?其实看看usb拓展坞上面的网口转usb就知道,其实网口是可以通过ic进行转换的,只要通讯速度跟得上,所以就可以使用并行的usb和spi进行转换操作。

https://doc.embedfire.com/module/module_tutorial/zh/latest/Module_Manual/spi_class/w5500.html
别人写好了驱动,进行引脚修改为了对应的spi调用收发函数,状态判断就可以了。初始化肯定也得有哦。ping成功之后,就可以get去访问了,自然mqtt那些也是可以用的。此时就不用主控去配置网络接口通过spi就能联网。

结语

使用mcu的硬件功能的时候,很多时候都需要配置给一些额外硬件设备。比如硬件iic,就需要外部上拉电阻;网口就要两个硬件来实现电平变化;can和485都需要差分电平芯片;就连串口ttl都最好加一个电平稳定芯片为好。

今天,分享一些快速入门linux驱动的路子。

起因

今天上班摸鱼看linux内核,感觉一下子就醒悟了,一下子就通透了,脑子里面终于有一种OS的感觉,资源的分配啊,线程管理,进程管理,内存管理的思路了。

OS

要知道OS是分等级的
1>.微内核 鸿蒙 QNX、Minix
2>.宏内核 linux windows
3>.混合内核 windows XNU内核
4>.rtos rt-thread freertos ucos
5>.裸坤

而且rtos也是有级别的
POSIX 的RTOS标准
PSE51, PSE52, PSE53, PSE54 共四个级别;
Minimal Real-time System Profile IEEE Std 1003.13 PSE51,基础 RTOS
Real-time Controller Profile IEEE Std 1003.13 PSE52,带有简单的文件系统…等
Dedicated Real-time Profile IEEE Std 1003.13 PSE53,拥有网络功能…等
Multi-Purpose Real-time Profile IEEE Std 1003.13 PSE54,完整的文件系统,带有 Shell 组件…等

1.最简单就是裸机编程了,就是顺序循环判断走下去就完事了。

2.然后就是freeRTOS和ucos了,这个就上一个台阶的难度,但是就是几个api用来用去,规划好每个任务的优先级和工作时间,利用好空闲任务,利用好内存资源,多线程就完事了

3.就是有点难度了,我这边建议学rtt了,因为rtt是仿照linux的,nano版本和标准版本是没有设备树这东西。首先呢,纯应用开发的话,和上面的rtos是差不多的。但是rtt有一个特殊的操作,就是命令行操作。没听错就是命令行,这个其实是uart串口,而且这些命令行没有linux那些的自带信号,比如CTRL+C CTRL+Z可能无法打断,需要手动添加接受结束才行,而且也是拥有驱动开发操作的。

4.linux了,这个就是在上面的基础上面多了一个设备树的东西和平台开发,其他的真差不多了,rtt也是有kconfig和menuconfig的。

5.后面那些就非常复杂了,其实学会linux,如果能够如火纯情,天下无敌

RTT的驱动开发和BSP

rtt标准和nano版本没有设备树,所以,所有功能实现都要依赖BSP,只有正确的驱动开发加上Kconfig,才能搞定BSP。搞stm32开发的时候一般是结合stm32Cube进行的开发,所以,实际上难的东西都没了,就是把那些gpio的复用功能再用init、open、write、read、control、callback、 close这几个函数进行封装。就是类似于linux的file_operations一样。但是这一步,你并没干啥,为什么这么说呢?因为代码是cube生成的,很多时候只要按照官网给的默认配置,进行cube生成代码,rtt就能进行编译,然后自动链接上去,就是rtt没有给你写这些驱动,他们自己以cube为模板进行驱动设计。只需要做的就是find设备,open init 调用那几个api即可,非常人性化。做BSP的就比较麻烦,大家得去gitee上面把rtt下载下来,里面就有一个BSP代码,然后跟着官方的教程走一遍就可以了,这里就不多赘述了。

这上面是硬件驱动,就是很底层基础,但明显不是我们想要的。比如我想要一个独立的led自定义驱动功能。首先,思路,使用了led,那么肯定就是占用一个gpio,那么就要产生依赖和绑定就是menuconfig中一定要有绑定关系。就需要在kconfig中设置一下关系。然后就是编写驱动程序,首先就申请设备,操作集,添加注册。使用的时候,就是find,open ,close,control就够了。编写一个驱动程序即可,使用时候遵循驱动指定的cmd指令就可以实现了。

一般使用rtt都是mcu版本,而且单核为主,多核也有,但是目前是支持多核同构。如果是异构的话,那么可以考虑使用freertos的方案就支持异构多核。如果是smart版本的话,其实就是和linux基本无异了,需要自己编写设备树,需要自己编写驱动代码。今天2024-04-10去到深圳参加了rt的线下会。主要就是为了解决异构问题还有linux与rtos之间的组合,就是大核是linux,小核心通过协议去接收来自大核心的任务和指令,就是dma那种操作。

驱动很多时候都是不需要做的,一般都是做了适配,就算添加,也只是添加一些硬件功能就ok了,也就是cube生成,kconfig修改就可以了。如果说新的国产平台rtt没有支持那么就难搞了,所有驱动和底层都要自己一个一个添加哦。rtt可以实现其他驱动,不过,需要根据范例进行模仿。

结语

加油兄弟们,千万不要呆在自己的舒适区中。不要自我感动和欺骗了。这样只是在加速淘汰自己,他人的堕落,正是推动你向成功的人前进的动力。

起因

今天在菜鸟编程中复习cpp的时候,看到了异常处理。就是try catch throw 三兄弟。python中就是try except else finally raise
两者是很相似的。中间一个个代码如下面所示

异常处理

就是try尝试运行,错误就到catch,catch中进行错误捕捉。还有一个就是throw,这个就是运行的时候,直接抛出错误这个就像人为中断错误,毕竟catch捕捉的东西有限。一旦执行throw就会立刻停止程序。

Const
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
#include <iostream>
#include <exception>
using namespace std;

struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};

int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他的错误
}
}

为什么会出现Const呢?首先,
const char* a
char* const b
const char* const c
const void * fun()
这四个都是见怪不怪的用法了。
但是我看到一个
const void *what() const throw()
重写throw的功能。如下代码所示

结果如下

MyException caught
C++ Exception

非常有意思,为什么函数还可以const呢 ?const char * what () const throw ()

我去网上和编译实测了一下,发现确实可以的。但是有限制
1.只能是结构体和类里面的成员方法(cpp中类和结构体都可以继承和实现函数的。)
2.只能调用同为const的函数
3.不能修改类里面的成员对象
4.其他函数可以正常调用const修饰了的函数
5.只能调用同为const的实例 如 const A a; 此时就能使用a

结语

大家感兴趣的话,可以去尝试用一下,我个人感觉,这个东西还是有意义,反正内部被修改可以作用一些,返回当前状态的接口上面如return systemTiem.hour;

这个博客的目的是为了让以后的笔记都变成md格式的,txt笔记确实不好。所以记录一下
Typora的用法,方便入门

Typora的使用

标题

自然就是先标题,那就是ctrl+数字,1-6从大到小

代码区域

输入”```”就是了,连按3次,会变成一个代码区域,可以选择语言

分割线
1
2
3
4
5
6
"***"
"---"
"___"
***
---
___
网站跳转超链接
1
"[百度一下](https://www.baidu.com/)"[百度一下](https://www.baidu.com/)
下标
1
~
上标
1
^
高亮
1
2
"==高亮=="
==高亮==
插入图片
1
直接右键选择图片插入即可
快捷键

img

以上图片来自(https://zhuanlan.zhihu.com/p/515945667)

结语
1
掌握以上主要的几个操作就可以加快编写速度了,我也要去奋斗努力去了,有机会再见

这是一篇关于我刚学习C语言和C++的时候留下来的关于字符串、数组、指针的问题

起因

今天在看python的时候看到一个表达式字符串,这个东西呢,非常奇妙。
举个例子python里面的字符串有

1
2
str1 = " csmznbo "
str2 = ' csmznbo '

可以看到此时的字符串都是有空格,所以下面的那个单引号的明显不对劲,对了。
这个就是shell脚本里面类似的原样输出
特殊字符串。如下例子

1
2
3
4
#bash
str3='The time now is `data`'
#python
str3='The time now is data'.replace('data',str(time.localtime(time.time())))

其实python的””格式的字符串也是可以做到输出像上面的东西的,f”{}”也是可以的(format)
bash的例子只是引出这种字符串的特殊用法原样输出。下面的替换输出,虽然不是同一
功能,但是也能知道遇到这奇奇怪怪的字符串的时候可以联想起来好玩的东西。

但是这个不是今天的主角。

填坑

这个帖子,主要是为了记录我对指针数组还有字符串的理解。首先呢,我之前总是不理解为什么字符串是不可写和不可修改现在知道了,因为它存在静态区中。但是我学了几个月的C语言我才知道了指针不等于字符串,我当时写那些拼接函数,比较函数,裁剪那些,我都是用数组去实现的。但是学的懵懵懂懂的,我甚至都把数组等于字符串,字符串又等于指针去了,因为用起来真的差不多。其实是错误的,虽然汇编是一样当写关于字符串的函数的的时候,传参指针,返回值那些都是天坑。要知道字符串是不能修改的,所以返回值只能是数组和指针,还有就是指针传参就会导致一些问题。比如不知道数据长度等问题。

关系

指针就是指针,指向一个地址,自己指针又有一个属于自己的地址。大小看处理器位数数组就是连续地址的元素组成,可以装指针,数组,结构体,函数等等

C语言中,字符串就是字符串,没别的东西就是全局区的data段中,不可修改的。当时第一次学cpp的时候,把String当成字符串了,当时也好奇为什么字符串又可以删减修改了,其实就是一个容器,不是字符串,你可以认为里面就是一个数组真正的字符串还是””这个,这个还是不能修改即便是cpp。python中字符串也是不能修改的,每一次的修改,都只是新生成一个字符串。并没有修改原本字符串

结语

这次就是一个知识的填坑和巩固罢了果然基础不牢地动山摇。很多时候都是简单的东西罢了。拜拜,下次继续