当前位置: 首页 > article >正文

C++:string类的模拟实现

目录

1.引言

2.C++模拟实现

2.1模拟实现构造函数

1)直接构造

2)拷贝构造

2.2模拟实现析构函数

2.3模拟实现其他常规函数

1)c_str函数

2)size函数

3)begin/end函数

4)reserve函数

5)resize函数

6)push_back函数

7)append函数

8)insert函数

9)erase函数

10)find函数

11)substr函数

2.4模拟实现操作符重载函数

1)赋值操作符重载(深拷贝)

2)[]访问操作符重载(读写/只读)

3)+=追加操作符重载

4)关系操作符重载

5)流提取,流插入操作符重载

3.完整string


1.引言

先来说说为什么要模拟实现库里的string?

我认为,模拟实现string有以下几个意义和好处

  1. 深入理解string的内部实现:通过模拟实现string,可以更深入地了解string的内部工作原理,包括字符串的存储方式、常用操作的实现方法等。这有助于提升对string的理解和掌握。

  2. 增强编程能力:通过模拟实现string,可以锻炼编程能力,提升自己的算法和数据结构水平。对于一些常见的操作,如字符串拼接、查找、替换等,可以通过模拟实现来加深对相应算法的理解和实现。

  3. 解决特定需求:有时候,标准库提供的string类可能无法满足特定需求,或者为了提高性能,我们需要自定义一个字符串类。通过模拟实现string,可以根据具体需求对其进行优化和扩展,满足特定的使用场景。

  4. 提高代码可读性和可维护性:自己实现string类的代码可以更加贴近业务需求和代码风格,使代码更易读、更易维护。另外,通过自己实现的string类,可以更好地理解和使用标准库提供的string类,提高自己的代码质量。

2.C++模拟实现

      提前声明,由于string对象中不同类型的函数重载太多太杂,且内存池等内容太过超前,本篇仅仅模拟实现简单的构造,析构,操作符重载,深浅拷贝,大小比较,增删查改等部分函数以及拓展写时拷贝的介绍,感谢读者的支持!

      建议先创建一个头文件,单独创建一个命名空间,防止已经展开了std的命名空间,实现的string与库中string发生冲突。

      我们就定义命名空间为drw,将string类的私有成员变量分别定义为:

char* _str      size_t size      size_t capacity

      并且这里还需要定义一个类外可以访问到的变量npos:

const static size_t npos

2.1模拟实现构造函数

1)直接构造

思路:

  1. 在接受的参数类型中必须给出一个缺省string,以便于在使用时能完成普通初始化
  2. 随后在初始化列表中进行初始化,给新string的_str开辟出参数字符串的长度+1个空间,请注意这里要使用strlen函数,str系列函数在遇到\0就会自动结束,因为在string中构造对象只会取字符串\0之前的内容,所以在这里用strlen实现,还请注意
  3. 将_capacity用字符串的长度赋值(这代表字符串能容纳有效字符的个数,不包括\0)
  4. 将_size用字符串的长度赋值(这代表有效字符个数,同样不包括\0)
  5. 最后将字符串的内容用memcpy拷贝到string中(选用memcpy的原因后面介绍)

实现:

string(const char* str = ""):_str(new char[strlen(str) + 1]), _capacity(strlen(str)), _size(strlen(str))
{//strcpy(_str, str);处理不了\0,改为memcpymemcpy(_str, str, sizeof(char) * (_size + 1));//多开一个留给\0
}

2)拷贝构造

思路:

  1. 拷贝构造,即用string构造新的string,思路和直接构造相差不大
  2. 先开辟参数string中_capacity+1的空间给新的string中的_str
  3. 用memcpy将参数string的内容拷贝到新string中,注意要拷贝_size+1个字节,包括\0 (注意:这里解释使用memcpy的原因:因为如果字符串中间有\0,strcpy就不会拷贝,这样实现不了拷贝构造,使用memcpy就不会有这个问题)
  4. 将参数string中的_size,_capacity分别赋值给新string的_size,_capacity

实现:

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

补充:

      能否用直接构造函数去实现拷贝构造函数?比如:

string(const string& s)
{string tmp(s._str);swap(*this, tmp);
}

答案:很抱歉不行,因为如果string的_str字符串中间存在\0的话,就会构造出一个只有字符串\0之前内容的string,但实际的拷贝构造函数可以拷贝string中的所有内容,这样实现存在缺陷!

2.2模拟实现析构函数

思路:

  1. 析构函数较简单,首先delete[] 释放掉开辟的空间,将_str赋值nullptr
  2. 再把_capacity和_size置为0就完成了

实现:

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

2.3模拟实现其他常规函数

1)c_str函数

思路:

  1. 该函数会将string的字符串以C语言的形式返回(本质就是返回一个字符指针指向字符串存储在堆上的空间)
  2. 直接返回_str

实现:

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

2)size函数

思路:

  1. size函数是返回字符串中有效字符个数的函数,实现起来也比较简单
  2. 直接返回_size即可

实现:

size_t size()const
{return _size;
}

3)begin/end函数

思路:

  1. begin函数用来返回迭代器位置,在string中返回字符串第一个位置的迭代器
  2. 直接返回_str即代表指向第一个位置的指针
  3. end函数返回迭代器位置,在string中返回字符串最后一个字符下一个位置的迭代器
  4. 直接返回_str+_size即代表指向最后一个字符下一个位置的指针
  5. begin/end代表左闭右开的区间
  6. 分别有两个版本,分别是可读可写版本和可读不可写版本
  7. 不要忘记在string中提前重定义char*/const char*为iterator/const_iterator

实现:

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

4)reserve函数

思路:

  1. reserve函数用来提前为string对象开辟一块空间,可以减少拷贝的次数,提高代码的运行效率
  2. 如果参数大于capacity时就扩容,如果小于在windows环境下不会缩容
  3. 扩容时重新开辟参数+1的空间,memcpy全部内容,_size,_capacity重新赋值给tmp临时string对象中,释放掉string原来的空间,再将tmp给_str

实现:

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

5)resize函数

思路:

  1. 与reserve有异曲同工之处,在第一个参数大于原string的_capacity之后再扩容,如果小于是不需要扩容的,这时候代表删除字符
  2. 第一种不需要扩容的情况:删除数据,直接将_str[_size]置为\0就可以代表删除数据了,不用进行重新赋值,同时_size=n
  3. 第二种需要扩容的情况:直接用reserve函数扩容,再初始化新开辟的空间,这里有默认缺省值为\0,也可以传字符用来初始化新空间
  4. 将最后一个位置置为\0

实现:

void resize(size_t n, char ch='\0')
{if (n < _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n){_str[_size++] = ch;}_str[_size] = '\0';}
}

6)push_back函数

思路:

  1. 首先需要检查扩容,如果_size==_capacity需要扩容,这里采用简单的扩容方式:二倍扩容,如果提前string没有任何内容,_capacity为0,那么就扩容4
  2. 在_str[_size]赋值尾插的字符,将_size++,最后一个位置赋值\0

实现:

void push_back(char ch)
{if (_size == _capacity)//二倍扩容{reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';
}

7)append函数

思路:

  1. append可以尾插一段字符串
  2. 尾插字符串:必须计算要插入字符串的长度,加上_size是否大于容量_capacity,如果大于就要先reserve扩容,再把待插入字符串全部拷贝到_size的位置即可

实现:

void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//至少扩size+len}memcpy(_str + _size, str,sizeof(char)*(len+1));_size += len;
}

8)insert函数

思路:

  1. insert函数有很多重载类型,这里只实现pos位置的两个函数,插入字符和插入字符串
  2. 插入字符:先检查是否需要扩容,将pos位置的字符直到最后一个字符向后移动n个单位,将插入的字符从pos位置开始插入
  3. 插入字符串:先检查是否需要扩容,将pos位置的字符直到最后一个字符向后移动插入字符串的长度个单位,将字符串每个字符从pos位置依次插入

实现:

void insert(size_t pos, size_t n, char ch)
{if (_size + n > _capacity){reserve(_size + n);}size_t end = _size;while ((int)end >= (int)pos)//强制转换为了防止整型提升 陷入死循环{_str[end + n] = _str[end];end--;}for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}void insert(size_t pos, const char* str)
{size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}size_t end = _size;while ((int)end >= (int)pos)//强制转换为了防止整型提升 陷入死循环{_str[end + len] = _str[end];end--;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}

补充:

强制类型转换的理由:如果pos=0,从0开始插入,那么最后end会减为-1,但end属于size_t类型,在比较大小时会发生强制类型转换,-1在size_t中等于非常大的数,会陷入死循环

9)erase函数

思路:

  1. 从一个pos位置开始删除len个长度的字符串,给len缺省参数npos,npos在前面提到过,是public中的不可修改的静态变量,这里我们现在类外定义npos为-1
  2. 确认要删除字符串的长度,如果len==npos或者pos+len>=_size就代表全删,只需在_str[_size]置为\0,_size置为pos就行
  3. 如果不全删,那么就将后面的字符移到前面来,最后_size减去len

实现:

void erase(size_t pos, size_t len = npos)
{assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[pos++] = _str[begin++];}_size -= len;}
}

10)find函数

思路:

  1. find函数可以默认从开始寻找单个字符或者字符串,如果找到就返回第一个字符位置,否则返回npos
  2. 查找字符我们一个一个比对,查找字符串就使用strstr实现

实现

size_t find(char ch, size_t pos = 0)
{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);const char* ret = strstr(_str + pos, str);if (ret){return ret - _str;}else{return npos;}
}

11)substr函数

思路:

  1. 从一个位置开始剪切一个字符串,字符串长度len有默认缺省值npos
  2. 先确定len的具体长度,如果len==npos或者pos+len>=_size代表从pos开始全部剪切
  3. 把返回的字符一个一个临时string tmp最后返回

实现:

string substr(size_t pos = 0, size_t len = npos)
{assert(pos < _size);string tmp;size_t n = len;if (len == npos || pos + len >= _size){n = _size - pos;}tmp.reserve(n);for (size_t i = pos; i < n + pos; i++){tmp += _str[i];}return tmp;
}

2.4模拟实现操作符重载函数

1)赋值操作符重载(深拷贝)

思路:

      =操作符如果不主动写编译器自动使用浅拷贝,浅拷贝和深拷贝的区别是什么?

  • 浅拷贝是一个一个字节直接拷贝,地址和其他变量全部一致,这样就会导致两个对象用同一块内存,如果销毁两个对象就会发生两次析构
  • 如果s1=s2,s1对象如果已经储存了字符串,那么就没有指针去管理了,这块空间被浪费,造成内存泄露
  1. 先用传参的string对象调用拷贝构造去构造tmp临时对象
  2. 将*this和tmp的三个成员变量分别交换
  3. tmp发生析构,销毁原属于*this的空间

实现:

string& operator=(const string& s)
{string tmp(s);swap(_str, tmp._str);swap(_capacity, tmp._capacity);swap(_size, tmp._size);//拷贝构造临时对象tmp,交换所有成员 出函数tmp析构销毁 因为交换了成员 连带着原字符串空间释放掉了return *this;
}

2)[]访问操作符重载(读写/只读)

思路:

  1. 读写就不需要修饰,只读需要修饰const返回const char
  2. 返回_str[pos]即可

实现:

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

3)+=追加操作符重载

思路:

  1. 分为两种类型:单个字符和字符串,+=功能强大可以直接代替append,push_back等函数
  2. 单个字符:调用写好的push_back函数就行
  3. 字符串:调用写好的append函数就行

实现:

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

4)关系操作符重载

关于>,<,==,<=,>=几种关系操作符的重载:

思路:

  1. 只需完成<,==两个函数,剩余的可以复用
  2. <:先选择最短的字符串的长度为比较长度,memcmp比较该段长度两个string的大小,如果相等再比较长度
  3. ==:相等必须满足长度相等以及memcmp==0

实现:

bool operator>(const string& s)const
{int ret = memcmp(_str, s._str, sizeof(char) * (_size > s._size ? s._size : _size));return ret == 0 ? _size > s._size:ret > 0;
}bool operator==(const string& s)const
{return _size == s._size && memcmp(_str, s._str, sizeof(char) * (_size)) == 0;
}bool operator>=(const string& s)const
{return *this > s || *this == s;
}bool operator<(const string& s)const
{return !(*this >= s);
}bool operator<=(const string& s)const
{return *this < s || *this == s;
}

5)流提取,流插入操作符重载

思路:

  1. 返回类型:必须是引用返回,ostream以及istream库做了反拷贝操作,不允许拷贝返回,在使用这两个库前先包含一下头文件,展开std的命名空间
  2. 参数类型:>>istream& in, drw::string& s,<<ostream& out, const drw::string& s,参数中的string前要加上drw命名空间,这点很容易忽视!>>要想s中写入数据,所以不带有const
  3. 关于这两个函数为什么不放在类中:成员函数默认第一个参数是this,如果放入使用时cout要放在操作符后面,不符合操作习惯
  4. <<:直接将每个字符输出
  5. >>:先将s中的内容清除,防止缓冲区中的残留数据影响提取,再定义char tmp用来接收读取的字符,用库中函数get读取字符(get类似于fget,读取完毕会指向下一个位置),(>>是不会读取空格或\n的,那么如果字符串带有空格不会读取,还存在死循环的可能,因此不能用>>读取)
  6. 不读取第一个字符之前的所有空格或是\n
  7. 用容量为128的数组收集数据多次传值给string,避免多次扩容

实现:

//流提取流插入不放入类中是因为类中成员函数第一个参数是this指针,
//s1<<cout不符合使用习惯,所以在类外定义
istream& operator>>(istream& in, drw::string& s)//这里不能加const
{s.clear();char tmp;//in >> tmp;//这样是不行的,>>不会读取空格或者是\n,默认这些为分隔符tmp = in.get();//这里get相当于fget,会读取空格和\n,读完自动指向下一个位置//str系列函数遇到\0都会结束 包括strcpywhile (tmp == ' ' || tmp == '\n'){tmp = in.get();}char arr[128] = { 0 };//提前开一个小空间,避免多次扩容int i = 0;while (tmp != ' ' || tmp != '\n'){if (i == 127){arr[i] = '\0';s += arr;i = 0;}arr[i++] = tmp;tmp = in.get();}if (i != 0){arr[i] = '\0';s += arr;}return in;
}

3.完整string

这里给出完整的实现代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<assert.h>
using namespace std;
namespace drw
{class string{public:typedef char* iterator;typedef const char* const_iterator;string(const char* str = ""):_str(new char[strlen(str) + 1]), _capacity(strlen(str)), _size(strlen(str)){//strcpy(_str, str);处理不了\0,改为memcpymemcpy(_str, str, sizeof(char) * (_size + 1));//多开一个留给\0}string(const string& str){_str = new char[str._capacity + 1];memcpy(_str, str._str, sizeof(char) * (str._size+ 1));_size = str._size;_capacity = str._capacity;}//拷贝构造现代写法//string(const string& s)//{//	string tmp(s._str);//但这样像hello\0world就不行 存在缺陷//	swap(*this, tmp);//}//写时拷贝/延时拷贝//先默认发生浅拷贝,让两个指向同一块空间,如果不对新的string进行改动,就不深拷贝//同时每个字符串有引用计数,代表多少个对象共用一块空间,只有当计数为1,发生析构//这样避免发生多次析构//windows下没有延时拷贝 Linux环境下延时拷贝//补充://windows环境下存在buffer数组,大小16,如果字符串大小<16就直接存在数组里//如果大于16就存在字符串中,buffer数组空间浪费掉,空间换取时间//深拷贝 神写法//浅拷贝有两种危害 1两次析构 2原string空间没有释放掉 造成内存泄露/*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){string tmp(s);swap(_str, tmp._str);swap(_capacity, tmp._capacity);swap(_size, tmp._size);//拷贝构造临时对象tmp,交换所有成员 出函数tmp析构销毁 因为交换了成员 连带着原字符串空间释放掉了return *this;}//现代://string& operator=(string tmp)//直接在传值时拷贝构造,形参的生成需要另外开辟空间,属于深拷贝//{//	swap(*this, tmp);//	return *this;//}const char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];memcpy(tmp, _str, sizeof(char) * (_size + 1));_capacity = n;delete[] _str;_str = tmp;}}void resize(size_t n, char ch='\0'){if (n < _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n){_str[_size++] = ch;}_str[_size] = '\0';}}void push_back(char ch){if (_size == _capacity)//二倍扩容{reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//至少扩size+len}memcpy(_str + _size, str,sizeof(char)*(len+1));_size += len;}string& operator+=(const char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}void insert(size_t pos, size_t n, char ch){if (_size + n > _capacity){reserve(_size + n);}size_t end = _size;while ((int)end >= (int)pos)//强制转换为了防止整型提升 陷入死循环{_str[end + n] = _str[end];end--;}for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;}void insert(size_t pos, const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}size_t end = _size;while ((int)end >= (int)pos)//强制转换为了防止整型提升 陷入死循环{_str[end + len] = _str[end];end--;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[pos++] = _str[begin++];}_size -= len;}}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);const char* ret = strstr(_str + pos, str);if (ret){return ret - _str;}else{return npos;}}string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);string tmp;size_t n = len;if (len == npos || pos + len >= _size){n = _size - pos;}tmp.reserve(n);for (size_t i = pos; i < n + pos; i++){tmp += _str[i];}return tmp;}bool operator>(const string& s)const{int ret = memcmp(_str, s._str, sizeof(char) * (_size > s._size ? s._size : _size));return ret == 0 ? _size > s._size:ret > 0;}bool operator==(const string& s)const{return _size == s._size && memcmp(_str, s._str, sizeof(char) * (_size)) == 0;}bool operator>=(const string& s)const{return *this > s || *this == s;}bool operator<(const string& s)const{return !(*this >= s);}bool operator<=(const string& s)const{return *this < s || *this == s;}void clear(){_str[0] = '\0';_size = 0;}~string(){delete[] _str;_capacity = 0;_size = 0;_str = nullptr;}private:char* _str;size_t _capacity;size_t _size;public:static size_t npos;};size_t string::npos = -1;
}
//这里必须要引用返回 不仅仅是为了提高效率 是因为io库反拷贝  后面的参数类型string
//不带上bit会和库中的string冲突!注意!
ostream& operator<<(ostream& out, const drw::string& s)
{/*for (size_t i = 0; i < s.size(); i++){out << s[i];}*/for (auto ch : s){out << ch;}return out;
}
//流提取流插入不放入类中是因为类中成员函数第一个参数是this指针,
//s1<<cout不符合使用习惯,所以在类外定义
istream& operator>>(istream& in, drw::string& s)//这里不能加const
{s.clear();char tmp;//in >> tmp;//这样是不行的,>>不会读取空格或者是\n,默认这些为分隔符tmp = in.get();//这里get相当于fget,会读取空格和\n,读完自动指向下一个位置//str系列函数遇到\0都会结束 包括strcpywhile (tmp == ' ' || tmp == '\n'){tmp = in.get();}char arr[128] = { 0 };//提前开一个小空间,避免多次扩容int i = 0;while (tmp != ' ' || tmp != '\n'){if (i == 127){arr[i] = '\0';s += arr;i = 0;}arr[i++] = tmp;tmp = in.get();}if (i != 0){arr[i] = '\0';s += arr;}return in;
}

- - - - - —————————本文结束————————— - - - - -

相关文章:

C++:string类的模拟实现

目录 1.引言 2.C模拟实现 2.1模拟实现构造函数 1&#xff09;直接构造 2&#xff09;拷贝构造 2.2模拟实现析构函数 2.3模拟实现其他常规函数 1&#xff09;c_str函数 2&#xff09;size函数 3&#xff09;begin/end函数 4&#xff09;reserve函数 5&#xff09;re…...

DMZ区的作用和原则

DMZ&#xff08;Demilitarized Zone&#xff0c;非军事化区&#xff09;是网络安全架构中一个重要的概念&#xff0c;其主要作用和原则如下&#xff1a; DMZ的作用 隔离风险 DMZ作为内外网络之间的缓冲区&#xff0c;能够有效隔离外部网络的攻击风险。将对外提供服务的服务器&…...

21.命令模式(Command Pattern)

定义 命令模式&#xff08;Command Pattern&#xff09; 是一种行为型设计模式&#xff0c;它将请求封装成一个对象&#xff0c;从而使您可以使用不同的请求、队列、日志请求以及支持撤销操作等功能。命令模式通过将请求&#xff08;命令&#xff09;封装成对象&#xff0c;使…...

如何将本地 Node.js 服务部署到宝塔面板:完整的部署指南

文章简介&#xff1a; 将本地开发的 Node.js 项目部署到线上服务器是开发者常见的工作流程之一。在这篇文章中&#xff0c;我将详细介绍如何将本地的 Node.js 服务通过宝塔面板&#xff08;BT 面板&#xff09;上线。宝塔面板是一个强大的服务器管理工具&#xff0c;具有简洁的…...

4.3 线性回归的改进-岭回归/4.4分类算法-逻辑回归与二分类/ 4.5 模型保存和加载

4.3.1 带有L2正则化的线性回归-岭回归 岭回归&#xff0c;其实也是一种线性回归&#xff0c;只不过在算法建立回归方程的时候1&#xff0c;加上正则化的限制&#xff0c;从而达到解决过拟合的效果 4.3.1.1 API 4.3.1.2 观察正则化程度的变化&#xff0c;对结果的影响 正则化力…...

Mac 部署Ollama + OpenWebUI完全指南

文章目录 &#x1f4bb; 环境说明&#x1f6e0;️ Ollama安装配置1. 安装[Ollama](https://github.com/ollama/ollama)2. 启动Ollama3. 模型存储位置4. 配置 Ollama &#x1f310; OpenWebUI部署1. 安装Docker2. 部署[OpenWebUI](https://www.openwebui.com/)&#xff08;可视化…...

工业物联网平台-视频识别视频报警新功能正式上线

前言 视频监控作为中服云工业物联网平台4.0的功能已经上线运行。已为客户服务2年有余&#xff0c;为客户提供多路视频、实时在线监视和控制能力。服务客户实时发现现场、产线、设备出现随机故障、事故等&#xff0c;及时到场处理维修。 视频识别&视频报警新功能当前正式上…...

面试题 17.19. 消失的两个数字

文章目录 1.题目2.思路3.代码 1.题目 面试题 17.19. 消失的两个数字 给定一个数组&#xff0c;包含从 1 到 N 所有的整数&#xff0c;但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗&#xff1f; 以任意顺序返回这两个数字均可。 示例 1: **输入:** […...

Licode简介及与SRS对比

Licode 是一个开源的 WebRTC 通信框架,专注于多人实时音视频互动(如视频会议),而 SRS 是一个通用的 流媒体服务器,支持直播、低延迟流分发等场景。以下是两者的详细对比和 Licode 的核心解析: 一、Licode 核心解析 1. 定位与设计目标 核心功能:基于 WebRTC 的多人实时音…...

mysql的cpu使用率100%问题排查

背景 线上mysql服务器经常性出现cpu使用率100%的告警&#xff0c; 因此整理一下排查该问题的常规流程。 1. 确认CPU占用来源 检查系统进程 使用 top 或 htop 命令&#xff0c;确认是否是 mysqld 进程导致CPU满载&#xff1a;top -c -p $(pgrep mysqld)2. 实时分析MySQL活动 …...

Debian 安装 Nextcloud 使用 MariaDB 数据库 + Caddy + PHP-FPM

前言 之前通过 docker在ubuntu上安装Nextcloud&#xff0c;但是现在我使用PVE安装Debian虚拟机&#xff0c;不想通过docker安装了。下面开始折腾。 安装过程 步骤 1&#xff1a;更新系统并安装必要的软件 sudo apt update && sudo apt upgrade -y sudo apt install…...

qt6.8安装mysql8.0驱动

qt6.8安装mysql8.0驱动 qt6.8本身是不带mysql驱动。想要在qt里面使用mysql,还是比较麻烦的。需要自己编译驱动 首先下载qt源码&#xff0c;链接Index of /archive/qt/6.8/6.8.1/single 下载mysql对于驱动文件&#xff0c;链接是MySQL :: Download MySQL Connector/C (Archiv…...

π0开源了且推出自回归版π0-FAST——打造机器人动作专用的高效Tokenizer:比扩散π0的训练速度快5倍但效果相当

前言 过去的半个多月 对于大模型 deepseek火爆全球&#xff0c;我对其的解读也写成了整整一个系列 详见《火爆全球的DeepSeek系列模型》&#xff0c;涉及对GRPO、MLA、V3、R1的详尽细致深入的解读 某种意义来讲&#xff0c;deepseek 相当于把大模型的热度 又直接拉起来了——…...

今日AI和商界事件(2025-02-07)

今日AI领域的相关事件包括但不限于以下几个方面&#xff1a; 一、政策与监管 美国众议员推动禁止政府设备使用中国AI应用DeepSeek&#xff1a;美国众议院两名来自两党的议员提议立法&#xff0c;禁止联邦政府设备使用中国人工智能应用DeepSeek&#xff0c;理由是中国政府可能…...

【算法篇】贪心算法

目录 贪心算法 贪心算法实际应用 一&#xff0c;零钱找回问题 二&#xff0c;活动选择问题 三&#xff0c;分数背包问题 将数组和减半的最小操作次数 最大数 贪心算法 贪心算法&#xff0c;是一种在每一步选择中都采取当前状态下的最优策略&#xff0c;期望得到全局最优…...

《金字塔原理》笔记

金字塔原理一书的原理是关于结构化写作的&#xff0c;里面提出一个MECE法则&#xff1a;各个分论点之间要“相互独立、完全穷尽”。 我的总结 写作思路都是总分总。 要凝练最顶部的信息&#xff0c;然后按照三叉树&#xff08;最多四叉树&#xff09;一直分下去。 书中优雅的…...

蓝桥杯准备 【入门3】循环结构

素数小算法&#xff08;埃氏筛&&欧拉筛&#xff09; 以下四段代码都是求20以内的所有素数 1.0版求素数 #include<iostream> using namespace std;int main() {int n 20;for(int i2;i<n;i){int j0;for(j2;j<i;j)//遍历i{if(i%j0){break;}}if(ij){cout&l…...

MySQL三大日志——binlog、redoLog、undoLog详解

日志是mysql数据库的重要组成部分&#xff0c;记录着数据库运行期间各种状态信息&#xff0c;能帮助我们进行很多容错及分析工作&#xff0c;其中有三大日志与我们这些开发者息息相关&#xff0c;本文将介绍binlog、redoLog、undoLog三种日志&#xff1a; 1. redoLog 1.1 为什么…...

IDEA中Resolving Maven dependencies卡着不动解决方案

一、修改settings.xml Maven配置阿里云仓库主要通过修改Maven的settings.xml文件来实现‌。以下是具体步骤: ‌1、找到settings.xml文件‌: 通常位于Maven安装目录下的conf文件夹中,或者在用户目录下的.m2文件夹中(如果用户自定义了settings.xml的位置)。 2、‌编辑se…...

组合(力扣77)

从这道题开始&#xff0c;我们正式进入回溯算法的学习。之前在二叉树中只是接触到了一丢丢&#xff0c;而这里我们将使用回溯算法解决很多经典问题。 那么这道题是如何使用回溯算法的呢&#xff1f;在讲回溯之前&#xff0c;先说明一下此题是如何递归的。毕竟回溯递归不分家&a…...

X86中的常用寄存器

通用寄存器16个 RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP, R8, R9, R10, R11, R12, R13, R14, R15 其中&#xff1a; RAX&#xff1a;调用程序时&#xff0c;用于存储返回值。RCX&#xff1a;在字符串处理指令中&#xff0c;常用做计数器。RSI&#xff1a;在字符串处理指令中…...

SpringAI系列 - 使用LangGPT编写高质量的Prompt

目录 一、LangGPT —— 人人都可编写高质量 Prompt二、快速上手2.1 诗人 三、Role 模板3.1 Role 模板3.2 Role 模板使用步骤3.3 更多例子 四、高级用法4.1 变量4.2 命令4.3 Reminder4.4 条件语句4.5 Json or Yaml 方便程序开发 一、LangGPT —— 人人都可编写高质量 Prompt La…...

git撤销上一次的提交

1、撤销提交 如果需要撤销上一次的提交&#xff0c;只是提交到了本地&#xff0c;可以通过命令&#xff1a; // 撤销最近的提交&#xff08;保留修改&#xff09; git reset --soft HEAD~1 这个操作可以保留之前的提交和当前的修改。最近一次的提交到本地的修改的提交会回到…...

springboot+vue导入ruoyi项目的框架

一、介绍 RuoYi-Vue版本&#xff0c;采用了前后端分离的单体架构设计软件环境&#xff1a;JDK、Mysql、Redis、Maven、Node技术选型: Spring Boot、Spring Security、MyBatis、Jwt、Vue3、Element-Plus官方地址: https://gitee.com/y_project/RuoYi-Vue 官方推荐的版本如下&a…...

Conmi的正确答案——Rider中添加icon作为exe的图标

C#版本&#xff1a;.net 8.0 Rider版本&#xff1a;#RD-243.22562.250&#xff08;非商业使用版&#xff09; 1、添加图标到解决方案下&#xff1a; 2、打开“App.xaml”配置文件&#xff0c;添加配置&#xff1a; <Applicationx:Class"ComTransmit.App"xmlns&q…...

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问&#xff1a;360手机已停产&#xff0c;现在和以后&#xff0c;能刷机吗&#xff1f; 答&#xff1a;360手机&#xff0c;是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…...

电风扇各国检测认证详细介绍美国FCC+UL欧盟CE+ROHS日本PSE+METI备案+英国UKCA

美国 &#xff1a; FCC认证 &#xff1a;产品进入美洲市场的通行证&#xff0c;需通过FCC SDOC认证。 FCC第15部分B: 该标准适用于非故意辐射设备&#xff0c;如家用电器、电脑设备等。它规定了这些设备在电磁环境中不会产生过多的辐射。 ​射频标准: FCC第15部分C:该标准适…...

实验3 词法分析(二)

实验3 词法分析(二) [实验目的]&#xff1a; 1 . 熟悉给定的词法分析程序&#xff1b; 2 . 改进词法分析程序。 [实验内容]&#xff1a; 1.尝试多方面改进TEST语言的文法&#xff0c;参考教材附录B词法分析程序TESTscan.c&#xff0c;在此词法分析程序的基础上改进程序&#x…...

VsCode创建VUE项目

1. 首先安装Node.js和npm 通过网盘分享的文件&#xff1a;vsCode和Node&#xff08;本人电脑Win11安装&#xff09; 链接: https://pan.baidu.com/s/151gBWTFZh9qIDS9XWMJVUA 提取码: 1234 它们是运行和构建Vue.js应用程序所必需的。 1.1 Node安装&#xff0c;点击下一步即可 …...

【自开发工具介绍】SQLSERVER的ImpDp和ExpDp工具04

SQLSERVER的ImpDp和ExpDp工具演示 1、指定某些表作为导出对象外 (-exclude_table) 验证用&#xff1a;导出的表&#xff0c;导入到新的数据库 2、指定某些表作为导出对象外 (-exclude_table) 支持模糊检索&#xff0c;可以使用星号 以s开头的表作为导出对象外&#xff0c;…...