【C++杂货铺】探索string的底层实现

news2025/1/9 15:28:57

在这里插入图片描述

文章目录

  • 一、成员变量
  • 二、成员函数
    • 2.1 默认构造函数
    • 2.2 拷贝构造函数
    • 2.3 operator=
    • 2.4 c_str()
    • 2.5 size()
    • 2.6 operator[ ]
    • 2.7 iterator
    • 2.8 reserve
    • 2.9 resize
    • 2.10 push_back
    • 2.11 append
    • 2.12 operator+=
    • 2.13 insert
    • 2.14 erase
    • 2.15 find
    • 2.16 substr
    • 2.17 operator<<
    • 2.18 operator>>
    • 2.19 operator<
    • 2.20 operator==
    • 2.21 <=、>、>=、!=
  • 三、结语

一、成员变量

private:
		char* _str;//用来存储字符串
		size_t _size;//用来表示有效字符数
		size_t _capacity;//用来表示可以存储有效字符的容量
public:
		static size_t npos;//要在类外面定义

string本质上是一个动态顺序表,它可以根据需要动态的扩容,所以字符串一定是通过在堆上动态申请空间进行存储的,因此_str指向存储字符串的空间,_size用来表示有效字符数,_capacity用来表示可以存储有效字符的容量数。

二、成员函数

2.1 默认构造函数

string(const char* str = "")
	:_str(new char[strlen(str) + 1])//strlen计算的是有效字符的个数,而我们存储的时候要在字符串的最后存一个'\0'
	,_size(strlen(str))
	,_capacity(_size)
{
	//memcpy(_str, str, _size);
	//strcpy(_str, str);//常量字符串就是遇到'\0'终止,所以直接用strcpy也可以
	memcpy(_str, str, strlen(str) + 1);
}

注意:默认构造函数需要注意的地方是:首先形参必须加上 const 修饰,这样才能用 C 语言中的常量字符串来初始化 string 类对象,形参的的缺省值直接给一个空字符串即可,注意空字符串是用""表示,该字符串只有结尾默认的一个 '\0'"\0"并不表示空字符串,它表示该字符串有一个字符 '\0' ,它的结尾还有一个默认的 '\0',因此有两个 '\0'nullptr也不能表示空字符串,他表示的是空指针。其次需要注意初始化列表的顺序,应该严格按照成员变量的出现顺序。strlen 计算的是字符串中有效字符的个数,不算 '\0',而常量字符串的结尾默认有一个 '\0',因此在用 new开辟空间的时候需要多开一个用来存储结尾的 \0_capacity表示的是可以存储有效字符的容量,而字符串结尾默认的 '\0' 并不算作有效字符,因此最初的 _capacity 就是形参 str 的长度。最后记得在构造函数体内将形参 str 的字符拷贝到动态申请的空间中。

小Tips:涉及到字符串拷贝的地方,建议使用 memcpystrcpy 默认遇到 \0 就终止,但是不排除 \0 就是 string 对象中的有效字符。但是 strcpy 会默认在结尾加 \0,而 memcpy 不会,因此使用 memcpy 的时候需要注意拷贝得到的字符串结尾是否有 \0

2.2 拷贝构造函数

//传统写法
string(const string& str)
	:_str(new char[str._size + 1])
	,_size(str._size)
	,_capacity(_size)
{
	memcpy(_str, str._str, str._size + 1);
}

//现代写法
string(const string& str)
	:_str(nullptr)
	, _size(0)
	,_capacity(0)
{
	string tmp(str._str);
	swap(tmp);
}

注意:现代写法不需要我们亲自去申请空间初始化,而是调用构造函数去帮我们完成。最后再将初始化好的 tmp 交换过来,这里一定要通过初始化列表对 *this 进行初始化,不然交换给 tmp 后,里面都是随机值,最终出了作用域 tmp 去销毁的时候就会出问题。现代写法的坑点在于,如果 string 对象中有 '\0',只会把 '\0' 前面的字符拷贝过去。

2.3 operator=

//传统写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;
}

注意:这种写法需要我们自己去开辟空间新空间 tmp,自己去释放旧空间 _str,下面将对这种写法加以改进,通过已有的接口来帮我们完成这些工作。

//现代写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);//通过调用拷贝构造来创建空间
		//tmp是局部变量,出了作用于会自动销毁,把待销毁的资源通过交换,给tmp
		std::swap(_str, tmp._str);
		std::swap(_size, tmp._size);
		std::swap(_capacity, tmp._capacity);
		//std::swap(*this, tmp);//错误的写法
	}

	return *this;
}

//现代写法优化
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
string& operator=(string s)
{
	swap(s);
	return *this;
}
//优化版本,连拷贝构造函数也不需要我们自己去调用啦,直接通过形参去调用

注意:这种写法通过调用拷贝构造来帮我们申请空间,在利用局部对象出了作用就会被销毁的特点,将需要释放的资源通过 swap 交换给这个局部变量,让这个局部变量帮我们销毁。这里不能直接用 swap 交换两个 string 类对象,会导致栈溢出,因为 swap 函数中会调用赋值运算符重载,而赋值运算符重载又要调用 swap 成了互相套娃。我们可以不用库里面的 swap,自己实现一个 Swap 用来交换两个 string 对象。

2.4 c_str()

char* c_str() const
{
	return _str;
}

注意:记得加上 const,这样普通的 string 类对象可以调用,const 类型的 string 类对象也可以调用,普通对象来调用就是权限的缩小。

2.5 size()

size_t size() const
{
	return _size;
}

2.6 operator[ ]

//读写版本
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}
//只读版本
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

注意:这两个运算符重载函数构成函数重载,对象在调用的时候会走最匹配的,普通对象会调用读写版本,const 对象会调用只读版本。

2.7 iterator

iteratorstring 类的内嵌类型,也可以说是在 string 类里面定义的类型,在一个类里面定义类型有两种方法,typedef 和 内部类。string 类的 iterator 是通过前者来实现的,即对字符指针 char* 通过 typedef 得到的。

typedef char* iterator;
typedef const char* const_iterator;

//可读可写版本
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

//只读版本
const_iterator begin() const
{
	return _str;
}

const_iterator end() const
{
	return _str + _size;
}

2.8 reserve

void reserve(size_t n = 0)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		//strcpy(tmp, _str);
		memcpy(tmp, _str, _size + 1);
		_capacity = n;
		delete[] _str;
		_str = tmp;
	}
}

2.9 resize

void resize(size_t n, char ch = '\0')
{
	if (n < _size)
	{
		erase(n);
	}
	else
	{
		reserve(n);
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = ch;
		}

		_size = n;
		_str[_size] = '\0';
	}
}

注意reserve 函数不会进行缩容,因此在扩容前要先进程判断,只有当形参 n 大于当前容量的时候才扩容。

2.10 push_back

void push_back(char ch)
{
	//先检查容量,进行扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size++] = ch;
	_str[_size] = '\0';
}

注意:需要注意对空串的追加,空串的 _capacity = 0 ,因此在调用 reserve 函数进行扩容的时候,不能简单传递 _capacity*2,要先进行判断,当 capacity == 0 的时候,给它一个初始大小。

2.11 append

void append(const char* str)
{
	if (_size + strlen(str) > _capacity)
	{
		reserve(_size + strlen(str));
	}
	//strcpy(_str + _size, str);//常量字符串就是遇到'\0'终止,所以直接用strcpy也可以
	memcpy(_str + _size, str, strlen(str) + 1);
	_size += strlen(str);
}

2.12 operator+=

//追加一个字符串
string& operator+=(const char* str)
{
	append(str);
	return *this;
}
//追加一个字符
string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}

注意:+= 需要有返回值。

2.13 insert

//插入n个字符
void insert(size_t pos, size_t n, char ch)
{
	assert(pos <= _size);
	//检查容量,扩容	
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}
	//挪动数据
	size_t end = _size;
	while (end != npos && end >= pos)
	{
		_str[end + n] = _str[end--];
	}

	//插入数据
	size_t i = pos;
	while (i < pos + n)
	{
		_str[i++] = ch;
	}

	_size += n;
}

注意:这里需要注意挪动数据时的判断条件,因为 endpos 都是 sizt_t 类型,所以当 pos = 0 的时候 end >= pos 永远成立,此时就会有问题,只把 end 改成 int 也解决不了问题,在比较的时候会发生整形提升,最终还是永远成立。一种解决方法就是想上面一样,加一个 size_t 类型的成员变量 npos,把它初始化成 -1,即整形最大值,判断 end 是否等于 npos,等于说明 end 已经减到 -1 了,就应该停止挪动。解决上面的问题还有一种方法,上面的问题出现在 pos = 0 时,end 会减到 -1,最终变成正的无穷大,导致判断条件永远成立,那我们可以将 end 初始化成 _size + n,把 end - n 上的字符挪到 end 位置上,此时计算 pos = 0,也不会出现 end 减到 -1 的情况,代码如下:

//插入n个字符
void insert(size_t pos, size_t n, char ch)
{
	assert(pos <= _size);
	//检查容量,扩容	
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}
	//挪动数据
	size_t end = _size + n;
	while (end >= pos + n)
	{
		_str[end] = _str[end - n];
		--end;
	}

	//插入数据
	size_t i = pos;
	while (i < pos + n)
	{
		_str[i++] = ch;
	}

	_size += n;
}

小Tipsnpos作为一个静态成员变量,必须在类外面进行初始化(定义),并且不能在声明时给默认值,默认值是给初始化列表用的,而静态成员变量属于该类所有对象共有,并不会走初始化列表。但是!但是!!,整形的静态成员变量变量在加上 const 修饰后就可以在声明的地方给默认值,注意!仅限整形。其他类型的静态成员变量在加 const 修饰后仍需要在类外面定义。

const static size_t npos = -1;//可以
//const static double db = 1.1//不可以
//插入一个字符串
void insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	//挪动
	size_t end = _size + len;
	while (end >= pos + len)
	{
		_str[end] = _str[end - len];
		--end;
	}

	//插入
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}

	_size += len;
}

2.14 erase

void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if (len == npos || pos + len >= _size)//
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		//挪动覆盖
		size_t end = pos + len;
		while (end <= _size)
		{
			_str[end - len] = _str[end++];
		}
		_size -= len;
	}
}

注意pos 将整个数组划分成两部分,[0,pos-1]是一定不需要删除的区域,[pos,_size-1]是待删除区域,一定不需要删除的区域有 pos 个元素,我们希望删除 len 个字符,当一定不会删除的字符数加我们希望删除的字符数如果大于或等于全部的有效字符数,那就说明待删除区域的所有字符都要删除,即当 pos + len >= _size 的时候就是要从 pos 位置开始删除后面的所有字符,删完后加的把 pos 位置的字符置为 \0

2.15 find

//查找一个字符
size_t find(char ch, size_t pos = 0)
{
	assert(pos < _size);
	for (size_t i = 0; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}
//查找一个字符串
size_t find(const char* str, size_t pos = 0)
{
	assert(pos < _size);
	const char* ptr = strstr(_str, str);
	if (ptr == NULL)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

2.16 substr

string substr(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);
	size_t n = len;
	if (len == npos || pos + len >= _size)
	{
		n = _size - pos;
	}

	string tmp;
	tmp.reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		tmp += _str[i + pos];
	}

	return tmp;
}

2.17 operator<<

ostream& operator<<(ostream& out, const wcy::string& str)
{
	for (auto e : str)
	{
		out << e;
	}
	return out;
}

注意:因为涉及到竞争左操作数的原因,流插入和流提取运算符重载要写在类外面。其次,不能直接打印 str._str 或者通过 str.c_str() 来打印,因为 string 对象中可能会有 \0 作为有效字符存在,前面两种打印方法,遇到 \0 就停止了,无法完整将一个 string 对象打印出来,正确的做法是逐个打印。

小Tips:无论是形参还是返回值,只要涉及到 ostreamistream 都必须要用引用,因为这俩类不允许拷贝或者赋值的。

2.18 operator>>

istream& operator>>(istream& in, wcy::string& str)
{
	if (str._size != 0)
	{
		str.erase(0);
	}
	//in >> str._str;//这样写是错的,空间都没有
	char ch;
	ch = in.get();
	while (ch == ' ' || ch == '\n')//清除缓冲区
	{
		ch = in.get();
	}
	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		ch = in.get();
	}
	return in;
}

注意:空格符 ' ' 和换行符 \n 作为输入时分割多个 string 对象的标志,是不能直接用 istream 对象来读取的,即 cin >> ch 是读不到空格符和换行符。需要借助 get() 成员函数才能读取到空格符和换行符。其次库中对 string 进行二次流提取的时候会进行覆盖,所以我们在插入前也要先进行判断。上面这种写法,在输入的字符串很长的情况下会多次调用 reserve 进行扩容,为了解决这个问题,我们可以对其进行优化。

//优化版本
istream& operator>>(istream& in, wcy::string& str)
{
	/*if (str._size != 0)
	{
		str.erase(0);
	}*/
	//in >> str._str;//这样写是错的,空间都没有
	str.clear();
	char buff[128] = { '\0' };
	char ch;
	ch = in.get();
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			str += buff;
			i = 0;
		}
		ch = in.get();
	}

	if (i != 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return in;
}

注意:这里的做法是,先开辟一个数组,将输入的字符存储到数组中,然后从数组中拷贝到 string 对象当中。

2.19 operator<

bool operator<(const string& s) const
{
	size_t i1 = 0;
	size_t i2 = 0;
	while (i1 < _size && i2 < s._size)
	{
		if (_str[i1] < s[i2])
		{
			return true;
		}
		else if (_str[i1] > s[i2])
		{
			return false;
		}
		else
		{
			i1++;
			i2++;
		}
	}

	if (i1 == _size && i2 == s._size)
	{
		return false;
	}
	else if (i1 < _size)
	{
		return false;
	}
	else
	{
		return true;
	}
}

注意string 类对象是按照 ASCII 进行比较的。其次,这里不能直接复用 strcmp 或者 memcmp,前者遇到 '\0' 就会终止,后者只能比较长度相等的部分。所以我们可以自己来写比较逻辑,也可以复用 memcmp 然后进行补充。

//复用memcpy
bool operator<(const string& s) const
{
	int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);

	return ret == 0 ? _size < s._size : ret < 0;
}

2.20 operator==

bool operator==(const string& s) const
{
	return _size == s._size
		&& memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}

有了 < 和 ==,剩下的直接复用即可。

2.21 <=、>、>=、!=

bool operator<=(const string& s) const
{
	return *this < s || *this == s;
}

bool operator>(const string& s) const
{
	return !(*this <= s);
}

bool operator>=(const string& s) const
{
	return !(*this < s);
}

bool operator!=(const string& s) const
{
	return !(*this == s);
}

三、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

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

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

相关文章

【数据结构】 LinkedList的模拟实现与使用

文章目录 &#x1f340;什么是LinkedList&#x1f334;LinkedList的模拟实现&#x1f6a9;创建双链表&#x1f6a9;头插法&#x1f6a9;尾插法&#x1f6a9;任意位置插入&#x1f6a9;查找关键字&#x1f6a9;链表长度&#x1f6a9;打印链表&#x1f6a9;删除第一次出现关键字为…

【技术】安防视频监控平台EasyNVR平台启用国标级联的操作步骤

安防视频监控汇聚EasyNVR视频集中存储平台&#xff0c;是基于RTSP/Onvif协议的安防视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。 为提高用户体验&#xff0c;让用户更加便捷…

【Midjourney电商与平面设计实战】创作效率提升300%

不得不说&#xff0c;最近智能AI的话题火爆圈内外啦。这不&#xff0c;战火已经从IT行业燃烧到设计行业里了。 刚研究完ChatGPT&#xff0c;现在又出来一个AI作图Midjourney。 其视觉效果令不少网友感叹&#xff1a;“AI已经不逊于人类画师了!” 现如今&#xff0c;在AIGC 热…

CSS实现一个交互感不错的卡片列表

0、需求分析 横向滚动鼠标悬停时突出显示 默认堆叠展示鼠标悬停时&#xff0c;完整展示当前块适当旋出效果 移动端样式优化、磁吸效果美化滚动条 1、涉及的主要知识块 flex 布局css 简单变换过渡 transform、transition 渐变色函数 linear-gradient… 伪类、伪元素 滚动条、…

突破欧美技术垄断,国产磁悬浮人工心脏再闯关

“现在身体状态还不错&#xff0c;一些不太剧烈的运动也可以参加。”一年前&#xff0c;湖北武汉市东西湖区的李女士突发暴发性心肌炎&#xff0c;出现心力衰竭。植入国产全磁悬浮人工心脏治疗后&#xff0c;现在李女士能正常生活。 心力衰竭是全球医学的重大挑战。据统计&…

猫云域名防红系统源码

大致功能&#xff1a;支持会员充值功能&#xff0c;对接的易支付&#xff0c;本站可以自行搭建。支持添加广告信息&#xff0c;例如进入网站前&#xff0c;先跳转个广告支持设置访问流量限制等支持设置伪域名&#xff0c;长短后缀支持屏蔽ip支持添加多个入口与落地域名支持对接…

信息安全史:半个世纪以来飞跃发展的信息安全

从20世纪60年代开始信息技术稳步上升&#xff0c;信息安全现已成为一个重要的现代问题。在过去的十年中&#xff0c;美国的雅虎、微软和Equifax等大公司都曾遭到黑客攻击。尽管近年来网络安全得到极大提高&#xff0c;但2017年的WannaCry勒索蠕虫攻击证明&#xff0c;不仅仅是信…

多个微信号怎么定时发圈?

多个微信号怎么定时发圈&#xff1f;https://mp.weixin.qq.com/s?__bizMzg2Nzg4NjEzNg&mid2247487136&idx2&sn036e1d5f9d3790b12a103a90de474957&chksmceb5fbf7f9c272e1f8e9acf644ad3d4d97fb8fdce77ec5e2a2976527d4d180ad1c277b4336c8&token495803628&…

OpenGL —— 2.5、绘制第一个三角形(附源码,glfw+glad)(更新:纹理贴图)

源码效果 C++源码 纹理图片 需下载stb_image.h这个解码图片的库,该库只有一个头文件。 具体代码: vertexShader.glsl #version 330 corelayout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aColor; layout(location = 2) in vec2 aUV;out vec4 outColor; ou…

红黑的插入

定义 红黑树是一种二叉搜索树 每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black 通过对任何一条从根到叶子的路径上各个结点着色方式的限制。 红黑树确保没有一条路径会比其他路径长出俩倍&#xff0c;因而是接近平衡的。 红黑树是如何保证该核心属性的呢…

DBeaver 无法执行多行查询,报错[1064]42000

简单的两行查询&#xff0c;有分号&#xff0c;查询报错&#xff0c;无语法问题&#xff1a; 编辑连接属性&#xff1a;允许多行查询

爱校对发布全新PDF校对工具,为用户带来更为便捷的校正体验

随着数字化文档使用的普及&#xff0c;PDF格式已经成为最为广泛使用的文件格式之一。为满足广大用户对于高效、准确PDF文档校对的需求&#xff0c;爱校对团队经过深入研发&#xff0c;正式推出全新的PDF校对工具&#xff01; 这一全新工具针对PDF文件格式进行了深度优化&#…

如何在Moonriver网络上向社区代表委托投票权利

我们之前介绍了「社区代表」这一概念&#xff0c;想必大家对社区代表在治理中扮演的角色和地位有了一定的了解。 本文将介绍如何将您的投票权利委托给社区代表。请注意&#xff0c;在委托Token给社区代表这一过程中&#xff0c;并非将您的Token转移给任何人&#xff0c;而且此…

关于农林气象站的基本介绍

农林气象站可以观测各种气象参数&#xff0c;并将观测到的参数上传至农业平台&#xff0c;通过平台向人们提供数据&#xff0c;保障农作物的健康成长。 面对人口增长比例减缓、老龄化逐渐严重的现象&#xff0c;粮食生产成为人们关注的问题&#xff0c;在这种背景下&#xff0…

dll修复工具下载,msvcr120.dll丢失怎样修复

在计算机编程中&#xff0c;msvcr120.dll是一个非常重要的动态链接库文件&#xff0c;它包含了Microsoft Visual C 2010 Redistributable Package所需的运行时库。当这个文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行&#xff0c;甚至出现错误提示。因此&#xff0c…

玩机搞机---安卓机型mtk和高通芯片查看分区 导出分区 备份分区的一些工具分析

前面分享过几期mtk和高通芯片机型对于备份系统 备份分区的一些博文。很多友友比较感兴趣&#xff0c;尤其是有些专门从事小众机型定制修改系统的朋友和其他从事安卓芯片类机型的玩家。因为其有些安卓设备各种途径无法获取到出厂系统。那么从当前机型备份系统和分区是有一定必要…

当一个程序员决定穿上粉裤子

作为一个大众眼中的“非典型程序员”&#xff0c;我喜欢拥抱时尚和潮流&#xff0c;比如我经常在演讲时穿粉色裤子&#xff0c;这甚至已经成为一个标志性打扮。某天又逢主题演讲日&#xff0c;我站在衣柜前挑选上衣的时候&#xff0c;忽然灵光乍现&#xff1a;有没有可能借助 M…

Windows10批处理命令行设置环境变量笔记,无需重新安装python与chrome

近期&#xff0c;工作中经常安装、部署python生产、开发环境&#xff0c;比较麻烦&#xff0c;也没有心情去优化。突然&#xff0c;我的电脑崩溃了&#xff0c;在重新安装电脑的过程中&#xff0c;保留了原来的安装软件&#xff08;有的没有放在系统盘中&#xff09;&#xff0…

DETRs with Collaborative Hybrid Assignments Training论文笔记

Title&#xff1a;[DETRs with Collaborative Hybrid Assignments Training Code 文章目录 1. Motivation2. one to one VS one to many3. Method&#xff08;1&#xff09;Encoder feature learning&#xff08;2&#xff09;Decoder attention learning 1. Motivation 当前…

关于异数OS服务器CPU效能分析工具

该工具发布背景 近年来&#xff0c;国产服务器CPU产业的逐渐发展&#xff0c;但由于专业性较差&#xff0c;与国外存在40年以上技术差距&#xff0c;一些服务器CPU厂商利用信息差来制造一些非专业的数据夸大并虚假宣传混淆视听&#xff0c;成功达到劣币驱良币的目标&#xff0…