Cpp::STL—string类的模拟实现(12)

文章目录

  • 前言
  • 一、string类各函数接口总览
  • 二、默认构造函数
    • string(const char* str = "");
    • string(const string& str);
      • 传统拷贝写法
      • 现代拷贝写法
    • string& operator=(const string& str);
      • 传统赋值构造
      • 现代赋值构造
    • ~string();
  • 三、迭代器相关函数
    • begin & end
  • 四、容量和大小相关函数
    • size & capacity
    • reserve
    • resize
    • empty
  • 五、修改字符串相关函数
    • c_str
    • push_back
    • append
    • operator+=
    • insert
    • erase
    • clear
    • swap
    • substr
  • 六、访问字符串相关函数
    • operator[ ]
    • find
  • 七、关系运算符重载函数
  • 八、 流插入与流提取
    • 流插入
    • 流提取
    • getline
  • 总结


前言

  string类的模拟实现源代码
  我好像把string类的模拟实现给遗漏了
  没关系,我们现在来补!


一、string类各函数接口总览

  同样我们先来简单看下我们要实现的接口,另外为了避免跟库里面的string发生冲突,我们要用自己的命名空间包起来:

namespace HQ
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		
		iterator begin();
		iterator end();

		const_iterator begin() const;
		const_iterator end() const;

		// string(); // 无参和带参往往可以合成同一个
		string(const char* str = "");
		string(const string& str);
		string& operator=(const string& str);
		~string();
		const char* c_str() const;

		size_t size() const;
		char& operator[](size_t pos);
		const char& operator[](size_t pos) const;

		void reserve(size_t n = 0);

		void push_back(char ch);
		void append(const char* str);

		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos = 0, size_t len = npos);

		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);

		void swap(string& str); // string自己实现一个,std里的代价极大
		string substr(size_t pos = 0, size_t len = npos);

		bool operator<(const string& s) const;
		bool operator>(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator==(const string& s) const;
		bool operator!=(const string& s) const;

		void clear();
	private:
		// char _buff[16];

		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		const static size_t npos;
	};
	
	istream& operator>> (istream& is, string& str);
	ostream& operator<< (ostream& os, const string& str);
}

不要害怕,跟着我一步一步来看!

二、默认构造函数

string(const char* str = “”);

我们设置缺省函数,可是我们试想一下,缺省值给nullptr合理吗?

显然不合理,因为成员变量应该无论如何要先赋值个\0,即默认构造为空字符串,而""(没有空格)就自带一个\0

string::string(const char* str) // 缺省值声明和定义分离
	:_size(strlen(str)) // 不算\0,且字符串大小才用初始化列表来初始化,这是顺序的原因
{
	// 三个strlen效率低,用_size来初始化
	_str = new char[_size + 1]; // 为存储字符串开辟空间(多开一个用于存放'\0')
	_capacity = _size; // 不算\0

	strcpy(_str, str); // 将C字符串拷贝到已开好的空间
}

string(const string& str);

拷贝构造,在实现之前我们再来回顾一下深拷贝和浅拷贝的定义:

浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响

很明显,我们并不希望拷贝出来的两个对象之间存在相互影响,因此,我们这里需要用到深拷贝。下面提供深拷贝的两种写法

传统拷贝写法

在这里插入图片描述
先开辟一块足以容纳源对象字符串的空间,然后将源对象的字符串拷贝过去,接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的

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

现代拷贝写法

在这里插入图片描述
现代写法与传统写法的思想不同,先根据源字符串的C字符串调用构造函数构造一个tmp对象,然后再将tmp对象与拷贝对象的数据交换即可。拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的

string::string(const string& s)
{
	string tmp(s._str);
	swap(tmp);
}

string& operator=(const string& str);

赋值运算符重载,与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝

传统赋值构造

传统写法与拷贝构造函数的传统写法几乎相同,只是左值的_str在开辟新空间之前需要先将原来的空间释放掉,并且在进行操作之前还需判断是否是自己给自己赋值,若是自己给自己赋值,则无需进行任何操作

string& string::operator=(const string& str)
{
	// 考虑到两个字符串的长度可能不太一样
	// 干脆直接销毁旧的,新开一个拷贝过去
	if (this != &str) {
		char* tmp = new char[str._capacity + 1];
		strcpy(tmp, str._str);

		delete[] _str;
		_str = tmp;
		_size = str._size;
		_capacity = str._capacity;
	}
	return *this;
}

现代赋值构造

通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可,但是这里为了避免自己给自己赋值,我们还是选择引用传值,在内部在拷贝构造一个临时字符串用来交换

string& string::operator=(const string& str)
{
	if (this != &str) // 防止自己给自己赋值
	{
		string tmp(str); // 用s拷贝构造出对象tmp
		swap(tmp); // 交换这两个对象
	}
	return *this; // 返回左值(支持连续赋值)
}

~string();

string类的析构函数需要我们进行编写,因为每个string对象中的成员_str都指向堆区的一块空间,当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,我们需要使用delete手动释放堆区的空间

string::~string()
{
	delete[] _str; // 不会产生矛盾,就算只有一个底层也是调用delete _str;
	_str = nullptr;
	_size = _capacity = 0;
}

三、迭代器相关函数

  string类中的迭代器实际上就是字符指针,只是给字符指针起了一个别名叫iterator而已

注:不是所有的迭代器都是指针
typedef char* iterator;
typedef const char* const_iterator;

begin & end

begin函数的作用就是返回字符串中第一个字符的地址
end函数的作用就是返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址)

string::iterator string::begin()
{
	return _str;
} 

string::iterator string::end()
{
	return _str + _size;
}

string::const_iterator string::begin() const
{
	return _str;
}

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

四、容量和大小相关函数

size & capacity

因为string类的成员变量是私有的,我们并不能直接对其进行访问,所以string类设置了size和capacity这两个成员函数,用于获取string对象的大小和容量

size函数用于获取字符串当前的有效长度(不包括’\0’)
capacity函数用于获取字符串当前的容量(不包括’\0’)

size_t string::size() const
{
	return _size;
}

size_t string::capacity() const
{
	return _capacity;
}

reserve

其规则:

  1. 当n大于对象当前的capacity时,将capacity扩大到n或大于n
  2. 当n小于对象当前的capacity时,什么也不做

代码中使用strncpy进行拷贝对象C字符串而不是strcpy,是为了防止对象的C字符串中含有有效字符’\0’而无法拷贝(strcpy拷贝到第一个’\0’就结束拷贝了)

void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strncpy(tmp, _str, _size + 1); // 将对象原本的C字符串拷贝过来(包括'\0')
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

在这里插入图片描述

resize

其规则:

  1. 当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’
  2. 当n小于当前的size时,将size缩小到n
void string::resize(size_t n, char ch = '\0')
{
	if (n < _size) // n小于当前size
	{
		_size = n; // 将size调整为n
		_str[_size] = '\0'; // 在size个字符后放上'\0'
	}
	else if (n > _capacity)
	{
		reserve(n); // 扩容
		
		for (size_t i = _size; i < n; i++) // 将size扩大到n,扩大的字符为ch
		{
			_str[i] = ch;
		}
		
		_size = n; // size更新
		_str[_size] = '\0'; // 字符串后面放上'\0'
	}
}

empty

empty是string的判空函数

bool string::empty() const
{
	return _size == 0;
}

五、修改字符串相关函数

c_str

按照C语言的格式返回字符串

const char* string::c_str() const
{
	return _str;
}

push_back

push_back函数的作用就是在当前字符串的后面尾插上一个字符,尾插之前首先需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’,否则打印字符串的时候会出现非法访问,因为尾插的字符后方不一定就是’\0’

void string::push_back(char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
}

append

append函数的作用是在当前字符串的后面尾插一个字符串,尾插前需要判断当前字符串的空间能否容纳下尾插后的字符串,若不能,则需要先进行增容,然后再将待尾插的字符串尾插到对象的后方,因为待尾插的字符串后方自身带有’\0’,所以我们无需再在后方设置’\0’

void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		// 大于2倍,需要多少开多少,小于2倍按2倍扩
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}

	strcpy(_str + _size, str);
	_size += len;
}

operator+=

有三个重载:
string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);

string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}

string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

string& string::operator+=(const string& str)
{
	append(str.c_str());
	return *this;
}

insert

insert函数的作用是在字符串的任意位置插入字符或是字符串

// 插入字符,注意end不会为-1
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	// 谨慎使用
	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}

	size_t end = _size;
	while (end >= pos) {
		_str[end + 1] = _str[end];
		if (end == 0) break; // end == -1 -> err
		--end;
	}
	_str[pos] = ch;
	++_size;
}

insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可
在这里插入图片描述

void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	reserve(_size + len);

	size_t end = _size;
	while (end >= pos) {
		_str[end + len] = _str[end];
		if (end == 0) break; // end == -1 -> err
		--end;
	}

	memcpy(_str + pos, str, len);
	_size += len;
}

insert函数用于插入字符串时,首先也是判断pos的合法性,若不合法则无法进行操作,再判断当前对象能否容纳插入该字符串后的字符串,若不能则还需调用reserve函数进行扩容,插入字符串时,先将pos位置及其后面的字符统一向后挪动len位(len为待插入字符串的长度),给待插入的字符串留出位置,然后将其插入字符串即可
在这里插入图片描述

erase

先来关注函数原型:

默认从0位置开始,一直清楚到末尾
void erase(size_t pos = 0, size_t len = npos);

erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,这时候一共有两种情况

  1. pos位置及其之后的有效字符都需要被删除

这时我们只需在pos位置放上’\0’,然后将对象的size更新即可
在这里插入图片描述

  1. pos位置及其之后的有效字符只需删除一部分

这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符,此时不用在字符串后方加’\0’,因为在此之前字符串末尾就有’\0’了
在这里插入图片描述

void string::erase(size_t pos, size_t len)
{
	assert(pos < _size);

	// 当len == npos时候,条件判断一定成立
	if (len >= _size - pos) {
		// pos后(含)全删完
		_str[pos] = '\0';
		_size = pos;
	}
	else {
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

clear

clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可

void string::clear()
{
	_str[0] = '\0';
	_size = 0;
}

swap

swap函数用于交换两个对象的数据,直接调用库里的swap模板函数将对象的各个成员变量进行交换即可

void string::swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}

substr

substr功能是返回从指定位置开始len长度的字符串,先创建string空对象用于接收截取字符串,当len == npos 或len >= _size - pos,代表了从pos位置到尾的字符串截取,并且尽量书写 len >= _size - pos ,而不是 len + pos >= _size 这种,是为了防止 len + pos 超过类型最大值范围

string string::substr(size_t pos, size_t len)
{
	if (len >= _size - pos) {
		string sub(_str + pos);
		return sub;
	}
	else {
		string sub;
		sub.reserve(len);
		for (size_t i = pos; i < pos + len ; i++) {
			sub += _str[i];
		}
		sub._size = len;
		return sub;
	}
}

六、访问字符串相关函数

operator[ ]

[ ]运算符的重载是为了让string对象能像C字符串一样,通过[ ] +下标的方式获取字符串对应位置的字符
这里要有两种版本,一种可读可写,一种只读不写

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

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

find

实现这两种重载:

size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);

// 正向查找第一个匹配的字符
size_t string::find(char ch, size_t pos)
{
	assert(pos < _size); //检测下标的合法性
	for (size_t i = pos; i < _size; i++) {
		if (_str[i] == ch) {
			return i;
		}
	}
	return npos;
}

// 正向查找第一个匹配的字符串
size_t string::find(const char* sub, size_t pos)
{
	assert(pos < _size); //检测下标的合法性
	char* p = strstr(_str + pos, sub);
	return p == NULL ? npos : p - _str;
}

  对于第一种,首先判断所给pos的合法性,然后通过遍历的方式从pos位置开始向后寻找目标字符,若找到,则返回其下标;若没有找到,则返回npos。(npos是string类的一个静态成员变量其值为整型最大值

  对于第二种,首先也是先判断所给pos的合法性,然后我们可以通过调用strstr函数进行查找。strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个C语言空指针NULL,若是找到了目标字符串,我们可以通过计算目标字符串的起始位置和对象C字符串的起始位置的差值,进而得到目标字符串起始位置的下标

七、关系运算符重载函数

  关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现

bool string::operator<(const string& s) const
{
	return strcmp(_str, s._str) < 0;
}

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

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

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

bool string::operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;
}

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

八、 流插入与流提取

流插入

重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入
输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取
另外还有,很明显得重载为全局函数

istream& operator>>(istream& is, string& str)
{
	str.clear(); // 要覆盖先前的内容,先清除一下
	char ch = is.get();
	//is >> ch; // 拿不到空格和换行
	
	while (ch != ' ' && ch != '\n') {
		str += ch;
		ch = is.get();
	}

	return is;
}

流提取

重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印。实现时我们可以直接使用范围for对对象进行遍历即可

ostream& operator<<(ostream& os, const string& str)
{
	for (size_t i = 0; i < str.size(); i++) {
		os << str[i];
	}

	return os;
}

getline

getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符

// 举个例子哈
int main()
{
	string str;
	cin >> str; // 假设输入hello world
	cout << str; // 只会输出hello
	
	return 0;
}

如上,我们会发现空格字符无法被插入str,这时候就是getline发挥的时候了
再来道具体的题目,说不定能让你有更深的认识
在这里插入图片描述


总结

  总算是补上了!可以看出string类的完整实现还是蛮复杂的

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

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

相关文章

JS基础练习|动态创建多个input,并且支持删除功能

效果图 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>动态生成输入框并保存数据</title>&…

誉天Linux云计算课程学什么?为什么保障就业?

一个IT工程师相当于干了哪些职业? 其中置顶回答生动而形象地描绘道&#xff1a; 一个IT工程师宛如一个超级多面手&#xff0c;相当于——加班狂程序员测试工程师实施工程师网络工程师电工装卸工搬运工超人。 此中酸甜苦辣咸&#xff0c;相信很多小伙伴们都深有体会。除了典…

【微服务】注册中心 - Eureka(day3)

CAP理论 P是分区容错性。简单来说&#xff0c;分区容错性表示分布式服务中一个节点挂掉了&#xff0c;并不影响其他节点对外提供服务。也就是一台服务器出错了&#xff0c;仍然可以对外进行响应&#xff0c;不会因为某一台服务器出错而导致所有的请求都无法响应。综上所述&…

uniapp修改uni-ui组件样式(对微信小程序/H5有效,vue3)

寻找要修改的样式 使用开发者工具找到具体要修改的class类名 修改 <style lang"scss">//.nav为上一级的class.nav::v-deep .uni-navbar--border {border-bottom-style: none !important;} </style>完整代码 <template><view><uni-na…

Kafka学习笔记(三)Kafka分区和副本机制、自定义分区、消费者指定分区

文章目录 前言7 分区和副本机制7.1 生产者分区写入策略7.1.1 轮询分区策略7.1.2 随机分区策略7.1.3 按key分区分配策略7.1.4 自定义分区策略7.1.4.1 实现Partitioner接口7.1.4.2 实现分区逻辑7.1.4.3 配置使用自定义分区器7.1.4.4 分区测试 7.2 消费者分区分配策略7.2.1 RangeA…

【华为HCIP实战课程三】动态路由OSPF的NBMA环境建立邻居及排错,网络工程师

一、NBMA环境下的OSPF邻居建立问题 上节我们介绍了NBMA环境下OSPF邻居建立需要手动指定邻居,因为NBMA环境是不支持广播/组播的 上一节AR1的配置: ospf 1 peer 10.1.1.4 //手动指定邻居的接口地址,而不是RID peer 10.1.1.5 area 0.0.0.0 手动指定OSPF邻居后抓包查看OSP…

C语言的内存结构

在电脑中C语言编译器也像其他软件一样占用一块内存空间。 为了更好的利用好这块内存&#xff0c;C语言将他们分为 在C语言中&#xff0c;变量定义的位置不一样&#xff0c;那么在内存中所处的位置也是不一样的。&#xff08;变量在函数内部是存储在栈里&#xff0c;而在函数外部…

SPI通信——FPGA学习笔记14

一、简介 SPI(Serial Periphera Interface&#xff0c;串行外围设备接口)通讯协议&#xff0c;是 Motorola 公司提出的一种同步串行接口技术&#xff0c;是一种高速、全双工、同步通信总线&#xff0c;在芯片中只占用四根管脚用来控制及数据传输&#xff0c;广泛用于 EEPROM、F…

基于STM32的智能家居灯光控制系统设计

引言 本项目将使用STM32微控制器实现一个智能家居灯光控制系统&#xff0c;能够通过按键、遥控器或无线模块远程控制家庭照明。该项目展示了如何结合STM32的外设功能&#xff0c;实现对灯光的智能化控制&#xff0c;提升家居生活的便利性和节能效果。 环境准备 1. 硬件设备 …

C--编译和链接见解

欢迎各位看官&#xff01;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论 感谢各位看官的支持&#xff01;&#xff01;&#xff01; 一&#xff1a;翻译环境和运行环境 在ANSIIC的任何一种实现中&#xff0c;存在两个不同的环境1&#xff0c;…

戴尔电脑怎么开启vt虚拟化_戴尔电脑新旧机型开启vt虚拟化教程

最近使用戴尔电脑的小伙伴们问我&#xff0c;戴尔电脑怎么开启vt虚拟。大多数可以在Bios中开启vt虚拟化技术&#xff0c;当CPU支持VT-x虚拟化技术&#xff0c;有些电脑会自动开启VT-x虚拟化技术功能。而大部分的电脑则需要在Bios Setup界面中&#xff0c;手动进行设置&#xff…

SpringCloud入门(九)Feign实战应用和性能优化

一、Feign实战应用 Feign的客户端与服务提供者的controller代码非常相似&#xff1a; 有没有一种办法简化这种重复的代码编写呢&#xff1f; 方式一&#xff1a;继承 优点&#xff1a; 简单。实现了代码共享。 缺点&#xff1a;服务提供方、服务消费方紧耦合。参数列表中的注解…

vscode安装及c++配置编译

1、VScode下载 VS Code官网下载地址&#xff1a;Visual Studio Code - Code Editing. Redefined。 2、安装中文插件 搜索chinese&#xff0c;点击install下载安装中文插件。 3、VS Code配置C/C开发环境 3.1、MinGW-w64下载 VS Code是一个高级的编辑器&#xff0c;只能用来写代…

Coggle数据科学 | Kaggle赛题解析:CMI 体育损伤指数预测

本文来源公众号“Coggle数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Kaggle赛题解析&#xff1a;CMI 体育损伤指数预测 赛题名称&#xff1a;Child Mind Institute — Problematic Internet Use 赛题类型&#xff1a…

Windows 环境上安装 NASM 和 YASM 教程

NASM 和 YASM NASM NASM&#xff08;Netwide Assembler&#xff09;是一个开源的、可移植的汇编器&#xff0c;它支持多种平台和操作系统。它可以用来编写16位、32位以及64位的代码&#xff0c;并且支持多种输出格式&#xff0c;包括ELF、COFF、OMF、a.out、Mach-O等。NASM使用…

GS-SLAM论文阅读笔记--GEVO

前言 这篇文章看着就让人好奇。众所周知&#xff0c;高斯是一个很不错的建图方法&#xff0c;但是本文的题目居然是只用高斯进行单目VO&#xff0c;咱也不知道这是怎么个流程&#xff0c;看了一下作者来自于MIT&#xff0c;说不定是个不错的工作&#xff0c;那就具体看看吧&am…

LeetCode从入门到超凡(五)深入浅出---位运算

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的LeetCode学习总结文档&#xff1b;本文主要讲解 位运算算法。&#x1f495;&#x1f495;&#x1f60a; 一、 位运算简介 1.什么是位…

简易CPU设计入门:取指令(三),ip_buf与rd_en的非阻塞赋值

在开篇&#xff0c;还是请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 下载本项目代码 准备好了项目源代码以后&…

【重学 MySQL】五十一、更新和删除数据

【重学 MySQL】五十一、更新和删除数据 更新数据删除数据注意事项 在MySQL中&#xff0c;更新和删除数据是数据库管理的基本操作。 更新数据 为了更新&#xff08;修改&#xff09;表中的数据&#xff0c;可使用UPDATE语句。UPDATE语句的基本语法如下&#xff1a; UPDATE ta…

【ADC】噪声(1)噪声分类

概述 本文学习于TI 高精度实验室课程&#xff0c;总结 ADC 的噪声分类&#xff0c;并简要介绍量化噪声和热噪声。 文章目录 概述一、ADC 中的噪声类型二、量化噪声三、热噪声四、量化噪声与热噪声对比 一、ADC 中的噪声类型 ADC 固有噪声由两部分组成&#xff1a;第一部分是量…