Cpp::STL—string类的模拟实现(12)
文章目录
- 前言
- 一、string类各函数接口总览
- 二、默认构造函数
- string(const char* str = "");
- string(const string& str);
- 传统拷贝写法
- 现代拷贝写法
- string& operator=(const string& str);
- 传统赋值构造
- 现代赋值构造
- ~string();
- 三、迭代器相关函数
- begin & end
- 四、容量和大小相关函数
- size & capacity
- reserve
- resize
- empty
- 五、修改字符串相关函数
- c_str
- push_back
- append
- operator+=
- insert
- erase
- clear
- swap
- substr
- 六、访问字符串相关函数
- operator[ ]
- find
- 七、关系运算符重载函数
- 八、 流插入与流提取
- 流插入
- 流提取
- getline
- 总结
前言
string类的模拟实现源代码
我好像把string类的模拟实现给遗漏了
没关系,我们现在来补!
一、string类各函数接口总览
同样我们先来简单看下我们要实现的接口,另外为了避免跟库里面的string发生冲突,我们要用自己的命名空间包起来:
namespace HQ
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;// string(); // 无参和带参往往可以合成同一个string(const char* str = "");string(const string& str);string& operator=(const string& str);~string();const char* c_str() const;size_t size() const;char& operator[](size_t pos);const char& operator[](size_t pos) const;void reserve(size_t n = 0);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos = 0, size_t len = npos);size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);void swap(string& str); // string自己实现一个,std里的代价极大string substr(size_t pos = 0, size_t len = npos);bool operator<(const string& s) const;bool operator>(const string& s) const;bool operator<=(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;void clear();private:// char _buff[16];char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;const static size_t npos;};istream& operator>> (istream& is, string& str);ostream& operator<< (ostream& os, const string& str);
}
不要害怕,跟着我一步一步来看!
二、默认构造函数
string(const char* str = “”);
我们设置缺省函数,可是我们试想一下,缺省值给nullptr合理吗?
显然不合理,因为成员变量应该无论如何要先赋值个\0,即默认构造为空字符串,而""(没有空格)就自带一个\0
string::string(const char* str) // 缺省值声明和定义分离:_size(strlen(str)) // 不算\0,且字符串大小才用初始化列表来初始化,这是顺序的原因
{// 三个strlen效率低,用_size来初始化_str = new char[_size + 1]; // 为存储字符串开辟空间(多开一个用于存放'\0')_capacity = _size; // 不算\0strcpy(_str, str); // 将C字符串拷贝到已开好的空间
}
string(const string& str);
拷贝构造,在实现之前我们再来回顾一下深拷贝和浅拷贝的定义:
浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响
很明显,我们并不希望拷贝出来的两个对象之间存在相互影响,因此,我们这里需要用到深拷贝。下面提供深拷贝的两种写法
传统拷贝写法

先开辟一块足以容纳源对象字符串的空间,然后将源对象的字符串拷贝过去,接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的
string::string(const string& str):_str(new char[str._capacity + 1]),_size(str._size),_capacity(str._capacity)
{strcpy(_str, str._str);
}
现代拷贝写法

现代写法与传统写法的思想不同,先根据源字符串的C字符串调用构造函数构造一个tmp对象,然后再将tmp对象与拷贝对象的数据交换即可。拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的
string::string(const string& s)
{string tmp(s._str);swap(tmp);
}
string& operator=(const string& str);
赋值运算符重载,与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝
传统赋值构造
传统写法与拷贝构造函数的传统写法几乎相同,只是左值的_str在开辟新空间之前需要先将原来的空间释放掉,并且在进行操作之前还需判断是否是自己给自己赋值,若是自己给自己赋值,则无需进行任何操作
string& string::operator=(const string& str)
{// 考虑到两个字符串的长度可能不太一样// 干脆直接销毁旧的,新开一个拷贝过去if (this != &str) {char* tmp = new char[str._capacity + 1];strcpy(tmp, str._str);delete[] _str;_str = tmp;_size = str._size;_capacity = str._capacity;}return *this;
}
现代赋值构造
通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可,但是这里为了避免自己给自己赋值,我们还是选择引用传值,在内部在拷贝构造一个临时字符串用来交换
string& string::operator=(const string& str)
{if (this != &str) // 防止自己给自己赋值{string tmp(str); // 用s拷贝构造出对象tmpswap(tmp); // 交换这两个对象}return *this; // 返回左值(支持连续赋值)
}
~string();
string类的析构函数需要我们进行编写,因为每个string对象中的成员_str都指向堆区的一块空间,当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,我们需要使用delete手动释放堆区的空间
string::~string()
{delete[] _str; // 不会产生矛盾,就算只有一个底层也是调用delete _str;_str = nullptr;_size = _capacity = 0;
}
三、迭代器相关函数
string类中的迭代器实际上就是字符指针,只是给字符指针起了一个别名叫iterator而已
注:不是所有的迭代器都是指针
typedef char* iterator;
typedef const char* const_iterator;
begin & end
begin函数的作用就是返回字符串中第一个字符的地址
end函数的作用就是返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址)
string::iterator string::begin()
{return _str;
} string::iterator string::end()
{return _str + _size;
}string::const_iterator string::begin() const
{return _str;
}string::const_iterator string::end() const
{return _str + _size;
}
四、容量和大小相关函数
size & capacity
因为string类的成员变量是私有的,我们并不能直接对其进行访问,所以string类设置了size和capacity这两个成员函数,用于获取string对象的大小和容量
size函数用于获取字符串当前的有效长度(不包括’\0’)
capacity函数用于获取字符串当前的容量(不包括’\0’)
size_t string::size() const
{return _size;
}size_t string::capacity() const
{return _capacity;
}
reserve
其规则:
- 当n大于对象当前的capacity时,将capacity扩大到n或大于n
- 当n小于对象当前的capacity时,什么也不做
代码中使用strncpy进行拷贝对象C字符串而不是strcpy,是为了防止对象的C字符串中含有有效字符’\0’而无法拷贝(strcpy拷贝到第一个’\0’就结束拷贝了)
void string::reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];strncpy(tmp, _str, _size + 1); // 将对象原本的C字符串拷贝过来(包括'\0')delete[] _str;_str = tmp;_capacity = n;}
}

resize
其规则:
- 当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’
- 当n小于当前的size时,将size缩小到n
void string::resize(size_t n, char ch = '\0')
{if (n < _size) // n小于当前size{_size = n; // 将size调整为n_str[_size] = '\0'; // 在size个字符后放上'\0'}else if (n > _capacity){reserve(n); // 扩容for (size_t i = _size; i < n; i++) // 将size扩大到n,扩大的字符为ch{_str[i] = ch;}_size = n; // size更新_str[_size] = '\0'; // 字符串后面放上'\0'}
}
empty
empty是string的判空函数
bool string::empty() const
{return _size == 0;
}
五、修改字符串相关函数
c_str
按照C语言的格式返回字符串
const char* string::c_str() const
{return _str;
}
push_back
push_back函数的作用就是在当前字符串的后面尾插上一个字符,尾插之前首先需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’,否则打印字符串的时候会出现非法访问,因为尾插的字符后方不一定就是’\0’
void string::push_back(char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';
}
append
append函数的作用是在当前字符串的后面尾插一个字符串,尾插前需要判断当前字符串的空间能否容纳下尾插后的字符串,若不能,则需要先进行增容,然后再将待尾插的字符串尾插到对象的后方,因为待尾插的字符串后方自身带有’\0’,所以我们无需再在后方设置’\0’
void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){// 大于2倍,需要多少开多少,小于2倍按2倍扩reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}strcpy(_str + _size, str);_size += len;
}
operator+=
有三个重载:
string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);
string& string::operator+=(char ch)
{push_back(ch);return *this;
}string& string::operator+=(const char* str)
{append(str);return *this;
}string& string::operator+=(const string& str)
{append(str.c_str());return *this;
}
insert
insert函数的作用是在字符串的任意位置插入字符或是字符串
// 插入字符,注意end不会为-1
void string::insert(size_t pos, char ch)
{assert(pos <= _size);// 谨慎使用if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}size_t end = _size;while (end >= pos) {_str[end + 1] = _str[end];if (end == 0) break; // end == -1 -> err--end;}_str[pos] = ch;++_size;
}
insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可

void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);reserve(_size + len);size_t end = _size;while (end >= pos) {_str[end + len] = _str[end];if (end == 0) break; // end == -1 -> err--end;}memcpy(_str + pos, str, len);_size += len;
}
insert函数用于插入字符串时,首先也是判断pos的合法性,若不合法则无法进行操作,再判断当前对象能否容纳插入该字符串后的字符串,若不能则还需调用reserve函数进行扩容,插入字符串时,先将pos位置及其后面的字符统一向后挪动len位(len为待插入字符串的长度),给待插入的字符串留出位置,然后将其插入字符串即可

erase
先来关注函数原型:
默认从0位置开始,一直清楚到末尾
void erase(size_t pos = 0, size_t len = npos);
erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,这时候一共有两种情况
- pos位置及其之后的有效字符都需要被删除
这时我们只需在pos位置放上’\0’,然后将对象的size更新即可

- pos位置及其之后的有效字符只需删除一部分
这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符,此时不用在字符串后方加’\0’,因为在此之前字符串末尾就有’\0’了

void string::erase(size_t pos, size_t len)
{assert(pos < _size);// 当len == npos时候,条件判断一定成立if (len >= _size - pos) {// pos后(含)全删完_str[pos] = '\0';_size = pos;}else {strcpy(_str + pos, _str + pos + len);_size -= len;}
}
clear
clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可
void string::clear()
{_str[0] = '\0';_size = 0;
}
swap
swap函数用于交换两个对象的数据,直接调用库里的swap模板函数将对象的各个成员变量进行交换即可
void string::swap(string& str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}
substr
substr功能是返回从指定位置开始len长度的字符串,先创建string空对象用于接收截取字符串,当len == npos 或len >= _size - pos,代表了从pos位置到尾的字符串截取,并且尽量书写 len >= _size - pos ,而不是 len + pos >= _size 这种,是为了防止 len + pos 超过类型最大值范围
string string::substr(size_t pos, size_t len)
{if (len >= _size - pos) {string sub(_str + pos);return sub;}else {string sub;sub.reserve(len);for (size_t i = pos; i < pos + len ; i++) {sub += _str[i];}sub._size = len;return sub;}
}
六、访问字符串相关函数
operator[ ]
[ ]运算符的重载是为了让string对象能像C字符串一样,通过[ ] +下标的方式获取字符串对应位置的字符
这里要有两种版本,一种可读可写,一种只读不写
char& string::operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}const char& string::operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}
find
实现这两种重载:
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
// 正向查找第一个匹配的字符
size_t string::find(char ch, size_t pos)
{assert(pos < _size); //检测下标的合法性for (size_t i = pos; i < _size; i++) {if (_str[i] == ch) {return i;}}return npos;
}// 正向查找第一个匹配的字符串
size_t string::find(const char* sub, size_t pos)
{assert(pos < _size); //检测下标的合法性char* p = strstr(_str + pos, sub);return p == NULL ? npos : p - _str;
}
对于第一种,首先判断所给pos的合法性,然后通过遍历的方式从pos位置开始向后寻找目标字符,若找到,则返回其下标;若没有找到,则返回npos。(npos是string类的一个静态成员变量,其值为整型最大值)
对于第二种,首先也是先判断所给pos的合法性,然后我们可以通过调用strstr函数进行查找。strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个C语言空指针NULL,若是找到了目标字符串,我们可以通过计算目标字符串的起始位置和对象C字符串的起始位置的差值,进而得到目标字符串起始位置的下标
七、关系运算符重载函数
关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现
bool string::operator<(const string& s) const
{return strcmp(_str, s._str) < 0;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}bool string::operator<=(const string& s) const
{return (*this == s || *this < s);
}bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}bool string::operator!=(const string& s) const
{return !(*this == s);
}
八、 流插入与流提取
流插入
重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入
输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取
另外还有,很明显得重载为全局函数
istream& operator>>(istream& is, string& str)
{str.clear(); // 要覆盖先前的内容,先清除一下char ch = is.get();//is >> ch; // 拿不到空格和换行while (ch != ' ' && ch != '\n') {str += ch;ch = is.get();}return is;
}
流提取
重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印。实现时我们可以直接使用范围for对对象进行遍历即可
ostream& operator<<(ostream& os, const string& str)
{for (size_t i = 0; i < str.size(); i++) {os << str[i];}return os;
}
getline
getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符
// 举个例子哈
int main()
{string str;cin >> str; // 假设输入hello worldcout << str; // 只会输出helloreturn 0;
}
如上,我们会发现空格字符无法被插入str,这时候就是getline发挥的时候了
再来道具体的题目,说不定能让你有更深的认识

总结
总算是补上了!可以看出string类的完整实现还是蛮复杂的
相关文章:
Cpp::STL—string类的模拟实现(12)
文章目录 前言一、string类各函数接口总览二、默认构造函数string(const char* str "");string(const string& str);传统拷贝写法现代拷贝写法 string& operator(const string& str);传统赋值构造现代赋值构造 ~string(); 三、迭代器相关函数begin &…...
一文搞懂SentencePiece的使用
目录 1. 什么是 SentencePiece?2. SentencePiece 基础概念2.1 SentencePiece 的工作原理2.2 SentencePiece 的优点 3. SentencePiece 的使用3.1 安装 SentencePiece3.2 训练模型与加载模型3.3 encode(高频)3.4 decode(高频&#x…...
一个简单的摄像头应用程序1
这个Python脚本实现了一个基于OpenCV的简单摄像头应用,我们在原有的基础上增加了录制视频等功能,用户可以通过该应用进行拍照、录制视频,并查看已拍摄的照片。以下是该脚本的主要功能和一些使用时需要注意的事项: 功能 拍照: 用户可以通过点击界面上的“拍照”按钮或按…...
通过PHP获取商品详情
在电子商务的浪潮中,数据的重要性不言而喻。商品详情信息对于电商运营者来说尤为宝贵。PHP,作为一种广泛应用的服务器端脚本语言,为我们提供了获取商品详情的便捷途径。 了解API接口文档 开放平台提供了详细的API接口文档。你需要熟悉商品详…...
【Android】获取备案所需的公钥以及签名MD5值
目录 重要前提 获取签名MD5值 获取公钥 重要前提 生成jks文件以及gradle配置应用该文件。具体步骤请参考我这篇文章:【Android】配置Gradle打包apk的环境_generate signed bundle or apk-CSDN博客 你只需要从头看到该文章的配置build.gradle(app&…...
看480p、720p、1080p、2k、4k、视频一般需要多大带宽呢?
看视频都喜欢看高清,那么一般来说看电影不卡顿需要多大带宽呢? 以4K为例,这里引用一位网友的回答:“视频分辨率4092*2160,每个像素用红蓝绿三个256色(8bit)的数据表示,视频帧数为60fps,那么一秒钟画面的数据量是:4096*2160*3*8*60≈11.9Gbps。此外声音大概是视频数据量…...
解决IDEA中@Autowired红色报错的实用指南:原因与解决方案
前言: 在使用Spring Boot开发时,Autowired注解是实现依赖注入的常用方式。然而,许多开发者在IDEA中使用Autowired时,可能会遇到红色报错,导致代码的可读性降低。本文将探讨导致这种现象的原因,并提供几种解…...
408知识点自检(一)
一、细节题 虚电路是面向连接的吗?虚电路线路上会不会有其他虚电路通过?虚电路适合什么类型的数据交换?虚电路的可靠性靠其他协议还是自己?固态硬盘的优势体现在什么存取方式?中断向量地址是谁的地址?多播…...
负载均衡--相关面试题(六)
在负载均衡的面试中,可能会遇到一系列涉及概念、原理、实践应用以及技术细节的问题。以下是一些常见的负载均衡面试题及其详细解答: 一、什么是负载均衡? 回答:负载均衡是一种将网络请求或数据传输工作分配给多个服务器或网络资源…...
【Unity踩坑】Unity更新Google Play结算库
一、问题描述: 在Google Play上提交了app bundle后,提示如下错误。 我使用的是Unity 2022.01.20f1,看来用的Play结算库版本是4.0 查了一下文档,Google Play结算库的维护周期是两年。现在需要更新到至少6.0。 二、更新过程 1. 下…...
Redis:hash类型
Redis:hash类型 hash命令设置与读取HSETHGETHMGET 哈希操作HEXISTSHDELHKEYSHVALSHGETALLHLENHSETNXHINCRBYHINCRBYFLOAT 内部编码ziplisthashtable 目前主流的编程语言中,几乎都提供了哈希表相关的容器,Redis自然也会支持对应的内容…...
力扣9.30
1749. 任意子数组和的绝对值的最大值 给你一个整数数组 nums 。一个子数组 [numsl, numsl1, ..., numsr-1, numsr] 的 和的绝对值 为 abs(numsl numsl1 ... numsr-1 numsr) 。 请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),…...
kafka下载配置
下载安装 参开kafka社区 zookeeperkafka消息队列群集部署https://apache.csdn.net/66c958fb10164416336632c3.html 下载 kafka_2.12-3.2.0安装包快速下载地址分享 官网下载链接地址: 官网下载地址:https://kafka.apache.org/downloads 官网呢下载慢…...
nlp任务之预测中间词-huggingface
目录 1.加载编码器 1.1编码试算 2.加载数据集 3.数据集处理 3.1 map映射:只对数据集中的sentence数据进行编码 3.2用filter()过滤 单词太少的句子过滤掉 3.3截断句子 4.创建数据加载器Dataloader 5. 下游任务模型 6.测试预测代码 7.训练代码 8.保…...
《程序猿之Redis缓存实战 · Redis 与数据库一致性》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
【无标题】observer: error while loading shared libraries: libmariadb.so.3处理办法
文章目录 1.记录新装的oceanbase,使用observer帮助时,出现lib文件无法找到的处理过程 ./observer --help ./observer: error while loading shared libraries: libmariadb.so.3: cannot open shared object file: No such file or directory2.做一个strace跟踪&…...
极客兔兔Gee-Cache Day1
极客兔兔7Days GeeCache - Day1 interface{}:任意类型 缓存击穿:一个高并发的请求查询一个缓存中不存在的数据项,因此这个请求穿透缓存直接到达后端数据库或数据源来获取数据。如果这种请求非常频繁,就会导致后端系统的负载突然…...
[MAUI]数据绑定和MVVM:MVVM的属性验证
一、MVVM的属性验证案例 Toolkit.Mvvm框架中的ObservableValidator类,提供了属性验证功能,可以使用我们熟悉的验证特性对属性的值进行验证,并将错误属性提取和反馈给UI层。以下案例实现对UI层的姓名和年龄两个输入框,进行表单提交验证。实现效果如下所示 View<ContentP…...
2024年水利水电安全员考试题库及答案
一、判断题 1.采用水下钻孔爆破方案时,侧面应采用预裂爆破,并严格控制单响药量以保护附近建(构)筑物的安全。 答案:正确 2.围堰爆破拆除工程的实施应成立爆破指挥机构,并应按设计确定的安全距离设置警戒。…...
【快速删除 node_modules 】rimraf
目录 1. 什么是node_modules 2. 卸载一个npm包 3. 删除 node_modules 为什么这么慢 4. rimraf 5. 为什么rimraf 这么快 作为前端开发,无论我们关注不关注,每天都能接触到node_modules。通常产生于一个npm install命令,之后就不会多加关注…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...
