【爱上C++】详解string类2:模拟实现、深浅拷贝
在上一篇文章中我们介绍了string类的基本使用,本篇文章我们将讲解string类一些常用的模拟实现,其中有很多细小的知识点值得我们深入学习。Let’s go!
文章目录
- 类声明
- 默认成员函数
- 构造函数
- 析构函数
- 拷贝构造函数
- 深浅拷贝问题
- 传统写法
- 现代写法
- 赋值运算符重载
- 传统写法
- 现代写法
- 容器操作
- 获取长度:size
- 获取当前容量:capacity
- 查询是否为空:empty
- 扩容:reserve
- 调整字符串大小:resize
- 字符串访问
- []访问
- 迭代器访问
- 插入类
- 尾插一个字符push_back
- append
- 在尾部追加一个string对象
- 在尾部追加一个C风格字符串
- 在尾部追加n个字符
- insert
- 在pos位置插入一个字符
- 在pos位置插入一个字符串
- operator+=
- 删除类
- erase
- 其他操作
- swap
- find
- 返回字符c在string中第一次出现的位置
- 返回子串s在string中第一次出现的位置
- substr
- printf_str
- clear
- c_str
- 逻辑判断
- 流操作
- 流插入<<
- 流提取>>
- 拓展:关于string其他常用函数
- to_string
- stoi
- 完整代码展示
- .h文件
- .cpp文件
类声明
namespace Mystring
{// 定义一个字符串类class string{public:// 公共成员函数和接口private:// 私有成员变量,限制直接访问底层数据size_t _capacity = 0; // 字符串的容量size_t _size = 0; // 字符串的长度char* _str = nullptr; // 指向字符串数据的指针const static size_t npos = -1; // 静态常量,表示未找到位置或无效位置};
}
在C++中,静态成员变量(static)的定义通常需要在类的外部进行,而非静态成员变量则需要在类的内部进行定义。然而,对于静态成员变量如果其为 const 且为整数类型(包括枚举类型),则可以在类内部直接进行初始化。因此,对于 const static size_t npos = -1; 这样的声明,其允许在类内部直接进行初始化。
对于npos的初始化,下面两种方式都可以
class string {
public:static const size_t npos = -1;
};
class string {
public:static const size_t npos;
};const size_t string::npos = -1;
声明变量时可以顺便初始化,这样可以确保对象在创建时具有合适的初始值。
结构上使用了命名空间Mystring来避免与标准库中的 std::string 冲突。
本篇文章的代码采用声明与定义分离的方式。声明放在string.h文件,定义放在string.cpp文件。.cpp文件中通过包对应头文件以及声明命名空间,然后通过类名::成员的方式定义和实现函数。
默认成员函数
构造函数
声明:
string(const char*str="");
//(提供了一个缺省值表示在没有提供参数时,str 默认初始化为一个空字符串
//(即一个以 null 结尾的字符数组,其中只有一个字符 '\0'))
这里是 "“不是” "。后者不为空, 有一个空格.
定义:
string(const char* str )
{_size = strlen(str); // 计算字符串长度_capacity = _size; // 初始容量与字符串长度相同_str = new char[_capacity + 1];// 为字符串分配内存空间,多开一个空间用于存放 '\0'strcpy(_str, str); // 将参数 str 的内容拷贝到 _str 中
}
如 string::string s1("Hello");
析构函数
~string()
{delete[] _str;// 释放字符串的内存空间,使用 delete[] 因为 _str 是数组形式的字符串_size = 0; // 将字符串长度置为 0,表示字符串已经被释放_capacity = 0;// 将容量置为 0,表示容量无效_str = nullptr; // 将指向字符串数据的指针置为 nullptr,防止出现悬空指针
}
析构函数在对象被销毁时自动调用,通常用来释放对象所持有的资源,例如动态分配的内存。
**拓展:**悬空指针
是指指向已经被释放或者无效的内存地址的指针。当一个指针被赋予了nullptr
或者指向的内存已经被释放时,这个指针就变成了悬空指针。
在C++中,如果一个对象的析构函数中没有将指针设置为nullptr
,那么当对象被销毁时,其指针成员可能会成为悬空指针。悬空指针引发的问题主要有两个:
- 未定义行为(Undefined Behavior):如果试图通过悬空指针访问内存,则会导致未定义行为,这可能会导致程序崩溃或者产生难以预料的结果。
- 内存泄漏或重复释放:悬空指针可能会导致内存泄漏,因为释放过的内存没有被正确释放,或者在程序的其他地方被重新分配,导致对同一块内存的多次释放。
在编程中,为了避免悬空指针的问题,通常有以下建议:
- 析构函数中将指针置为
nullptr
:在对象被销毁时,确保将指针成员设置为nullptr
,这样可以避免在对象的生命周期结束后访问悬空指针。- 使用智能指针:C++11引入的智能指针(如
std::unique_ptr
和std::shared_ptr
)可以帮助自动管理动态内存,避免手动释放内存和悬空指针问题。- 注意指针的生命周期:确保在指针可能成为悬空指针的情况下,适时将其置为
nullptr
,或者避免在对象生命周期结束后继续使用该指针。
通过良好的编程实践和注意内存管理,可以有效避免悬空指针带来的问题,提高程序的健壮性和可靠性。
拷贝构造函数
深浅拷贝问题
如果我们不写拷贝构造函数,编译器会默认生成一个浅拷贝的拷贝构造函数。但是,默认生成的拷贝构造函数只会简单地逐成员进行赋值拷贝,这在处理指针成员变量时会导致严重问题
当使用默认的浅拷贝构造函数时,两个对象会共享同一个内存空间,会导致以下问题
- 共享内存:s1 和 s2 共享同一块内存,这意味着修改一个对象会影响另一个对象。
- 悬空指针:当 s1 或 s2 析构时,内存会被释放,另一个对象的指针会变成悬空指针。
- 双重释放:当 s1 和 s2 都析构时,会尝试释放同一块内存两次,导致程序崩溃。
为了解决浅拷贝带来的问题,我们需要实现一个深拷贝的拷贝构造函数。深拷贝会为新对象分配独立的内存空间,并将原对象的数据复制到新对象中,从而避免共享内存的问题。
传统写法
string(const string& s) {// 为新对象分配独立的内存空间,并且多分配一个字节用于存储终止符 '\0'_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 交换当前对象和临时对象的 _str 指针。std::swap(_size, s._size);//使用 std::swap 交换当前对象和临时对象的 _size 值。std::swap(_capacity, s._capacity);//使用 std::swap 交换当前对象和临时对象的 _capacity 值}//s2(s1) //下面的s 就是s1string(const string& s) :_str(nullptr),_size(0),_capacity(0){string tmp(s._str); // 注意!是构造
// 使用 s 对象的内部 C 风格字符串 _str 构造一个临时的字符串对象 tmpswap(tmp);
// 交换当前对象和临时对象的数据,使当前对象的内容变为 tmp 的内容,临时对象则被销毁}
//解析:tmp和s1有一样大的空间,一样的值。然后s2和tmp一交换,那s2就和s1一样了,就完成了。
图解: 交换前
交换后
在C++中,当我们用string s2(s1)
来创建string
对象时,s1
是用来初始化string s2
的源对象。现代写法中的string(const string& s)
构造函数会被调用来实现这一点。
理解现代写法:
在这个构造函数中,我们可以理解成:
- 调用构造函数:
- 当我们写
string s2(s1)
时,编译器调用string
类的拷贝构造函数string(const string& s)
。 - 这里的
s
就是s1
,表示用s1
对象来初始化新创建的s2
对象。
- 当我们写
- 创建临时对象:
- 在构造函数内部,首先使用
s
对象(即s1
)的内部 C 风格字符串_str
来构造一个临时对象tmp
。 string tmp(s._str)
这行代码会调用另一个构造函数string(const char* str)
,用s1
对象的字符串数据来初始化临时对象tmp
。
- 在构造函数内部,首先使用
- 交换数据:
- 调用
swap(tmp)
将当前对象(即s2
)的成员变量与临时对象tmp
的成员变量进行交换。 - 在交换之后,
s2
对象持有了tmp
的数据,即持有了s1
的数据副本,而tmp
则持有了s2
的初始数据(在这时通常为空或者默认值)。
- 调用
- 析构临时对象:
- 当构造函数结束时,临时对象
tmp
离开作用域,自动析构,释放它持有的资源。 - 由于
tmp
持有的是s2
的初始数据(在构造时通常是无效数据),所以释放时不会影响s2
,也不会造成资源泄漏。
- 当构造函数结束时,临时对象
这种现代写法通过创建临时对象和交换数据,确保了拷贝构造的简洁性和异常安全性,同时避免了资源泄漏和浅拷贝带来的问题。
❓为什么要在初始化列表中给 _str 初始化为空指针?
string(const string& s)
: _str(nullptr)
如果不对它进行处理,一开始指向的是 未定义的(随机值)。在交换之后,这个随机值就给了tmp了,tmp出了作用域后调用析构函数进行释放会对随机值指向的空间进行释放。 这种情况下,系统可能无法正确处理释放操作,从而导致程序崩溃或者其他未定义行为。
delete 或者 free 一个空指针是安全的操作,不会导致运行时错误,所以这里把它初始化为nullptr,tmp最后释放空,不会出现问题。
赋值运算符重载
传统写法
string& operator=(const string& s)
{if (this != &s) // 防止自我赋值{char* tmp = new char[s._capacity + 1]; // 为临时存储空间分配内存,大小为 s 对象的容量加一(用于存放字符串末尾的 '\0')strcpy(tmp, s._str); // 将 s 对象的字符串复制到临时存储空间 tmpdelete[] _str; // 删除当前对象已有的字符串内存_str = tmp; // 将当前对象的 _str 指向新分配的字符串内存_size = s._size; // 更新当前对象的字符串长度_capacity = s._capacity; // 更新当前对象的容量}return *this; // 返回当前对象的引用,支持连续赋值操作
}
传统写法图解:
现代写法
//s1=s3
string& operator=(string s) // 使用传值方式传入参数 s,利用了移动语义
{swap(s); // 使用交换函数进行赋值操作,此时 s 是通过拷贝构造函数传入的临时对象return *this; // 返回当前对象的引用,支持连续赋值操作
}
交换前
交换后
在 string& operator=(string s)
中使用传值传参主要有以下几个原因:
- 移动语义的利用:
- 传值传参允许编译器在需要的时候使用移动语义,这样可以避免不必要的深拷贝,提升性能。
- 如果传递的参数是右值(例如,
s1 = std::move(s3)
),则会调用移动构造函数而不是拷贝构造函数,从而避免了数据的复制。
- 简化代码:
- 通过传值传参,可以在函数体内直接交换当前对象和参数对象的数据。这使得代码更简洁,并且更容易理解和维护。
- 异常安全性:
- 传值传参结合交换操作可以确保资源的正确释放,避免资源泄漏和其他异常问题。
详细过程解释
假设我们有以下赋值操作:s1 = s3;
- 传值传参:
string s(s3); // 临时对象 s 通过拷贝构造或移动构造函数创建
- 当调用
s1 = s3;
时,会创建一个临时对象s
。这个临时对象s
是通过拷贝构造函数(如果s3
是左值)或移动构造函数(如果s3
是右值)创建的。
- 交换操作:
swap(s); // 交换 s1 和 s 的数据
- 在赋值运算符的实现中,调用
swap(s);
。这会交换当前对象s1
和临时对象s
的内部数据指针。
- 临时对象销毁:
// 临时对象 s 离开作用域,被销毁,释放旧资源
- 在赋值运算符函数结束时,临时对象
s
离开作用域并被销毁,其析构函数会释放它所持有的资源。这些资源实际上是原来属于s1
的旧资源。
- 返回当前对象:
return *this; // 返回当前对象的引用
- 返回当前对象
s1
的引用,以支持连续赋值操作。
容器操作
获取长度:size
size_t size() const
//考虑到不需要修改,我们加上 const。{return _size;}
获取当前容量:capacity
size_t capacity() const{return _capacity;}
查询是否为空:empty
bool empty() const{return _size == 0;}
扩容:reserve
void reserve(size_t n)
{// 如果请求的容量大于当前的容量,才需要重新分配内存if (n > _capacity){char* tmp = new char[n + 1]; // 分配新的内存空间,比请求的容量多一个字符// 这个额外的字符用于存放字符串结尾的空字符 '\0',确保字符串的有效性和正确性strcpy(tmp, _str); // 将原字符串内容拷贝到新内存,也会拷贝结尾的 '\0'delete[] _str; // 释放原来的内存_str = tmp; // 更新指针,使其指向新的内存_capacity = n; // 更新容量}
}
扩容扩容,所以n要≥_capacity
调整字符串大小:resize
记得用缺省值,用户在调用 resize 函数时可以选择性地提供第二个参数.
假如有一个字符串对象 str,当前大小为 5,内容为 “hello”,容量为 10。调用 str.resize(8, ‘x’) 后 就是 helloxxx\0
声明void resize(size_t, char c = '\0');
定义
void resize(size_t n, char c)
{// 如果新的大小大于当前大小,需要扩展字符串if (n > _size){// 如果新的大小大于当前容量,需要扩展内存if (n > _capacity){reserve(n); // 调用 reserve 函数扩展容量}// 将新的字符填充到扩展后的字符串中for (size_t i = _size; i < n; i++){_str[i] = c;}}else if (n < _size){// 如果新的大小小于当前大小,只需更新大小_size = n; // 注意:此时容量不会改变}// 更新字符串的实际大小,并确保字符串以空字符结尾_str[_size] = '\0';
}
缩容就直接在下标为n的位置设置为\0即可。
字符串访问
[]访问
//仅能访问const char& operator[](size_t pos) const{assert(pos < _size);//assert 括号里为假的时候才会报错return _str[pos];}//访问+修改char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}
迭代器访问
迭代器在 C++ 中常常被描述为类似指针的对象,它提供了对容器(比如字符串)中元素的访问和操作。
对于模拟实现的字符串类,我们可以直接使用原生指针来作为迭代器,通过 typedef 进行重命名,这样就可以在类中直接使用迭代器。
首先,我们使用 typedef 将指针重命名为迭代器,同时定义了常量迭代器:typedef char* iterator;
typedef const char* const_iterator;
// 返回字符串的起始位置iterator begin() {return _str;}// 返回字符串的结束位置'\0' 的下一个位置(即 null 字符的位置)iterator end() {return _str + _size;}// 返回字符串的起始位置(const 版本,不能修改数据)
//常量成员函数
//const_iterator begin() const 和 const_iterator end() const 被声明为常量成员函数。
//这意味着它们不会修改对象的任何成员变量,并且它们可以被常量对象调用。//为什么需要最右边的const???
//如果没有最后的 const 修饰符,编译器将认为 begin() 和 end() 可能会修改对象。因此,当你试图在一个常量对象上调用这些函数时,会产生编译错误,因为编译器不允许通过常量对象调用非常量成员函数。const_iterator begin() const {return _str;}// 返回字符串的结束位置的下一个位置(const 版本)const_iterator end() const {return _str + _size;}
这些函数使得我们可以像操作指针一样操作迭代器,比如使用 ++ 和 – 来移动迭代器指向的位置,或者使用 * 来访问迭代器指向的元素。这样,我们就可以通过迭代器来遍历字符串中的字符了。
插入类
尾插一个字符push_back
void push_back(char ch){if (_size == _capacity)//大小和总容量一样的时候,说明不够用了{size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}_str[_size] = ch;_size++;_str[_size] = '\0';//一定要处理好\0}
append
在尾部追加一个string对象
string& append(const string& str)
{// 检查是否需要扩展容量if (str._size >= _capacity - _size) // 判断是否需要扩容{reserve(_capacity + str._size + 1); // 扩容并预留足够空间}// 复制传入的字符串到当前字符串的末尾strcpy(_str + _size, str._str); // _str + _size 是当前字符串尾部// 更新当前字符串的大小_size += str._size; // 更新_size// 手动设置字符串的结尾_str[_size] = '\0'; // 手动设置字符串尾部的\0// 返回当前对象的引用return *this; // 返回string对象
}
在尾部追加一个C风格字符串
void append(const char* str) //注意传的是指针{ size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//char *strcpy(char *dest, const char *src); strcat(_str,str)也行,但是效率不行strcpy(_str + _size, str);_size += len;}
在尾部追加n个字符
void append(size_t n, char ch){// 检查是否需要扩展容量if (_size + n > _capacity){reserve(_size + n); // 扩展容量以容纳新字符}// 将字符 ch 追加 n 次到字符串末尾for (size_t i = 0; i < n; i++){_str[_size + i] = ch;}// 更新字符串的大小_size += n;// 确保字符串以 '\0' 结尾_str[_size] = '\0';}
注意:_size和_capacity是不计算\0的
insert
在pos位置插入一个字符
在 C++ 中,通常情况下,字符串的位置索引 pos 是从 0 开始的,即第一个字符的位置为 0,第二个为 1,依此类推。这种习惯是因为 C++ 中的数组和字符串的索引都是从 0 开始计数的。
_str 表示字符串的起始位置,即第一个字符的地址。
_str + 1 表示字符串中第二个字符的地址。
_str + pos 表示字符串中第 pos 个位置的地址,即要进行插入或其他操作的位置。
void insert(size_t pos, char ch)
{// 确保插入位置在有效范围内assert(pos <= _size); // pos 等于 _size 时表示尾插// 检查是否需要扩展容量if (_size == _capacity) // 大小和总容量一样的时候,说明不够用了{size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2; // 扩展容量,最小扩展到 4reserve(newCapacity); // 调用 reserve 函数扩展容量}// 从后往前 移动数据以腾出插入位置int end = _size;while (end >= (int)pos) // 循环直到 end 小于 pos{_str[end + 1] = _str[end]; // 将当前位置的数据向后移动一位--end; // end 减 1}// 在指定位置插入新字符_str[pos] = ch; // 在 pos 位置插入字符 ch_size++; // 更新大小_str[_size] = '\0'; // 确保字符串以 '\0' 结尾
}
为什么 while (end >= (int)pos) 要强制转换成int类型呢?
因为end 是一个 size_t 类型的变量,这是一个无符号整数类型。pos 也是 size_t 类型。如果直接比较 end 和 pos,即使 end 被减到负值,由于 size_t 是无符号类型,负值会被当成一个非常大的正整数。这可能会导致无限循环和访问越界。
通过将 pos 强制转换为 int,确保 end 和 pos 在比较时都是有符号整数类型,从而避免了无符号整数类型转换的问题。这种做法保证了在 end 小于 pos 时,循环能正确退出。
在pos位置插入一个字符串
void insert(size_t pos, const char* str)
{// 确保插入位置在当前字符串长度范围内assert(pos <= _size);// 计算要插入字符串的长度size_t len = strlen(str);// 如果当前容量不足以容纳插入后的新字符串,则增加容量if (_size + len > _capacity){reserve(_size + len); // 调用 reserve 函数扩展容量}// 使用有符号整数类型的 end 变量,以避免无符号整数类型带来的潜在问题int end = _size;// 从字符串末尾向前移动字符,以腾出插入位置while (end >= (int)pos){_str[end + len] = _str[end]; // 将当前位置的数据向后移动 len 位--end; // end 减 1}// 将新的字符串插入到指定位置strncpy(_str + pos, str, len); // 使用 strncpy 复制字符串内容,但不包括末尾的 '\0'// 更新字符串的大小_size += len; // 新字符串的长度增加_str[_size] = '\0'; // 确保字符串以 '\0' 结尾
}
operator+=
string& operator+=(char ch)//+=一个字符{push_back(ch);return *this;}string& operator+=(const char* str)//+=一个 char* 字符串{append(str);return *this;}string& operator+= (const string& str) //+=一个string对象{append(str);return *this;}
删除类
erase
从pos位置开始,删除长度为len的字符串。若未给出len,则默认删完.void erase(size_t pos, size_t len = npos);
void erase(size_t pos,size_t len) //pos 是下标,删除1个就是pos位置的那个{//assert(pos <_size);// xxxx size=4,//assert(_size > 0);assert(pos < _size); // 这里不需要检查 pos >= 0,因为 pos 是无符号类型if (len == npos||pos+len>=_size)//要删完{//但我们不用删,直接缩大小,_str[pos] = '\0';_size = pos;}else {//后面数据挪过去覆盖// hello,wordl// ↑ ↑:pos+len// pos 删3个strcpy(_str + pos, _str + pos + len);_size -= len;//覆盖之后减少_size即可}//"abcdefghi"。假如pos是3,len是4。pos是下标//_str 指向字符串的第一个字符,即 'a'。//_str + pos 指向字符串的第 4 个字符,即 'd'。//_str + pos + len 指向字符串的第 8 个字符,即 'h'。}
其他操作
swap
尽管标准库中的 std::swap 可以用于交换两个对象,但是它仅在你提供的交换操作对你特定类的成员变量的交换上不能直接进行。
标准库的 std::swap 无法直接处理类的私有成员变量的交换,而必须通过类提供的接口进行交换操作。
所以 自定义类型要自己写,上面的拷贝构造和赋值重载的现代写法都用到了此处的swap函数
void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
find
返回字符c在string中第一次出现的位置
size_t find(char ch,size_t pos=0)
size_t find(char ch,size_t pos)//半缺省{for (size_t i =pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}
返回子串s在string中第一次出现的位置
size_t find(const char* str,size_t pos=0);
size_t find(const char* str, size_t pos )
{// 使用 strstr 函数从 _str + pos 位置开始查找子字符串 strconst char* ptr = strstr(_str + pos, str);// 如果 ptr 为空,表示没有找到子字符串if (ptr == nullptr){// 返回 npos 表示查找失败return npos;}else{// 返回子字符串在字符串中的起始位置return ptr - _str;}
}
strstr 是 C 语言标准库 (或 <string.h>)中的函数,用于在一个字符串中查找第一次出现另一个字符串的位置。
constchar* strstr(constchar* str1, constchar* str2);
- str1:要在其中搜索的主字符串。
- str2:要搜索的子字符串。
- 如果 str2 是 str1 的子串,则返回指向 str1 中第一次出现 str2 的位置的指针。
- 如果 str2 不是 str1 的子串,则返回 nullptr。
substr
从当前字符串中提取子串。 string substr(size_t pos = 0, size_t len = npos);
string substr(size_t pos , size_t len ){assert(pos < _size);size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; i++){str += _str[i];}return str;//返回str的拷贝}
printf_str
打印C风格字符串。C风格字符串是以空字符 ‘\0’ 结尾的字符数组
void printf_str(const string& s)//权限放大,上面的末尾要加const
//在C++中,成员函数末尾的const关键字用于指示该函数不会修改对象的状态。这种函数被称为const成员函数,它们对于保证对象的不可变性非常重要。
//c_str() 和 size() 都是访问器函数,它们不会修改字符串对象的内容,因此应该声明为const成员函数以确保它们可以在const对象上调用。
//这样做不仅符合面向对象的设计理念,还允许用户在const对象上调用这些函数,以便于在const上下文中使用你的类。
//而对于const char& operator[](size_t pos) const,它是一个重载的下标运算符,用于访问字符串中指定位置的字符。由于该函数不会修改对象的内容,因此也应该声明为const成员函数。{for (size_t i = 0; i < s.size(); i++){// s[i]++; 参数加const 就是为了防止这里进行修改。cout << s[i] << " ";}cout << endl;}
clear
清空当前字符串对象,使其变为空字符串
void clear(){_size = 0;_str[0] = '\0';//}
c_str
获取字符串源指针
有些场景下,例如使用C语言的字符串操作函数,处理字符串时只能使用char*指针去传参,string为了兼容C字符串操作函数,支持获取字符串源指针,为了不破坏string的数据结构,这个返回的源字符串指针不支持修改,只能访问内容!
这个函数非常短小,直接在类中实现!
const char* c_str() const {assert(_str);return _str;}
逻辑判断
实现了小于和等于,其他的直接复用.
都被声明为 const 成员函数。 const 关键字的作用是告诉编译器这些成员函数不会修改类的成员变量 _str 和 _size。
在 C++ 中,类的 const 成员函数可以确保在函数内部不会修改对象的任何成员变量,从而提供了对调用者的额外保证。这样的设计有助于代码的可维护性和可理解性。
如果没有将比较操作符声明为 const,则无法在常量对象上调用这些操作符,因为常量对象只能调用 const 成员函数。例如,对于声明为 const 的对象或者在常量上下文中使用的对象(如 const String s1, s2;),可以正常地执行比较操作。
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) || (*this == s);
}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);
}
流操作
当我们在 C++ 中定义流插入运算符 << 和流提取运算符 >> 时,如果将它们定义为类的成员函数,会遇到一个问题:类的成员函数默认会有一个隐含的 this 指针作为第一个参数。这样的话,如果我们试图将 operator<< 或 operator>> 定义为成员函数,形式上会与预期不符,因为它们需要接受两个参数(左操作数和右操作数),而类成员函数形式下只能接受一个参数(除非将其定义为静态成员函数,但这不符合重载运算符的惯用方式)。
因此,为了正确地重载这些运算符,我们将它们定义为类的友元函数。友元函数可以在不通过对象接口(即不使用 this 指针)的情况下访问类的私有成员和受保护成员。这种做法不仅符合语法要求,还能保持类的封装性和安全性,因为只有特定的函数(即声明为友元的函数)才能直接访问类的私有部分。
流插入<<
ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;//返回ostream对象 以支持cout<<s1<<s2<<s3}
流提取>>
istream& operator >>(istream& in, string& s){s.clear(); // 清空当前字符串,以免变成尾插了char buff[128] = {0}; // 创建一个缓冲区用于暂存读取的字符序列char ch = in.get(); // 从输入流中读取一个字符int i = 0; // 初始化缓冲区索引// 循环读取字符直到遇到空格或换行符while (ch != ' ' && ch != '\n'){buff[i++] = ch; // 将读取的字符存储到缓冲区中if (i == 127) // 如果缓冲区即将满了{buff[i] = '\0'; // 在缓冲区末尾添加字符串终止符s += buff; // 将缓冲区中的字符序列插入到字符串对象中i = 0; // 重置缓冲区索引}ch = in.get(); // 读取下一个字符}// 处理剩余的字符序列if (i > 0){buff[i] = '\0'; // 在缓冲区末尾添加字符串终止符s += buff; // 将缓冲区中的字符序列插入到字符串对象中}return in; // 返回输入流对象的引用}
拓展:关于string其他常用函数
to_string
to_string 是 C++ 中的一个标准库函数,用于将各种类型的数据转换为对应的字符串表示形式。
头文件:#include<string>
语法:std::string to_string(类型 value);
类型可以是整数、浮点数。value: 要转换为字符串的数值。 返回转换后的 std::string 类型对象,表示数值的字符串形式。
stoi
stoi 是 C++ 中的一个标准库函数,用于将字符串转换为对应的整数类型。
头文件:#include<string>
int stoi(const std::string& str, size_t* pos = 0, int base = 10);
- str: 要转换的字符串。
- pos (可选): 指向 size_t 类型的指针,用于存储第一个无效字符的索引。
- base (可选): 数字的基数,默认为 10。
返回:
- 返回转换后的整数值。
完整代码展示
.h文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<iostream>
using namespace std;
namespace Mystring
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}size_t size() const{return _size;}size_t capacity(){return _capacity;}bool empty() const{return _size == 0;}void printf_str(const string& s){for (size_t i = 0; i < s.size(); i++){cout << s[i] << " ";}cout << endl;}void clear(){_size = 0;_str[0] = '\0';//}const char* c_str() const{assert(_str);return _str;}string(const char* str = "");~string();string(const string& s);string& operator=(string s);void reserve(size_t n);void resize(size_t, char c = '\0');const char& operator[](size_t pos) const;char& operator[](size_t pos);void push_back(char ch);void append(const char* str);void append(size_t n, char ch);string& append(const string& str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);string& operator+=(const char* str);string& operator+=(char ch);string& operator+= (const string& str);void erase(size_t pos, size_t len = npos);void swap(string& s);size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);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) || (*this == s);}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);}private:size_t _capacity = 0;size_t _size = 0;char* _str = nullptr;const static size_t npos = -1;};istream& operator>>(istream& in, string& s);ostream& operator<<(ostream& out, const string& s);
}
.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1
//这个是声明和定义分离的版本
#include"string16.h"namespace Mystring
{//构造函数string::string(const char* str){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//析构函数string::~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//拷贝构造 现代写法string::string(const string& s){string tmp(s._str);swap(tmp);}//运算符重载string& string::operator=(string s){swap(s);return *this;}//仅能访问const char& string::operator[](size_t pos) const{assert(pos < _size);//assert 括号里为假的时候才会报错return _str[pos];}//访问+修改char& string::operator[](size_t pos){assert(pos <= _size);return _str[pos];}void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void string::resize(size_t n, char c){if (n > _size){if (n > _capacity){reserve(n); }for (size_t i = _size; i < n; i++){_str[i] = c;}}else if (n < _size){_size = n;}_str[_size] = '\0';}void string::push_back(char ch){if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& string::append(const string& str){// 检查是否需要扩展容量if (str._size >= _capacity - _size) // 判断是否需要扩容{reserve(_capacity + str._size + 1); // 扩容并预留足够空间}// 复制传入的字符串到当前字符串的末尾strcpy(_str + _size, str._str); // _str + _size 是当前字符串尾部// 更新当前字符串的大小_size += str._size; // 更新_size// 手动设置字符串的结尾_str[_size] = '\0'; // 手动设置字符串尾部的\0// 返回当前对象的引用return *this; // 返回string对象}void string::append(size_t n, char ch){// 检查是否需要扩展容量if (_size + n > _capacity){reserve(_size + n); // 扩展容量以容纳新字符}// 将字符 ch 追加 n 次到字符串末尾for (size_t i = 0; i < n; i++){_str[_size + i] = ch;}// 更新字符串的大小_size += n;// 确保字符串以 '\0' 结尾_str[_size] = '\0';}void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;}void string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}size_t string::find(char ch, size_t pos){for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t string::find(const char* str, size_t pos){const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}string string::substr(size_t pos, size_t len){assert(pos < _size);size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; i++){str += _str[i];}return str;}ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[128] = { 0 };char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}
- 📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
- 本人也很想知道这些错误,恳望读者批评指正!
相关文章:
![](https://img-blog.csdnimg.cn/img_convert/8a092bff140e4ebae41ec6dda08fb4f2.png)
【爱上C++】详解string类2:模拟实现、深浅拷贝
在上一篇文章中我们介绍了string类的基本使用,本篇文章我们将讲解string类一些常用的模拟实现,其中有很多细小的知识点值得我们深入学习。Let’s go! 文章目录 类声明默认成员函数构造函数析构函数拷贝构造函数深浅拷贝问题传统写法现代写法…...
![](https://www.ngui.cc/images/no-images.jpg)
狄克斯特拉算法
狄克斯特拉算法(Dijkstra’s algorithm)是一种用于在带权图中找到从单一源点到所有其他顶点的最短路径的算法。它适用于处理带有非负权值的图。 下面将详细解释算法的工作原理、时间复杂度以及如何通过优化数据结构来改进其性能。 狄克斯特拉算法的工作…...
![](https://img-blog.csdnimg.cn/direct/b996904cdef04f44aff1abb33484a633.jpeg)
2024推荐整理几个磁力导航网站可提供海量资源的
都2024现在网上找资源像流水得鱼一样,抓一大把结果很难吃,我通宵特意整理的网站,网上有许多磁力导航网站可以提供海量的磁力链接资源,以下是一些有效的磁力导航网站推荐: 磁力搜索 链接: 资源类型&#x…...
![](https://www.ngui.cc/images/no-images.jpg)
链式访问:C语言中的函数调用技巧
链式访问:C语言中的函数调用技巧 在C语言编程中,链式访问(chained calls)是一个常见的编程技巧,它允许你在一行代码中连续调用多个函数或方法。这种技巧不仅能够让代码更加简洁和易读,还能减少临时变量的使…...
![](https://www.ngui.cc/images/no-images.jpg)
数据库设计(实战项目)-1个手机号多用户身份
一. 背景: 该需求是一个互联网医院的预约单场景,护士在小程序上申请患者查房预约单,医生在小程序上对预约单进行接单,护士开始查房后填写查房小结,客户需要对用户信息进行授权,医生查房后进行签字ÿ…...
![](https://i-blog.csdnimg.cn/direct/7eaedef26b134768b5877f949f5c1169.png)
vue+fineReport 使用前端搜索+报表显示数据
--fineReprot 将需要搜索的参数添加到模版参数 sql: --前端传递参数 注:因为每次点击搜索的结果需要不一样,还要传递一个时间戳的参数: let timesamp new Date().getTime()...
![](https://www.ngui.cc/images/no-images.jpg)
高阶面试-存储系统的设计
概述 分类 块存储 block storage文件存储 file storage对象存储 object storage 区别: 块存储 概述 位于最底层,块,是物理存储设备上数据存储的最小单位。硬盘(Hard Disk Drive,HDD)就属于块存储。常见的还有固态硬盘(SSD)、…...
![](https://img-blog.csdnimg.cn/img_convert/da17fdbd845db39aa5548ff640e8c146.png)
柔性测斜仪:土木工程与地质监测的得力助手
在现代土木工程和地质工程领域,精确监测土壤和岩石的位移情况对于确保工程安全至关重要。柔性测斜仪作为一种高精度、稳定性和灵活性兼备的测量设备,已逐渐成为工程师和研究人员的得力助手。本文将深入探讨柔性测斜仪在多个关键领域的应用及其重要性。 点…...
![](https://www.ngui.cc/images/no-images.jpg)
数字资产和数据资产你真的了解吗?
数据作为新型生产要素,是数字化、网络化、智能化的基础,已快速融入生产、分配、流通、消费和社会服务管理等各环节,深刻改变着生产方式、生活方式和社会治理方式。 何为数据资产?即由个人或企业拥有或控制的,能为企业带…...
![](https://img-blog.csdnimg.cn/direct/dce2f17947364053b44296afc0661c59.png)
【每日一练】python运算符
1. 算术运算符 编写一个Python程序,要求用户输入两个数,并执行以下运算:加法、减法、乘法、求余、除法、以及第一个数的第二个数次方。将结果打印出来。 a input("请输入第一个数:") b input("请输入第二个数&…...
![](https://img-blog.csdnimg.cn/direct/1eaf67aa26374b9a94ed9aba3280a483.png)
CesiumJS【Basic】- #032 绘制虚线(Primitive方式)
文章目录 绘制虚线(Primitive方式)1 目标2 代码2.1 main.ts绘制虚线(Primitive方式) 1 目标 使用Primitive方式绘制虚线 2 代码 2.1 main.ts // 定义线条的起点和终点var start = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)...
![](https://img-blog.csdnimg.cn/direct/302c0a534f86495b9023a2374c9e3fba.png)
海尔智家:科技优秀是一种习惯
海尔智家:科技优秀是一种习惯 2024-06-28 15:19代锡海 6月24日,2023年度国家科学技术奖正式揭晓。海尔智家“温湿氧磁多维精准控制家用保鲜电器技术创新与产业化”项目荣获国家科学技术进步奖,成为家电行业唯一牵头获奖企业。 很多人说&…...
![](https://img-blog.csdnimg.cn/direct/b3f5e47c620b4f968487538803f7ccc8.png)
【Android】实现图片和视频混合轮播(无限循环、视频自动播放)
目录 前言一、实现效果二、具体实现1. 导入依赖2. 布局3. Banner基础配置4. Banner无限循环机制5. 轮播适配器6. 视频播放处理7. 完整源码 总结 前言 我们日常的需求基本上都是图片的轮播,而在一些特殊需求,例如用于展览的的数据大屏,又想展…...
![](https://www.ngui.cc/images/no-images.jpg)
VLAN基础
一、什么是Vlan VLAN(Virtual Local Area Network)是虚拟局域网的简称,是一种将单一物理局域网(LAN)在逻辑层面上划分为多个独立的广播域的技术。每个VLAN都是一个独立的广播域,其内部主机可以直接通信&am…...
![](https://img-blog.csdnimg.cn/img_convert/d87a3ecb2fbdcc7d33844c9a7a17b1d5.png)
pytest-yaml-sanmu(五):跳过执行和预期失败
除了手动注册标记之外,pytest 还内置了一些标记可直接使用,每种内置标记都会用例带来不同的特殊效果,本文先介绍 3 种。 1. skip skip 标记通常用于忽略暂时无法执行,或不需要执行的用例。 pytest 在执行用例时,如果…...
![](https://www.ngui.cc/images/no-images.jpg)
linux指令整合(centos系统持续更新中。。。)
1、查询java进程 ps -ef|grep java 2、查询端口占用 lsof -i:端口号 3、 启动java程序 java -jar jar包路径 后台启动 nohup java -jar jar包路径 -Xms512m -Xmx512m > 日志路径 2>&1 & 4、查看服务器资源占用 top 5、关闭进程 kill -9 进程号...
![](https://img-blog.csdnimg.cn/direct/16a6340c84f645efb5b91a7447ebd66a.png)
个人开发实现AI套壳网站快速搭建(Vue+elementUI+SpringBoot)
目录 一、效果展示 二、项目概述 三、手把手快速搭建实现本项目 3.1 前端实现 3.2 后端方向 五、后续开发计划 一、效果展示 默认展示 一般对话展示: 代码对话展示: 二、项目概述 本项目是一个基于Web的智能对话服务平台,通过后端与第…...
![](https://img-blog.csdnimg.cn/direct/7f877b18ec2f42479f81b2914a658720.png)
Cesium与Three相机同步(3)
Cesium与Three融合的案例demo <!DOCTYPE html> <html lang"en" class"dark"><head><meta charset"UTF-8"><link rel"icon" href"/favicon.ico"><meta name"viewport" content&q…...
![](https://www.ngui.cc/images/no-images.jpg)
PMP考试报名项目经历怎么填写?指引请收好
PMP,这一全球公认的项目管理金牌认证,不仅是对项目管理能力的认可,更是职业生涯中的一大助力。然而,在报名PMP时,很多小伙伴都面临一个共同的难题:如何书写项目经验?今天,就让我们一…...
![](https://www.ngui.cc/images/no-images.jpg)
Git的基本使用方法
Git的基本使用方法 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨Git的基本使用方法,Git作为目前最流行的版本控制系统之一&…...
![](https://www.ngui.cc/images/no-images.jpg)
深入剖析 @Autowired 和 @Resource 在 Spring 中的区别
在 Spring 框架中,Autowired 和 Resource 是两个常用的注解,用于实现依赖注入。尽管它们都能达到将依赖对象注入到目标 bean 的目的,但在细节上存在一些显著的差异。本文将深入探讨这两个注解的区别,并结合 Spring 源码进行分析&a…...
![](https://img-blog.csdnimg.cn/img_convert/af778a773c356602e7b9b6081ef46e27.png)
Golang-slice理解
slice golang-slice语雀笔记整理 slicego为何设计slice?引用传递实现扩容机制 go为何设计slice? 切片对标其他语言的动态数组,底层通过数组实现,可以说是对数组的抽象,底层的内存是连续分配的所以效率高,可…...
![](https://img-blog.csdnimg.cn/direct/eec8d53bf1e34b599df7a69e1db053a1.png)
【Linux系统】文件描述符fd
1.回顾一下文件 我们之前对文件的理解是在语言层上,而语言层去理解文件是不可能的!!! 下面是一份c语言文件操作代码!!! #include<stdio.h> int main() {FILE* fd fopen("lo…...
![](https://www.ngui.cc/images/no-images.jpg)
【嵌入式——FreeRTOS】启动任务调度器
【嵌入式——FreeRTOS】启动任务调度器 开启任务调度器vTaskStartScheduler()xPortStartScheduler()prvStartFirstTask()启动第一个任务 开启任务调度器 用于启动任务调度器,任务调度器启动后,FreeRTOS便会开始进行任务调度。 //启动任务,开…...
![](https://www.ngui.cc/images/no-images.jpg)
EFCore_客户端评估与服务端评估
定义 客户端评估: 先将表的所有数据读取至内存,再在内存中对数据进行筛选,数据的筛选工作在客户端服务端评估: 先将代码翻译为SQL语句,再执行SQL语句对数据进行筛选,数据的筛选工作在服务端(默认方式) 如何…...
![](https://img-blog.csdnimg.cn/direct/ad7bfe81622e45bfb1337095fdfe9140.png)
Java面试题--JVM大厂篇之深入了解G1 GC:高并发、响应时间敏感应用的最佳选择
引言: 在现代Java应用的性能优化中,垃圾回收器(GC)的选择至关重要。对于高并发、响应时间敏感的应用而言,G1 GC(Garbage-First Garbage Collector)无疑是一个强大的工具。本文将深入探讨G1 GC适…...
![](https://img-blog.csdnimg.cn/direct/931e3e36e13446008323a13d88b734fb.png)
SAP配置发布WebService接口并调用(超级详细)
文章目录 前言一、案例介绍/笔者需求二、WebService是什么? a.传输协议 b.数据协议 c.WSDL d.UDDI 三、WebService 和 WebApi 的区别以及优缺点 a.主要区别 b.优缺点 四、SAP如何发布一个webser…...
![](https://www.ngui.cc/images/no-images.jpg)
中英双语介绍美国首都:华盛顿哥伦比亚特区(Washington, D.C.)
中文版 华盛顿哥伦比亚特区(Washington, D.C.),简称华盛顿或D.C.,是美国的首都和联邦直辖区。以下是对华盛顿哥伦比亚特区各方面的详细介绍: 人口 截至2020年,美国人口普查数据显示,华盛顿哥…...
![](https://www.ngui.cc/images/no-images.jpg)
java:aocache的单实例缓存(一)
上一篇博客《java:aocache:基于aspectJ实现的方法缓存工具》介绍了aocache的基本使用, 介绍AoCacheable注解时说过,AoCacheable可以定义在构造方法上,定义在构造方法,该构建方法就成了单实例模式。 也就是说,只要构建…...
![](https://img-blog.csdnimg.cn/img_convert/71a47142d9142ee3d4d623b58a01ae1d.png)
pcap包常见拆分方法
文章目录 Wireshark 拆分流量包SplitCap使用简介魔数报错示例结果 在进行流量分析时,经常需要分析pcap流量包。但是体积过大的流量包不容易直接分析,经常需要按照一定的规则把它拆分成小的数据包。 这里统一选择cic数据集里的Thursday-WorkingHours.pcap…...
![](https://img-blog.csdnimg.cn/direct/c336abcff45c47cea0a488f3264fcffa.png)
C++中的类型转换操作符:static_cast reinterpret_cast const_cast dynamic_cast
目录 C语言中的类型转换 C中的类型转换 C中的类型转换操作符 static_cast reinterpret_cast const_cast volatile关键字 赋值兼容 dynamic_cast C语言中的类型转换 基本概念:赋值运算符左右两侧类型不同,或形参与实参类型不匹配…...
![](https://www.ngui.cc/images/no-images.jpg)
MySQL-SQL优化Explain命令以及参数详解
前言 在MySQL优化的众多手段中,EXPLAIN命令扮演着至关重要的角色。它是数据库管理员和开发者手中的利器,用于分析SQL查询的执行计划。通过执行EXPLAIN,MySQL会提供一份详细的查询执行计划报告,这份报告揭示了查询将如何执行&…...
![](https://img-blog.csdnimg.cn/direct/c7a3046e38764e97ae60cd0ea5a88b02.png)
别只会重启了!进来告诉你AP无法上线怎么办
号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 你们好,我的网工朋友。 作为网工,咱们都知道无线网络的重要性,尤其是对于企业网络来说,无线接入点…...
![](https://i-blog.csdnimg.cn/direct/4e730fe090c44976bf52e44180216b85.png)
数据恢复篇:如何在 Android 手机上恢复未保存/删除的 Word 文档
在 Android 手机上访问 Word 文档通常很简单,但是当这些重要文件被删除或未保存时会发生什么?这种情况虽然令人痛苦,但并非毫无希望。到 2024 年,有几种强大的方法来处理此类数据丢失。本指南重点介绍如何在Android手机上恢复已删…...
![](https://img-blog.csdnimg.cn/direct/b58cb062d762436d892213f055786d43.png)
Python | Leetcode Python题解之第208题实现Trie(前缀树)
题目: 题解: class Trie:def __init__(self):self.children [None] * 26self.isEnd Falsedef searchPrefix(self, prefix: str) -> "Trie":node selffor ch in prefix:ch ord(ch) - ord("a")if not node.children[ch]:retur…...
![](https://img-blog.csdnimg.cn/direct/4ed13ce8f5ab49a280f3262909268ef1.png)
Ethernet是以太网通讯
...
![](https://img-blog.csdnimg.cn/img_convert/6e558ab71ae9f466faeddd2e1d187ec3.png)
咖啡消费旺季到来 为何想转让的库迪联营商却越来越多
文 | 智能相对论 作者 | 霖霖 去年还在朝“三年万店”计划狂奔的库迪,今年已出现明显“失速”。 早在今年2月,库迪就官宣其门店数已超过7000家,如今4个多月过去,据极海品牌监测数据显示,截至6月27日,其总…...
![](https://img-blog.csdnimg.cn/75e85eac459d4ce4bc62303bf867abc5.jpg)
神经网络原理
神经网络原理是一种模拟人脑的机器学习技术,通过大量的神经元和层次化的连接进行信息处理和学习。 图1 神经元 神经网络由许多简单的计算单元或“神经元”组成,这些神经元通过连接传递信息。每个连接都有一个权重,用于调整传递的信号强度。这…...
![](https://img-blog.csdnimg.cn/direct/a94943da8d784fbfa7f55e2974626a09.jpeg)
安卓应用开发学习:获取经纬度及地理位置描述信息
前段时间,我在学习鸿蒙应用开发的过程中,在鸿蒙系统的手机上实现了获取经纬度及地理位置描述信息(鸿蒙应用开发学习:手机位置信息进阶,从经纬度数据获取地理位置描述信息)。反而学习时间更长的安卓应用开发…...
![](https://img-blog.csdnimg.cn/img_convert/3968fa658c19f4b35a92ef2d9d6d86db.png)
各类排序方法 手撕快排 回顾经典快排 优化版快排
快排的主要思想是分而治之 第一步,确定分界点,a 第二步,调整区间,利用分界点a,把小于分界点a的数放在左边,大于的放在右边,相等的放在哪都可以 第三步,递归处理左右两段 实现(暴…...
![](https://img-blog.csdnimg.cn/direct/0e3beda5d4514881ab3b9250da14f1c7.jpeg#pic_center)
独一无二的设计模式——单例模式(Java实现)
1. 引言 亲爱的读者们,欢迎来到我们的设计模式专题,今天的讲解的设计模式,还是单例模式哦!上次讲解的单例模式是基于Python实现(独一无二的设计模式——单例模式(python实现))的&am…...
![](https://www.ngui.cc/images/no-images.jpg)
使用MoA(Mixture of Agents)混合智能体技术,结合多个开源大语言模型如Llama3、phi-3和Mistral,实现一个强大的AI智能体
1.简介 论文简介: 论文提出了一种称为混合智能体(Mixture-of-Agents,MoA)的方法,利用多个大语言模型(LLM)的集体智慧来提高自然语言理解和生成任务的性能。 MoA采用了分层结构,每一层包含多个LLM智能体。每个智能体都将前一层所有智能体的输出作为辅助信息来生成自己的回答。通…...
![](https://www.ngui.cc/images/no-images.jpg)
前端面试题_Css
一、说一下Css的盒子模型? HTML中所有元素都可以看成是一个盒子 盒子的组成:content、padding、border、margin 盒子的类型: 标准盒模型:marginborderpaddingcontent -- box-sizing:content-box(默认&a…...
![](https://img-blog.csdnimg.cn/direct/0c17824a4f274ff59d0c16feb1991b8d.png)
AI在线免费视频工具3:声音生视频
1、声音生视频 Noisee:通过声音生成对应视频,可以增加prompt指定生成内容相关视频 https://noisee.ai/create...
![](https://www.ngui.cc/images/no-images.jpg)
final、const、readonly关键字在不同语言中代表着什么
一、Java 1.被final修饰的类不能被继承。 2.被final修饰的方法不能被重写。 被 final 修饰的类中所有的成员方法都会隐式的定义为 final 方法。 若父类中 final 方法的访问权限为 private ,则子类中不能直接继承该方法。此时可以在子类中定义相同方法名的函数&…...
![](https://img-blog.csdnimg.cn/direct/2b0aabb2ff1f4b0b8eb4133ae94484d0.png)
HarmonyOS ArkUi Tabs+TabContent+List实现tab吸顶功能
Demo效果 Entry Component struct StickyNestedScroll {State message: string Hello WorldState arr: number[] []scroller new Scroller()StyleslistCard() {.backgroundColor(Color.White).height(72).width("100%").borderRadius(12)}build() {Scroll(this.sc…...
![](https://img-blog.csdnimg.cn/img_convert/cdbb25770a3c65bbb3f87990fd626d09.png)
Hugging Face Accelerate 两个后端的故事:FSDP 与 DeepSpeed
社区中有两个流行的零冗余优化器 (Zero Redundancy Optimizer,ZeRO)算法实现,一个来自DeepSpeed,另一个来自PyTorch。Hugging FaceAccelerate对这两者都进行了集成并通过接口暴露出来,以供最终用户在训练/微调模型时自主选择其中之…...
![](https://www.ngui.cc/images/no-images.jpg)
TextField是用于在用户界面中输入文本的控件。它广泛应用于表单、搜索框、评论区等需要用户输入文字的场景
TextField是用于在用户界面中输入文本的控件。它广泛应用于表单、搜索框、评论区等需要用户输入文字的场景。以下是对TextField的详细解释,涵盖其各个方面的功能和属性。 基本属性 text 描述:TextField中当前显示的文本。用法:text: "示…...
![](https://img-blog.csdnimg.cn/direct/0c53ade432ae4bbb8a79ca6d0a0912a4.png)
MYSQL 四、mysql进阶 5(InnoDB数据存储结构)
一、数据库的存储结构:页 索引结构给我们提供了高效的索引方式,不过索引信息以及数据记录都是保存在文件上的,确切说时存储在页结构中,另一方面,索引是在存储引擎中实现的,Mysql服务器上的存储引擎负责对表…...
![](https://img-blog.csdnimg.cn/direct/02c652d3cd06460f8a6cf23e476ed41b.png)
Spring企业开发核心框架-下
五、Spring AOP面向切面编程 1、场景设定和问题复现 ①准备AOP项目 项目名:Spring-aop-annotation ②声明接口 /*** - * / 运算的标准接口!*/ public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, in…...
![](https://www.ngui.cc/images/no-images.jpg)
redis的数据类型对应的使用场景
Redis提供了多种数据类型,每种数据类型都有其特定的适用场景。以下是Redis主要数据类型及其典型应用场景:1. 字符串(String) 应用场景:适用于存储简单的键值对数据,如用户基本信息、计数器(如网页访问次数&…...
![](https://img-blog.csdnimg.cn/direct/b4fcc393b59a40efb05605b972e18e19.png)
【机器学习】在【Pycharm】中的应用:【线性回归模型】进行【房价预测】
专栏:机器学习笔记 pycharm专业版免费激活教程见资源,私信我给你发 python相关库的安装:pandas,numpy,matplotlib,statsmodels 1. 引言 线性回归(Linear Regression)是一种常见的统计方法和机器学习算法&a…...
![](https://www.ngui.cc/images/no-images.jpg)
三大常用集合
1.Set集合 在Java中,Set是一种集合类型,它是一种不允许包含重复元素的集合,每个元素在Set中是唯一的。Set接口的常用实现类有HashSet、TreeSet和LinkedHashSet。以下是关于Set集合的一些重要特点和用法: 特点: 不允…...
![](https://img-blog.csdnimg.cn/direct/be89a01f0d7e4e7b99aee8512f538965.png)
poi-tl 生成 word 文件(插入文字、图片、表格、图表)
文章说明 本篇文章主要通过代码案例的方式,展示 poi-tl 生成 docx 文件的一些常用操作,主要涵盖以下内容 : 插入文本字符(含样式、超链接)插入图片插入表格引入标签(通过可选文字的方式,这种方…...
![](https://www.ngui.cc/images/no-images.jpg)
Java小白入门基础教程
Java入门基础教程可以概括为以下几个关键部分,每个部分都包含了重要的概念和知识点。以下是一个结构清晰、内容丰富的教程概览: 一、Java环境配置 下载JDK: 访问Oracle官网或其他JDK提供者的网站,下载适合你的操作系统和处理器类…...
![](https://img-blog.csdnimg.cn/img_convert/c8cca4c94b33a4931afc45478a220a17.png)
NestJs 使用 RabbitMQ
NestJs 使用 RabbitMQ 既然是使用 RabbitMQ 那先不管其他的 把 RabbitMQ 装上再说 RabbitMQ 安装 这里直接找他们官网就行 Installing RabbitMQ | RabbitMQ 这里我们选择使用 docker 安装 快捷方便 这里直接参考: https://juejin.cn/post/719843080185010591…...
![](https://www.ngui.cc/images/no-images.jpg)
超长期特别国债成险资“必选项”,与地方债利差或成配置干扰因素
21世纪经济报道记者叶麦穗 广州报道 超长期特别国债火热出圈,不仅成为个人投资的香饽饽,保险资金对其的配置热情也持续升温。目前,在20年期、30年期超长期特别国债发行中,多家保险机构已入场认购。对于险资配置超长期特别国债的原因,主流观点认为,在“资产荒”的当下,安…...
![](https://img-blog.csdnimg.cn/direct/59d6c89d0ec64db0aeac46d3a042239e.png)
【全开源】知识库文档系统源码(ThinkPHP+FastAdmin)
知识库文档系统源码:构建智慧知识库的基石 引言 在当今信息爆炸的时代,知识的有效管理和利用对于企业和个人来说至关重要。知识库文档系统源码正是为了满足这一需求而诞生的,它提供了一个高效、便捷的平台,帮助用户构建、管理、…...
![](https://img-blog.csdnimg.cn/direct/f2132e06aba94ec3918ecff69c15eb9e.png)
【Unity】Unity项目转抖音小游戏(三)资源分包,抖音云CDN
业务需求,开始接触一下抖音小游戏相关的内容,开发过程中记录一下流程。 使用资源分包可以优化游戏启动速度,是抖音小游戏推荐的一种方式,抖音云也提供存放资源的CDN服务 抖音云官方文档:https://developer.open-douyi…...
![](https://img-blog.csdnimg.cn/direct/9a86835cd13745caaee367c1d273c7df.png)
【Qt】Qt框架文件处理精要:API解析与应用实例:QFile
文章目录 前言:1. Qt 文件概述2. 输入输出设备类3. 文件读写类3.1. 打开open3.2. 读read / readline/ readAll3.3. 写write3.4. 关闭close 4. 读写文件示例5. 文件件和目录信息类总结: 前言: 在现代软件开发中,文件操作是应用程序…...
![](https://www.ngui.cc/images/no-images.jpg)
202309青少年软件编程(Python)等级考试试卷(四级)
第 1 题 【单选题】 用枚举算法求解“100 以内既能被 3 整除又能被 4 整除的元素”时, 在下列数值范围内,算法执行效率最高的是? ( ) A :1~101 B :4~100 C :12~100 D :12~96 正确答案:D 试题解析: 在选取循环控制变量时, 枚举范围应尽可能小, 但又不能遗漏。 第 …...
![](https://www.ngui.cc/images/no-images.jpg)
AWS安全性身份和合规性之Amazon Detective
分析和直观呈现安全数据,以调查潜在的安全问题。 Amazon Detective使您可以更轻松地分析、调查和快速确定潜在安全问题或可疑活动的根本原因。Amazon Detective会自动从您地AWS资源中收集日志数据并使用机器学习、统计分析和图论来构建一组关联的数据,使…...