C++——string容器模拟实现

news2024/12/24 21:45:13

目录

1. 基本成员变量

2. 默认成员函数

2.1 构造函数

2.2 析构函数

2.3 拷贝构造函数

2.4 赋值运算符重载

3. 容量与大小相关函数

3.1 size

3.2 capacity

4. 字符串访问相关函数

4.1 operator[ ]重载

4.2 迭代器

5.  增加函数接口

5.1 reserve

5.2 resize

5.3 push_back

5.4 append追加字符串 

5.5 operator+=

5.6 insert插入

6. 删除相关接口

6.1 erase

6.2 clear清楚数据

7. find查找函数

8. c_str

9. swap交换函数

10. 非成员函数

10.1 关系运算符号重载

10.2 流插运算符重载

10.3 getline函数

1. 基本成员变量

namespace cpp
{
    //使用命名空间防止定义的string类与库里的string类冲突
	class string
	{
	public:
	//……
	private:
		char* _str;       //存储字符串
		size_t _size;	  //有效字符个数
		size_t _capacity; //实际存储有效字符的空间,不包含'\0'
        const static size_t npos;
	};
    const size_t string::npos = -1;
}

2. 默认成员函数

2.1 构造函数

这里的构造函数最好写成全缺省函数,与标准库里的构造函数相一致。

//全缺省的默认构造函数
string(const char* str = "")//标准库里string定义对象的默认值为空串""
	//按声明的顺序进行初始化
	:_size(strlen(str))
	, _capacity(_size)
{
	_str = new char[_capacity + 1];//在堆上为_str开空间,+1是给'\0'留的
	strcpy(_str, str);//把常量字符串的内容拷贝过去
}

2.2 析构函数

这里string类里的_str是动态开辟建立在堆中的,堆区的空间不能自动销毁因此需要我们手动去销毁。

//析构函数
~string()
{
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}

2.3 拷贝构造函数

首先,我们不写,编译器会默认生成一个拷贝构造函数,不过是值拷贝或者浅拷贝,按字节拷贝的。

浅拷贝:两个对象指向同一块空间,析构时导致同一块空间析构两次。

浅拷贝针对于日期类这种是非常适合的,不过对于string类这样_str是动态开辟到堆上的,如果使用值拷贝会导致1、析构两次 2、一个对象修改会影响另外一个。因此我们需要写深拷贝。深拷贝的核心要点在于我和你的有一样的值,但是使用的不是同一块空间。

深拷贝有两种写法:传统写法和现代写法。

  • (1)传统写法

传统写法就是先开辟一块能够容纳原字符串大小的空间,最后把拷贝的对象的字符串数据拷贝到新开的空间里头即可。

//拷贝构造函数
//不能用浅拷贝,原因如下:1、析构两次 2、一个对象修改会影响另外一个
//传统写法
//s2(s1);
string(const string& s)
	:_size(strlen(s._str))
	,_capacity(_size)
{
	_str = new char[_capacity + 1];
	strcpy(_str, s._str);
}
  • (2)现代方法

传统写法是本分的自己开空间,然后再拷贝数据,而现代方法就是剥削,要完成深拷贝,自己不想干活就安排别人干活,然后窃取别人的劳动成果。

假如拿s1去拷贝s2、s2(s1),现代方法就是我设定了一个tmp对象,拿s1._str的字符串作为参数去给tmp对象完成构造函数,再利用swap函数把tmp对象的_str、_size、_capacity全部与s1的交换即可完成现代方法的深拷贝。但是在这之前注意把s1的数据置空,避免交换后tmp调用析构函数出现析构随机值的错误现象。

/*现代写法*/
string(const string& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);//调用构造函数,构造一个字符串作为s.c_str的对象
	swap(tmp);
}

2.4 赋值运算符重载

这里和上述拷贝构造函数一样,我们不写编译器会自动生成,不过对于string类的_str来说,在堆上申请的空间需要自己去释放,否则会导致同一块空间析构两次。此深拷贝依旧有传统写法和现代写法。

  • 思路:

如若把s1=s3,这里不能直接进行赋值。要考虑两个问题。

  1. 如若s1的空间小于s3,此时直接拷贝过去会导致越界
  2. 如若s1的空间过分大于s3的空间,又会导致直接拷贝后空间过渡浪费。只有在我s1和s3的空间差不多大时,才可以直接进行拷贝。

综上:先把s1原先指向的空间delete释放掉,再把s1重新开辟和s3一样大的空间,记得多开一个字节,因为还有'\0'。再利用strcpy把s3的内容拷贝给s1即可。不过要避免一种特殊情况:自己给自己赋值,如若自己赋值给自己,直接返回,所以加个if条件判断即可。

如果我new开空间失败了,那么就要抛异常,而先前我依旧释放了s1,此时就把s1给破坏了。为了避免这一点,我们可以先开空间再拷贝数据最后再释放从而进行优化,具体见下文。

  • 1、传统写法:
//赋值运算符重载 --> 深拷贝
//s1 = s3  s1.operator=(&s1, s3);
string& operator=(const string& s)
{
	//防止自己给自己赋值
	if (this != &s)
	{
	/*	
//法一:
		//先删除原先s1的所有空间,防止赋值后s1过大导致空间浪费,s1过小导致空间不够
		delete[] _str;
		//给s1开辟与s3等价的空间大小,要多开一字节给'\0'
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	*/
//法二优化
        //先开辟空间
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

C语言的动态开辟内存malloc需要检查合法性,而C++的new不需要,new失败的话需要抛异常捕获:

int main()
{
	try
	{
		//C++new失败要抛异常捕获
		//test_string1();
		test_string2();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}
  • 2、现代写法:
//现代写法1:
string& operator=(const string& s)
{
	if (this != &s)//避免自己给自己赋值
	{
		string tmp(s._str);
		swap(tmp);
	}
	return *this;
}

这里还有另一种更加简洁的现代方法,上述写法是引用传参,这里我们可以直接传值传参,让编译器自动调用拷贝构造函数,再把拷贝出来的对象作为右值与左值交换即可

//法二:简洁版	
//s1 = s3;
string& operator=(string s)//传值传参调用拷贝构造,s就是s3的深拷贝结果
{
	swap(s);//交换这俩对象
	return *this;
}

不过这种简洁的版本无法避免自己给自己赋值,但很少会出现自己给自己赋值的行为,除非你有啥癖好。所以上述两种方法都可使用。

3. 容量与大小相关函数

3.1 size

直接返回隐含this指针指向的_size即为字符串长度。

//返回字符串的长度
size_t size() const //不改变内部成员,最好加上const
{
	return _size;
}

3.2 capacity

直接返回隐含this指针指向的_capacity即可。

//返回字符串容量
size_t capacity() const //不改变内部成员,最好加上const
{
	return _capacity;
}

4. 字符串访问相关函数

4.1 operator[ ]重载

有了operator[ ]运算符重载,便可以直接用下标+[ ]进行元素访问,不过这里还应提供一个const版本的operator[ ]运算符重载,以便于普通对象和const对象均可调用而不会出现权限放大的问题。

//版本1:
char& operator[](size_t pos)//引用返回,便于后续修改返回的字符
{
	assert(pos < _size);//记得确保pos位置的合法性,不能超过字符串
	return _str[pos]; //返回pos位置字符的引用
}
//版本2:
const char& operator[](size_t pos) const//引用返回,便于后续修改返回的字符
{
	assert(pos < _size);
	return _str[pos]; //返回pos位置字符的引用
}

4.2 迭代器

string类的迭代器就是像字符指针一样的东西

  1. begin函数的作用就是返回字符串中第一个字符的地址。
  2. end函数的作用就是返回字符串最后一个字符的后一个位置的地址,即'\0'的地址。
//版本1:
typedef char* iterator;
iterator begin()
{
	return _str;//返回第一个有效字符的指针
}
iterator end()
{
	return _str + _size;//返回最后一个字符后一个位置的地址,即'\0'的地址
}

和上文的operator[ ]重载一样,这里也要写一个const版本的迭代器,以便于后续的const对象也能够调用。

//版本2:只读,const对象可调用
typedef const char* const_iterator;
const_iterator begin() const
{
	return _str;//返回第一个有效字符的指针
}
const_iterator end() const
{
	return _str + _size;//返回最后一个字符后一个位置的地址,即'\0'的地址
}

这里还有一种基于迭代器的遍历方式:范围for

范围for的底层实现原理和迭代器没两样,只不过写法看着很高端。

void test_string()
{
	cpp::string s1("hello world");
	//迭代器
	cpp::string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " "; //h e l l o   w o r l d
		it++;
	}
	cout << endl;
	//范围for
	for (auto& ch : s1) //加上引用,相当于是每个字符的别名,便于修改
	{
		ch -= 1;
	}
	for (auto& ch : s1)
	{
		cout << ch << " "; //g d k k n  v n q k c
	}
}

5.  增加函数接口

5.1 reserve

reserve扩容只影响_capacity空间,不影响_size,其有以下两点规则

  1. 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
  2. 当n小于对象当前的capacity时,无需操作。
//reserve扩容
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];//每次开空间一定要多给一个字节给'\0'
		strcpy(tmp, _str);
        //释放旧空间
		delete[] _str;
        //把新空间赋给_str
		_str = tmp;
        //更新容量_capacity
		_capacity = n;
	}
}

5.2 resize

resize是将字符串调整为n个字符的长度,不仅会改变_size,还会改变_capacity空间。规则如下:

  1. 如果n小于当前的_size长度,将_size缩小到n。
  2. 如果n大于当前的_size长度,将_size扩大到n,扩大的字符默认为'\0'。

//resize调整大小
void resize(size_t n, char ch = '\0')
{
	//如果n < _size,就保留前n个字符即可,把下标n置为'\0'
	if (n < _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		//如果n > _capacity,就要扩容了
		if (n > _capacity)
		{
			reserve(n);
		}
		for (size_t i = _size; i < n; i++)
		{
			//把剩余的字符置为ch
			_str[i] = ch;
		}
		_size = n;
		_str[_size] = '\0';
	}
}

5.3 push_back

首先要考虑需不需要扩容,如若需要,直接复用reserve函数进行增容,追加字符后,记得把最后一个下标_size对应的值置为'\0'。

//push_back
void push_back(char ch)
{
/*法一*/
	//先检查是否需要扩容
	if (_size == _capacity)
	{
		//复用reserve进行扩容,如果一开始容量为0,记得处理,否则容量*2依旧为0
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0'; //注意最后一个值恒为'\0'以确保字符串的完整性
}

这里我们还可使用后文写好的insert尾插字符,因为当insert函数中的pos为_size时即为尾插:

//push_back尾插字符
void push_back(char ch)
{
	//法二:复用insert尾插入字符
	insert(_size, ch);
}

5.4 append追加字符串 

使用append追加字符串首先要判断是否需要扩容,扩容后利用strcpy函数把追加的字符串拷贝到原字符串末尾即可,不需要额外处理'\0',因为strcpy默认把'\0'拷贝过去。

//append
void append(const char* str)
{
	//统计追加字符串后的长度
	size_t len = _size + strlen(str);
	//判断是否需要扩容
	if (len > _capacity)
	{
		reserve(len);
	}
	//把字符串追加到末尾
	strcpy(_str + _size, str);
	_size = len;
}

这里也可以使用后文的insert追加字符串来完成,因为当pos为_size时,就是在尾部追加字符串。

void append(const char* str)
{
	//法二:复用insert函数
	insert(_size, str);
}

5.5 operator+=

operator+=可以追加字符、字符串、对象。因此我们可以分开来讨论:

  • 追加字符:直接复用push_back
//operator+=字符
string& operator+=(char ch)
{
	//复用push_back
	push_back(ch);
	return *this;
}
  • 追加字符串:直接复用append
//operator+=字符串
string& operator+=(const char* str)
{
	//复用append
	append(str);
	return *this;
}

5.6 insert插入

insert的作用是在指定pos位置往后插入字符或字符串。

  • insert在pos位置插入字符

这里首先要判断pos的合法性,接下来就要挪动数据了,这里我们优先考虑从最后一个'\0'位置的下一个位置(_size + 1)开始往前挪动。因此定义end指向'\0'后一个位置,当end挪到与pos位置重合时停止,最后把插入的字符ch挪到下标pos处。记得最后更新_size++。

 

//insert插入字符
string& insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		//复用reserve进行扩容,如果一开始容量为0,记得处理,否则容量*2依旧为0
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	size_t end = _size + 1; //最好把end放到_size + 1的位置,防止后续出现整型提升等问题
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}
	//当end挪动到pos的位置时停止挪动,并把ch赋到pos的下标处
	_str[pos] = ch;
	_size += 1;
	return *this;
}

测试插入功能:

void test_string()
{
	cpp::string s("hello world");
	s.insert(6, '@');
	s += '@';
	cout << s.c_str() << endl; //hello @world@
	for (auto& ch : s)
	{
		cout << ch << " "; 
	}
	cout << "#" << endl; //h e l l o   @ w o r l d @ #
	
 	s += '\0';
	for (auto& ch : s)
	{
		cout << ch << " "; 
	}
	cout << "#" << endl; //h e l l o   @ w o r l d @  #
	s.insert(0, '@');
	cout << s.c_str() << endl;//@hello @world@
}
  • insert在pos位置插入字符串

首先判断是否需要扩容,接下来挪动数据。定义变量end为_size + len的位置,把pos处往后的字符串整体往后挪动直至空出插入字符串的长度。利用循环+ _str[end] = _str[end - len];来完成。当end挪动到pos + len - 1时结束循环,再利用strncpy函数把插入的字符串拷贝过去即可。

 

//insert插入字符串
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
    if (len == 0)
	{
		//如果传进来的字符串为空,直接返回即可
		return *this;
	}
	if (_size + len > _capacity)
	{
		//判断是否扩容
		reserve(_size + len);
	}
	size_t end = _size + len;
	//当end >= pos + len时都不结束循环
	while (end >= pos + len)
	{
		_str[end] = _str[end - len];
		end--;
	}
	//不能使用strcpy,因为会把\0也拷过去,就会出错
	strncpy(_str + pos, str, len);
	_size += len;
	return *this;
}
//insert插入字符串
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
    if (len == 0)
	{
		//如果传进来的字符串为空,直接返回即可
		return *this;
	}
	if (_size + len > _capacity)
	{
		//判断是否扩容
		reserve(_size + len);
	}
	size_t end = _size + len;
	//当end >= pos + len时都不结束循环
	while (end >= pos + len)
	{
		_str[end] = _str[end - len];
		end--;
	}
	//不能使用strcpy,因为会把\0也拷过去,就会出错
	strncpy(_str + pos, str, len);
	_size += len;
	return *this;
}

测试:

void test_string()
{
	cpp::string s("hello world");
	s.insert(0, "xxx");
	cout << s.c_str() << endl;//xxxhello world
}

6. 删除相关接口

6.1 erase

如果给定删除的长度len为npos无符号值,或者说len + pos的长度>=_size,那么直接把pos位置的值设定为'\0\即可,因为此时就是把pos后的所有数据全部删除。出去这种特殊情况,其余的就是从pos + len处开始先前挪动到_size + 1为止。pos后的数据往前覆盖即可。

 

//erase删除
void erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);
	if (len == npos || pos + len >= _size)
	{
		//这种情况是删除pos后的所有数据,直接把pos处设定为'\0'即可
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
	}
}

测试如下:

void test_string9()
{
	cpp::string s("hello world");
	s.insert(0, "xxx");
	cout << s.c_str() << endl;//xxxhello world
	s.erase(0, 3);
	cout << s.c_str() << endl;//hellow world
}

6.2 clear清楚数据

 clear函数是用来清除原字符串的所有数据,并不是连空间一并清除了,所以我们只需要把下标0置为'\0',并把有效字符个数_size置为0即可。

//clear清除数据
void clear()
{
	_str[0] = '\0';
	_size = 0;
}

7. find查找函数

find函数也分查找字符和字符串.

  • find查找字符:

直接遍历即可:

//find查找字符	
size_t find(char ch, size_t pos = 0)
{
	for (; pos < _size; pos++)
	{
		if (_str[pos] == ch)
			return pos;
	}
	//没找到就返回npos,-1
	return npos; //-1
}
  • find查找字符串:

这里可以直接复用C语言的strstr函数进行查找,不过该函数返回的是地址,想要获得最终的下标直接利用地址相减即可,p - _str。

size_t find(const char* str, size_t pos = 0)
{
	//直接复用C语言库函数strstr即可,strstr函数返回的是地址
	const char* p = strstr(_str + pos, str);
	if (p == nullptr)
	{
		return npos;
	}
	else
	{
		//返回下标直接用p - str即可
		return p - _str;
	}
}

8. c_str

c_str用于获取c类型的字符串,直接返回字符串即可.

//c_str  获取c形式的字符串
const char* c_str() const //最好加上const,便于普通及const对象均可调用
{
	return _str;
}

9. swap交换函数

swap函数用于交换两个对象的数据,我们可以通过复用库里的swap函数来完成,但是要在前面加上作用域限定符"::"。让编译器在全局域的库里调用swap函数。

//swap交换函数
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

10. 非成员函数

10.1 关系运算符号重载

  • 1、operator<

直接借用库函数strcmp进行字符串大小比较即可。此外,和日期类一样,写好了<和==的重载,剩下的4个关系运算符直接复用即可。

//1、operator<
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}	
//2、operator==
bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}

剩下4个关系运算符复用上面两个:

//3、operator<=
bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}
//4、operator>
bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}
//5、operator>=
bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}
//6、operator!=
bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

10.2 流插运算符重载

  • <<流插入运算符重载

这里我们可以通过范围for来完成<<运算符的重载。

//<<流插入运算符重载
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}
  • >>提取入运算符重载

这里实现的过程种要注意当遇到空格或换行符时就要停止读取了。此外,在一开始要记得调用clear函数把原字符串的所有数据给清空,然后才能正常往后输入新的数据,否则新数据会累加到原数据后面,就不是>>预期的效果了。

//>>流提取运算符重载
istream& operator>>(istream& in, string& s)
{
	法一:
    //要先把原字符串的所有数据给清空才可以输入新的数据,否则会累加到原数据后面,出错
	s.clear();
	char ch;
	ch = in.get();//使用get()函数才能获取空格或者换行字符
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

这里有个缺陷,如若频繁输入大量字符,那么就会多次扩容,扩容也会在效率上有所损耗,因此我们可以提前开辟一个128字节大小的数组,把每次输进的字符放到数组里头,最后当遇到停止的符号时,+=到字符串s上,如若下标加到127,把数组的字符+=到字符串s上,并充值数组为'\0',更新下标为0即可。以此类推。
 

//>>流提取运算符重载
istream& operator>>(istream& in, string& s)
{
    //法二:
    //要先把原字符串的所有数据给清空才可以输入新的数据,否则会累加到原数据后面,出错
	s.clear();
	char ch;
	ch = in.get();//使用get()函数才能获取空格或者换行字符
	char buff[128] = { '\0' };
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			s += buff;
			memset(buff, '\0', 128);
			i = 0;
		}
		ch = in.get();
	}
	s += buff;
	return in;
}

10.3 getline函数

getline函数与上述写的<<流提取运算符重载非常相似,唯一不同的地方在于getline只有在遇到换行符才停止读取,而<<在遇到换行符停止外,遇到空格也会停止读取,因此,在<<的基础上改变下if种的判断条件即可:

//getline函数
istream& getline(istream& in, string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	//getline函数只有在遇到换行符才会停止
	while (ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

 

 

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

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

相关文章

【二叉树part01】| 二叉树的递归遍历、二叉树的迭代遍历

目录 ✿二叉树的递归遍历❀ ☞LeetCode144.前序遍历 ☞LeetCode145.二叉树的后序遍历 ☞LeetCode94.二叉树的中序遍历 ✿二叉树的迭代遍历❀ ☞LeetCode144.前序遍历 ☞LeetCode145.二叉树的后序遍历 ☞LeetCode94.二叉树的中序遍历 ✿二叉树的递归遍历❀ ☞LeetCode…

docker-compose启动opengauss数据库——华为“自研”数据库

文章目录 1. 启动数据库2. 登录2.1 本地登录2.2 远程登录 1. 启动数据库 yml文件 创建opengauss目录&#xff0c;里边创建docker-compose.yml文件内容如下&#xff1a; 华为开源数据库&#xff0c;默认5432端口&#xff0c;是不是很熟悉&#xff0c;疑似又是个套壳子的事件。果…

Cortext-M3系列:调试系统架构(8)

1、调试特性概述 单片机的调试功能在程序开发中有着十分重要的地位&#xff0c;好的调试工具&#xff0c;能让程序开发大大加快。笔者在刚开始学单片机相关知识时&#xff0c;使用的是pintf打印相关参数&#xff0c;进行调试&#xff08;虽然现在很多时候也这样&#xff09;&am…

MyBatis 的使用方法

观前提示:本篇博客演示使用的 IDEA 版本为2021.3.3版本,使用的是Java8(又名jdk1.8) 前端使用 VSCode(Visual Studio Code1.78.2) 电脑使用的操作系统版本为 Windows 10 目录 Mybatis是什么? Mybatis 有什么用? Mybatis 框架交流 Mybatis 项目环境搭建 1. 添加 Mybatis…

设计模式之生成器(建造者)模式笔记

设计模式之建造者模式笔记 说明Builder(生成器)目录UML生成器(建造者)模式示例类图自行车类建造者抽象类摩拜单车对象类小黄车单车对象类指挥者类测试类优缺点 模式扩展手机类测试类 说明 记录下学习设计模式-生成器(也叫建造者)模式的写法。 Builder(生成器) 意图:将一个复…

监控中的计算机科学

文章目录 一、前言二、监控分类2.1 模拟摄像头2.1 数字摄像头 三、监控系统四、后端产品4.1 硬盘录像机4.2 视频矩阵4.3 控制设备4.4 显示设备 五、传输端5.1 光纤视频线5.1.2 单模光纤5.1.3 多模光纤5.1.4 光端机 5.1 双绞线 六、畅享 一、前言 布林肯访华&#xff0c;黑我们…

Redis6之事务与锁

事务 Redis事务是一个单独的隔离操作&#xff1a;事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中&#xff0c;不会被其他客户端发送来的命令请求所打断。 Redis事务的主要作用就是串联多个命令防止别的命令插队。 命令 1、开启事务&#xff1a;multi 2、执行事…

STM32 RGB屏幕

使用ST的HAL库进行开发&#xff0c;RGB屏幕是480*272的4.3寸LCD&#xff0c;由于驱动RGB屏幕需要较多的内存&#xff0c; 所以使用了外部SDRAM&#xff0c;内存是32M字节&#xff0c;关于SDRAM的驱动本文不进行讨论。 RGB屏幕常用的像素格式有&#xff1a;ARGB8888、RGB888、…

Vue----Vue项目的目录结构

【原文链接】Vue----Vue项目的目录结构 Vue 项目的目录结构 VUE项目的目录结构如下所示 .vscode VSCode工具的配置文件&#xff0c;和VUE项目没有什么关系 node_modules VUE项目运行依赖文件&#xff0c;通过npm install 安装的文件即存放在此文件夹 public 资源文件夹&am…

【现代密码学】(网安)期末复习笔记

现代密码学 【考后感悟】还是得注重简答题&#xff0c;需每个密码算法都要有所了解&#xff08;有些难的可以不用了解完整算法过程&#xff0c;估计考不上&#xff1f;&#xff09;&#xff0c;并对几个重要密码算法&#xff08;重点下面会讲&#xff09;着重复习&#xff08;会…

部署kubernets v1.27.3集群

本文将演示如何使用kubeadm快速部署一个Kubernetes v1.27.1集群&#xff0c;并会简单说明如何在集群上部署nginx容器 主机环境预设 本示例中的Kubernetes集群部署将基于以下环境进行。 OS: Ubuntu 20.04 Kubernetes&#xff1a;v1.27.3 Container Runtime: Docker CE 23.0.…

【并发编程】深入探索AQS

文章目录 一、AQS 介绍二、通过ReentrantLock分析AQS的实现2.1、获取锁流程2.2、获取锁源码分析2.2.1、acquire2.2.2、tryAcquire2.2.3、addWaiter2.2.4、acquireQueued2.2.5、shouldParkAfterFailedAcquire 2.3、解锁源码分析2.3.1、unlock2.3.2、release2.3.3、tryRelease2.3…

实验篇(7.2) 17. 站对站安全隧道 - FortiGate作为SSL客户端(SSL) ❀ 远程访问

【简介】虽然常用的站到站的连接用的是IPsec VPN&#xff0c;但是在某些特殊情况下&#xff0c;UDP500或4500端口被阻断&#xff0c;IPsec VPN无法连接&#xff0c;那么还有其它办法实现站到站的连接吗&#xff1f;SSL VPN也可以的。 实验要求与环境 OldMei集团深圳总部部署了域…

NodeJS File Upload⑩

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言文件上传 后端接口 Form表单上传 Axios前后端分离上传 实现效果演示 记录 读取图片文件总结 ✨文章有误请指正&#xff0c;如果觉得对你有用&a…

RK3568平台开发系列讲解(外设篇)四线风扇驱动实验

🚀返回专栏总目录 文章目录 一、硬件连接二、原理图分析三、驱动适配3.1、内核配置3.2、修改设备树3.3、实验沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解四线风扇的使用。 一、硬件连接 风扇模块如下所示,黑线是负,红线是正,黄线是测速,蓝线是…

算法——字符串匹配算法——BM(Boyer-Moore) 算法

字符串匹配算法——BM &#xff08;Boyer-Moore&#xff09; 算法 概述场景一 坏字符场景且模式串中没有匹配字符场景二 坏字符场景且模式串中有匹配字符场景三 好后缀场景且模式串中没有匹配字符场景四 好后缀场景且模式串中有匹配字符场景五 好后缀场景且模式串中有匹配子串后…

EfficientDet-pytorch目标检测训练

目录 1. EfficientDet-pytorch版本代码下载 2.数据集准备 2.1数据集格式 2.2 定义自己数据集的yml文件 3. 训练配置 4.模型评估 5.测试模型性能 1. EfficientDet-pytorch版本代码下载 GitHub - zylo117/Yet-Another-EfficientDet-Pytorch: The pytorch re-implement…

chatgpt赋能python:Python提取指定数据的方法与技巧

Python提取指定数据的方法与技巧 在SEO优化中&#xff0c;数据的提取和分析是非常重要的环节之一。而Python具有方便易用的数据处理能力&#xff0c;成为了SEO优化工程师们的重要工具之一。本文将介绍Python中提取指定数据的方法与技巧&#xff0c;以及实现的具体案例。 数据…

python:使用Scikit-image库进行单波段遥感图像颜色直方图特征提取(histogram)

作者:CSDN @ _养乐多_ 本文记录了使用Scikit-image库对单波段遥感图像做颜色直方图特征提取的代码。 文章目录 一、颜色直方图特征详解二、代码一、颜色直方图特征详解 颜色直方图是一种用于描述图像中颜色分布的特征表示方法。它将图像中每个像素的颜色值作为输入,统计并显…

Java 基础进阶篇(十七):反射概述及获取对象的方式

文章目录 一、反射概述二、反射获取类对象三、反射获取构造器对象四、反射获取成员变量对象五、反射获取方法对象六、 反射的作用6.1 绕过编译阶段为集合添加数据6.2 通用框架的底层原理 一、反射概述 反射是指对于任何一个Class类&#xff0c;在 “运行的时候”&#xff0c;不…