string【2】模拟实现string类

string模拟实现

  • 引言(实现概述)
  • string类方法实现
    • 默认成员函数
      • 构造函数
      • 拷贝构造
      • 赋值运算符重载
      • 析构函数
    • 迭代器
      • begin
      • end
    • 容量
      • size、capacity、empty
      • reserve
      • resize
    • 访问元素
      • operator[]
    • 修改
      • insert
        • 插入字符
        • 插入字符串
      • append
      • push_back
      • operator+=
      • erase
      • clear
      • swap
    • 比较运算符重载
      • operator<
      • operator==
      • 其他
    • 查找
      • 查找字符
      • 查找字符串
    • 非成员函数
      • operator<<
      • operator>>
  • 源码概览
  • 总结

引言(实现概述)

在上一篇文章中,我们介绍了string类的使用:
戳我康string类的使用详解哦

在这里插入图片描述

在本篇文章中就要来模拟实现一下string类,以帮助我们更好的理解与使用string

在我们模拟实现的string中,要具备库中string所具有的主要接口,例如:默认成员函数、迭代器、容量、元素访问、运算符重载、非成员函数。其中只实现这些函数的常用重载形式。

我们将模拟实现的string类放在我们创建的命名空间内,以防止与库中的string发生命名冲突。在以后的STL模拟实现时,也会将其放在命名空间内。

string类的实现与之前C语言部分的顺序表类似,结合类和对象的知识,这个string类的属性有:指向堆中一块用于存放字符序列的空间的指针(_str)、字符序列的字符个数(_size)、字符序列的容量(_capacity

string类方法实现

默认成员函数

构造函数

在构造函数部分,就只实现用常量字符串构造string对象

在这个构造函数中,参数类型就是const char*,给这个参数一个空串""作为缺省值;
在函数内部首先assert判断参数是否为空指针;
然后令_size的值等于常量字符串str的长度,令_capacity等于_size的值;
然后new一块空间,将这块空间的指针赋给_str,这里需要注意的是,strlen计算字符串长度时,是以'\0'为结束标志的,所以在动态开辟空间时,需要开辟_capacity + 1个char的空间
最后使用memcpy将常量字符串中的数据拷贝到刚刚申请的空间中(这里拷贝时也需要将'\0'拷贝进去,所以要拷贝_size + 1 个字节):

    string(const char* str = ""){assert(str);_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];memcpy(_str, str, _size + 1);}

拷贝构造

拷贝构造是构造函数的重载,参数类型为const string&如果不是引用就会导致无穷递归调用

实现拷贝构造时,不能直接将原对象的_str直接赋值给新对象的_str,否则就会导致浅拷贝,这样在析构时,同一块空间就要被析构两遍,显然就会导致崩溃。所以我们需要新申请一块空间后将原对象的字符序列拷贝到新的空间

先将原对象的_size 与 _capacity 直接赋值给新对象
然后new一块空间,将这块空间的指针赋给_str,这里需要注意的是,在我们实现的string类中,_capacity是不包括'\0'的,所以在动态开辟空间时,需要开辟_capacity + 1个char的空间
最后,使用memcpy将原对象中的字符序列拷贝到刚刚新开辟的空间中。

这里需要注意的是,C字符串的结束标志为'\0',但string对象没有结束标志,它的数据个数就是_size的值,所以当这个字符序列的中间出现'\0'时,使用strcpy就不会拷贝'\0'后面的数据,所以这里使用包括后面都使用memcpy

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

赋值运算符重载

在实现赋值运算符重载时,也存在深浅拷贝的问题,我们当然可以像上面的拷贝构造那样申请一块空间然后完成拷贝,但是那样的写法有点麻烦,于是就有了现代版本:

现代版本的参数类型为string,而不是引用,这就使得string对象在传参时会生成一个临时对象,我们将这个临时对象与要替换的对象*this互换,就实现了将一个对象赋值到了*this,最后返回*this即可,临时对象会在函数栈帧销毁时析构。
(这里的交换需要用到swap函数,这个函数后面会实现)

    //string& operator=(const string& s);   //老版本string& operator=(string s){swap(s);return *this;}

析构函数

析构函数只需使用delete[] 释放_str指向的堆中的空间即可,还可以顺带将_size_capacity置0:

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

迭代器

前面提到过,string的迭代器就是原生指针,所以string中的 iterator就是char*const_iterator 就是const char* ,我们只需要使用typedefchar*const char* 重命名即可

    typedef char* iterator;typedef const char* const_iterator;

需要注意的是,因为在string类外也需要使用迭代器,所以这样的重命名应在pubilc中。

begin

begin获取的是字符序列首元素的地址,有两个重载版本,即对于非const对象返回iterator,对于const对象返回const_iterastor,首元素的地址就是_str

需要注意的是:const版本需要使用const修饰this指针

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

end

end获取的是字符序列最后一个元素下一个位置的地址,有两个重载版本,即对于非const对象返回iterator,对于const对象返回const_iterastor,最后一个元素下一个位置的地址就是_str + _size

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

容量

size、capacity、empty

这三个成员函数的实现逻辑类似:
size用于获取string对象中字符序列的元素个数,返回_size即可;
capacity用于获取string对象的容量,返回_capacity即可;
empty用于判断string对象是否为空,若为空返回true,否则返回false:

	size_t size() const{return _size;}size_t capacity() const{return _capacity;}bool empty() const{if (_size == 0){return true;}return false;}

reserve

reserve用于修改string对象的容量,这个函数只有一个参数n,表示要扩容到多少个char。

reserve在扩容时,当n小于当前对象的容量时,reserve会将容量调整为比n大的值,所以在模拟实现时,当n小于容量时,将不做任何事。所以先判断n是否大于_capacity

C++中使用new不能像realloc一样实现扩容,必须使用new新开一块空间(开空间时,由于_capacity没有包括'\0',所以要开n + 1个char的空间);
再将原空间中的数据拷贝到新空间,然后释放原空间;
然后使_str指向新空间;
最后将_capacity的值改为n

	void reserve(size_t n){if (n > _capacity)			{char* newstr = new char[n + 1]{ 0 };memcpy(newstr, _str, _size + 1);delete[] _str;_str = newstr;_capacity = n;}}

resize

resize用于修改string对象中字符序列的个数,当参数n小于size就删,大于size则用指定的字符c补足。

首先判断,当n大于_size的值时,就需要扩容,复用reserve扩容至n
然后循环,将下标为_sizen - 1位置的元素改为指定的c
最后在末尾加上'\0',并更新_size的值

n小于_size的值时,直接在下标为n的位置加上'\0',并更新_size即可:

	void resize(size_t n, char c){if (n > _size){if (n > _capacity){reserve(n + _size);}for (size_t i = _size; i < n; ++i){_str[i] = c; //这里的[]是访问数组元素,并非运算符重载的调用,所以不会越界}_size = n;_str[_size] = '\0';}else{_size = n;_str[_size] = '\0';}}

访问元素

operator[]

通过重载[]可以实现像数组下标一样访问string对象中字符序列的元素,有两个重载版本,即对普通对象与const对象。函数有一个参数index即要访问元素的下标。

首先assert判断参数index是否越界;
然后返回_str[index]即可:

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

修改

insert

insert实现将一个字符或字符串插入到string对象的pos位置,实现两个重载版本,即在pos位置插入一个字符与一个字符串:

插入字符

首先assert判断pos是否越界,pos为无符号整数,所以只需要判断是否大于_size即可;
然后当_size等于_capacity时,即空间已满,需要扩容;
扩容时,当_capacity的值为0时扩容到4,不为0时二倍扩容;

然后就需要循环,将pos位置后的数据全部向后移动(需要注意的是循环的终止条件,当pos为0时,若end的初始值为_size且为endend + 1赋值,循环的终止条件就为end >= pos而pos为size_t,当end与pos比较时,会转化为size_t而永远不可能小于0,故end的初始值为_size + 1,将end + 1给前赋值,终止条件就为end > pos);
最后将c填充到pos位置,并更新_size

	string& insert(size_t pos, char c){assert(pos <= _size);if (_size == _capacity){if (_capacity != 0){reserve(2 * _capacity);}else{reserve(4);}}size_t end = _size + 1;while (end > pos)   //pos为size_t,当end与pos比较时,会转化为size_t而永远不可能小于0.故将end+1,后给前赋值{_str[end] = _str[end - 1];--end;}_str[pos] = c;++_size;return *this;}

插入字符串

插入字符串的逻辑与插入字符类似:

首先判断pos是否越界,并判断是否需要扩容,当容量小于_size + len时就需要扩容(len 为插入字符串的长度,这里可以直接调用reserve,因为reserve中会判断参数是否大于原容量);
然后将pos位置后的数据全部向后移动len个位置(依旧需要注意终止条件:必须为 end > pos + len - 1 ,若为end >= pos+len时,当在0位置插入一个空串就会导致死循环,因为无符号整型不可能小于0。当为end > pos + len - 1时,遇到上面的情况,0 - 1为 -1,对无符号整型就是一个很大的数,将直接不进入循环

然后循环将字符串中的数据拷贝到pos位置,并更新_size

	string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);reserve(len + _size);size_t end = _size + len;while (end > pos + len - 1)   //写成end >= pos+len就会有问题(在0位置插一个""){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; ++i){_str[pos + i] = str[i];}_size += len;return *this;}

append

append实现在string对象后追加一个字符串:

首先判断str是否为空指针,并判断是否需要扩容(当newlenth > _capacity时即需要扩容,当然也可以交给reserve中判断);
然后使用strcpy将str中的数据拷贝到_str + _size的后面(这里不需要使用memcpy,因为这里的字符串拷贝就是按照'\0'为结束标志的);
最后更新_size

	//尾追加void append(const char* str){assert(str);size_t newlenth = _size + strlen(str);if (newlenth > _capacity){reserve(newlenth);}strcpy(_str + _size, str);_size = newlenth;}

push_back

push_back用于在string对象末尾添加一个字符

实现时首先判断是否需要扩容(与insert插入字符时的逻辑一致);
然后将c放在_str_size位置,并更新_size
最后需要手动补上'\0'

	//尾插void push_back(char c){if (_size == _capacity){if (_capacity != 0){reserve(2 * _capacity);}else{reserve(4);}}_str[_size] = c;++_size;_str[_size] = '\0';}

operator+=

operator+=即在string对象的末尾追加数据,实现两个重载版本,即追加字符与追加字符串:

实现时,复用appendpush_back即可(当然,上面的append与push_back也可以借助insert实现)

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

erase

erase实现删除pos位置上的len个元素

len等于npos时,即将pos位置后全删,将pos位置改为'\0'并更新_size即可(npos为无符号整型的-1,即一个很大的数);
否则,就需要循环,将pos + len位置后的数据全部向前移动 len个位置,覆盖原数据实现删除,最后更新_size

	// 删除pos位置上的len个元素,并返回string& erase(size_t pos, size_t len){if (len == npos){_size = pos;_str[_size] = '\0';}for (size_t i = 0; i < _size - pos; ++i){_str[pos + i] = _str[pos + len + i];}_size -= len;return *this;}

clear

clear即清空string对象中的数据,只需要将0位置改为'\0',并将_size更新为0即可:

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

swap

swap实现交换两个string对象

在之前的swap函数,包括算法库中的swap函数均是通过临时变量的方式交换的。但是对于string对象而言,要创建临时对象通过三次赋值来交换的话,就会产生三次深拷贝,十分影响效率

在实现交换时,其实没有必要出现深拷贝,string对象中有_str指向一块空间中存储数据,只要交换string对象中的这个存储数据的指针即可实现交换数据,所以将string的对象的属性分别实现交换即可,交换_size_capacity_str即可

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

比较运算符重载

在实现比较运算符重载时,我们其实只需要实现两种,即<==,其他的运算符重载通过复用这两种即可,此类函数都需要使用const修饰this以适配const对象:

operator<

operator<实现两个string对象的比较:当第一个对象小于第二个对象时,返回true,否则返回false

我们可以for循环逐字节判断,循环的终止条件为两个string对象_size的较小值;
当遇到对应字符不相等的情况时,直接返回truefalse
当循环结束,说明前面的元素都是相等的。此时,哪个对象的_size较大,则该对象较大:

	bool operator<(const string& s) const{for (size_t i = 0; i < (_size < s._size ? _size : s._size); ++i){if (_str[i] < s._str[i])return true;if (_str[i] > s._str[i])return false;}if (_size < s._size)return true;elsereturn false;}

operator==

operator==用于判断两个string对象是否相等,相等返回true,否则返回false

当两个string对象的_size不同时,直接返回false
然后循环遍历两个对象,遇到对应位置不相同的,直接返回false
最后,出循环说明均相等,返回true

	bool operator==(const string& s) const{if (_size != s._size)return false;for (size_t i = 0; i < _size; ++i){if (_str[i] != s._str[i])return false;}return true;}

其他

其他函数,根据比较的逻辑复用即可:

	bool operator<=(const string& s) const{if (*this < s || *this == s)return true;elsereturn false;}bool operator>(const string& s) const{if (!(*this <= s))return true;elsereturn false;}bool operator>=(const string& s) const{if (!(*this < s))return true;elsereturn false;}bool operator!=(const string& s) const{if (!(*this == s))return true;elsereturn false;}

查找

find用于在string对象中查找是否存在某字符或某字符串,若存在就返回其第一次出现的位置,否则返回npos,有两个重载版本,即查找字符与查找字符串:

查找字符

查找字符时,即使用循环从pos位置开始遍历string对象中的数据,当遇到与c相等的字符时,就返回该位置。若出循环,就表示没有找到,返回npos

	// 返回c在string中第一次出现的位置size_t find(char c, size_t pos) const{assert(pos < _size);for (size_t i = pos; i < _size; ++i){if (_str[i] == c){return i;}}return npos;}

查找字符串

在string对象中查找字符串时,可以使用之前C语言时学过的strstr函数,用于查找字串。(当第2个参数为第1个参数的子串时,返回在其中的第一个位置的地址,否则返回空指针)

首先assert判断s是否为空指针,以及pos是否越界;
然后调用strstr,第一个参数为_str,第二个参数为s,并创建一个指针pchar来接收返回值;

pchar为空时,返回npos,当pchar - _str的值不小于pos时,返回该差,否则返回npos

	// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos) const{assert(s);assert(pos < _size);char* pchar = strstr(_str, s);if (pchar == nullptr){return npos;}if ((size_t)(pchar - _str) >= pos){return pchar - _str;}return npos;}

非成员函数

非成员函数中只实现流插入与流提取运算符的重载(operator<<operator>>):

在之前的日期类实现中,我们使用友元函数,实现在这两个函数中可以访问对象的属性。但在string类中,由于之前实现过访问元素的operator[],所以可以不使用友元就可以实现在这两个函数中访问string对象的元素

operator<<

operator<<中, 我们只需要将string对象s中的元素依次流入到ostream的对象_cout即可:

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

operator>>

在向内存中输入数据时,我们当然可以逐字符的+=,但是这样会造成多次的扩容而影响效率。

我们可以直接创建一个128字节的数组来转存数据,当在这个数组中存满后再将这个数组中的数据+=到string对象中,然后清空数据继续接收数据,等到全部接收完毕后,将其中剩余的元素再**+=**到string对象后即可:

	istream& operator>>(istream& _cin, string& s){s.clear();char ch = _cin.get();//清除缓冲区中的空格与换行while (ch == ' ' || ch == '\n'){ch = _cin.get();}//定义一个128的字符数组char temp[128] = { 0 };int i = 0;while (ch != ' ' && ch != '\n'){if (i == 127){s += temp;i = 0;}temp[i] = ch;++i;ch = _cin.get();}//如果i>0 即temp中还有数据,将其转存即可if (i > 0){temp[i] = '\0';s += temp;}return _cin;}

源码概览

#include<iostream>
#include<cassert>
using namespace std;namespace qqq
{class string{public:typedef char* iterator;typedef const char* const_iterator;public:string(const char* str = ""){assert(str);_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];memcpy(_str, str, _size + 1);}string(const string& s){_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];memcpy(_str, s._str, s._size + 1);}//string& operator=(const string& s);   //老版本string& operator=(string s){swap(s);return *this;}~string(){_size = 0;_capacity = 0;delete[] _str;}//// iteratorstring::iterator begin(){return _str;}string::iterator end(){return _str + _size;}string::const_iterator begin()  const{return _str;}string::const_iterator end() const{return _str + _size;}/// modify// 在pos位置上插入字符c/字符串str,并返回string& insert(size_t pos, char c){assert(pos <= _size);if (_size == _capacity){if (_capacity != 0){reserve(2 * _capacity);}else{reserve(4);}}size_t end = _size + 1;while (end > pos)   //pos为size_t,当end与pos比较时,会转化为size_t而永远不可能小于0.故将end+1,后给前赋值{_str[end] = _str[end - 1];--end;}_str[pos] = c;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);reserve(len + _size);size_t end = _size + len;while (end > pos + len - 1)   //写成end >= pos+len就会有问题(在0位置插一个""){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; ++i){_str[pos + i] = str[i];}_size += len;return *this;}// 删除pos位置上的元素,并返回string& erase(size_t pos, size_t len){if (len == npos){_size = pos;_str[_size] = '\0';}for (size_t i = 0; i < _size - pos; ++i){_str[pos + i] = _str[pos + len + i];}_size -= len;return *this;}//尾插void push_back(char c){if (_size == _capacity){if (_capacity != 0){reserve(2 * _capacity);}else{reserve(4);}}_str[_size] = c;++_size;_str[_size] = '\0';}string& operator+=(char c){push_back(c);return *this;}//尾追加void append(const char* str){assert(str);size_t newlenth = _size + strlen(str);if (newlenth > _capacity){reserve(newlenth);}strcpy(_str + _size, str);_size = newlenth;}string& operator+=(const char* str){append(str);return *this;}void clear(){_size = 0;_str[_size] = '\0';}void swap(string& s){std::swap(_size, s._size);std::swap(_capacity, s._capacity);std::swap(_str, s._str);}const char* c_str() const{return _str;}/// capacitysize_t size() const{return _size;}size_t capacity() const{return _capacity;}bool empty() const{if (_size == 0){return true;}return false;}void resize(size_t n, char c){if (n > _size){if (n > _capacity){reserve(n + _size);}for (size_t i = _size; i < n; ++i){_str[i] = c; //这里的[]是访问数组元素,并非运算符重载的调用,所以不会越界}_size = n;_str[_size] = '\0';}else{_size = n;_str[_size] = '\0';}}void reserve(size_t n){if (n > _capacity){char* newstr = new char[n + 1]{ 0 };memcpy(newstr, _str, _size + 1);//strcpy(newstr, _str);delete[] _str;_str = newstr;_capacity = n;}}/// accesschar& operator[](size_t index){assert(index < _size);return _str[index];}const char& operator[](size_t index) const{assert(index < _size);return _str[index];}///relational operatorsbool operator<(const string& s) const{for (size_t i = 0; i < (_size < s._size ? _size : s._size); ++i){if (_str[i] < s._str[i])return true;if (_str[i] > s._str[i])return false;}if (_size < s._size)return true;elsereturn false;}bool operator==(const string& s) const{if (_size != s._size)return false;for (size_t i = 0; i < _size; ++i){if (_str[i] != s._str[i])return false;}return true;}bool operator<=(const string& s) const{if (*this < s || *this == s)return true;elsereturn false;}bool operator>(const string& s) const{if (!(*this <= s))return true;elsereturn false;}bool operator>=(const string& s) const{if (!(*this < s))return true;elsereturn false;}bool operator!=(const string& s) const{if (!(*this == s))return true;elsereturn false;}// 返回c在string中第一次出现的位置size_t find(char c, size_t pos) const{assert(pos < _size);for (size_t i = pos; i < _size; ++i){if (_str[i] == c){return i;}}return npos;}// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos) const{assert(s);assert(pos < _size);char* pchar = strstr(_str, s);if (pchar == nullptr){return npos;}if ((size_t)(pchar - _str) >= pos){return pchar - _str;}return npos;}private:char* _str;size_t _capacity;size_t _size;const static size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& _cout, const string& s){for (size_t i = 0; i < s.size(); ++i){_cout << s[i];}return _cout;}istream& operator>>(istream& _cin, string& s){s.clear();char ch = _cin.get();//清除缓冲区中的空格与换行while (ch == ' ' || ch == '\n'){ch = _cin.get();}//定义一个128的字符数组char temp[128] = { 0 };int i = 0;while (ch != ' ' && ch != '\n'){if (i == 127){s += temp;i = 0;}temp[i] = ch;++i;ch = _cin.get();}//如果i>0 即temp中还有数据,将其转存即可if (i > 0){temp[i] = '\0';s += temp;}return _cin;}
}

总结

到此,关于string类的模拟实现就介绍完了
相信通过模拟实现string类可以使我们更深入地理解string

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

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

相关文章

uni-app在小米手机上运行【步骤细节】

注意细节重点&#xff1a; 1.手机使用数据线与电脑连接&#xff0c;手机连接模式必须是传输文件模式 2.手机必须打开开发者模式 3.打开开发者模式后&#xff0c;仔细浏览并调整USB调试权限&#xff0c;重点打开USB是否允许安装按钮&#xff01;&#xff01;&#xff01; 操作步…

RWEQ模型参量提取

土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2&#xff0c;占国土总面积的16.7%&#xff0c;严重影响这些地区的资源开发和社会经…

windows环境下,安装elasticsearch

目录 前言准备安装 jdk 安装nodejsElasticSearch下载ElasticSearch-head 下载 安装ElasticSearch安装ElasticSearch-head插件设置用户名密码访问ElasticSearch 默认用户名和密码参考 前言 win10elasticsearch 8.9.0 准备 安装 jdk ElasticSearch 是基于lucence开发的&#…

MATLAB | 如何绘制这样的描边散点图?

part.-1 前前言 最近略忙可能更新的内容会比较简单&#xff0c;见谅哇&#xff0c;今日更新内容&#xff1a; part.0 前言 看到gzhBYtools科研笔记(推荐大家可以去瞅瞅&#xff0c;有很多有意思的图形的R语言复现&#xff01;&#xff01;)做了这样一张图&#xff1a; 感觉很…

docker简单web管理docker.io/uifd/ui-for-docker

要先pull这个镜像docker.io/uifd/ui-for-docker 这个软件默认只能使用9000端口&#xff0c;别的不行&#xff0c;因为作者在镜像制作时已加入这一层 刚下下来镜像可以通过docker history docker.io/uifd/ui-for-docker 查看到这个端口已被 设置 如果在没有设置br0网关时&…

视频标注是什么?和图像数据标注的区别?

视频数据标注是对视频剪辑进行标注的过程。进行标注后的视频数据将作为训练数据集用于训练深度学习和机器学习模型。这些预先训练的神经网络之后会被用于计算机视觉领域。 自动化视频标注对训练AI模型有哪些优势 与图像数据标注类似&#xff0c;视频标注是教计算机识别对象…

【解惑笔记】树莓派+OpenCV+YOLOv5目标检测(Pytorch框架)

【学习资料】 子豪兄的零基础树莓派教程https://github.com/TommyZihao/ZihaoTutorialOfRaspberryPi/blob/master/%E7%AC%AC2%E8%AE%B2%EF%BC%9A%E6%A0%91%E8%8E%93%E6%B4%BE%E6%96%B0%E6%89%8B%E6%97%A0%E7%97%9B%E5%BC%80%E6%9C%BA%E6%8C%87%E5%8D%97.md#%E7%83%A7%E5%BD%95…

Flink - souce算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 目录 1. 从Java的集合中读取数据 2. 从本地文件中读取数据 3. 从HDFS中读取数据 4. 从Socket中读取数据 5. 从Kafka中读取数据 6. 自定义Source 官方文档 - Flink1.13 1. 从Java的集合中读取数据 …

Vue 3:玩一下web前端技术(一)

前言 本章内容为VUE前端环境搭建与相关前端技术讨论。 下一篇文章地址&#xff1a; Vue 3&#xff1a;玩一下web前端技术&#xff08;二&#xff09;_Lion King的博客-CSDN博客 一、环境搭建 1. 安装Node.js Vue是基于Node.js的&#xff0c;因此首先需要安装Node.js。官网…

缓存数据同步技术Canal

说明&#xff1a;缓存数据同步&#xff0c;以Redis为例&#xff0c;如何保证从Redis中取出来的数据与MySQL中的一致&#xff1f;在微服务架构下&#xff0c;通常可以用以下两种技术来实现&#xff1a; MQ&#xff1a;在修改数据的同时&#xff0c;发送一个消息修改缓存&#x…

Go Ethereum源码学习笔记 001 Geth Start

Go Ethereum源码学习笔记 前言[Chapter_001] 万物的起点: Geth Start什么是 geth&#xff1f;go-ethereum Codebase 结构 Geth Start前奏: Geth Consolegeth 节点是如何启动的NodeNode的关闭 Ethereum Backend附录 前言 首先读者需要具备Go语言基础&#xff0c;至少要通关菜鸟…

【wsl-windows子系统】安装、启用、禁用以及同时支持docker-desktop和vmware方案

如果你要用docker桌面版&#xff0c;很可能会用到wsl&#xff0c;如果没配置好&#xff0c;很可能wsl镜像会占用C盘很多空间。 前提用管理员身份执行 wsl-windows子系统安装和启用 pushd "%~dp0" dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper…

06. 管理Docker容器数据

目录 1、前言 2、Docker实现数据管理的方式 2.1、数据卷&#xff08;Data Volumes&#xff09; 2.2、数据卷容器&#xff08;Data Volume Containers&#xff09; 3、简单示例 3.1、数据卷示例 3.2、数据卷容器示例 1、前言 在生产环境中使用 Docker&#xff0c;一方面…

211. 添加与搜索单词 - 数据结构设计---------------字典树

211. 添加与搜索单词 - 数据结构设计 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 211. 添加与搜索单词 - 数据结构设计 https://leetcode.cn/problems/design-add-and-search-words-data-structure/descriptio…

Exadata磁盘损坏导致磁盘组无法mount恢复(oracle一体机磁盘组异常恢复)---惜分飞

Oracle Exadata客户,在换盘过程中,cell节点又一块磁盘损坏,导致datac1磁盘组&#xff08;该磁盘组是normal方式冗余)无法mount Thu Jul 20 22:01:21 2023 SQL> alter diskgroup datac1 mount force NOTE: cache registered group DATAC1 number1 incarn0x0728ad12 NOTE: ca…

【iOS】Frame与Bounds的区别详解

iOS的坐标系 iOS特有的坐标是&#xff0c;是在iOS坐标系的左上角为坐标原点&#xff0c;往右为X正方向&#xff0c;向下为Y正方向。 bounds和frame都是属于CGRect类型的结构体&#xff0c;系统的定义如下&#xff0c;包含一个CGPoint&#xff08;起点&#xff09;和一个CGSiz…

windows使用多账户Git,多远程仓库版本管理

1 清除全局配置 git config --global --list // 看一下是否配置过user.name 和 user.email git config --global --unset user.name // 清除全局用户名 git config --global --unset user.email // 清除全局邮箱 2 本地仓库&#xff0c;每个远程对应的本地仓库目录下执行 $…

求三个球面交点的高效解法

文章目录 一、问题描述二、推导步骤代数法几何法 三、MATLAB代码 一、问题描述 如图&#xff0c;已知三个球面的球心坐标分别为 P 1 ( x 1 , y 1 , z 1 ) , P 2 ( x 2 , y 2 , z 2 ) , P 3 ( x 3 , y 3 , z 3 ) P_1(x_1,y_1,z_1),P_2(x_2,y_2,z_2),P_3(x_3,y_3,z_3) P1​(x1​,…

idea项目依赖全部找不到

目录 1&#xff0c;出错现象2&#xff0c;解决3&#xff0c;其他尝试 1&#xff0c;出错现象 很久没打开的Java项目&#xff0c;打开之后大部分依赖都找不到&#xff0c;出现了所有的含有import语句的文件都会报错和一些注解报红报错&#xff0c;但pom文件中改依赖是确实被引入…

深度学习实践——循环神经网络实践

系列实验 深度学习实践——卷积神经网络实践&#xff1a;裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 代码可见于&#xff1a; 深度学习实践——循环神经网络实践 0 概况1 架构实现1.1 RNN架构1.1.1 RNN架…