【C++】vector的底层封装和实现

news2025/4/17 6:54:41

目录

  • 目录
    • 前言
    • 基本框架
    • 迭代器
    • 容量
      • 第一个测试,野指针异常
      • 第二轮测试,浅拷贝的问题
    • 元素访问
    • 修改操作
      • push_back
      • insert
        • 迭代器失效问题
      • erase
    • 默认成员函数
      • 构造函数
        • 双重构造引发调用歧义
      • 拷贝构造
      • 赋值重载
      • 析构函数
    • 源码
    • end

目录

前言

废话不多说,我们直接来模拟实现vector的底层实现,有了上篇文章string的底层封装和模拟实现,相信大家已经对stl的容器的底层有了一定的了解

基本框架

namespace bit {
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	// 主要接口函数
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

  • 这里我们的三个成员变量参考源码中的实现方式,用指针来模拟实现。
  • 并且采用缺省参数的形式将它们都初始化为nullptr,这样当我们后面在写 构造函数和析构函数 的时候就不需要再去做初始化了

迭代器

  • 这里就很简单的返回我们指向开头的指针和指向最后一个元素的指针就行了
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin()  const
{
	return _start;
}

const_iterator end()	const
{
	return _finish;
}

容量

  • 如何获取元素个数和内存容量,在C语言中我们讲过指针减去指针就是他们之间的元素个数,如此即可
size_t size()
{
	return _finish - _start;
}

size_t capacity()
{
	return _end_of_storage - _start;
}

  • 这里我们来重点说一下这个reserve扩容函数,他牵扯到一个很大的问题。

在这里插入图片描述

  • 我们这里再写一个push_back的接口(后面讲),让代码先跑起来
void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newCapacity);
	}
	*_finish = x;
	_finish++;
}

第一个测试,野指针异常

  • 测试代码
bit::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

结果:
在这里插入图片描述

  • 可以看到,程序崩溃了,那是什么原因了,不妨调试看一下
    在这里插入图片描述
  • 插入的时候对空指针解引用,我们再一步步看看_finish指针为啥是空指针
_finish = _start + size();

  • 这个时候就知道了,我们扩容的时候_start已经更新到tmp新空间的位置,但是更新_finish的时候,size()函数的结构是这样的
    在这里插入图片描述
  • 但是现在我们的_start已经是更新到tmp新空间的位置上去了,这时候_finish是旧空间,两个指针不是同一块空间相减就是一个随机值
    在这里插入图片描述
  • 那么又如何来解决这个问题呢?
  • 根据上面的描述,我们发现其实就是因为更新_finish我们调用了size函数造成的问题,那么这个时候我们其实就是想要知道元素个数,但是必须要在_start更新之前,所以我们就在_start更新之前获取元素个数即可
if (n > capacity())
{
	// 先保存一下原先的size()
	size_t sz = size();
	T* tmp = new T[n];		// 开一块新空间
	if (_start)
	{
		memcpy(tmp, _start, sizeof(T) * size());
		delete[] _start;
	}
	_start = tmp;
	_finish = _start + sz;
	_end_of_storage = _start + n;
}

第二轮测试,浅拷贝的问题

  • 下面是我们要进行第二轮测试的代码,内部类型使用的是 string类
void test_vector2()
{
	bit::vector<string> v;

	v.push_back("11111");
	v.push_back("22222");
	v.push_back("33333");
	v.push_back("44444");

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

  • 运行起来看并没有什么问题
    在这里插入图片描述
  • 但是呢当我再去push_back(“55555”)的时候程序却出现了问题,这个时候发生了扩容

在这里插入图片描述

  • 就是对于自定义类型,虽然我们的确是tmp独立开了一块新空间,但是memcpy函数就是把指针_str的值拷贝过来,导致原来的_str和tmp的_str指向同一块空间。就会发生两个指针指向同一块空间。
  • 那么在执行下面这句代码的时候,先会去把this指针的_str指向的那块空间析构掉,这个时候因为他们指向的是同一块空间,改变_str也同时更改了tmp中的_str
delete[] _start;

在这里插入图片描述

  • 那么知道了是什么问题,我们又该如何解决这个问题呢?
  • 我们知道导致这个原因的就是因为memcpy是浅拷贝,那么我们实现一个深拷贝就行了

这里我们就直接执行下面的代码:

for (size_t i = 0; i < OldSize; i++)
{
				tmp[i] = _start[i];
}
  • 这里的逻辑就是把_start的每个值赋值给tmp用来更新新的_start,这个时候就要重载一下赋值重载完成深拷贝,这会在后面介绍,请往下看。

接下来再看一下resize这个函数的实现

void resize(size_t n, const T& val = T())
{
	if (n <= size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		while (_finish < _start + n)
		{
			*_finish = val;
			_finish++;
		} 
	}
}
  • 如果重置的长度小于原来的长度,那么直接把_finish更新到_start + n的位置,就是n个长度就行
  • 如果大于原来的长度,直接扩容到n个,如果扩容到n个后_finish还是小于n个的时候,再把_finish更新到n个。

元素访问

  • 对于元素访问的话我们最常用的就是下标 + []的形式,这里给出两种,一个是const版本和非const版本
T& operator[](size_t pos)
{
	assert(pos < size());
	return _start[pos];
}

T& operator[](size_t pos)	const
{
	assert(pos < size());
	return _start[pos];
}

修改操作

push_back

void push_back(const T& x)
{
	if (_finish == _endofstorage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finish = x;
	++_finish;
}
  • 这一块的逻辑非常简单,当最后一个元素的指针和容量的指针位置相同的时候,就需要扩容,否则直接插入,更新_finish指针就行了

insert

void insert(iterator pos, const T& x)
{
	assert(pos >= _start && pos <= _finish);
	// 1.首先考虑扩容逻辑
	if (_finish == _end_of_storage)
	{
		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newCapacity);
	}

	// 2.挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

  • 这一块同理,插入数据先考虑是否需要扩容
  • 然后把pos位置后面的数据往后面挪,知道pos位置插入x即可。
迭代器失效问题

1.这里指定位置插入函数,如果空间不管扩容的时候就出现了迭代器失效的问题
2.如果不更新pos的位置,那我们还在释放的旧空间里面插入,就会发生访问野指针!

在这里插入图片描述

  • 那么如何去解决这个问题呢?

首先我们要明确导致这个问题发生的就是pos没有正确的更新,pos需要更新到新空间和原空间的相应位置,那么我们就算出旧空间中pos的相对位置,在新空间开辟后,把pos更新到新空间的相对位置就行了

  • 但是呢就上面这样还不够,我们只解决了内部迭代器失效的问题,而外部迭代器失效的问题并没有很好地解决。
  • 外部迭代器又是个什么东西,接下来看看这段代码
bit::vector<int>::iterator it = v.begin();
v.insert(it, 33);
bit::print(v);

cout << *it << endl;

bit::print(v);

  • 可以看到,在使用完这个这个迭代器之后再去访问就出现了问题
    在这里插入图片描述
  • 这是因为,形参迭代器的传值并不能改变外面的实参迭代器。
  • 有的同学就会说,那简单,传个引用就行了,但是你再来看看这个情况
v.insert(v.begin() + 3, 6);

在这里插入图片描述

在这里插入图片描述

erase

  • 对于【erase】来说,我们也是需要先去挪动数据的,但是在这里呢我们需要从前往后挪,也是防止造成覆盖的情况
void erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

	iterator end = pos + 1;
	// 移动覆盖
	while (end != _finish)
	{
		*(end - 1) = *end;
		++end;
	}
	--_finish;
}

  • 立马来测试一下

在这里插入图片描述

  • 看起来并没啥问题,那接下来看看下面的2个场景
  • 场景1:我们在中间插入两个偶数,现在我们要对容器中的偶数进行删除,可以看看下方结果
    在这里插入图片描述
    在这里插入图片描述
  • 可以看到发生了逻辑错误,我们的目的是把所有的偶数都删除了,可是现在容器中还有偶数!
    Why?
    如下图,我们删除一个数后,会把他前面的一个数挪到被删除数字的位置,那么这个时候it++,如果被挪到到删除位置的数是一个偶数就会漏掉这个数的判断。
    在这里插入图片描述
  • 场景2:如果最后一个元素是偶数,那么删除会咋样呢?如图看看我们的代码,那这样把最后一个元素删除后,不就越界访问了吗,it 会永远不等于 _finish
    在这里插入图片描述
  • 这个时候有人说,我们就要修改一下,当删除偶数的时候,不要让it++,这是对的,但还缺点东西
    在这里插入图片描述
  • 可是,万一别人底层实现erase很多次,容器的数据变少了,别人要缩容呢? 缩容是异地缩容,就是重新开一段比之前小的空间,把原来的空间数据拷贝过去,最后释放原来的空间,异地缩容是因为C++开辟内存只能是连续的内存,所以释放的时候不能只释放一部分。这时候it不就又变成,inset中的那样野指针的问题导致迭代器失效了吗?
    在这里插入图片描述
  • 这个时候,就返回被删除元素的下一个元素的位置值,这里返回pos,是因为我们的算法是被删除元素的下一个位置元素移到这个被删除元素的位置,所以被删除元素的下一个元素的位置就是pos本身.当然这里我们没有实现缩容,但是如果这里缩容,就会更新pos的位置,把新pos返回的
    在这里插入图片描述

默认成员函数

构造函数

  • 首先的话一定是构造函数,有参构造是一定要实现的,因为这里的逻辑和resize()是类似的,因此我们直接去做一个复用即可
// 有参构造
vector(size_t n, const T& val = T())
{
	resize(n, val);
}

  • 那有同学可能会问,三个私有成员变量不需要去做初始化吗?
  • 这个时候我们的缺省值就发挥作用了,很好的避免了忘记初始化三个成员变量的问题
private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;

  • 除了上面这种初始化,我再介绍一种方法:那就是使用 迭代器区间
// [first, last)
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

  • 复用push_back接口就行了
  • 我们再补充一个构造
		vector(initializer_list<T> lt)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			//initializer_list底层就是自己开了一块空间,类似数组这种,那么他就支持迭代器
			for (auto e : lt)
			{
				push_back(e);
			}
		}
  • 这个考前使用迭代器就行,他底层是类似数组
双重构造引发调用歧义

在这里插入图片描述

  • 那么如何解决呢?我们模板匹配还有个原则就是有现成吃现成的,如果有int类型的构造,模板就不用去实例化,所以重载一个int类型的,让他匹配

在这里插入图片描述

拷贝构造

vector(const vector<T>& v)
{
	reserve(v.capacity()); //和被拷贝的对象开一样大的空间,防止一直扩容,提高效率
	for (auto& e : v)
	{
		push_back(e);
	}
}
  • push_back扩容的时候完成深拷贝

赋值重载

vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}
  • 这里的逻辑类似string里面的,this想要得到v的资源并且把原来的资源抛弃,得到v的资源直接交换他们指向的指针就行了。由于v是局部变量,调用完会析构,就把this的资源析构了。

在这里插入图片描述

析构函数

~vector()
{
	if (_start)
	{
		delete[] _start;	//同一块空间
		_start = _finish = _endofstorage = nullptr;
	}
}

源码

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{

		}
		vector(initializer_list<T> lt)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			//initializer_list底层就是自己开了一块空间,类似数组这种,那么他就支持迭代器
			for (auto e : lt)
			{
				push_back(e);
			}
		}
		vector(const vector<T>& v)
		{
			reserve(v.capacity()); //和被拷贝的对象开一样大的空间,防止一直扩容,提高效率
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		template <class InputIterator>	//类模板的成员函数也可以是一个函数模板, 这里的迭代器可以是任何类型如:string, list的迭代器构造
		vector(InputIterator first, InputIterator last)		  
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		void swap(vector<T>& tmp)
		{
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_endofstorage, tmp._endofstorage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;	//同一块空间
				_start = _finish = _endofstorage = nullptr;
			}
		}

		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}
		const T& operator[](size_t i) const

		{
			assert(i < size());
			return _start[i];
		}
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _endofstorage - _start;
		}
		void reserve(size_t n)
		{
			size_t	OldSize = size();
			if (n > capacity())
			{
				T* tmp = new T[n];
				for (size_t i = 0; i < OldSize; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				_start = tmp;
				_finish = _start + OldSize;
				_endofstorage = _start + n;
				
			}
		}
		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = x;
			++_finish;
		}
		bool empty() const
		{
			return size() == 0;
		}
		void pop_back()
		{
			assert(!empty()); 
			--_finish;
		}
		void resize(size_t n, const T& val = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				} 
			}
		}
		iterator insert(iterator pos, const T& x)	//这里改成引用我希望形参可以改变实参
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len;
			}
			iterator end = _finish;
			while (end != pos)
			{
				*end = *(end - 1);
				end--;
			}
			*pos = x;
			_finish++;
			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator i = pos + 1;
			while (i < _finish)
			{
				*(i - 1) = *i;
				i++;
			}
			_finish--;
			return pos;
		}
	private:
		iterator _start = nullptr;	//写一个类一定要把缺省参数写上,防止初始化列表没初始化导致的一系列越权访问问题
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
		
	};
}

end

感谢阅读,希望对大家有所帮助。快去实现一下吧

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

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

相关文章

Open CASCADE学习|读取点集拟合样条曲线(续)

问题 上一篇文章已经实现了样条曲线拟合&#xff0c;但是仍存在问题&#xff0c;Tolerance过大拟合成直线了&#xff0c;Tolerance过大头尾波浪形。 正确改进方案 1️⃣ 核心参数优化 通过调整以下参数控制曲线平滑度&#xff1a; Standard_Integer DegMin 3; // 最低阶…

【Django】教程-11-ajax弹窗实现增删改查

【Django】教程-1-安装创建项目目录结构介绍 【Django】教程-2-前端-目录结构介绍 【Django】教程-3-数据库相关介绍 【Django】教程-4-一个增删改查的Demo 【Django】教程-5-ModelForm增删改查规则校验【正则钩子函数】 【Django】教程-6-搜索框-条件查询前后端 【Django】教程…

R语言:气象水文领域的数据分析与绘图利器

R 语言是一门由统计学家开发的用于统计计算和作图的语言&#xff08;a Statistic Language developed for Statistic by Statistician&#xff09;&#xff0c;由 S 语言发展而来&#xff0c;以统计分析功能见长。R 软件是一款集成 了数据操作、统计和可视化功能的优秀的开源软…

Kotlin与HttpClient编写视频爬虫

想用Apache HttpClient库和Kotlin语言写一个视频爬虫。首先&#xff0c;我需要确定用户的具体需求。视频爬虫通常涉及发送HTTP请求&#xff0c;解析网页内容&#xff0c;提取视频链接&#xff0c;然后下载视频。可能需要处理不同的网站结构&#xff0c;甚至可能需要处理动态加载…

图形化编程语言:低代码赛道的技术革命与范式突破

在 2024 年 Gartner 低代码平台魔力象限报告中&#xff0c;传统低代码厂商市场份额增速放缓至 12%&#xff0c;而图形化编程语言赛道融资额同比激增 370%。本文深度剖析低代码平台的技术瓶颈&#xff0c;系统阐释图形化编程语言的核心优势&#xff0c;揭示其如何重构软件开发范…

蓝桥杯每日刷题c++

目录 P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 (luogu.com.cn) P8748 [蓝桥杯 2021 省 B] 时间显示 - 洛谷 (luogu.com.cn) P10900 [蓝桥杯 2024 省 C] 数字诗意 - 洛谷 (luogu.com.cn) P10424 [蓝桥杯 2024 省 B] 好数 - 洛谷 (luogu.com.cn) P8754 [蓝桥杯 2021 省 AB2…

arthas之dump/classloader命令的使用

文章目录 1. dump2. classloader 1. dump 作用&#xff1a;将已加载类的字节码文件保存到特定目录&#xff1a;logs/arthas/classdump/ 参数 数名称参数说明class-pattern类名表达式匹配[c:]类所属 ClassLoader 的 hashcode[E]开启正则表达式匹配&#xff0c;默认为通配符匹…

第一次3D打印,一个简单的小方块(Rhino)

一、建模 打开犀牛&#xff0c;我们选择立方体 我们点击上册的中心点 输入0&#xff0c;然后回车0 而后我们输长度&#xff1a;10&#xff0c;回车确认 同样的&#xff0c;宽度10 高度同样是10 回车确认后&#xff0c;我们得到一个正方形 二、导出模型 我们选择文件—>保存…

VMware-workstation-full-12.5.2 install OS X 10.11.1(15B42).cdr

手把手虚拟机安装苹果操作系统 VMware_workstation_full_12.5.2 unlocker208 Apple Max OS X(M&#xff09;-CSDN博客 vcpu-0:VERIFY vmcore/vmm/main/physMem_monitor.c:1180 FILE: FileCreateDirectoryRetry: Non-retriable error encountered (C:\ProgramData\VMware): Cann…

[ctfshow web入门] web16

信息收集 提示&#xff1a;对于测试用的探针&#xff0c;使用完毕后要及时删除&#xff0c;可能会造成信息泄露 试试url/phpinfo.php url/phpsysinfo.php url/tz.php tz.php能用 点击phpinfo&#xff0c;查看phpinfo信息&#xff0c;搜索flag&#xff0c;发现flag被保存为变量…

全面支持MCP协议,开启便捷连接之旅,MaxKB知识库问答系统v1.10.3 LTS版本发布

2025年4月7日&#xff0c;MaxKB开源知识库问答系统正式发布v1.10.3 LTS版本。 在MaxKB v1.10.3 LTS版本中&#xff0c;应用方面&#xff0c;MaxKB新增支持MCP调用节点&#xff0c;AI对话节点新增MCP工具调用功能&#xff0c;支持设置MCP服务配置&#xff1b;函数库方面&#x…

VTK知识学习(51)- 交互与Widget(二)

1、交互器样式 前面所讲的观察者/命令模式是 VTK实现交互的方式之一。在前面示例 所示的窗口中可以使用鼠标与柱体进行交互&#xff0c;比如用鼠标滚轮可以对柱体放大、缩小;按下鼠标左键不放&#xff0c;然后移动鼠标&#xff0c;可以转动柱体;按下鼠标左键&#xff0c;同时按…

底盘---麦克纳姆轮(Mecanum Wheel)

一、基本定义与起源 定义&#xff1a;麦克纳姆轮是一种实现全向移动的特殊轮式结构&#xff0c;通过在主轮周边安装多个倾斜的辊子&#xff08;小轮&#xff09;&#xff0c;使设备能够在平面上向任意方向移动&#xff08;包括横向、斜向、旋转等&#xff09;&#xff0c;无需…

深入源码级别看spring bean创建过程

我们通常聊到spring bean的生命周期&#xff0c;大多是从网上找帖子背些基本概念&#xff0c;这样我们学到的东西是不够直观清晰的&#xff0c;这篇文章我就试着从源码级别来讲清楚bean的创建过程。 一、准备demo代码 我们既然要深入源码来看bean的创建过程&#xff0c;那么就…

I/O进程1

day1 一、标准IO 1.概念 在C库中定义的一组用于输入输出的函数 2.特点 (1).通过缓冲机制减少系统调用&#xff0c;提高效率 (2.)围绕流进行操作&#xff0c;流用FILE *来描述(3).标准IO默认打开了三个流&#xff0c;stdin&#xff08;标准输入&#xff09;、stdout&#xff08;…

解决Python与Anaconda中pip的冲突,安装包失败问题(此应用无法在你电脑上运行,无法访问)

1、Anaconda安装在D盘 2、Python安装在C盘&#xff08;当时刚换电脑&#xff0c;新电脑还未分盘&#xff0c;着急用python直接安装&#xff09; 问题&#xff1a; &#xff08;1&#xff09;winr&#xff0c;cmd无法访问c盘下的pip&#xff0c;安装包失败。采用管理者身份&…

Java全栈面试宝典:JMM内存模型与Spring自动装配深度解析

目录 一、Java内存模型&#xff08;JMM&#xff09;核心原理 &#x1f525; 问题8&#xff1a;happens-before原则全景解析 JMM内存架构图 happens-before八大规则 线程安全验证案例 &#x1f525; 问题9&#xff1a;JMM解决可见性的三大武器 可见性保障机制 volatile双…

AI前端组件库Ant DesIgn X

Ant Design X AI&#xff1a;体验新秩序 Ant Design 团队精心打造 RICH 设计范式&#xff0c;为 AI 界面提供卓越解决方案&#xff0c;引领智能交互新体验。 设计语言与理论 官网&#xff1a; Ant Design X - 轻松打造 AI 驱动的界面。 AI 设计范式 —— RICH 是我们在蚂蚁…

追踪大型语言模型的思维过程:提示词工程重要

追踪大型语言模型的思维过程:提示词工程重要 目录 追踪大型语言模型的思维过程:提示词工程重要**1. 分步思考能力:像人类一样打草稿****2. 跨语言概念词典:突破语言符号的束缚****3. 诗歌押韵规划:神经元提前预留韵脚****4. 编造专业解释:数据模式导致的“客服式回应”**…

BGP路由协议之属性1

公认属性是所有 BGP 路由器都必须能够识别的属性 公认必遵 (Well-known Mandatory) : 必须包括在每个 Update 消息里公认任意 (Well-known Discretionary) : 可能包括在某些 Update 消息里。 可选属性不需要都被 BGP 路由器所识别 可选过渡(OptionalTransitive) : BGP 设备不…