C++必修:模拟实现STL之string

news2024/9/23 21:24:36

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

为了让我们更加深入理解string,接下来我们将模拟实现一个·简易版的string。而为了和STL库中的string以示区分,我们将使用命名空间namespace对其封装。

1. string的成员变量

string简单来说就是一个被封装可动态增长的字符数组,这与我们在数据结构中学的串非常类似,所以我们可以借助实现串的思路来大致模拟string的结构。

下面是string的成员变量:

namespace betty
{
	class string 
    {
	public:
		//...
	private:
		size_t _size;//当前有效字符的个数
		size_t _capacity;//当前容量的大小,方便扩容
		char* _str;//存储的字符串
	};
}

值得注意的是\0既不占据有效长度的大小,也不占据容量的大小。

img

2. string的成员函数

再知道string的成员变量之后,接下来我们将探究string的成员函数,而常见成员函数的用法我们早在之前就已经介绍过了 ,下面我们将来具体实现一下:

2.1. string的迭代器

首先我们来模拟实现一下迭代器iterator,而在string中迭代器iterator就是一个指针。所以我们直接使用typedef实现

typedef char* iterator;//普通迭代器
typedef const char* const_iterator;//const迭代器

接下来我们来实现begin()end(),其中begin()指向的是字符串的起始位置即_str,而end()指向有效字符最后的下一位即\0的位置。

img

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

实现完普通迭代器之后,我们可以顺便重载一个const_iterator的版本。

const_iterator begin() const
{
	return _str;
}

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

我们知道在string中还有一个反向迭代器,这个我们在之后会统一实现。

2.2. string的初始化与销毁

2.2.1. 构造函数与拷贝构造

我们之前在学习string时知道其初始化方式有很多,可以通过默认构造函数给其初始化,也可以通过字符数组给其初始化。当然我们在实现时不用这么麻烦,直接给缺省值就可以了。

string(const char* str = "")//默认缺省为空串
		:_size(strlen(str))
		, _capacity(_size)
		, _str(new char[_capacity + 1])//也要存储'\0'
	{
		strcpy(_str, str);
	}

拷贝构造也十分简单,直接拷贝就行了

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

当然我们也可以通过一个取巧的方式来实现拷贝构造。

string(const string& s)
	: _str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);
	this->swap(tmp);
}

首先通过构造出一个与原字符串相同的字符串tmp,然后让this所指向的字符串与其交换,这样出了作用域之后销毁的就是原this所指向的字符串。当然我们必须先将this所指向的字符串先初始化,不然析构函数去释放未初始化的空间会出错。

2.2.2. 赋值重载与析构函数

赋值运算符重载与拷贝构造的实现就非常类似了,直接实现即可。只需要对自己赋值给自己的情况进行特判即可。

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;
}

当然我们也能实现赋值重载的简易版本。

string& operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);
		this->swap(tmp);
	}
	return *this;
}

最后我们实现析构函数,只需要清理资源即可

~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

2.3. string的容量操作

2.3.1. 有效长度与容量大小

首先我们先实现返回字符串有效长度的size() 与容量大小的capacity()。并且为了适配const对象,最后用const修饰this指针。

size_t size() const
{
	return _size;
}
size_t capacity() const
{
	return _capacity;
}
2.3.2. 容量操作

首先我们实现判断字符串是否为空的empty()以及情况字符串的clear()。其中emty()不需要修改,可以加上const

void clear()
{
	_str[0] = '\0';//直接将首字符改为'\0'
	_size = 0;
}
bool empty() const
{
	return _size == 0;
}

接下来我们来实现扩容函数reserve()与·resize(),其中reserve()最简单,只要新容量大于旧容量就发生扩容。

void reserve(size_t newCapacity = 0)
{
	// 当新容量大于旧容量的时才扩容
	if (newCapacity > _capacity)
	{
		char* tmp = new char[newCapacity + 1];
        //拷贝原有数据
		memcpy(tmp, _str, _size);
		delete[] _str;
		// 让_str指向新空间
		_str = tmp;
		_capacity = newCapacity;
	}
}

resize()的逻辑就比较复杂,需要分三种情况讨论。设字符串原来有效长度为size,容量为capacity,新容量为n

  1. n<size时,resize会删除有效字符到指定大小。
  2. size<n<capcity时,resize会补充有效字符(默认为’\0)到指定大小。
  3. n>capacity时,resize会补充有效字符(默认为’\0)到指定大小。
void resize(size_t n, char c = '\0')
{
	if (n > _size)
	{
		// n>capacity则扩容
		if (newSize > _capacity)
		{
			reserve(n);
		}
		//填充新数据c至有效长度为n
		memset(_str + _size, c, n - _size);
	}

	// 当n<=_size,删除多余的字符
	_size = n;
	_str[newSize] = '\0';
}

2.4. string的访问操作

为了符合我们C语言访问数组的习惯,我们可以先重载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];
}

同理我们也可以实现front()back()函数。

// 可读可写
char& front()
{
	return _str[0];
}
char& back()
{
	return _str[_size - 1];
}
// 可读不可写
const char& front()const
{
	return _str[0];
}
const char& back()const
{
	return _str[_size - 1];
}

2.5. string的修改操作

2.5.1. 字符串的添加

首先我们将实现两个常用的修改函数:push_back()append()

void push_back(char ch)
{
	// 如果数据满了,则需要进行扩容
	if (_size == _capacity)
	{ 	
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size++] = ch;
	_str[_size] = '\0';
}
// 追加字符串
void append(const char* s)
{
	int len = strlen(s);// 获取字符串的长度
	// 如果大于原来容量,则就需要扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	// 将字符串拷贝到末尾的_size位置
	memcpy(_str + _size, s, len + 1);
	_size += len;
}

而后我们可以复用前两个函数实现operator+=()

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

最后我们来实现随机插入insert()函数。将pos位置后所有字符移动len个单位,如果为字符len=1,否则len=字符串长度

img

//添加一个字符
void 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 + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
}
//添加一个字符串
void insert(size_t pos, const char* s)
{
    //防止越界访问
	assert(pos <= _size);
    //检查是否需要扩容
	size_t len = strlen(s);
	if (_size+len > _capacity)
	{
		reserve(_size+len);
	}
	size_t end = _size + len;
	while (end > pos)
	{
		_str[end] = _str[end - len];
		--end;
	}
	memcpy(_str + pos, s, len);
}
2.5.2. 字符串的删除

字符串的删除我们需要实现pop_back()erase()两个函数。

void pop_back()
{
	_str[_size - 1] = '\0';
    --_size;
}

而随机删除erase()需要再定义一个类成员变量npos来实现,它为无符号数的-1,一般为整型的最大值

// 类内声明
static size_t npos;
// 类外初始化
size_t string::npos = -1;

pos位置后所有字符往前移动len个单位,如果为字符len=1,否则len=字符串长度

img

void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
    //判断是否将后面字符之间删除完
	if (len == npos || pos + len >= _size)
	{
		_str[0] = '\0';
		_size = pos;
	}
	else
	{
        //往前移len个字符
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
	}
}
2.5.3. 字符串的交换

最后我们来实现字符串的交换swap()函数,我们知道string的交换其实就是指针_str_sizecapacity的交换。

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

img

2.6. string的其他操作

首先我们将常见字符串的比较函数实现,实现时相互之间还可以复用。

bool operator <(const string& s)const
{
	return strcmp(_str, s._str) < 0;
}
bool operator >(const string& s)const
{
	return strcmp(_str, s._str) > 0;
}
bool operator == (const string& s)const
{
	return strcmp(_str, s._str) == 0;
}
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);
}

接下来让我们实现流插入operator<<()与流提取operator>>()。但是我们要注意普通istream对象无法提前空格与\n。这是我们就需要一个函数get()来提取

img

同时为了避免频繁扩容,我们可以借助一个辅助数组buf来辅助填充。

// 流提取
istream& operator>>(istream& in, string& s)
{
	s.clear();//先清空原字符串
	char ch = in.get();
	char buf[128];
	int i = 0;
	while (ch != '\n')//以换行为分隔符
	{
		buf[i++] = ch;
		// 为\0留空间
		if (i == 127)
		{
			buf[i] = '\0';
			s += buf;
			i = 0;
		}
		ch = in.get();
	}
	//将buf中剩余数据直接填入
	if (i != 0)
	{
		buf[i] = '\0';
		s += buf;
	}
	return in;
}

最后我们实现c_str()find()substr()函数。

const char* c_str()const		
{		
    return _str;        
}
//找字符
size_t find(char ch, size_t pos = 0) const
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}
//找字符串
size_t find(const char* s, size_t pos = 0) const
{
	const char* p = strstr(_str + pos, s);
	if (p)
	{
		return p - _str;
	}
	return npos;
}
//截取一段字符串
string substr(size_t pos, size_t len = npos)
{
    string s;
    size_t end = pos + len;
    //判断是否截取到最后
    if (len == npos || pos + len >=_size)
    {
        len = _size - pos;
        end = _size;
    }
    //提前开辟空间
    s.reserve(len);
    for (size_t i = pos; i < end; i++)
    {
        s += _str[i];
    }
    return s;
}

3. 源码

#pragma once
#include <iostream>
#include<assert.h>
#include<stdbool.h>
using namespace std;
namespace betty
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return iterator(_str);
		}
		const_iterator begin()const
		{
			return const_iterator(_str);
		}
		iterator end()
		{
			return iterator(_str + _size);
		}
		const_iterator end()const
		{
			return const_iterator(_str + _size);
		}
		//string()
		//	:_size(0)
		//	, _capacity(0)
		//	, _str(new char[1])
		//{
		//	_str[0] = '\0';
		//}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
			, _str(new char[_capacity + 1])
		{
			strcpy(_str, str);
		}
		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		//	strcpy(_str,s._str);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[s._capacity + 1];
		//		strcpy(tmp, s._str);
		//		delete[] _str;
		//		_str = tmp;
		//		_size = s._capacity;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}
		string& operator=(string tmp)
		{
			if (this != &tmp)
			{
				swap(tmp);
			}
			return *this;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos)const
		{
			assert(pos < _size);
			return _str[pos];
		}
		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}
		const char* c_str()const
		{
			return _str;
		}
		void reserve(size_t newCapacity )
		{
			if (newCapacity > _capacity)
			{
				char* tmp = new char[newCapacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = newCapacity;
			}
		}
		void resize(size_t newSize, char c = '\0')
		{
			if (newSize > _size)
			{
				if (newSize > _capacity)
				{
					reserve(newSize);
				}
				memset(_str + _size, c, newSize - _size);
			}
			_size = newSize;
			_str[newSize] = '\0';
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		bool empty() const
		{
			return _size == 0;
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}
			_str[_size++] = ch;
			_str[_size] = '\0';
		}
		void append(const char* s)
		{
			size_t len = strlen(s);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			memcpy(_str + _size, s, len + 1);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}
		//找字符
		size_t find(char ch, size_t pos = 0) const
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		//找字符串
		size_t find(const char* s, size_t pos = 0) const
		{
			const char* p = strstr(_str + pos, s);
			if (p)
			{
				return p - _str;
			}
			return npos;
		}
		string substr(size_t pos, size_t len = npos)
		{
			string s;
			size_t end = pos + len;
			//判断是否截取到最后
			if (len == npos || pos + len >=_size)
			{
				len = _size - pos;
				end = _size;
			}
			//提前开辟空间
			s.reserve(len);
			for (size_t i = pos; i < end; i++)
			{
				s += _str[i];
			}
			return s;
		}
		void 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 + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			_size++;
		}
		void insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			if (_size+len> _capacity)
			{
				reserve(_size+len);
			}
			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				--end;
			}
			memcpy(_str + pos, s, len);
		}
		// 可读可写
		char& front()
		{
			return _str[0];
		}
		char& back()
		{
			return _str[_size - 1];
		}
		// 可读不可写
		const char& front()const
		{
			return _str[0];
		}
		const char& back()const
		{
			return _str[_size - 1];
		}
		void pop_back()
		{
			_str[_size - 1] = '\0';
			--_size;
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[0] = '\0';
				_size = pos;
			}
			else
			{
				size_t begin = pos + len;
				while (begin <= _size)
				{
					_str[begin - len] = _str[begin];
					++begin;
				}
				_size -= len;
			}
		}
		bool operator <(const string& s)const
		{
			return strcmp(_str, s._str)<0;
		}
		bool operator >(const string& s)const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator == (const string& s)const
		{
			return strcmp(_str, s._str) == 0;
		}
		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);
		}
		~string()
		{
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}

	private:
		size_t _size;
		size_t _capacity;
		char* _str;
		static size_t npos;
	};
	 size_t string::npos = -1;
	 ostream& operator<<(ostream& out, const string& s)
	 {
		 for (auto& e : s)
		 {
			 out << e;
		 }
		 return out;
	 }
	 //istream& operator>>(istream& in,  string& s)
	 //{
		// s.clear();
		// char ch = in.get();
		// while (ch != '\n'&&ch!=' ')
		// {
		//	 s += ch;
		//	 ch = in.get();
		// }
		// return in;
	 //}
	 // 流提取
	 istream& operator>>(istream& in, string& s)
	 {
		 s.clear();
		 char ch = in.get();
		 char buf[128];
		 int i = 0;
		 while (ch != '\n')
		 {
			 buf[i++] = ch;
			 if (i == 127)
			 {
				 buf[127] = '\0';
				 i = 0;
				 s += buf;
			 }
			 ch = in.get();
		 }
		 if (i != 0)
		 {
			 buf[i] = '\0';
			 s += buf;
		 }
		 return in;
	 }

}

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

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

相关文章

力扣高频SQL 50题(基础版)第十七题

文章目录 力扣高频SQL 50题&#xff08;基础版&#xff09;第十七题1075. 项目员工 I题目说明思路分析实现过程准备数据实现方式结果截图 力扣高频SQL 50题&#xff08;基础版&#xff09;第十七题 1075. 项目员工 I 题目说明 项目表 Project&#xff1a; ----------------…

四、GD32 MCU 常见外设介绍 (2) GPIO 模块介绍

2.GPIO 模块介绍 GPIO的全称为通用输入输出口&#xff0c;是很多外设能够正常工作的必要条件。除了一些特定功能的引脚(如电源脚)外&#xff0c;MCU上其他的引脚都可以当做GPIO来使用。本章&#xff0c;我们将对GPIO进行简单介绍&#xff0c;并通过一个“流水灯”的实验来熟悉…

力扣刷题-图论-岛屿类问题-集合实现(c++实现)

我的老师&#xff1a;力扣链接这道题题解中最高赞的回答nettee&#xff0c;从这篇题解中我学到了dfs框架以及解决思路&#xff0c;并独立完成了该题解里的几道习题本人刷题的习惯是学会一个板子&#xff0c;然后之后的同类题都机械的用这个板子去做&#xff0c;最好不做创新&am…

广州某展厅门牌创新案例:1*2 OLED柔性屏的精致应用

在广州这座繁华都市的心脏地带&#xff0c;一座现代感十足的展厅悄然矗立&#xff0c;其独特的门牌设计成为了过往行人目光的焦点。这座展厅的门牌采用了前沿的1*2 OLED柔性屏技术&#xff0c;不仅展现了科技与艺术的完美融合&#xff0c;更彰显了展厅的高端定位与创新精神。 项…

容器 string 的模拟实现

容器 string 的模拟实现 开篇解释代码实现&#xff1a;myString.h 头文件myString.cpp 实现文件 模拟实现 string 能对 STL 有更深刻的认识&#xff0c;底层了解越丰富&#xff0c;使用起来越顺手 接下来我会以 .h 头文件以及其 .cpp 的实现文件展示其大致模拟&#xff0c;这只…

【代码】Python3|Scrapy框架初探(汽车之家大连市二手车车辆数据爬取、清洗与可视化)

本篇主要是整个项目的介绍&#xff0c;没提到太多琐碎的技术细节&#xff0c;以后有空的话会整理一下 Scrapy 和原生爬虫的差异&#xff0c;还有它坑人的一些地方&#xff0c;单发出来。 开源地址&#xff1a;https://github.com/shandianchengzi/car_home_spider 使用说明&a…

学习日记:数据类型2

目录 1.转义字符 2.隐式类型转换 2.1 强制类型转换 2.2 不同类型间赋值 3.运算符 表达式 3.1 算术运算符 3.2 算术运算优先级 3.3 赋值运算 3.3.1 不同类型间混合赋值 3.4 逗号运算 4.生成随机数 5. 每日一练 1.转义字符 \n 表示换行 \t …

Vue3可媲美Element Plus Tree组件实战之移除节点

Element Plus Tree自定义节点内容示例中介绍了移除节点的用法&#xff0c;个人觉得作为提供给用户API&#xff0c;应该遵循迪米特法则&#xff0c;把功能实现的细节封装在组件内部&#xff0c;而提供给用户最简单的操作方式&#xff0c;同时在此基础上支持用户的扩展。 因此&a…

ITK-中值滤波

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 中值滤波原理 中值滤波是一种常用的非线性滤波技术&#xff0c;用于去除图像中的噪声&#xff0c;特别是椒盐噪声和脉冲噪声。它…

MM 7 -采购- 询报价

思维导图 说明 sap提供了 询价 报价和比价的功能。 不建议在sap管理。一般引导不在sap管理。 流程&#xff1a; 操作 询价 ME41 报价ME47 比价ME49 拒绝 对于不符合条件的报价进行拒绝 生成信息记录 后台表 Ekko ekpo 。采购凭证类别&#xff1a;A

csa笔记6-网络管理命令

nmcli命令 字符终端&#xff0c;可以立即生效且重启系统后配置也不会丢失 nmtui命令 可视终端&#xff0c;立即生效&#xff0c;重启有效 network.service 管理网络 RHEL 7 以前&#xff1a;使用network.service管理网络 RHEL 7&#xff1a;使用network.service和Netwo…

Docker(十一)-Docker运行nginx1.10容器实例

1.下载镜像 docker pull nginx:1.102.直接启动实例(目的&#xff1a;复制出配置文件) 2.1启动实例 docker run -d -p 80:80 --namenginx1.10 nginx:1.102.2将容器内文件拷贝到本地目录 docker cp nginx1.10:/etc/nginx /software/nginx/将/software/nginx/nginx目录改为/so…

07 JSP

文章目录 JSP1、JSP 概述2、JSP 脚本和缺点3、EL 表达式4、JSTL标签5、MVC模式和三层架构6、案例 JSP 1、JSP 概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java 服务端页面 &#xff08;1&#xff09;是一种动态的网页技术 &#xff08;2&…

11 逻辑运算符

逻辑运算符 and、or、not 常用来连接条件表达式构成更加复杂的条件表达式&#xff0c;并且 and 和 or 具有惰性求值或逻辑短路的特点&#xff0c;当连接多个表达式时只计算必须要计算的值。 运算符 and 和 or 并不一定会返回 True 或 False &#xff0c;而是得到最后一个被计算…

WordPress主题追格企业官网主题免费开源版V1.1.6

追格企业官网主题免费开源版由追格开发的一款开源wordpress主题&#xff0c;专为企业建站和追格企业官网小程序&#xff08;开源版&#xff09;PC配套而设计&#xff0c;功能集新闻动态、留言反馈、产品与服务、公司简介、联系我们等模块。

【MySQL进阶篇】锁:全局锁、表级锁以及行级锁

一、锁的概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须要解决的一个问题&am…

Apache Doris + Paimon 快速搭建指南|Lakehouse 使用手册(二)

湖仓一体&#xff08;Data Lakehouse&#xff09;融合了数据仓库的高性能、实时性以及数据湖的低成本、灵活性等优势&#xff0c;帮助用户更加便捷地满足各种数据处理分析的需求。在过去多个版本中&#xff0c;Apache Doris 持续加深与数据湖的融合&#xff0c;已演进出一套成熟…

java学习--枚举

问题引入&#xff1a; 当需要解决一个季节类的问题&#xff0c;我们使用学到的类与对象&#xff0c;创建一个季节的类然后添加构造器在进行分装就可以实现&#xff0c;但问题也随之而来&#xff0c;这样不仅可以有正常的四季还可以添加其他不存在的四季以及可以更改四季的属性…

ElasticSearch学习篇15_《检索技术核心20讲》进阶篇之TopK检索

背景 学习极客实践课程《检索技术核心20讲》https://time.geekbang.org/column/article/215243&#xff0c;文档形式记录笔记。 相关问题&#xff1a; ES全文检索是如何进行相关性打分的&#xff1f;ES中计算相关性得分的时机?如何加速TopK检索&#xff1f;三种思路 精准To…

广州数据中心机房服务器搬迁规划原则

数据中心机房搬迁&#xff0c;需要各个技术团队的紧密配合&#xff0c;制定详细周密的搬迁方案和实施流程&#xff0c;分批实施。作为华南地区主流的数据中心服务商&#xff0c;友力科技&#xff08;广州&#xff09;有限公司&#xff0c;专注数据中心机房服务器搬迁&#xff0…