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

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…...

UE制作2d游戏

2d免费资产: Free 2D Game Assets - CraftPix.net 需要用到PaperZD插件 官网下载后启用即可 导入png素材 然后全选 - 创建Sprite 创建 人物基类 设置弹簧臂和相机 弹簧臂设置成旋转-90 , 取消碰撞测试 设置子类Sprite 拖到场景中 绑定设置输入映射,让角色移动跳跃 神似卡拉比…...

verilog练习:i2c slave 模块设计

文章目录 前言1. 结构2.代码2.1 iic_slave.v2.2 sync.v2.3 wr_fsm.v2.3.1 状态机状态解释 2.4 ram.v 3. 波形展示4. 建议5. 资料总结 前言 首先就不啰嗦iic协议了&#xff0c;网上有不少资料都是叙述此协议的。 下面将是我本次设计的一些局部设计汇总&#xff0c;如果对读者有…...

mysql的语句备份详解

使用mysqldump工具备份&#xff08;适用于逻辑备份&#xff09; mysqldump是 MySQL 自带的一个非常实用的逻辑备份工具&#xff0c;它可以将数据库中的数据和结构以 SQL 语句的形式导出到文件中。 1. 备份整个数据库 mysqldump -u [用户名] -p [数据库名] > [备份文件名].…...

BFS算法篇——广度优先搜索,探索未知的旅程(上)

文章目录 前言一、BFS的思路二、BFS的C语言实现1. 图的表示2. BFS的实现 三、代码解析四、输出结果五、总结 前言 广度优先搜索&#xff08;BFS&#xff09;是一种广泛应用于图论中的算法&#xff0c;常用于寻找最短路径、图的遍历等问题。与深度优先搜索&#xff08;DFS&…...

短剧APP开发:短剧风口下的机遇

今年春节以来&#xff0c;各种精品短剧不断上线&#xff0c;短剧赛道热度持续上升&#xff0c;展现出了强大的经济效益&#xff0c;为影视市场提供了越来越多的机会。短剧的持续火爆也带动了短剧APP的发展&#xff0c;“短剧”迎来了爆发式增长&#xff0c;本文将探讨短剧APP开…...

PT2021K 单触控同步输出 IC

1. 产品概述 PT2021K 是一款电容式触摸控制 ASIC &#xff0c;支持 1 通道触摸输入&#xff0c; 1 通道同步型开关输出。具有低 功耗、高抗干扰、宽工作电压范围、高穿透力的突出优势。 2. 主要特性 工作电压范围&#xff1a; 2.4~5.5V 待机电流约 9uAV DD 5V&…...

[论文笔记] Deepseek-R1R1-zero技术报告阅读

启发: 1、SFT&RL的训练数据使用CoT输出的格式,先思考再回答,大大提升模型的数学与推理能力。 2、RL训练使用群体相对策略优化(GRPO),奖励模型是规则驱动,准确性奖励和格式化奖励。 1. 总体概述 背景与目标 报告聚焦于利用强化学习(RL)提升大型语言模型(LLMs)…...

选择LabVIEW开发外协还是自己做开发?

在决定是否选择外协进行LabVIEW开发时&#xff0c;选择合适的外协团队是至关重要的。一个专业的外协团队不仅能提高项目的开发效率&#xff0c;还能帮助解决技术难题&#xff0c;确保项目的高质量交付。在选择团队时&#xff0c;以下几个方面值得特别注意&#xff1a; ​ 1. 团…...

网络工程师 (20)计算机网络的概念

一、定义 计算机网络是指将地理位置不同、具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路及通信设备连接起来&#xff0c;在网络操作系统、网络管理软件及网络通信协议的管理和协调下&#xff0c;实现信息传递和资源共享的计算机通信系统。 二、组成 资源子网&…...

RockyLinux9.4安装教程

Rocky Linux 9.4 安装教程 Rocky Linux 是由 CentOS 创始人 Gregory Kurtzer 发起的一个社区企业操作系统,旨在与 Red Hat Enterprise Linux (RHEL) 保持二进制兼容。本教程将详细介绍如何在您的系统上安装 Rocky Linux 9.4。 步骤 1: 下载 Rocky Linux 9.4 ISO 文件 打开 R…...

单片机之基本元器件的工作原理

一、二极管 二极管的工作原理 二极管是一种由P型半导体和N型半导体结合形成的PN结器件&#xff0c;具有单向导电性。 1. PN结形成 P型半导体&#xff1a;掺入三价元素&#xff0c;形成空穴作为多数载流子。N型半导体&#xff1a;掺入五价元素&#xff0c;形成自由电子作为多…...

TOTP实现Google Authenticator认证工具获取6位验证码

登录遇到Google认证怎么办? TOTP是什么?(Google Authenticator) TOTP(Time-based One-Time Password)是一种基于时间的一次性密码算法,主要用于双因素身份验证。其核心原理是通过共享密钥和时间同步生成动态密码,具体步骤如下: 共享密钥:服务端与客户端预先共享一个…...

114,【6】攻防世界 web wzsc_文件上传

进入靶场 传个桌面有的 直接空白了 我们 访问一下上传的东西 /index 没显示用于解析的.htaccess和.user.ini 文件&#xff0c;还两个都不显示 .htaccess 和 .user.ini 文件分别用于 Apache 服务器和 PHP-FPM 环境的目录级配置 但上传的时候bp查看状态码是200&#xff0c;…...

Rust unresolved import `crate::xxx` 报错解决

问题阐述 该问题出现在我使用actix编写的crud后端api中&#xff0c;我的后端结构如下: D:. | handle_err.rs | lib.rs | main.rs | ---application | mod.rs | user_service.rs | ---domain | dto.rs | mod.rs | user.rs | ---infrastru…...

dl学习笔记(9):pytorch数据处理的完整流程

1&#xff09;自动导入常用库的设置方式 在开始之前&#xff0c;这里介绍一下自动导入常用的你需要加载的库的操作方式。 首先在我们的目录下找到ipython文件&#xff0c;如下图&#xff1a; 然后找到里面的startup文件&#xff1a; 然后新建一个文本文档&#xff0c;输入你每…...

结构化表达(三):归纳分组

目录 归纳分组 一、如何归纳分组 二、如何掌握更多模型 归纳分组 一、如何归纳分组 整理思路&#xff0c;多用分类模型&#xff0c;列如&#xff1a; 1、内部、外部分类。 2、市场营销学中的4P&#xff1a;产品、渠道、价格、促销。 3、战略3C&#xff1a;公司、客户、竞…...

BUU17 [RoarCTF 2019]Easy Calc1

自用 源代码 $(#calc).submit(function(){$.ajax({url:"calc.php?num"encodeURIComponent($("#content").val()),type:GET,success:function(data){$("#result").html(<div class"alert alert-success"><strong>答案:&l…...

openGauss 3.0 数据库在线实训课程2:学习客户端工具gsql的使用

openGauss数据库状态查看 前提 我正在参加21天养成好习惯| 第二届openGauss每日一练活动 课程详见&#xff1a;openGauss 3.0.0数据库在线实训课程 学习目标 学习openGauss数据库客户端工具gsql的使用。 课程作业 gsql是openGauss提供在命令行下运行的数据库连接工具&am…...

mac环境下,ollama+deepseek+cherry studio+chatbox本地部署

春节期间&#xff0c;deepseek迅速火爆全网&#xff0c;然后回来上班&#xff0c;我就浅浅的学习一下&#xff0c;然后这里总结一下&#xff0c;我学习中&#xff0c;总结的一些知识点吧&#xff0c;分享给大家。具体的深度安装部署&#xff0c;这里不做赘述&#xff0c;因为网…...

C# SQlite使用流程

前言 不是 MySQL 用不起&#xff0c;而是 SQLite 更有性价比&#xff0c;绝大多数的应用 SQLite 都可以满足。 SQLite 是一个用 C 语言编写的开源、轻量级、快速、独立且高可靠性的 SQL 数据库引擎&#xff0c;它提供了功能齐全的数据库解决方案。SQLite 几乎可以在所有的手机…...

mysql8 C++源码中创建表函数,表字段最大数量限制,表行最大存储限制

在 MySQL 8 的 C 源码中&#xff0c;表的最大字段数量限制体现在 MAX_FIELDS 宏定义中。这个宏定义了表中可以拥有的最大字段数量。 代码中的体现 在 mysql_prepare_create_table 函数中&#xff0c;有以下代码段检查表的字段数量是否超过最大限制&#xff1a; cpp if (alt…...

stable diffusion安装包与常用模型下载

本地安装包&#xff08;直接借用秋叶大佬的包&#xff09;&#x1f64f; 1.最好用的 Forge 版整合包 网盘&#xff1a;https://pan.quark.cn/s/4e9459b17c94 解压密码&#xff1a;bilibili-秋葉aaaki 2.最好用的整合包&#xff01; https://pan.baidu.com/s/1MjO3CpsIvTQID…...

Redis | 十大数据类型

文章目录 十大数据类型概述key操作命令数据类型命令及落地运用redis字符串&#xff08;String&#xff09;redis列表&#xff08;List&#xff09;redis哈希表&#xff08;Hash&#xff09;redis集合&#xff08;Set&#xff09;redis有序集合&#xff08;ZSet / SortedSet&…...

19爬虫:使用playwright登录超级鹰

本次案例一共解决了如下两个问题&#xff1a; &#xff08;1&#xff09;如何使用playwright截图&#xff0c;特别是验证码图片 &#xff08;2&#xff09;在playwright中如何判断一个元素可见 1.截图 我们首先解决第一个问题&#xff0c;截图。如果前期接触过selenium的小伙…...

强化学习笔记6——异同策略、AC、等其他模型总结

异步两种方法&#xff1a;1&#xff1a;经验回放 2&#xff1a;数据动作非同时产生 举例QLearning为什么是异策略&#xff1f; 生成动作时e的概率从Q表选&#xff0c;1-e概况随机。 更新策略时&#xff0c;贪心策略选择Q_max作为动作。 策略优化两种主要方法&#xff1a;基于梯…...

【工具变量】上市公司企业渐进式创新程度及渐进式创新锁定数据(1991-2023年)

测算方式&#xff1a; 参考顶刊《经济研究》孙雅慧&#xff08;2024&#xff09;老师的做法&#xff0c;用当期创新和往期创新的内容重叠度作为衡量渐进式创新程度的合理指标。通过搜集海量专利摘要&#xff0c;测算当前专利申请和既有专利的内容相似度&#xff0c;反映企业在…...

高级测试工程师,在接口自动化方面,如何用AI提升?DeepSpeek回答

思考过程&#xff1a; 嗯&#xff0c;用户现在的问题是作为高级测试工程师&#xff0c;在接口自动化方面如何用AI来提升。首先&#xff0c;我需要回顾之前的对话历史&#xff0c;看看之前给过哪些建议。之前讨论过AI在测试中的核心应用场景&#xff0c;比如智能测试用例生成、自…...

每日一题——插入排序实现数据流中的中位数

插入排序实现数据流中的中位数 题目描述功能要求数据范围 解题思路算法流程 代码实现代码详解1. 全局变量2. Insert 函数3. GetMedian 函数 复杂度分析Insert 函数GetMedian 函数空间复杂度&#xff08;整体&#xff09; 注意事项 题目描述 设计一个算法&#xff0c;用来计算数…...

arcgis for js范围内天地图高亮,其余底图灰暗

在GIS地图开发中&#xff0c;有时我们需要突出显示某个特定区域&#xff0c;而将其他区域灰暗处理&#xff0c;以达到视觉上的对比效果。本文将介绍如何使用ArcGIS for JavaScript实现这一功能&#xff0c;具体效果为&#xff1a;在指定范围内&#xff0c;天地图高亮显示&#…...

【Unity】从父对象中获取子对象组件的方式

1.GetComponentInChildren 用于获取对与指定组件或游戏对象的任何子级相同的游戏对象上的组件类型的引用。 该方法在Unity脚本API的声明格式为&#xff1a; public T GetComponentInChildren(bool includeInactive false) includeInactive参数&#xff08;可选&#xff09…...

code run使用vs2015工具链构建

"cpp": "cmd.exe /C \"\"D:\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86 && cl.exe $fileName /Fe:$fileNameWithoutExt.exe && $dir$fileNameWithoutExt.exe\"" 效果如下&#xff1a; // hello#includ…...

matlab快速入门(2)-- 数据处理与可视化

MATLAB的数据处理 1. 数据导入与导出 (1) 从文件读取数据 Excel 文件&#xff1a;data readtable(data.xlsx); % 读取为表格&#xff08;Table&#xff09;CSV 文件&#xff1a;data readtable(data.csv); % 自动处理表头和分隔符文本文件&#xff1a;data load(data.t…...

UnityShader学习笔记——动态效果

——内容源自唐老狮的shader课程 目录 1.原理 2.Shader中内置的时间变量 3.Shader中经常会改变的数据 4.纹理动画 4.1.背景滚动 4.1.1.补充知识 4.1.2.基本原理 4.2.帧动画 4.2.1.基本原理 5.流动的2D河流 5.1.基本原理 5.2.关键步骤 5.3.补充知识 6.广告牌效果 …...

Docker Desktop安装到其他盘

Docker Desktop 默认安装到c盘&#xff0c;占用空间太大了&#xff0c;想给安装到其他盘&#xff0c;网上找了半天的都不对 正确安装命令&#xff1a; start /w "" "Docker Desktop Installer.exe" install --installation-dirF:\docker命令执行成功&am…...

详细教程 | 如何使用DolphinScheduler调度Flink实时任务

Apache DolphinScheduler 非常适用于实时数据处理场景&#xff0c;尤其是与 Apache Flink 的集成。DolphinScheduler 提供了丰富的功能&#xff0c;包括任务依赖管理、动态调度、实时监控和日志管理&#xff0c;能够有效简化 Flink 实时任务的管理和部署。通过 DolphinSchedule…...

稻盛和夫如何描述能力

1. 能力的三要素 稻盛和夫认为&#xff0c;能力由以下三个核心要素组成&#xff1a; 知识&#xff08;Knowledge&#xff09;&#xff1a;掌握的专业知识、技术技能和行业经验。 技能&#xff08;Skill&#xff09;&#xff1a;将知识应用于实际工作的能力&#xff0c;包括解决…...

【LeetCode 刷题】贪心算法(4)-区间问题

此博客为《代码随想录》二叉树章节的学习笔记&#xff0c;主要内容为贪心算法区间问题的相关题目解析。 文章目录 55. 跳跃游戏45. 跳跃游戏 II452. 用最少数量的箭引爆气球435. 无重叠区间763. 划分字母区间56. 合并区间 55. 跳跃游戏 题目链接 class Solution:def canJump…...

javaEE初阶————多线程初阶(3)

大家新年快乐呀&#xff0c;今天是第三期啦&#xff0c;大家前几期的内容掌握的怎么样啦&#xff1f; 1&#xff0c;线程死锁 1.1 构成死锁的场景 a&#xff09;一个线程一把锁 这个在java中是不会发生的&#xff0c;因为我们之前讲的可重入机制&#xff0c;在其他语言中可…...

Deep Sleep 96小时:一场没有硝烟的科技保卫战

2025年1月28日凌晨3点&#xff0c;当大多数人还沉浸在梦乡时&#xff0c;一场没有硝烟的战争悄然打响。代号“Deep Sleep”的服务器突遭海量数据洪流冲击&#xff0c;警报声响彻机房&#xff0c;一场针对中国关键信息基础设施的网络攻击来势汹汹&#xff01; 面对美国发起的这场…...

【AI应用】免费的文本转语音工具:微软 Edge TTS 和 开源版 ChatTTS 对比

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】【AI应用】 我试用了下Edge TTS&#xff0c;感觉还不错&#xff0c;不过它不支持克隆声音&#xff08;比如自己的声音&#xff09; 微软 Edge TTS 和 开源版 ChatTTS 都是免费的 文本转语音&…...

Deepseek-v3 / Dify api接入飞书机器人go程序

准备工作 开通了接收消息权限的飞书机器人&#xff0c;例如我希望用户跟飞书机器人私聊&#xff0c;就需要开通这个权限&#xff1a;读取用户发给机器人的单聊消息 im:message.p2p_msg:readonly准备好飞书机器人的API key 和Secretdeepseek-v3的api keysecret&#xff1a;http…...

流媒体技术原理

流媒体技术的原理主要涉及以下几个核心概念和技术&#xff1a; 1. 编码和压缩 编码&#xff1a;视频和音频原始数据通常非常庞大。为了传输和存储&#xff0c;首先需要通过编码将这些数据转换成更小、更易处理的格式。常见视频编码标准包括H.264、H.265&#xff08;HEVC&…...

matlab simulink 三级倒立摆LQR控制

1、内容简介 略 matlab simulink 122-三级倒立摆LQR控制 可以交流、咨询、答疑 2、内容说明 略 要求初始条件&#xff3b;0.01 0.01 0.01 0.01 0 0 0 0&#xff3d; 调节时间希望在3s内&#xff0c;超调量尽量的小&#xff0c;最大不能超过0.05&#xff1b; 用simulink的…...

使用令牌桶算法通过redis实现限流

令牌桶算法是一种常用的限流算法&#xff0c;它可以平滑地控制请求的处理速率。在 Java 中结合 Redis 实现令牌桶算法&#xff0c;可以利用 Redis 的原子操作来保证多节点环境下的限流效果。 一 实现思路 初始化令牌桶&#xff1a;在 Redis 中存储令牌桶的相关信息&#xff0…...

Tableau实用技巧 —— 提取Tableau文件中图片

需求背景 在日常开发过程中&#xff0c;我们时常会遇到两种图片提取需求&#xff1a;一是本地报告中的图片文件丢失需要找回&#xff0c;二是从网络论坛中发现有价值的报告图片希望保存使用。针对这些实际应用场景&#xff0c;以下将详细介绍有效的图片提取方法。 解决思路 …...

记一次golang环境的变化

前两天编译打包了了个文件&#xff0c;把env的 goos 搞坏了 导致运行项目一直报错 先是这样 go: unsupported GOOS/GOARCH pair windows/amd64再是这样 /amd64supported GOOS/GOARCH pair linux咱就说&#xff0c;咱也是知道环境配置的有问题 &#xff08; go env GOOS &…...

web直播弹幕抓取分析 signature

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言 最近遇到太多难点了卡了很久&am…...

CVE-2024-13025-Codezips 大学管理系统 faculty.php sql 注入分析及拓展

Codezips 里面有很多cms系统&#xff0c;其中的一个College Management System In PHP With Source Code存在sql注入漏洞。 复现 对源码进行下载登录。 里面有很多远程js加载不出来但是不影响接口使用。 对于/college-mgmt-php-master/Front-end/faculty.php接口进行测试。…...

第二个Qt开发实例:在Qt中利用GPIO子系统和sysfs伪文件系统实现按钮(Push Button)点击控制GPIO口(效果为LED2灯的灭和亮)

引言 本文承接博文 https://blog.csdn.net/wenhao_ir/article/details/145420998 里的代码&#xff0c;在那里面代码的基础上添加上利用sysfs伪文件系统实现按钮(Push Button)点击控制GPIO口的代码&#xff0c;进而实现LED2灯的灭和亮。 最终的效果是点击下面的LED按钮实现LED…...