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

【C++杂货铺】探索string的底层实现

在这里插入图片描述

文章目录

  • 一、成员变量
  • 二、成员函数
    • 2.1 默认构造函数
    • 2.2 拷贝构造函数
    • 2.3 operator=
    • 2.4 c_str()
    • 2.5 size()
    • 2.6 operator[ ]
    • 2.7 iterator
    • 2.8 reserve
    • 2.9 resize
    • 2.10 push_back
    • 2.11 append
    • 2.12 operator+=
    • 2.13 insert
    • 2.14 erase
    • 2.15 find
    • 2.16 substr
    • 2.17 operator<<
    • 2.18 operator>>
    • 2.19 operator<
    • 2.20 operator==
    • 2.21 <=、>、>=、!=
  • 三、结语

一、成员变量

private:char* _str;//用来存储字符串size_t _size;//用来表示有效字符数size_t _capacity;//用来表示可以存储有效字符的容量
public:static size_t npos;//要在类外面定义

string本质上是一个动态顺序表,它可以根据需要动态的扩容,所以字符串一定是通过在堆上动态申请空间进行存储的,因此_str指向存储字符串的空间,_size用来表示有效字符数,_capacity用来表示可以存储有效字符的容量数。

二、成员函数

2.1 默认构造函数

string(const char* str = ""):_str(new char[strlen(str) + 1])//strlen计算的是有效字符的个数,而我们存储的时候要在字符串的最后存一个'\0',_size(strlen(str)),_capacity(_size)
{//memcpy(_str, str, _size);//strcpy(_str, str);//常量字符串就是遇到'\0'终止,所以直接用strcpy也可以memcpy(_str, str, strlen(str) + 1);
}

注意:默认构造函数需要注意的地方是:首先形参必须加上 const 修饰,这样才能用 C 语言中的常量字符串来初始化 string 类对象,形参的的缺省值直接给一个空字符串即可,注意空字符串是用""表示,该字符串只有结尾默认的一个 '\0'"\0"并不表示空字符串,它表示该字符串有一个字符 '\0' ,它的结尾还有一个默认的 '\0',因此有两个 '\0'nullptr也不能表示空字符串,他表示的是空指针。其次需要注意初始化列表的顺序,应该严格按照成员变量的出现顺序。strlen 计算的是字符串中有效字符的个数,不算 '\0',而常量字符串的结尾默认有一个 '\0',因此在用 new开辟空间的时候需要多开一个用来存储结尾的 \0_capacity表示的是可以存储有效字符的容量,而字符串结尾默认的 '\0' 并不算作有效字符,因此最初的 _capacity 就是形参 str 的长度。最后记得在构造函数体内将形参 str 的字符拷贝到动态申请的空间中。

小Tips:涉及到字符串拷贝的地方,建议使用 memcpystrcpy 默认遇到 \0 就终止,但是不排除 \0 就是 string 对象中的有效字符。但是 strcpy 会默认在结尾加 \0,而 memcpy 不会,因此使用 memcpy 的时候需要注意拷贝得到的字符串结尾是否有 \0

2.2 拷贝构造函数

//传统写法
string(const string& str):_str(new char[str._size + 1]),_size(str._size),_capacity(_size)
{memcpy(_str, str._str, str._size + 1);
}//现代写法
string(const string& str):_str(nullptr), _size(0),_capacity(0)
{string tmp(str._str);swap(tmp);
}

注意:现代写法不需要我们亲自去申请空间初始化,而是调用构造函数去帮我们完成。最后再将初始化好的 tmp 交换过来,这里一定要通过初始化列表对 *this 进行初始化,不然交换给 tmp 后,里面都是随机值,最终出了作用域 tmp 去销毁的时候就会出问题。现代写法的坑点在于,如果 string 对象中有 '\0',只会把 '\0' 前面的字符拷贝过去。

2.3 operator=

//传统写法
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;
}

注意:这种写法需要我们自己去开辟空间新空间 tmp,自己去释放旧空间 _str,下面将对这种写法加以改进,通过已有的接口来帮我们完成这些工作。

//现代写法
string& operator=(const string& s)
{if (this != &s){string tmp(s);//通过调用拷贝构造来创建空间//tmp是局部变量,出了作用于会自动销毁,把待销毁的资源通过交换,给tmpstd::swap(_str, tmp._str);std::swap(_size, tmp._size);std::swap(_capacity, tmp._capacity);//std::swap(*this, tmp);//错误的写法}return *this;
}//现代写法优化
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string& operator=(string s)
{swap(s);return *this;
}
//优化版本,连拷贝构造函数也不需要我们自己去调用啦,直接通过形参去调用

注意:这种写法通过调用拷贝构造来帮我们申请空间,在利用局部对象出了作用就会被销毁的特点,将需要释放的资源通过 swap 交换给这个局部变量,让这个局部变量帮我们销毁。这里不能直接用 swap 交换两个 string 类对象,会导致栈溢出,因为 swap 函数中会调用赋值运算符重载,而赋值运算符重载又要调用 swap 成了互相套娃。我们可以不用库里面的 swap,自己实现一个 Swap 用来交换两个 string 对象。

2.4 c_str()

char* c_str() const
{return _str;
}

注意:记得加上 const,这样普通的 string 类对象可以调用,const 类型的 string 类对象也可以调用,普通对象来调用就是权限的缩小。

2.5 size()

size_t size() const
{return _size;
}

2.6 operator[ ]

//读写版本
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
//只读版本
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

注意:这两个运算符重载函数构成函数重载,对象在调用的时候会走最匹配的,普通对象会调用读写版本,const 对象会调用只读版本。

2.7 iterator

iteratorstring 类的内嵌类型,也可以说是在 string 类里面定义的类型,在一个类里面定义类型有两种方法,typedef 和 内部类。string 类的 iterator 是通过前者来实现的,即对字符指针 char* 通过 typedef 得到的。

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;
}

2.8 reserve

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

2.9 resize

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

注意reserve 函数不会进行缩容,因此在扩容前要先进程判断,只有当形参 n 大于当前容量的时候才扩容。

2.10 push_back

void push_back(char ch)
{//先检查容量,进行扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';
}

注意:需要注意对空串的追加,空串的 _capacity = 0 ,因此在调用 reserve 函数进行扩容的时候,不能简单传递 _capacity*2,要先进行判断,当 capacity == 0 的时候,给它一个初始大小。

2.11 append

void append(const char* str)
{if (_size + strlen(str) > _capacity){reserve(_size + strlen(str));}//strcpy(_str + _size, str);//常量字符串就是遇到'\0'终止,所以直接用strcpy也可以memcpy(_str + _size, str, strlen(str) + 1);_size += strlen(str);
}

2.12 operator+=

//追加一个字符串
string& operator+=(const char* str)
{append(str);return *this;
}
//追加一个字符
string& operator+=(char ch)
{push_back(ch);return *this;
}

注意:+= 需要有返回值。

2.13 insert

//插入n个字符
void insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);//检查容量,扩容	if (_size + n > _capacity){reserve(_size + n);}//挪动数据size_t end = _size;while (end != npos && end >= pos){_str[end + n] = _str[end--];}//插入数据size_t i = pos;while (i < pos + n){_str[i++] = ch;}_size += n;
}

注意:这里需要注意挪动数据时的判断条件,因为 endpos 都是 sizt_t 类型,所以当 pos = 0 的时候 end >= pos 永远成立,此时就会有问题,只把 end 改成 int 也解决不了问题,在比较的时候会发生整形提升,最终还是永远成立。一种解决方法就是想上面一样,加一个 size_t 类型的成员变量 npos,把它初始化成 -1,即整形最大值,判断 end 是否等于 npos,等于说明 end 已经减到 -1 了,就应该停止挪动。解决上面的问题还有一种方法,上面的问题出现在 pos = 0 时,end 会减到 -1,最终变成正的无穷大,导致判断条件永远成立,那我们可以将 end 初始化成 _size + n,把 end - n 上的字符挪到 end 位置上,此时计算 pos = 0,也不会出现 end 减到 -1 的情况,代码如下:

//插入n个字符
void insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);//检查容量,扩容	if (_size + n > _capacity){reserve(_size + n);}//挪动数据size_t end = _size + n;while (end >= pos + n){_str[end] = _str[end - n];--end;}//插入数据size_t i = pos;while (i < pos + n){_str[i++] = ch;}_size += n;
}

小Tipsnpos作为一个静态成员变量,必须在类外面进行初始化(定义),并且不能在声明时给默认值,默认值是给初始化列表用的,而静态成员变量属于该类所有对象共有,并不会走初始化列表。但是!但是!!,整形的静态成员变量变量在加上 const 修饰后就可以在声明的地方给默认值,注意!仅限整形。其他类型的静态成员变量在加 const 修饰后仍需要在类外面定义。

const static size_t npos = -1;//可以
//const static double db = 1.1//不可以
//插入一个字符串
void insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//挪动size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];--end;}//插入for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}

2.14 erase

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 end = pos + len;while (end <= _size){_str[end - len] = _str[end++];}_size -= len;}
}

注意pos 将整个数组划分成两部分,[0,pos-1]是一定不需要删除的区域,[pos,_size-1]是待删除区域,一定不需要删除的区域有 pos 个元素,我们希望删除 len 个字符,当一定不会删除的字符数加我们希望删除的字符数如果大于或等于全部的有效字符数,那就说明待删除区域的所有字符都要删除,即当 pos + len >= _size 的时候就是要从 pos 位置开始删除后面的所有字符,删完后加的把 pos 位置的字符置为 \0

2.15 find

//查找一个字符
size_t find(char ch, size_t pos = 0)
{assert(pos < _size);for (size_t i = 0; 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* ptr = strstr(_str, str);if (ptr == NULL){return npos;}else{return ptr - _str;}
}

2.16 substr

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

2.17 operator<<

ostream& operator<<(ostream& out, const wcy::string& str)
{for (auto e : str){out << e;}return out;
}

注意:因为涉及到竞争左操作数的原因,流插入和流提取运算符重载要写在类外面。其次,不能直接打印 str._str 或者通过 str.c_str() 来打印,因为 string 对象中可能会有 \0 作为有效字符存在,前面两种打印方法,遇到 \0 就停止了,无法完整将一个 string 对象打印出来,正确的做法是逐个打印。

小Tips:无论是形参还是返回值,只要涉及到 ostreamistream 都必须要用引用,因为这俩类不允许拷贝或者赋值的。

2.18 operator>>

istream& operator>>(istream& in, wcy::string& str)
{if (str._size != 0){str.erase(0);}//in >> str._str;//这样写是错的,空间都没有char ch;ch = in.get();while (ch == ' ' || ch == '\n')//清除缓冲区{ch = in.get();}while (ch != ' ' && ch != '\n'){str += ch;ch = in.get();}return in;
}

注意:空格符 ' ' 和换行符 \n 作为输入时分割多个 string 对象的标志,是不能直接用 istream 对象来读取的,即 cin >> ch 是读不到空格符和换行符。需要借助 get() 成员函数才能读取到空格符和换行符。其次库中对 string 进行二次流提取的时候会进行覆盖,所以我们在插入前也要先进行判断。上面这种写法,在输入的字符串很长的情况下会多次调用 reserve 进行扩容,为了解决这个问题,我们可以对其进行优化。

//优化版本
istream& operator>>(istream& in, wcy::string& str)
{/*if (str._size != 0){str.erase(0);}*///in >> str._str;//这样写是错的,空间都没有str.clear();char buff[128] = { '\0' };char ch;ch = in.get();while (ch == ' ' || ch == '\n'){ch = in.get();}size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){str += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';str += buff;}return in;
}

注意:这里的做法是,先开辟一个数组,将输入的字符存储到数组中,然后从数组中拷贝到 string 对象当中。

2.19 operator<

bool operator<(const string& s) const
{size_t i1 = 0;size_t i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{i1++;i2++;}}if (i1 == _size && i2 == s._size){return false;}else if (i1 < _size){return false;}else{return true;}
}

注意string 类对象是按照 ASCII 进行比较的。其次,这里不能直接复用 strcmp 或者 memcmp,前者遇到 '\0' 就会终止,后者只能比较长度相等的部分。所以我们可以自己来写比较逻辑,也可以复用 memcmp 然后进行补充。

//复用memcpy
bool operator<(const string& s) const
{int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);return ret == 0 ? _size < s._size : ret < 0;
}

2.20 operator==

bool operator==(const string& s) const
{return _size == s._size&& memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}

有了 < 和 ==,剩下的直接复用即可。

2.21 <=、>、>=、!=

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++杂货铺】探索string的底层实现

文章目录 一、成员变量二、成员函数2.1 默认构造函数2.2 拷贝构造函数2.3 operator2.4 c_str()2.5 size()2.6 operator[ ]2.7 iterator2.8 reserve2.9 resize2.10 push_back2.11 append2.12 operator2.13 insert2.14 erase2.15 find2.16 substr2.17 operator<<2.18 opera…...

c++ day1

定义一个命名空间Myspace&#xff0c;包含以下函数&#xff1a;将一个字符串中的所有单词进行反转&#xff0c;并输出反转后的结果。例如&#xff0c;输入字符串为"Hello World"&#xff0c;输出结果为"olleH dlroW"&#xff0c;并在主函数内测试该函数。 …...

变动的Python爬虫实现

在电商时代&#xff0c;了解商品价格的变动对于购物者和卖家来说都非常重要。本文将分享一种基于Python的实时监控电商平台商品价格变动的爬虫实现方法。通过本文的解决方案和代码示例&#xff0c;您将能够轻松监控商品价格&#xff0c;并及时做出决策。 一、了解需求和目标 在…...

mybatis-plus--配置-(sql)日志输出-自动填充-分页-多数据源-逻辑删除

写在前面&#xff1a; 本文主要介绍mybatis-plus的配置&#xff0c;以后在有的时候在补充。欢迎交流。 文章目录 日志输出自动填充分页全局字段配置多数据源 日志输出 调试的时候需要看执行的sql&#xff0c;这时候就很需要日志来记录查看了。 mybatis-plus的日志配置在yml…...

数据API服务管理功能:解放数据潜力,提升业务效率

数据API服务的重要性 在数字化时代&#xff0c;数据被认为是企业的重要资产。数据API服务的管理功能能够有效帮助企业实现数据的整合和利用。通过合理的数据API服务管理&#xff0c;企业可以更好地解放数据潜力&#xff0c;提升业务效率。 ​ 解放数据潜力 数据API服务管理功…...

云南森林火灾vr消防模拟安全演练系统训练消防员火灾和事故的适应和应对能力

据统计,每一场破坏性地震发生后,会引发次生的灾害,而火灾是其中之一。导致火灾的原因,推测是地震时使供电线路短路,引燃易燃物,火灾就随即发生。所以,在日常生活中,定期的消防演练还是非常必要的, VR消防&#xff0c;是VR公司深圳华锐视点利用VR虚拟现实技术&#xff0c;将VR和…...

(6)(6.2) 任务命令

文章目录 前言 6.2.1 概述 6.2.2 导航命令 6.2.3 条件命令 6.2.4 DO命令 前言 本文介绍了 Copter、Plane 和 Rover 切换到自动模式时支持的任务指令。 &#xff01;Warning 这是一项正在进行中的工作&#xff0c;尚未经过全面审核。有关 Copter 的更佳列表&#xff0c;请…...

【consul】

consul 一、什么是服务注册与发现1.11.2 二、 什么是consul2.1定义2.2特性2.2.1服务注册与发现&#xff1a;2.2.2健康检查&#xff1a;2.2.3Key/Value存储&#xff1a; 三、consul部署-datacenter &#xff1a;指定数据中心名称&#xff0c;默认是dc1。consul &#xff1a;指定…...

Electron环境搭建

Electron是一个优秀的开源框架&#xff0c;用于构建跨平台的桌面应用程序。它基于Chromium和Node.js&#xff0c;使得开发者可以使用Web技术&#xff08;HTML、CSS和JavaScript&#xff09;来构建可在Windows、macOS和Linux等多个操作系统上运行的应用程序。本文将介绍如何搭建…...

MinIO线上扩容实战

硬件投入肯定是随着业务的增长而增长&#xff0c;这就要求中间件平台必须提供水平伸缩机制&#xff0c;MinIO对象存储服务也不例外&#xff0c;本文就详细介绍MinIO的扩容。 Minio支持通过增加新的Server Pool来扩容老的集群。每个Server Pool都是一个相对独立的故障域&#x…...

【微服务】微服务的概论

微服务&#xff1a;构建面向为了解决这个问题&#xff0c;微服务架构应运而生。本文将向您介绍微服务的概念、优势、实现原理以及应用场景&#xff0c;带您领略微服务在构建面向未来的高效应用中的魅力。 一、微服务的概念和优势 微服务是一种将应用拆分为一系列小型、独立服…...

基于Jenkins自动打包并部署docker环境

目录 1、安装docker-ce 2、阿里云镜像加速器 3、构建tomcat 基础镜像 4、构建一个Maven项目 实验环境 操作系统 IP地址 主机名 角色 CentOS7.5 192.168.200.111 git git服务器 CentOS7.5 192.168.200.112 Jenkins git客户端 jenkins服务器 CentOS7.5 192.168…...

jvm 运行时数据区

Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁 1.1程序计数器 程序计数器也叫pc寄存器 可以看作是当前线程…...

Jobs Portal求职招聘系统源码v3.5版本

Jobs Portal求职招聘系统 是为求职者和公司发布职位而开发的交互式求职招聘源码。它使求职者能够发布简历、搜索工作、查看个人工作列表。 它将提供各种公司在网站上放置他们的职位空缺资料&#xff0c;并且还可以选择搜索候选人简历。 除此之外&#xff0c;还有一个管理模块供…...

Android kotlin系列讲解(入门篇)使用Intent在Activity之间穿梭

<<返回总目录 上一篇:Android kotlin系列讲解(入门篇)Activity的理解与基本用法 文章目录 1、使用显式Intent2、使用隐式Intent3、更多隐式Intent的用法4、向下一个Activity传递数据5、返回数据给上一个Activity1、使用显式Intent 你应该已经对创建Activity的流程比较…...

音频编码类型及对应的封装文件

音频编码类型及对应的封装文件 如下表格 编码类型解释文件封装audio/mp4a-latmMPEG-4 Audio Advanced Audio Coding (AAC) Low-Overhead Audio Transport Multiplex (LATM) 压缩的音频格式mp4audio/3gpp3rd Generation Partnership Project (3GPP) 定义的音频编码格式3GPaudi…...

初探科研 | 第一次科研经历

1 . 自己的experiences 自己大二下学期中比较幸运加入到科研组里&#xff0c;做的方向是3D人体姿态估计&#xff0c;不过由于是一个全新领域&#xff0c;基本也是自己这个小白探索&#xff0c;所以成果甚微。在八月初由于各种原因退出了组&#xff0c;但是在这期间收获还是蛮多…...

Wireshark数据抓包分析之HTTP协议

一、实验目的&#xff1a; 主要时熟悉wireshark的使用 二、预备知识&#xff1a; HTTP协议的相关知识 what fk&#xff0c;原来只要在右页点击切换&#xff0c;就可以开启2台不同的机器欸&#xff01;nice 三、实验过程&#xff1a; 1.在机器1中通过管理员身份运行hfs之后&a…...

研发管理工具大揭秘!6款利器助你高效研发

"研发管理工具有哪些&#xff1f;6款研发管理利器分析Zoho Projects、Trello、Asana、Monday.com、Smartsheet、Jira。" 在如今的科技发展日新月异的时代&#xff0c;研发管理工具的重要性日益凸显。研发管理工具有助于提高研发效率&#xff0c;降低成本&#xff0c;…...

云知识入门-什么是虚拟机、磁盘、镜像和快照

一、虚拟机 1、什么是虚拟机 虚拟机&#xff08;VM&#xff09;是一种创建于物理硬件系统&#xff08;位于外部或内部&#xff09;、充当虚拟计算机系统的虚拟环境&#xff0c;它模拟出了自己的整套硬件&#xff0c;包括 CPU、内存、网络接口和存储器。通过名为虚拟机监控程序…...

C/C++编译整理

1. 编译器 查看编译情况 gcc main.c -o main -v # -v查看编译详情 g main.c -o main -v # -v查看编译详情添加预处理头文件 在PATH中找到可执行文件程序的路径 export PATH $PATH:$HOME/bin gcc找到头文件的路径 C_INCLUDE_PATH/usr/include/libxml2:/MyLib export C_I…...

数据结构——栈和队列

栈和队列的建立 前言一、栈1.栈的概念2.栈的实现3.代码示例&#xff08;1&#xff09;Stack.h&#xff08;2&#xff09;Stack.c&#xff08;3&#xff09;Test.c&#xff08;4&#xff09;运行结果&#xff08;5&#xff09;完整代码演示 二、队列1.队列的概念2.队列的实现3.代…...

苍穹外卖阿里云oss存储笔记

阿里云oss 阿里云对象存储oss&#xff08;Object Storage Service&#xff09;&#xff0c;是一款海量&#xff0c;安全&#xff0c;低成本&#xff0c;高可靠得云存储服务&#xff0c;使用oss&#xff0c;您可以通过网络随时存储和调用包括文本&#xff0c;图片&#xff0c;视…...

Kafka 集群搭建过程

前言 跟着尚硅谷海哥文档搭建的Kafka集群环境&#xff0c;在此记录一下&#xff0c;侵删 注意&#xff1a;博主在服务器上搭建环境的时候使用的是一个服务器&#xff0c;所以这篇博客可能会出现一些xsync分发到其他服务器时候的错误&#xff0c;如果你在搭建的过程中出现了错…...

【算法随记】在计算过程中模的情况

https://leetcode.cn/problems/power-of-heroes/ 计算过程中&#xff0c;可以放心模的情况&#xff1a; 加减乘 先模再加再模和直接加再模一样 a m o d m b m o d m ≡ a b ( m o d m ) a\mod mb\mod m ≡ ab \ (\mod m) amodmbmodm≡ab (modm) 先模再减再模和直接减再模…...

MSTP多生成树协议(第二课)

MSTP负载均衡 实验 需求 1&#xff09;PC1属于 vlan 10 &#xff0c;IP地址为 192.168.10.1/24&#xff0c; 网关为 192.168.10.2542&#xff09;PC2属于 vlan 20 &#xff0c;IP地址为 192.168.20.1/24&#xff0c; 网关为 192.168.20.254**3&#xff09;确保PC1与PC2互通4…...

数组指针、函数指针、指针数组、函数 指针数组、指针函数详细总结

1.数组指针概念和应用 首先数组指针应该是一个数组&#xff0c;它的定义如下&#xff1a; 数组指针&#xff0c;指的是数组名的指针&#xff0c;即数组首元素地址的指针。即是指向数组的指针。例&#xff1a;int (*p)[10]; p即为指向数组的指针&#xff0c;又称数组指针。 数…...

Linux存储学习笔记

相关文章 Linux 存储系列&#xff5c;请描述一下文件的 io 栈&#xff1f; - tcpisopen的文章 - 知乎 https://zhuanlan.zhihu.com/p/478443978 深入学习 Linux 操作系统的存储 IO 堆栈 - KaiwuDB的文章 - 知乎 https://zhuanlan.zhihu.com/p/636720297 linux存储栈概览 - st…...

ubuntu执行jmeter端口不够用报错(Address not available)

ubuntu执行jmeter端口不够用报错(Address not available) 解决方案 // 增加本地端口范围 echo 1024 65000 > /proc/sys/net/ipv4/ip_local_port_range// 启用快速回收TIME_WAIT套接字 sudo sysctl -w net.ipv4.tcp_tw_recycle 1// 启用套接字的重用 sudo sysctl -w net.ipv4…...

MongoDB:简单的增删改查操作

一.概述 本篇文章介绍在Navicat中对MongoDB数据库进行增删改查操作,在后面会介绍在Spring Boot中使用MongoTemplate对MongoDB数据库进行相关操作.如有必要可以先看看前面几篇文章. MongoDB:MySQL,Redis,ES,MongoDB的应用场景 MongoDB:数据库初步应用 二.在Navicat进行增删改…...