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

news2024/11/28 1:52:48

文章目录

  • 前言
  • 一、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.coloradmin.cn/o/2186889.html

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

相关文章

leetcode打卡001-约瑟夫问题

约瑟夫问题 其背景故事是关于一组人站成一个圈&#xff0c;从某个人开始报数&#xff0c;每数到特定数字的人将被淘汰出圈&#xff0c;然后从被淘汰人的下一个人重新开始报数&#xff0c;直到最后剩下一个人。问题的目标是确定最后剩下的那个人在最初的位置。 关键词 递归&a…

HCIP-HarmonyOS Application Developer 习题(四)

1、以下哪个Harmonyos的AI能力可以提供文档翻拍过程中的辅助增强功能? A.文档检测矫正 B.通用文字识别 C.分词 D.图像超分辨率 答案&#xff1a;A 分析&#xff1a;文档校正提供了文档翻拍过程的辅助增强功能&#xff0c;包含两个子功能&#xff1a; 文档检测&#xff1a;能够…

基于单片机人体反应速度测试仪系统

** 文章目录 前言概要设计思路 软件设计效果图 程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们…

kubernetes基础操作(pod生命周期)

pod生命周期 一、Pod生命周期 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期&#xff0c;它主要包含下面的过程&#xff1a; ◎pod创建过程 ◎运行初始化容器&#xff08;init container&#xff09;过程 ◎运行主容器&#xff08;main container&#xff…

【Redis入门到精通九】Redis中的主从复制

目录 主从复制 1.配置主从复制 2.主从复制中的拓扑结构 3.主从复制原理 4.主从复制总结 主从复制 在分布式系统中为了解决单点问题&#xff0c;通常会把数据复制多个副本部署到其他服务器&#xff0c;满⾜故障恢复和负载均衡等需求。Redis 也是如此&#xff0c;它为我们提…

kafka基本概念以及用法

kafka基本概念以及用法目录 文章目录 kafka基本概念以及用法目录一、什么是kafka&#xff1f;二、为什么要使用kafka?三、kafka的基本概念四、安装kafka(windows版本)五、命令行控制kafka生产消费数据&#xff0c;创建 删除topic六、java操作kafka消费生产 提示&#xff1a;以…

Ubuntu操作系统版本服务支持时间(更新到24.04)

文章参考链接 以下是解释&#xff1a; 开发代号&#xff1a;Ubuntu的每个版本都有一个开发代号&#xff0c;例如“Mantic Minotaur”。 版本命名&#xff1a;Ubuntu的版本号是根据发布年份和月份来命名的。例如&#xff0c;Ubuntu 23.10是在2023年10月发布的。 LTS版本&…

Windows 11 24H2正式发布

微软最近正式发布了Windows 11 24H2&#xff0c;这是Windows 11的最新功能更新&#xff0c;带来了多项新特性和改进。 主要新功能&#xff1a; 人工智能增强&#xff1a;此次更新特别强调AI能力&#xff0c;推出了如Windows Copilot的增强版本。Copilot的界面得到了改善&#…

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

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

关于Mybatis框架操作时注意的细节,常见的错误!(博主亲生体会的细节!)

目录 1.在对DB进行CRUD时&#xff0c;除了查&#xff0c;其余的操作都要进行事务的提交否则不成功。 2.用sqlSession原生方法时&#xff0c;第一个参数方法名&#xff0c;是xml文件中定义的id名&#xff0c;底层找的是你这个接口所定义的方法名。 3.以包为单位引入映射文件 …

第三节-类与对象(2)默认成员函数详解

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类&#xff08;空类大小为1&#xff09;。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;…

DOM树(下) -- 第八课

文章目录 前言一、DOM属性操作1. 获取属性值2. 设置属性值3. 移除属性值 二、节点1.什么是节点?2. 节点层级1. 获取父级节点2. 获取兄弟节点3. 获取子节点 3. 节点操作1. 创建节点2. 添加和删除节点 三、事件进阶1. 注册事件1. 传统方式2. 监听方式 2. 删除事件3. 事件流 四、…

第4篇:MSSQL日志分析----应急响应之日志分析篇

常见的数据库攻击包括弱口令、SQL注入、提升权限、窃取备份等。对数据库日志进行分析&#xff0c;可以发现攻击行为&#xff0c;进一步还原攻击场景及追溯攻击源。 0x01 MSSQL日志分析 首先&#xff0c;MSSQL数据库应启用日志记录功能&#xff0c;默认配置仅限失败的登录&…

Veritus netbackup 管理控制台无法连接:未知错误

节假日停电&#xff0c;netbackup服务器意外停机后重新开机&#xff0c;使用netbackup管理控制台无法连接&#xff0c;提示未知错误。 ssh连接到服务器&#xff0c;操作系统正常&#xff0c;那应该是应用有问题&#xff0c;先试一下重启服务器看看。重新正常关机&#xff0c;重…

【Ubuntu】使用阿里云apt源来更新apt源

1.前言 我在京东云买了一个云服务器&#xff0c;但是我第一次使用apt的时候&#xff0c;发现遇到了下面这些情况 后面听老师讲&#xff0c;还需要执行下面这个 但是我再次使用apt下载软件的时候&#xff0c;还是出现了下面这个情况 后面问了老师才知道是apt源的问题&#x…

解决Github打不开或速度慢的问题

一、原因 我们先分析一下Github在国内访问慢或有时候登陆不上去的问题原因&#xff1a;其实这都是因为我们访问github官网时是直接访问域名即github.com&#xff0c;那么中间有个域名通过DNS解析的过程&#xff0c;将域名解析为对应的ip地址&#xff0c;其实主要时间都是花在了…

【寻找one piece的算法之路】——双指针算法!他与她是否会相遇呢?

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;寻找one piece的刷题之路 什么是双指针算法 双指针算法是一种常用的编程技巧&#xff0c;尤其在处理数组和字符串问题时非常有效。这种方法的核心思想是使用两个指针来遍历数据结构&#xff0c;这两…

学习记录:js算法(五十二):验证二叉搜索树

文章目录 验证二叉搜索树我的思路网上思路 总结 验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的 左子树 只包含 小于 当前节点的数。 节点的 右子树 只包含 大于 当前节点的数。 所有…

【Python】AudioLazy:基于 Python 的数字信号处理库详解

AudioLazy 是一个用于 Python 的开源数字信号处理&#xff08;DSP&#xff09;库&#xff0c;设计目的是简化信号处理任务并提供更直观的操作方式。它不仅支持基础的滤波、频谱分析等功能&#xff0c;还包含了滤波器、信号生成、线性预测编码&#xff08;LPC&#xff09;等高级…

Mybatis框架梳理

Mybatis框架梳理 前言1.ORM2.模块划分2.1 ORM的实现2.2 SQL的映射2.3 插件机制2.4 缓存机制2.5 其他 3. 愿景 前言 如果让我聊一聊mybatis&#xff0c;我该怎么说呢&#xff1f;开发中时时刻刻都在用它&#xff0c;此时此刻&#xff0c;脑海中却只浮现ORM框架这几个字&#xff…