【C++】手把手教你模拟实现string类

news2024/11/19 0:42:28

模拟实现string

    • 前言
    • 类的成员变量
    • 构造函数
    • 析构函数
    • size和length
    • [ ] 重载
    • 迭代器
    • 赋值运算符重载和拷贝构造函数
      • 拷贝构造函数
      • 赋值运算符重载
      • 现代式写法
    • reserve 和 resize
      • reserve
      • resize
    • 字符串追加
      • push_back
      • append
      • +=
    • insert
      • pos位置插字符
      • pos位置插字符串
    • erase
    • >> 和 <<
      • <<
      • >>
    • find
    • substr
    • 最后一点
      • vs上的
      • gcc下

在这里插入图片描述

前言

本篇博客是结合上一篇string介绍中的内容来讲的,如果对于string不熟悉的话,可以点击下面的传送门先看看:
【C++】string介绍

为了和库中的string做区分,我下面的所有代码都是放在FangZhang命名空间中写的:
在这里插入图片描述
如果不明白为什么这样做的话,可以看我第一篇关于C++的博客:从C语言入门C++的基础知识中关于命名空间的知识。

类的成员变量

在这里插入图片描述
_str是用来存放字符串的,包括 ‘\0’ 。
_size用来记录当前字符串中的有效字符个数。
_capacity用来记录有效字符的总个数。

_size和_capacity都不记录 ‘\0’ 这个字符。

例子:若当前字符串中保存有 hello
那么如下图下所示:
在这里插入图片描述
在vs2019中,标准库中的类对象初始化时会直接将capacity开到15(如果字符个数小于15的话),即使是空对象也是。

在这里插入图片描述

下面的模拟实现肯定不会像vs里面一样,只是简单的实现一下,更方便了解string类,不然我都直接去写编译器去了😂。

构造函数

我的写法:

string(const char* str = "")
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

这里缺省参数给的是 “”, 这个就是个常量字符串,里面会有 ‘\0’ 作为结尾。有的同学可能会写成 “\0”,这样的字符串其实里面有两个 ‘\0’,写起来反而有点画蛇添足了。

前面在讲类和对象时,说了构造函数尽量使用初始化列表,但是这里初始化列表不好用。

例如:
在这里插入图片描述
这样不能直接赋值,因为前面成员变量中_str是char * 的,但是这里的参数是const char *的,不能直接赋值, 会发生权限放大,所以初始化列表不能这样直接搞。

还有这样的:
在这里插入图片描述
注意,若初始化的时候没有给值,不能初始化为nullptr。不然下列场景会报错:
在这里插入图片描述
这里用到了c_str,讲下这个:
在这里插入图片描述

其实就是返回成员变量_str,也就是字符串的地址,直接用const修饰上,防止被修改,不管是不是const对象都不能修改。

那上面的例子就好说了,就是不能直接打印nullptr,打印了就相当于解引用空指针了。

还有这样的:
在这里插入图片描述
认为这样就能成功了,但是这里的初始化顺序不是按照初始化列表中的,而是类的成员变量的声明中的。这在我前面类和对象的介绍中也是讲了这一点的。
我这里的声明是:
在这里插入图片描述
也就是说,这里初始化时会先初始化_str,但_str中又用到了_capacity,而_capacity还没有初始化,是未知数,一个很大的数,所以这里初始化是有问题的。

在vs2013下这个开空间的时候会直接报错bad allocation。

但在vs2019下会先开空间,然后析构的时候再报错。
在这里插入图片描述
反正就是错的,不要这样写。

最上面的那个就是标准写法。

析构函数

析构没啥好注意的,就是 delete 的时候加 [ ]
在这里插入图片描述

size和length

很简单,size就是length,都不能被修改,给一个就行。
在这里插入图片描述

[ ] 重载

这个可以说是 string 里面最有用的。

要实现两个,一个const对象用,一个非const对象用。

char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

char& operator[](size_t pos)const
{
	assert(pos < _size);
	return _str[pos];
}

然后就没什么好说的了。
直接给个例子:
在这里插入图片描述

迭代器

这个也比较重要。
其实string的迭代器底层就是指针。
长这样:
在这里插入图片描述
在这里插入图片描述

把用例给出来:
在这里插入图片描述

这个迭代器实现了就能用范围for了,因为范围for的底层就用的是迭代器。

在这里插入图片描述

赋值运算符重载和拷贝构造函数

将这两个放一块是因为若类的成员变量中定义时必须要动态开辟空间,则二者在使用时都会出现深浅拷贝的问题。

拷贝构造函数

这个比赋值简单点,先说这个。

首先,这两个函数若自己不提供,编译器会默认提供的,但是编译器提供的只是浅拷贝,这一点在我讲类和对象那三篇的时候也是说过的。

那就先给出浅拷贝的情况:
代码如下:
在这里插入图片描述
我这里没有自己实现拷贝构造,运行:
在这里插入图片描述
程序崩掉了,原因就是 s1 和 s2 中的 _str 是指向同一块空间的。
在这里插入图片描述
当代码运行完了,析构的时候会析构两次同一块空间。第一次析构s2的时候没事,但是第二次再次析构s1时就是非法行为了。因为第一次 delete s2 中的 _str 时已经把而这指向的同一块空间还给系统了,第二次再delete时就属于私闯民宅了,在米国可是犯法的,屋子里的人可以直接向你开枪的。

所以这时候就需要我们自己写拷贝构造。
代码如下:
在这里插入图片描述

此时再运行上面的例子就不会出问题了:
在这里插入图片描述
因为s1和s2指向的空间是不一样的:
在这里插入图片描述
此时再析构,不会出现释放同一片空间的情况。

最后代码如下:

string(const string& s)
	:_str(new char[s._capacity + 1])
	, _size(s._size)
	, _capacity(s._capacity)
{
	strcpy(_str, s._str);
}

赋值运算符重载

这里难度稍微上升一点。

先说编译器默认给的赋值运算符重载:
在这里插入图片描述
跑起来出现浅拷贝。

下面说自己写的赋值运算符重载。
s1 赋值给 s2 时有几个注意事项:

  1. s1和s2的空间不相同怎么办
  2. 自己给自己赋值的情况
  3. new失败了,怎么办

第一点:不论谁的空间大。都将s2的空间重新开,开为跟s1一样大。因为如果出现s2空间很大,s1空间很小,就会出现非常浪费的情况;s2的空间小于s1的,又要重新给s2开空间,再把s1拷过去。两种情况和一块,不如直接给s2重开。

先将代码给出:
在这里插入图片描述
运行起来,不会出现浅拷贝的情况:
在这里插入图片描述
能跑,但是不够完善。

再说第二点:如果自己给自己赋值
如果是上面的实现方式:
在这里插入图片描述
结果乱码。
看代码:
在这里插入图片描述
s2给自己赋值的时候,进去先释放其本来的空间
在这里插入图片描述
开新空间,然后再strcpy,就会将新开的空间中的内容拷回去,此时新开的空间中的内容还没有初始化,就会导致内容中存放的是未初始化数据。此时赋值结束,就会打印乱码。

再把代码改改:
在这里插入图片描述
再运行,就没问题了。
在这里插入图片描述
再说第三点:new失败
可能开空间比较大时就会new失败,new失败的时候会抛异常,利用这一点,写下如下代码:
在这里插入图片描述
运行:
在这里插入图片描述
跑起来很顺利。

说一下为什么:
在这里插入图片描述
但前面的代码中
在这里插入图片描述

所以最后的代码如下:

string& operator= (const string& s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

现代式写法

这个标题是什么意思?

就是上面的两个还可以改成别的写法。
叫现代式写法,不如叫资本主义写法。

拷贝构造

看:
在这里插入图片描述
这里的三个swap不是我自己给的,是标准库中提供的。
STL中提供了一些简单算法,其中就包括swap,这个在我讲模板的那篇中也说了的,这里再提一下。
在这里插入图片描述
在这里插入图片描述

分析一下:
在这里插入图片描述
如下:
在这里插入图片描述

这里编译器做了优化,会自动将this的所有内容初始化。
在2013中是不做初始化的,this中的所有的值都是随机值。

不然标准写法应该是这样:
在这里插入图片描述

然后还可以简化一下,就是将下面的那些库中的swap换成自己类中的:
在这里插入图片描述
可能有的同学看不懂,没事,看解析:

在这里插入图片描述

赋值运算符重载

看代码:
在这里插入图片描述
其实这里判不判断相等都无所谓了,只要tmp给出了,就算相等,交换了也没有什么效果。

可以不判断相等,但是还有更绝的写法:
在这里插入图片描述

这样也是成立的。因为tmp是传值的方式传参的,就是新定义的对象。

到这里两个关于深拷贝的函数代码就算彻底讲完了。

再说点细节上的东西。

库中的swap和我们自己提供的swap有什么区别。

首先,先把库中的实现给出来:
在这里插入图片描述
如果我这里用库中的swap来交换两个string对象的话:
在这里插入图片描述
可以实现,但是可以看到,库中提供的swap交换的时候全是深拷贝。
传参深拷贝,赋值深拷贝。都给原来的两个对象拷没了。
看:
在这里插入图片描述
地址变化非常大,因为全是深拷贝,不断的在开新空间,就会导致效率变低。

再用一下我们自己实现的:
在这里插入图片描述
可以看到地址就没变,还是那两个地址。

所以说,库中的swap对于交换自定义类型的时候要慎用,尤其是当自定义类型中有动态开辟空间的,若此时用库中的swap会产生一堆深拷贝,效率就会降低,不如自己实现swap。

我们自己实现的swap,对于带动态开辟的成员的自定义类型的成员来说只是内部成员值的交换,地址不会发生改变,相对而言效率会更高一些。

还有一点就是在赋值重载和拷贝构造中是不能使用库中的swap来直接交换两个对象的。

看:

对于赋值运算符重载

在这里插入图片描述
如出现上面的代码,会直接崩掉。
在这里插入图片描述
因为在库中的swap中,赋值的时候会用到赋值运算符重载,而赋值运算符重载又要用到库中的swap,两个互相调,这样就死循环了。

对于拷贝构造

和上面的赋值运算符同理,传参的时候要调用拷贝构造,而拷贝构造又要调用库的swap,又死循环了。

最后一点,库中的swap和自己实现的swap不是函数重载,函数重载是在同一作用域下才会发生的,而库中的swap和我们自己实现的swap不是同一作用域。

我们的swap在类中,而且还套在命名空间里。
库里的swap在std中,虽然展开了,但也是在全局中的。

二者的实现是不在同一个作用域下的。

reserve 和 resize

reserve

先说reserve()
按照库中的reserve来。

在这里插入图片描述
库中的reserve没有缩小这一说的。
然后这里实现的时候要考虑new失败,所以搞了个tmp。
其他就没什么要注意的了,跟拷贝构造很像。

然后这个说完就可以说字符串追加了,但是还要说一下resize。

resize

代码如下:
在这里插入图片描述
细节就不讲了,很简单。

给几个例子:

在这里插入图片描述

字符串追加

三个:

  1. push_bach(专门追加字符)
  2. append(专门追加字符串)
  3. +=(两个都有)

按照库中的来,push_back没有返回值,后面两个返回值都是string&。

挨个说。

push_back

在这里插入图片描述
很简单,扩容的时候注意 _capacity 是否为0。
为零的情况就是初始化的时候没有给字符串,是默认的空字符串,此时 _capacity 就是0。

然后这个就讲完了。

append

代码:
在这里插入图片描述

注意的点:
首先要知道你要追加的字符串的长度 len 。

判断扩容的条件为 _size + len > _capacit 才是对的。这个很好理解,你觉得不好理解是因为全是字符太抽象了,给个例子就行了。比如说 _size = 0, len = 2, _capacity = 2。是不用扩容的,两个就够了,正好。我就不再多给例子了,你们自己好好琢磨一下就行。

运行一下:
在这里插入图片描述

+=

其实前面的这两个 push_back 和 append 已经顺带把这个讲了,因为 += 可以直接复用这两个函数。

看:
在这里插入图片描述
完事,就是这么简单。

运行:
在这里插入图片描述

insert

这个函数就是在 pos 位置处插入字符或者字符串。

实现起来比较恶心,尤其是插入字符串的时候对于边界的把控。
我感觉这个实现起来类似于插入排序的思想。

pos位置插字符

string& insert(size_t pos, const char& ch);

先给张图:
在这里插入图片描述

假如说我要在 2 下标处插入 #,那么结果就是这样:
在这里插入图片描述

大致思路就是,先讲pos位置后面的字符往后挪一个单位。
然后再把pos位置处的字符换成#。

定义一个end来表示字符串中的最后一个字符(‘\0’)的位置,通过end来后挪字符。

看图解:
在这里插入图片描述
在这里插入图片描述

然后就开整:
在这里插入图片描述
判断扩容这东西记住,只要是往里面加东西就要判断扩容。
同理,删除的时候,就要判空。
这要形成条件反射的。

运行:
在这里插入图片描述

多插入几个:
在这里插入图片描述

没有问题。

但是其实上面的代码是有问题的,当我在0处插入时:
在这里插入图片描述
光标一直在闪动,说明死循环了。

原因是循环那里出问题了。
在这里插入图片描述
这个地方,end是size_t的,pos也是size_t的,当pos为0时,end >=0 是永远成立的,虽然end在一直- -。其实每次到0的时候又变成一个非常大的数。就死循环了。

怎么解决呢?
可以将二者都改为int,但是这种方式是很挫的,因为库中给的pos就是 size_t。

也可以将二者判断时强转为int。
在这里插入图片描述
但是同样很挫。

我们要改一下思想。
让end初始值改为 _size + 1,然后数值在后挪的时候让 _str[end] = _str[end-1],然后判断条件改为 > 就OK了。
在这里插入图片描述

插字符的就到这,下面讲插字符串的。

pos位置插字符串

同样的思路,只不过字符在往后挪的时候要改变一下策略。

来例子:

还是第二个位置插#,只不过这次插三个。
在这里插入图片描述

再看下图解:
在这里插入图片描述

其实就是把插字符时代码中的1改为len就行。
len是插入字符串的长度。

就不讲那么多了,直接给代码:
在这里插入图片描述

如果对边界把控不熟悉,就画画图,给几个数值,比对比对就ok了。

比如说我写插入之前就要把图画出来,搞几个值标到下面,然后给几个例子,就写出来了。
在这里插入图片描述
图画的有点潦草,也不是给大家看的,就是给我看的,只是给大家说一下我怎么写的insert。。。

erase

有insert肯定少不了erase。

那么先看库中的实现:
在这里插入图片描述

string& erase(size_t pos, size_t len = npos)

还是pos位置开始删字符,前一篇讲string的时候也说了npos是string类中的一个静态成员变量,而且用const修饰了的。

这里要说一点:

普通的静态成员变量必须类内声明,类外初始化。
在这里插入图片描述
但是C++中有个恶心的地方,就是如果静态成员变量加了const修饰,就可以直接在声明处初始化。
在这里插入图片描述

如果此时在类外定义就会报错。
在这里插入图片描述

上面把npos设置为私有的了,但是库中的npos其实在类外是可以访问的,所以我们这里就要把npos设置为公有:
在这里插入图片描述

就这么些。

接着前面的讲:
删除的时候,len缺省值为-1,但是len是size_t的,那就可以说是无穷大了。也就是说,len不给值的时候就是默认pos位置往后的全部劈掉。这一点要注意。

给个例子:
2位置往后删两个。
在这里插入图片描述

直接给代码了:
在这里插入图片描述

还可以用strcpy:
在这里插入图片描述

这个就说到这。

>> 和 <<

每个类重载流提取和流插入都是很有必要的,因为重载了会好用很多。

注意要在类外定义,类内定义的话this指针会和流对象抢位置。

<<

ostream& operator<<(ostream& out, string& s)

直接给代码:
在这里插入图片描述
这里得用循环,不能用out << s.c_str();
因为如果_str中间有\0就没法全部打印。

>>

istream& operator>>(istream& in, string& s)

这里不能直接用in,因为字符串中如果包含空格cin是不认这个字符的,就导致没办法停止。

用in的代码如下:
在这里插入图片描述
运行:
在这里插入图片描述
像这样根本停不下来。

所以说不能用in,得换一个,用istream类中的get。
在这里插入图片描述
这个函数可以获得字符或者字符串,肯定是认识 空格 或者 \n 的。

这里获得字符就够了。

代码如下:
在这里插入图片描述

运行起来:

在这里插入图片描述
这样就ok了。

但是上面的>>效率是比较低的,因为用+=的时候,如果输入的字符串越长,那么扩容的次数就会越多,效率就变低了。

解决方法可以提前在while循环前直接给s扩容,比如下面这样:
在这里插入图片描述

但是这样又出现问题了,当输入的字符串比较短的时候如果后续s不变,就会永久性的浪费一些空间。

利用输入缓冲区的概念,再改改:
在这里插入图片描述

运行起来是成立的:
在这里插入图片描述

其实代码中还存在一个bug。

我们的:
在这里插入图片描述

标准库的:
在这里插入图片描述
可以看到,标准库里是先将s1中的字符清空,然后再重新输入。

所以还得改改:

在类中提供一个clear函数接口就行。
在这里插入图片描述

再运行:
在这里插入图片描述

<< 和 >> 到这就完了。
下面说一下find。

find

先看库中的:
在这里插入图片描述

实现找子串和字符的。

找字符:
在这里插入图片描述

找字串(这里偷个懒,直接用C库中的strstr了):
在这里插入图片描述

strstr这个函数如果找到子串返回的就是一个常量字符串,如果找不到就返回空。

像这种找子串的有专门的kmp算法和BM算法,如果各位感兴趣的话可以自己搜一搜了解一下。

substr

这个也要实现一下

在这里插入图片描述
这个函数功能是从 pos 位置开始产生一个长度为 len 的子串。

代码如下:
在这里插入图片描述

细节上就是对于边界的把控,其他就没啥了。

运行:
在这里插入图片描述

代码还可这样写:
在这里插入图片描述

我们再拿上一篇中的那个网址分割的测试一下我们substr和find是否正确。
在这里插入图片描述
结果正确。

最后一点

关于函数的日常string中也不会用那么多,前面的就够用了。

再说一下vs上的和gcc上的实现string和前面我们自己实现的区别。

vs上的

vs上是以空间换时间的做法。

vs中成员变量里面还有一个_buff数组,大小16,最后一个放字符 \0 ,这个数组是用来放长度小于16的字符串的,也就是说,当你初始化一个字符串的长度小于16时,就不会用到 _str,是直接用 _buff 数组的。

也就是这样:
在这里插入图片描述

这也就是为什么我们就算不初始化string对象,调试的时候能看到string中capacity的大小为15。

看:
在这里插入图片描述
上面库中的string对象大小为28,我们实现的大小为12,差了16,这16就差在buff数组上了。

当初始化字符串长度大于16时,就会不用buff,将字符串放到堆中,有_str来维护。

gcc下

这里采用的是引用计数 + 写时拷贝的方法。

引用计数是指有几个对象的 _str 指向某空间,计数就是多少,当计数大于1时,析构一个对象不会直接释放其 _str 所指向的空间,而是将那个计数-1。当剩余最后一个对象的时候,此时计数就是1,析构时才会释放空间。

在这里插入图片描述

写时拷贝本质是延时拷贝,当多个对象中的 _str 指向同一块空间,某个对象去修改这块空间的内容的时候才会为这个对象开辟一块新空间来让其修改,剩余对象仍指向那块空间,也就是拷贝对象的时候不直接深拷贝,先浅拷贝,当某个对象想要修改内容的时候再深拷贝。这样的方式就是当多个对象没有写入的时候就目的就达到了。

到此结束。。。

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

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

相关文章

Linux---用户权限(权限位、chowd、chown)

1. 权限位 序号1&#xff0c;表示文件、文件夹的权限控制信息 序号2&#xff0c;表示文件、文件夹所属用户 序号3&#xff0c;表示文件、文件夹所属用户组 权限细节总共分为10个槽位&#xff1a; 举例&#xff1a;drwxr-xr-x&#xff0c;表示&#xff1a; 这是一个文件夹&…

如何使用Metasploit进行后渗透攻击?

后渗透攻击&#xff08;PostExploitation&#xff09;是整个渗透测试过程中最能够体现渗透测试团队创造力与技术能力的环节。前面的环节可以说都是在按部就班地完成非常普遍的目标&#xff0c;而在这个环节中&#xff0c;需要渗透测试团队根据目标组织的业务经营模式、保护资产…

关于PyQt5的环境搭建

目录 一、需要的环境 二、安装python 1、python安装链接 三、安装PyQt5 1、使用豆瓣的镜像 2、配置环境变量 四、安装pycharm 1、pycharm官网链接 五、配置环境 1、找到设置 2、添加designer 3、配置ui 4、配置rc 六、注意问题 一、需要的环境 1、安装好python安装…

【Linux CAN应用编程(1)】初识CAN总线(附全文代码)

接下来我们学习 CAN 应用编程&#xff0c;CAN 是目前应用非常广泛的现场总线之一&#xff0c;主要应用于汽车电子和工业领域&#xff0c;尤其是汽车领域&#xff0c;汽车上大量的传感器与模块都是通过 CAN 总线连接起来的。CAN 总线目前是自动化领域发展的热点技术之一&#xf…

1、Vue简介与环境搭建

目录 一、Vue简介二、Vue开发环境1 - 环境安装2 - 新建Vue项目3 - VS Code4 - Vue项目的目录结构 一、Vue简介 官方文档&#xff1a;https://cn.vuejs.orgVue的api风格&#xff1a;选项式 API&#xff08;Vue 2&#xff09; 和组合式 API&#xff08;Vue 3&#xff09;**选项式…

怎么把pdf转成word?转换途径一览

在日常生活和工作中&#xff0c;我们常常需要处理各种文档格式。其中&#xff0c;PDF 作为一种流行的跨平台文件格式&#xff0c;广泛应用于技术文档、报告、合同和电子书等领域。但是&#xff0c;当我们需要修改 PDF 文件内容时&#xff0c;却往往会遇到困难。这时&#xff0c…

JavaScript引擎,V8引擎的原理

为什么需要JavaScript引擎&#xff1f; 高级的编程语言都是需要转成最终的机器指令来执行 我们所编写的js无论h是交给浏览器或者node执行&#xff0c;最后都是需要被CPU执行的 CPU只认识自己的指令集&#xff08;机器语言&#xff09;才能被CPU所执行 所以我们需要js引擎帮助我…

掌握了它,软件测试拿下25K轻轻松松!

了解软件测试这行的人都清楚&#xff0c;功能测试的天花板可能也就15k左右&#xff0c;而自动化的起点就在15k左右&#xff0c;当然两个岗位需要掌握的技能肯定是不一样的。 如果刚入门学习完软件测试&#xff0c;那么基本薪资会在7-8k左右&#xff0c;这个薪资不太高主要是因…

JQuery - JavaScript 的框架 - 语法简化版本

目录 js文件与 script 标签的位置问题总结&#xff1a; 有的时候是很坑&#xff0c;看了挺多资料&#xff0c;有时候乱糟糟的&#xff0c;都不说清楚&#xff0c;这是JavaScript的语法&#xff0c;还是 JQuery的语法&#xff0c;所以就会因为没有导入 JQuery的js文件&#xff0…

「功能测试进阶到自动化测试」一路走来都离不每个阶段的计划

关于「从功能测试&#xff0c;转岗到自动化测试」&#xff0c;主要的几个问题 &#xff1a; 001 目前一直在走功能测试&#xff0c;工作中也没有机会接触自动化测试 。想后续从事自动化测试&#xff0c;我应该从哪里开始学 &#xff1f; 解答&#xff1a;自动化 &#xff0c…

【六】设计模式~~~结构型模式~~~适配器模式(Java)

【学习难度&#xff1a;★★☆☆☆&#xff0c;使用频率&#xff1a;★★★★☆】 1.1. 模式动机 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。通常情况下&#xff0c;客户端可以通过目标类的接口访问它所提供的服务。有时&#xff0c;现有的类可以满…

全渠道电子商务指南

希望将全渠道电子商务纳入您的业务战略&#xff0c;但不确定从哪里开始。这篇博文将指导您了解全渠道商务的基础知识&#xff0c;以及它与多渠道方法的区别。 全渠道电子商务旨在为线上和线下多个平台的客户创造全面的购物体验。客户可以通过各种接触点&#xff08;例如网站、…

网络安全-02-BurpSuite工具安装

网络安全-02-BurpSuite工具安装 &#x1f53b;一、BurpSuite简介&下载&#x1f4d7; 二、Windows安装Jdk&#x1f4f0; 2.1 下载Jdk并安装&#x1f4f0; 2.2 设置Jdk环境变量&#x1f4dc; 2.2.1 设置JAVA_HOME&#x1f4dc; 2.2.2 设置path&#x1f4dc; 2.2.3 Jdk验证 &a…

数据库基础——8.单行函数

这篇文章我们来讲一下数据库里面的单行函数 目录 1. 函数的理解 1.1 什么是函数 1.2 不同DBMS函数的差异 1.3 MySQL的内置函数及分类 2. 数值函数 2.1 基本函数 2.2 角度与弧度互换函数 2.3 三角函数 2.4 指数与对数 2.5 进制间的转换 3. 字符串函数 4. 日期和…

PyCharm配置Opencv

具体方案&#xff1a; Plan A&#xff1a;可以直接在pycharm中配置&#xff08;如果有梯子可以试试&#xff0c;没有可能下载不了&#xff09; 参考视频&#xff1a;直接在Pycharm中配置即可 两分钟用Pycharm安装并配置OpenCV_哔哩哔哩_bilibili PlanB&#xff1a;通过终端安…

dolphinscheduler3.1.7windows部署启动说明

简介 Apache DolphinScheduler是一个新一代分布式大数据工作流任务调度平台&#xff0c;致力于“解决大数据任务之间错综复杂的依赖关系&#xff0c;整个数据处理开箱即用”。它以 DAG(有向无环图) 的方式将任务连接起来&#xff0c;可实时监控任务的运行状态&#xff0c;同时…

01Redis单线程 VS 多线程

不同版本&#xff0c;情况不同 Redis的版本很多3.x、4.x、6.x&#xff0c;版本不同架构也是不同的&#xff0c;不限定版本问是否单线程也不太严谨。 版本3.x &#xff0c;最早版本&#xff0c;也就是大家口口相传的redis是单线程 数据结构简单避免锁的开销和上下文切换可以有…

YOLOv7 tiny 新增小目标检测层

YOLOv7 tiny 新增小目标检测层 YOLOv7 tiny 新增小目标检测层修改yolov7-tiny.yaml文件YOLOv7 tiny 结构图调用 models/yolo.py验证 YOLOv7 tiny 新增小目标检测层 根据已有的结构进行新增小目标层&#xff0c;&#xff0c;个人理解&#xff0c;仅供参考&#xff01;&#xff…

GPT模型应用及遥感云大数据在灾害、水体与湿地领域典型案例展示

GPT GPT的全称&#xff0c;是Generative Pre-Trained Transformer&#xff08;生成式预训练Transformer模型&#xff09;是一种基于互联网的、可用数据来训练的、文本生成的深度学习模型。 GPT与专注于下围棋或机器翻译等某一个具体任务的“小模型”不同&#xff0c;AI大模型…

Seata XA 模式理论学习、使用及注意事项 | Spring Cloud54

一、前言 通过以下系列章节&#xff1a; docker-compose 实现Seata Server高可用部署 | Spring Cloud 51 Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52 Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53 我们对Seata及其AT事务模式的…