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

std::string的底层实现 (详解)

目录

std::string的底层实现*

写时复制原理探究

CowString代码初步实现

短字符串优化(SSO)

最佳策略


std::string的底层实现*

我们都知道, std::string的一些基本功能和用法了,但它底层到底是如何实现的呢? 其实在std::string的历史中,出现过几种不同的方式。

我们可以从一个简单的问题来探索,一个std::string对象占据的内存空间有多大,即sizeof(std::string)的值为多大?如果我们在不同的编译器(VC++, GCC, Clang++)上去测试,可能会发现其值并不相同;即使是GCC,不同的版本,获取的值也是不同的。

虽然历史上的实现有多种,但基本上有三种方式:

  • Eager Copy(深拷贝)

  • COW(Copy-On-Write 写时复制)

  • SSO(Short String Optimization 短字符串优化)

    std::string的底层实现是一个高频考点,虽然目前std::string是根据SSO的思想实现的,但是我们最好能够掌握其发展过程中的不同设计思想,在回答时会是一个非常精彩的加分项。

    首先,最简单的就是深拷贝。无论什么情况,都是采用拷贝字符串内容的方式解决,这也是我们之前已经实现过的方式。这种实现方式,在不需要改变字符串内容时,对字符串进行频繁复制,效率比较低下。所以需要对其实现进行优化,之后便出现了下面的COW的实现方式。

    //如果string的实现直接用深拷贝
    String str1("hello,world");
    String str2 = str1;

    如上,str2保存的字符串内容与str1完全相同,但是根据深拷贝的思想,一定要重新申请空间、复制内容,这样效率较低、开销较大。

写时复制原理探究

Q1: 当字符串对象进行复制控制时,可以优化为指向同一个堆空间的字符串,接下来的问题就是何时回收堆空间的字符串内容呢?

引用计数 refcount当字符串对象进行复制操作时,引用计数+1;当字符串对象被销毁时,引用计数-1;只有当引用计数减为0时,才真正回收堆空间上字符串

string str2("hello,wuhan");
string str3 = str2;

补充:如果是如下创建对象,能不能共用空间,存放"hello"

—— 单独创建对象没有优化的空间,每一个string对象需要一片独立的空间存放自己的字符串

string s1("hello");
//在创建s2之前可能有很多String对象
//不可能遍历这些对象
//看看哪个对象保存的内容是hello
//s2再去共用空间 —— 只有确保内容一致时,才能共用空间 —— 复制或赋值时
string s2("hello");

Q2: 引用计数应该放到哪里?

—— 需要改变str1的数据成员,不合理。

—— 静态数据成员被该类所有对象共享,但此时str1和str2的引用计数应该都是2,但是str3的引用计数应该是1.

方案三可行,还可以优化一下

按常规的思路,需要使用两次new表达式(字符串、引用计数);可以优化成只用一次new表达式,因为申请堆空间的行为一定会涉及系统调用,程序员要尽量少使用系统调用,提高程序的执行效率。

—— 优化方向:把引用计数和字符串内容保存到一起。

引用计数保存到字符串内容的前面,方便访问。

除了复制操作,赋值操作也可以确定两个string对象保存的字符串内容是相同的,也可以复用空间,引用计数随之改变。

但相比复制操作,还需要考虑string对象原本用来保存字符串的堆空间是否需要回收。

(1)原本空间的引用计数-1,引用计数减到0,才真正回收堆空间

(2)让自己的指针指向新的空间,并将新空间的引用计数+1

CowString代码初步实现

根据写时复制的思想来模拟字符串对象的实现,这是一个非常有难度的任务(源码级),理解了COW的思想后可以尝试实现一下

重点关注一下赋值运算符函数,赋值运算符函数此时也需要考虑要不要回收空间了。

下标访问运算符函数也不能沿用以前的做法,直接返回对应位置的字符

这就是写时复制的意义。

在我们建立了基本的写时复制字符串类的框架后,发现了一个遗留的问题。

如果str1和str3共享一片空间存放字符串内容。如果进行读操作,那么直接进行就可以了,不用进行复制,也不用改变引用计数;如果进行写操作,那么应该让str1重新申请一片空间去进行修改,不应该改变str3的内容。

cout << str1[0] << endl; //读操作 str1[0] = 'H'; //写操作 cout << str3[0] << endl;//发现str3的内容也被改变了

我们首先会想到运算符重载的方式去解决。但是str1[0]返回值是一个char类型变量。

读操作 cout << char字符 << endl;

写操作 char字符 = char字符;

无论是输出流运算符还是赋值运算符,操作数中没有自定义类型对象,无法重载。而CowString的下标访问运算符的操作数是CowString对象和size_t类型的下标,也没办法判断取出来的内容接下来要进行读操作还是写操作。

—— 思路:创建一个CowString类的内部类,让CowString的operator[]函数返回是这个新类型的对象,然后在这个新类型中对<<和=进行重载,让这两个运算符能够处理新类型对象,从而分开了处理逻辑。

因为CharProxy定义在CowString的私有区域,为了让输出流运算符能够处理CharProxy对象,需要对此operator<<函数进行两次友元声明(内外都需要)。

对于读操作,还可以给CharProxy类定义类型转换函数来进行处理 —— 稍稍实现了一定的简化。

总结:当运算符需要处理自定义类型对象时,先看一看这个自定义类型有没有相应的运算符重载函数,如果有,那么这个运算符就可以处理这个自定义类型对象;

如果没有运算符重载,就无法直接处理,需要进行转换。先看看这个自定义类型中有没有类型转换函数,转换成一个该运算符可以直接处理的类型的数据。如果没有类型转换函数,会再看看有没有隐式转换的途径。(一般,大多数情况谨慎使用隐式转换)

下面为测试代码,可以自行测试

#define _CRT_SECURE_NO_WARNINGS 1;
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
using std::ostream;class CowString
{class CharProxy {public://先明白charproxy的创造条件就是str1[0] 就是一个str1 和一个下标//就是将引用数据成员绑定到str1本体,本体会一直存在直到对象销毁CharProxy(CowString& self, size_t idx):_self(self), _idx(idx){}//为读操作重载一个输出流运算符函数  //friend ostream& operator<<(ostream& os, const CharProxy& rhs);// 输出流运算符要多次声明友元,既要声明内部类又要声明外部类,// 还有什么方式可以解决这个问题呢// 使用类型转化函数,将自定义类型转化为一个cout输出的类型//将自定义类型转化为char就可以输出了operator char(){cout << "operator char() 被调用" << endl;return _self._pstr[_idx];}//为写操作重载一个赋值运算符函数char& operator=(char ch);private:CowString& _self;size_t _idx;};
public://构造函数CowString();//函数声明的时候可以不给变量名,但是后面定义的时候就要给CowString(const char*);CowString& operator=(const CowString&);~CowString();CowString(const CowString&);//这里不能是charproxy的引用因为,返回的charproxy对象是在这个函数中创建的函数结束生命周期结束,引用本体死亡//传参数也就是一个this指针和一个下标CharProxy operator[](size_t idx);//char& operator[](size_t _idx);const char* c_str() const {return _pstr;}size_t size() const {//strlen()传递的参数是指针类型和cout输出流的原理相同return (int)strlen(_pstr);}int use_count(){return *(int*)(_pstr - KRefCountLength);}friendostream& operator<<(ostream& os, const CowString& rhs);//friend //ostream& operator<<(ostream& os, const CharProxy& rhs);private://初始化引用计数的数据void initRefCount(){//先指向第4个字节向前移动初始化引用计数的数据*(int*)(_pstr - KRefCountLength) = 1;}void increaseRefCount(){++* (int*)(_pstr - KRefCountLength);}void decreaseRefCount() {--* (int*)(_pstr - KRefCountLength);}char* malloc(const char* pstr = nullptr){if (pstr){return new char[1 + KRefCountLength + strlen(pstr)]() + KRefCountLength;}else//没有传参数,就是无参构造{return new char[KRefCountLength + 1]() + KRefCountLength;}}void release(){decreaseRefCount();if (use_count() == 0){delete[](_pstr - KRefCountLength);_pstr = nullptr;cout << ">>delete heap" << endl;}}
private:char* _pstr;//静态常量数据成员static const int KRefCountLength = 4;};ostream& operator<<(ostream& os, const CowString& rhs)
{if (rhs._pstr){//当输出流对象拿到一个char*指针之后他会一直向后寻找直到'\0'结束os << rhs._pstr;}else{os << endl;}return os;
}CowString::CowString()
//new 返回的是申请空间的首地址,但是我不能是指针指向首地址,因为前面是用来记录引用计数的,所以返回的位置向后偏移4:_pstr(malloc())
{cout << "CowString()" << endl;initRefCount();
}//有参构造
CowString::CowString(const char* pstr):_pstr(malloc(pstr))
{//字符串拷贝strcpy(_pstr, pstr);//初始化计数initRefCount();
}
CowString:: ~CowString()
{release();
}
CowString::CowString(const CowString& rhs):_pstr(rhs._pstr) //浅拷贝
{increaseRefCount();
}CowString& CowString::operator=(const CowString& rhs)
{if (this != &rhs)//判断自复制{release();//尝试回收堆空间_pstr = rhs._pstr;//浅拷贝改变指针指向increaseRefCount();//数值++}return *this;
}
//str1[0]需要返回一个charproxy对象
//只有利用str1对象和下标0来进行创建
// 所以charproxy的构造函数写成如下形式//str1[0] = 'H'需要给charproxy定义一个赋值运算符函数
//cout << str1[0] << endl;  需要为charproxy重载一个输出流运算符函数// charproxy类需要设计一个cowstring引用向上绑定str1对象
//以及一个size_t的数据成员保存下标值
CowString::CharProxy CowString::operator[](size_t idx) {//外部类不能访问内部类,要把函数声明为友元return CharProxy(*this, idx);
}//还是会有this指针
char& CowString ::CharProxy::operator=(char ch)
{if (_idx < _self.size()){//如果引用计数大于1,就进行深拷贝申请一块新的空间if (_self.use_count() > 1){//原本空间引用计数减1_self.decreaseRefCount();//深拷贝char* tmp = _self.malloc(_self._pstr);strcpy(tmp, _self._pstr);//改变指向_self._pstr = tmp;//初始化新空间的引用计数_self.increaseRefCount();} //进行写操作_self._pstr[_idx] = ch;//没有直接返回下标引用return _self._pstr[_idx];}else//越界访问 {cout << "out of range" << endl;//不能返回局部变量,不能返回右值,所以创建了一个nullcharstatic char nullchar = '\0';return nullchar;}
}
//不会修改数据成员的以友元的形式进行重载,这个函数要在两个类中都进行友元函数的声明因为在charproxy中声明允许访问
//charproxy的私有的数据成员,而charproxy是cowstring的私有类(私有数据类型),所以在外部类中也要声明友元
//ostream& operator<<(ostream& os, const CowString::CharProxy& rhs)
//{
//	if (rhs._idx < rhs._self.size())
//	{
//		os << rhs._self._pstr[rhs._idx];
//	}
//	else
//	{
//		cout << "out of range" << endl;
//	}
//	return os;
//}//读操作的处理逻辑
//一种方法通过下标访问进行读操作但是都会对数据进行更改
//char& CowString::operator[](size_t idx)
//{
//	if (idx < size())
//	{
//		return _pstr[idx];
//	}
//	else//越界访问 
//	{
//		cout << "out of range" << endl;
//		//不能返回局部变量,不能返回右值,所以创建了一个nullchar
//		static char nullchar = '\0';
//		return nullchar;
//	}
//}//写操作的处理逻辑
//一种方法通过下标访问进行写操作,进行读操作也会进行深拷贝所以会造成效率下降
//char& CowString::operator[](size_t idx)
//{
//	if (idx < size())
//	{
//		//如果引用计数大于1,就进行深拷贝申请一块新的空间
//		if (use_count() > 1)
//		{
//			//原本空间引用计数减1
//			decreaseRefCount();
//			//深拷贝
//			char* tmp = malloc(_pstr);
//			strcpy(tmp, _pstr);
//			//改变指向
//			_pstr = tmp;
//			//初始化新空间的引用计数
//			increaseRefCount();
//		}
//		//没有直接返回下标引用
//		return _pstr[idx];
//	}
//	else//越界访问 
//	{
//		cout << "out of range" << endl;
//		//不能返回局部变量,不能返回右值,所以创建了一个nullchar
//		static char nullchar = '\0';
//		return nullchar;
//	}
//}void test0()
{CowString str1;CowString str2 = str1;cout << "str1:" << str1 << endl;cout << "str2:" << str2 << endl;cout << str1.use_count() << endl;cout << str2.use_count() << endl;CowString str3 = "hello";CowString str4 = str3;cout << "str3:" << str3 << endl;cout << "str4:" << str4 << endl;cout << str3.use_count() << endl;cout << str4.use_count() << endl;cout << endl;str2 = str3;cout << "str1:" << str1 << endl;cout << "str2:" << str2 << endl;cout << "str3:" << str3 << endl;cout << "str4:" << str4 << endl;cout << str1.use_count() << endl;cout << str2.use_count() << endl;cout << str3.use_count() << endl;cout << str4.use_count() << endl;
}
void test1()
{CowString str1 = "hello";CowString str2 = str1;cout << str1[0] << endl;cout << "str1:" << str1 << endl;cout << "str2:" << str2 << endl;cout << str1.use_count() << endl;cout << str2.use_count() << endl;str2[0] = 'H';cout << "str1:" << str1 << endl;cout << "str2:" << str2 << endl;cout << str1.use_count() << endl;cout << str2.use_count() << endl;
}
int main()
{//test0();test1();return 0;
}

短字符串优化(SSO)

当字符串的字符数小于等于15时, buffer直接存放整个字符串;当字符串的字符数大于15时, buffer 存放的就是一个指针,指向堆空间的区域。这样做的好处是,当字符串较小时,直接拷贝字符串,放在 string内部,不用获取堆空间,开销小。

union表示共用体,允许在同一内存空间中存储不同类型的数据。共用体的所有成员共享一块内存,但是每次只能使用一个成员。

class string {union Buffer{char * _pointer;char _local[16];};size_t _size;size_t _capacity;Buffer _buffer;
};

我发现在堆上的时候vs和vscode的编译器的规则好像不太一样,在栈上的规则是一样的,容量就是15,但是在堆上好像规则不太一样

先展示vs的环境:

再展示vscode的环境(G++):

可以看出g++编译器在堆上的空间的时候,他的空间就是用多少开多少,而vs环境下,他的开空间的规则就和vector动态数组的规则好像当空间满了之后会自动扩容。

短字符串优化的简单实现

#define _CRT_SECURE_NO_WARNINGS 1;
#include <iostream>
#include <string>
using std::endl;
using std::cout;
using std::ostream;
class String{
public://构造函数,使用一个char类型指针进行初始化String(const char* pstr){//strlen(char *) 传的参数是 char *_size = strlen(pstr);//判断使用栈上空间还是堆上空间if (_size <= 15)//使用栈上的空间{//给其他数据成员进行初始化_capacity = 15;//sizeof(数组名本质也是指针)所以sizeof()传的本质也是指针//先将数组进行清理memset(_buffer._local, 0, sizeof(_buffer._local));//进行浅拷贝字符串复制strcpy(_buffer._local, pstr);}else {//使用堆上的空间_capacity = _size;//使用指针申请空间_buffer._pointer = new char[strlen(pstr) + 1]();strcpy(_buffer._pointer, pstr);}}//拷贝构造String(const String& rhs):_size(rhs._size),_capacity(rhs._capacity){//还是要看字符串有多大if (_size <= 15){//sizeof(数组名本质也是指针)所以sizeof()传的本质也是指针//先将数组进行清理memset(_buffer._local, 0, sizeof(_buffer._local));//进行浅拷贝字符串复制strcpy(_buffer._local, rhs._buffer._local);}else{//这里要多申请一个空字符存放 '\0'_buffer._pointer = new char[_capacity +1]();strcpy(_buffer._pointer, rhs._buffer._pointer);}}//析构函数~String(){//对于短字符串,由于是联合体使用的是同一块空间所以_local如果保存了内容//也会导致_pointer不是空指针//这种情况也不应该直接使用pointer这个成员来判空然后回收//if(_buffer._pointer)if (_size > 15){delete[] _buffer._pointer;_buffer._pointer = nullptr;}}//成员访问运算符重载char& operator[](size_t idx){//先判断是否越界访问if (idx > _size - 1){cout << "out of range!" << endl;static char nullchar = '\0';return nullchar;}else{if (_size > 15){return _buffer._pointer[idx];}else{return _buffer._local[idx];}}}//输出运算符重载函数使用友元的形式friend ostream& operator<<(ostream& os, const String& rhs);
private:union Buffer{char* _pointer;char _local[16];};size_t _size;size_t _capacity;//联合体对象Buffer _buffer;
};
ostream& operator<< (ostream& os, const String& rhs)
{if (rhs._size > 15){os << rhs._buffer._pointer;}else {os << rhs._buffer._local;}return os;
}
void test()
{String str1("hello");cout << str1 << endl;cout << &str1 << endl;printf("%p\n", &str1[0]);cout << str1[0] << endl;str1[0] = 'H';cout << str1 << endl;cout << endl;String str2("hello, world!!!!!");cout << str2<< endl;cout << &str2 << endl;printf("%p\n", &str2[0]);cout << str2[0] << endl;str2[0] = 'H';cout << str2 << endl;cout << endl;String str3 = str1;cout << str3 << endl;String str4 = str2;cout << str4 << endl;
}
int main()
{test();return 0;
}

最佳策略

Facebook提出的最佳策略,将三者进行结合:

因为以上三种方式,都不能解决所有可能遇到的字符串的情况,各有所长,又各有缺陷。综合考虑所有情况之后,facebook开源的folly库中,实现了一个fbstring, 它根据字符串的不同长度使用不同的拷贝策略, 最终每个fbstring对象占据的空间大小都是24字节。

  1. 很短的(0~22)字符串用SSO,23字节表示字符串(包括'\0'),1字节表示长度

  2. 中等长度的(23~255)字符串用eager copy,8字节字符串指针,8字节size,8字节capacity.

  3. 很长的(大于255)字符串用COW, 8字节指针(字符串和引用计数),8字节size,8字节capacity.

相关文章:

std::string的底层实现 (详解)

目录 std::string的底层实现* 写时复制原理探究 CowString代码初步实现 短字符串优化&#xff08;SSO&#xff09; 最佳策略 std::string的底层实现* 我们都知道&#xff0c; std::string的一些基本功能和用法了&#xff0c;但它底层到底是如何实现的呢? 其实在std::stri…...

蓝桥杯 11. 最大距离

最大距离 原题目链接 题目描述 在数列 a1, a2, ⋯, an 中&#xff0c;定义两个元素 ai 和 aj 的距离为&#xff1a; |i - j| |ai - aj|即元素下标的距离加上元素值的差的绝对值&#xff0c;其中 |x| 表示 x 的绝对值。 给定一个数列&#xff0c;请找出元素之间最大的元素…...

【运维】使用 DataX 实现 MySQL 到 PostgreSQL 的数据同步

🚀 使用 DataX 实现 MySQL 到 PostgreSQL 的数据同步 在日常的数据开发工作中,数据同步是一项极其常见的任务。而 DataX 作为阿里开源的一款通用数据同步工具,支持多种数据源之间的互通,使用简单,扩展性强,非常适合进行结构化数据的迁移和同步。 本文将详细介绍如何通…...

Mangodb基本概念和介绍,Mango三个重要的概念:数据库,集合,文档

MongoDB基本概念和介绍 MongoDB 是一个开源的、基于分布式文件存储的NoSQL数据库&#xff0c;由 C 编写。 它的主要特点是&#xff1a; 使用**面向文档&#xff08;Document-Oriented&#xff09;**的存储方式&#xff0c;不是传统的表格行列模式。存储的数据格式是BSON&…...

什么是ICSP编程

ICSP编程介绍 ICSP 编程&#xff08;In-Circuit Serial Programming&#xff09;&#xff0c;即“在线串行编程”&#xff0c;是一种通过 SPI 协议 直接对微控制器&#xff08;如 Arduino 的 ATmega328P&#xff09;进行编程的技术&#xff0c;无需移除芯片。它常用于以下场景…...

LeetCode 155题解 | 最小栈

最小栈 一、题目链接二、题目三、算法原理思路1&#xff1a;用一个变量存储最小元素思路2&#xff1a;双栈普通栈和最小栈 四、编写代码五、时间复杂度 一、题目链接 最小栈 二、题目 三、算法原理 栈用数组、链表实现都行&#xff0c;最主要的就是在能在常数时间内检索到最…...

Modal 深度解析:无服务器高性能计算平台实战指南

概览 Modal 是一个 “零配置,无需 YAML” 的云函数平台,通过将你的 Python 代码打包进容器并在 Modal 自建的云环境中执行,实现秒级启动、按秒计费、自动弹性扩缩容等能力。它构建在高性能 Rust 容器堆栈与 gVisor 沙箱之上,为大规模 AI 推理、批量数据处理、作业调度、Web…...

数字逻辑--期末大复习

写卷子前准备&#xff1a;二进制串、卡诺图的数序、分析与设计的步骤&#xff0c;直接写上省的忘了 进制转化 二进制 刚开始做题前可以把0-9次方的列出来 十进制转二进制:不断除以2得到余数&#xff0c;直到商为0&#xff0c;再将余数倒着拼起来即可。 如十六进制&#xff…...

【Redis】缓存|缓存的更新策略|内存淘汰策略|缓存预热、缓存穿透、缓存雪崩和缓存击穿

思维导图&#xff1a; Redis最主要的用途&#xff0c;三个方面&#xff1a; 1.存储数据&#xff08;内存数据库&#xff09; 2.缓存&#xff08;redis最常用的场景&#xff09; 3.消息队列 一、什么是缓存 我们知道对于硬件的访问速度来说&#xff0c;通常情况下&#xff1…...

kubelet 清理资源以缓解磁盘压力

kubelet 资源清理缓解磁盘压力指南 在 Kubernetes 集群中&#xff0c;当节点磁盘压力过大时&#xff0c;可通过以下几种方式利用 kubelet 清理资源&#xff0c;从而缓解磁盘压力。 一、镜像垃圾回收 自动回收 kubelet 内置了镜像垃圾回收机制&#xff0c;其行为由配置参数控…...

机器人“跨协议对话”秘籍:EtherNet IP转PROFINET网关应用实录

近期&#xff0c;我们工厂在进行自动化生产线升级改造时&#xff0c;引进了一批全新的机器人手臂设备。这批机器人采用EtherNet/IP通信协议&#xff0c;而生产线上原有的终端控制器则使用PROFINET协议。由于两种协议在通信标准和数据格式上存在差异&#xff0c;导致机器人手臂无…...

松下机器人快速入门指南(2025年更新版)

松下机器人快速入门指南&#xff08;2025年更新版&#xff09; 松下机器人以其高精度、稳定性和易用性在工业自动化领域广泛应用。本文将从硬件配置、参数设置、手动操作、编程基础到维护保养&#xff0c;全面讲解松下机器人的快速入门方法&#xff0c;帮助新手快速掌握核心操…...

开启健康养生,重塑生活品质

当你习惯性地用咖啡开启忙碌的一天&#xff0c;当熬夜加班成为生活常态&#xff0c;当外卖占据一日三餐&#xff0c;或许未曾察觉&#xff0c;健康正悄然亮起红灯。在快节奏的现代生活中&#xff0c;健康养生不再是可选项&#xff0c;而是关乎生活质量与生命活力的必答题&#…...

百度「心响」:通用超级智能体,重新定义AI任务执行新范式

在AI技术从“对话交互”迈向“任务执行”的转折点&#xff0c;百度于2025年4月正式推出移动端超级智能体应用——心响。这款以“AI任务完成引擎”为核心的创新产品&#xff0c;被誉为“AI指挥官”&#xff0c;通过自然语言交互实现复杂任务的全流程托管&#xff0c;覆盖知识解析…...

AXPA17388: 4x45W 车用AB类四通道桥式输出音频功率放大器

AXPA17388是采用BCD(双极型&#xff0c;CMOS&#xff0c;DMOS)工艺技术设计的四通道桥式输出AB类车用音频功率放大器&#xff0c;采用完全互补的P型/ N型输出结构&#xff0c; 具有轨到轨的输出电压摆幅&#xff0c;高输出电流&#xff0c;具有出色的低失真性能。 AXPA17388可以…...

【codeforces 2086d】背包+组合数学

【codeforces 2086d】背包组合数学 Problem - D - Codeforces 题意&#xff1a; 给出字符串中每个字符的出现次数 c i ( 1 ≤ i ≤ 26 ) c_i(1 \leq i \leq 26) ci​(1≤i≤26)。现构造一个字符串&#xff0c;要求任意相同字母之间的距离必须是偶数。求满足要求的字符串的数量…...

[特殊字符]OCR,给交通领域开了“外挂”?

OCR 技术是什么 宝子们&#xff0c;OCR 其实就是光学字符识别&#xff08;Optical Character Recognition&#xff09;的英文缩写。简单来说&#xff0c;它能让电子设备&#xff0c;比如扫描仪、摄像头这些&#xff0c;像长了眼睛一样&#xff0c;“看” 懂图片或文档里的文字&…...

【语法】C++继承中遇到的问题及解决方法

目录 1.子类构造函数中初始化父类成员 2.子类显式调用父类的析构函数 第一种说法&#xff1a;重定义 反驳&#xff1a; 第二种说法&#xff1a;operator~ 3.因编译器版本过低而出现错误 贴主在学习C的继承时&#xff0c;遇到了很多问题&#xff0c;觉得很变态&#xff0c…...

【自然语言处理与大模型】LangChain大模型应用框架入门②

本文介绍LangChain的另一个重要组件——提示词模板&#xff08;Prompt Template&#xff09;组件&#xff0c;它主要用于将用户输入和参数转换为语言模型可理解的指令。有助于引导模型生成符合预期的响应&#xff0c;帮助其更好地理解上下文&#xff0c;从而输出相关且连贯的语…...

首页数据展示

排版 现在做首页的排版&#xff0c;依旧是偷antd里面的东西 使用card包裹list的样式 import React from react import axios import { Card, Col, Row, List } from antd import { EditOutlined, EllipsisOutlined, SettingOutlined } from ant-design/icons; import { Avat…...

推荐系统实验指标置信度:p值核心原理与工程应用指南

目录 一、推荐系统实验中的置信度困境二、p值核心原理&#xff1a;从假设检验到推荐场景适配2.1 基础概念与数学定义2.2 通俗版本核心白话总结&#xff1a; 2.2 推荐系统指标分类与统计方法 三、推荐系统实验p值计算全流程3.1 实验设计阶段&#xff1a;流量分配与检验效能3.2 数…...

linux FTP服务器搭建

FTP服务器搭建 系统环境&#xff1a;ubuntu 搭建方式&#xff1a;win系统下通过ssh连接ubuntu&#xff0c;搭建FTP服务 一、ssh连接 ssh -p 端口 用户名IP ssh -p 22 ubuntu192.168.1.109 密码&#xff1a;ubuntu123456 二、安装配置FTP服务器 1、安装 sudo apt install v…...

如何搭建一个简单的文件服务器的方法

搭建一个简易的文件服务器可以让你在局域网或互联网中共享文件&#xff0c;方便不同设备之间的访问与管理。以下是基于常见平台(Windows、Linux)分别介绍如何搭建一个简单的文件服务器的方法&#xff0c;适合个人或小型办公环境使用。 一、文件服务器的准备工作 所需条件&#…...

通信原理第七版与第六版的区别附pdf

介绍 我用夸克网盘分享了「通信原理 第7版》樊昌信」&#xff0c; 链接&#xff1a;https://pan.quark.cn/s/be7c5af4cdce 《通信原理&#xff08;第7版&#xff09;》是在第6版的基础上&#xff0c;为了适应当前通信技术发展和教学需求&#xff0c;并吸取了数十所院校教师的反…...

【工具】PDF转HTML

【工具】PDF转HTML 可通过命令执行&#xff0c; 集成到项目中 pdf2htmlEX windows系统可执行版下载地址&#xff1a; http://soft.rubypdf.com/software/pdf2htmlex-windows-version https://github.com/coolwanglu/pdf2htmlEX .\pdf2htmlEX.exe --zoom 1.8 a.pdf .\pdf2html…...

Latex全面汇总

文章目录 简介1.基本使用中文编码的方式2.文章标题日期等3.加粗斜体等格式4.章节问题5.图片问题6.列表7.数学公式8.表格9.常用的latex网站汇总总结 简介 Latex 基本使用教程,主要还是为manim而准备的.   现在发现用typora来记录笔记更方便些&#xff0c;csdn用的就很少了&…...

AI日报 - 2025年04月30日

&#x1f31f; 今日概览(60秒速览) ▎&#x1f916; AGI突破 | 扎克伯格预言通用智能将超越个体&#xff0c;Neuralink助ALS患者思维交流 通用智能系统潜力巨大&#xff0c;脑机接口实现重大应用突破。 ▎&#x1f4bc; 商业动向 | 阿里巴巴发布Qwen3&#xff0c;xAI推Grok 3 M…...

redis高级进阶

1.redis主从复制 redis主从复制1 2.redis哨兵模式 哔哩哔哩视频 redis哨兵模式1 redis哨兵模式2 redis哨兵模式3 3.redis分片集群 redis分片集群1 redis分片集群2 redis分片集群3...

【android bluetooth 协议分析 06】【l2cap详解 11】【l2cap连接超时处理逻辑介绍】

我们在使用蓝牙的过程中&#xff0c; 当上层 应用 断开所有的 profile 后&#xff0c; 协议栈就会帮我们下发 disconnect 命令。本节就让笨叔&#xff0c; 带大家一起梳理这块内容&#xff0c;具体在协议栈如何处理的。 梳理开始前&#xff0c; 先思考一下。 我们为什么要梳理…...

Spring、Spring MVC 与 Spring Boot 的关系与核心用途

1. 三者关系图解 ------------------- | Spring Boot | → 基于 Spring&#xff0c;简化配置与部署 -------------------▲| 依赖 ------------------- | Spring Framework | → 核心容器&#xff08;IoC/AOP&#xff09;与基础模块 -------------------▲| 扩展 ---…...

如何搭建spark yarn 模式的集群集群

&#xff08;一&#xff09;什么是SparkONYarn模式 Spark on YARN&#xff08;Yet Another Resource Negotiator&#xff09;是 Spark 框架在 Hadoop 集群中运行的一种部署模式&#xff0c;它借助 Hadoop YARN 来管理资源和调度任务。 架构组成 ResourceManager&#xff1a;作为…...

共探蓝海赛道增长新方法 阿里国际站智能AI全球买家分析峰会在深落幕

来源&#xff1a;深圳晚报 随着全球贸易环境不断变化&#xff0c;跨境电商已成为推动企业发展的重要动力。为帮助企业更好地应对新的市场挑战&#xff0c;阿里巴巴国际站深莞惠大区于4月29日举办了“万亿商机 蓝海新市场”智能AI全球买家分析峰会&#xff0c;现已圆满落幕&…...

今日行情明日机会——20250429

指数依然在区间震荡&#xff0c;等待方向&#xff0c;重点关注决定大盘方向的板块&#xff0c;如证券的走势~ 2025年4月29日涨停主要行业方向分析 一、核心主线方向 一季报增长&#xff08;业绩驱动资金避险&#xff09; • 涨停家数&#xff1a;16家。 • 代表标的&#xff…...

什么是缓存?在NGINX中如何配置缓存以提升性能?

大家好&#xff0c;我是锋哥。今天分享关于【什么是缓存&#xff1f;在NGINX中如何配置缓存以提升性能&#xff1f;】面试题。希望对大家有帮助&#xff1b; 什么是缓存&#xff1f;在NGINX中如何配置缓存以提升性能&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java…...

价值投资笔记:企业护城河——虚假陷阱与隐性壁垒的深度解析

一、护城河的本质与误判风险 护城河是企业抵御竞争、维持超额利润的核心能力。然而&#xff0c;市场中充斥着大量“虚假护城河”&#xff0c;它们看似构成壁垒&#xff0c;实则脆弱易碎。晨星公司研究显示&#xff0c;超过60%的企业竞争优势被误判为护城河&#xff0c;投资者需…...

2025年04月29日Github流行趋势

项目名称&#xff1a;Deep-Live-Cam 项目地址url&#xff1a;https://github.com/hacksider/Deep-Live-Cam项目语言&#xff1a;Python历史star数&#xff1a;52291今日star数&#xff1a;380项目维护者&#xff1a;hacksider, KRSHH, vic4key, pereiraroland26, kier007项目简…...

docker排查OOM Killer

文章目录 一.检查1.内存不足 (OOM Killer)2. CPU 资源限制3. 存储空间不足4. 应用自身崩溃5. 健康检查失败针对性建议 二.内存不足问题根源解决方案&#xff08;按优先级排序&#xff09;1. 紧急措施&#xff1a;立即释放内存2. 启用 Swap 交换空间&#xff08;必须做&#xff…...

leetcode继续c++10/100

不应该是10-13-3吗 ChatGLM 引用 从代码片段来看&#xff0c;函数 findAnagrams 的目的是在字符串 s 中找到所有与字符串 p 是字母异位词的子串的起始索引。 代码中有一些调试输出语句&#xff0c;这些语句可能会影响程序的正常逻辑。具体来说&#xff1a; cpp 复制 cout …...

Kubernetes集群使用Harbor容器镜像仓库

实验环境 一、容器镜像仓库Harbor部署 1、配置主机名 192.168.10.14&#xff1a; hostnamectl set-hostname harbor 2、安装Docker wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum -y install docker-…...

归并排序排序总结

1. 归并排序 1.1 基本思想 归并排序&#xff08;Merge Sort&#xff09;是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。它的基本思想是将一个数组分成两个子数组&#xff0c;分别对这两个子数组进行排序&#xff0c;然后将排好序的子数组合并…...

面试手撕——快速排序

思路 partition方法将整个区间分为两部分&#xff0c;一部分比pivot小&#xff0c;一部分比pivot大&#xff0c; i表示&#xff0c;小于等于pivot的下标&#xff0c;j表示当前遍历到哪一个元素了&#xff0c;如果发现当前元素j小于等于pivot&#xff0c;i&#xff0c;在i1的位…...

大模型微调之LLaMA-Factory 系列教程大纲

LLaMA-Factory 系列教程大纲 一、基础入门篇&#xff1a;环境搭建与核心功能解析 环境部署与框架特性 硬件要求&#xff1a; 单机训练&#xff1a;推荐 24GB 显存 GPU&#xff08;如 RTX 4090&#xff09;&#xff0c;支持 7B-32B 模型 LoRA 微调。分布式训练&#xff1a;2 块…...

26考研 | 王道 | 计算机网络 | 第一章 计算机网络的体系结构

26考研 | 王道 | 第一章 计算机网络的体系结构 文章目录 26考研 | 王道 | 第一章 计算机网络的体系结构1.1 计算机网络概述1.计算机网络的概念2.计算机网络的组成**从组成部分看****从工作方式看****从逻辑功能看** 3.计算机网络的功能4.电路交换、报文交换、分组交换1. 电路交…...

CentosLinux系统crontab发现执行删除命令失效解决方法

权限或安全策略限制 ​​可能场景​​&#xff1a; ​​### ​​目录权限冲突​​&#xff1a; 你的目录权限为 drwxr-xr-x&#xff08;属主 mssql&#xff09;&#xff0c;但 cron 任务以 root 执行。 ​​风险点​​&#xff1a;若目录内文件属主为 mssql 且权限为 700&…...

UniApp页面路由详解

一、路由系统概述 1.1 路由机制原理 UniApp基于Vue.js实现了一套跨平台的路由管理系统&#xff0c;其核心原理是通过维护页面栈来管理应用内不同页面之间的跳转关系。在小程序端&#xff0c;UniApp的路由系统会映射到对应平台的原生导航机制&#xff1b;在H5端则基于HTML5 Hi…...

探索无人机模拟环境的多元景象及AI拓展

无人驾驶飞行器&#xff08;UAVs&#xff09;在各行各业的迅速普及&#xff0c;从农业和检测到空中操作和人机交互等令人兴奋的前沿领域&#xff0c;都引发了一个关键需求&#xff1a;强大而逼真的模拟环境。直接在物理硬件上测试尖端算法存在固有的风险——成本高昂的坠机、中…...

Java后端开发day39--方法引用

&#xff08;以下内容全部来自上述课程&#xff09; 1.1 含义 把已经有的方法拿过来用&#xff0c;当作函数式接口中抽象方法的方法体。 已经有的方法&#xff1a;可以是Java自己写的&#xff0c;也可以是第三方的。 示例语句&#xff1a; &#xff1a;&#xff1a;是方法引…...

C# 14 field keyword:属性简化新利器

引言 在 C# 的不断发展历程中&#xff0c;每一个新版本都带来了令人期待的新特性&#xff0c;而 C# 14 中的 field keyword 无疑是其中一颗璀璨的明星 。对于广大 C# 开发者来说&#xff0c;属性的使用频率极高&#xff0c;而 field keyword 的出现&#xff0c;为我们简化属性…...

破茧成蝶:一家传统制造企业的年轻化转型之路

2004 年&#xff0c;在长三角的轻工业重镇杭集&#xff0c;一家专注于植毛机器设备研发的小工厂悄然诞生。那时&#xff0c;它以 “齿轮与钢铁” 为语言&#xff0c;为全国近千家牙刷生产企业提供核心装备&#xff0c;用机械臂的精准律动&#xff0c;编织着传统制造业的经纬。然…...

【语法】C++的继承

目录 继承基本语法&#xff1a; protected访问限定符&#xff1a; 子类和父类之间的赋值兼容规则&#xff1a; 重定义(隐藏)&#xff1a; 继承中的友元/继承中的静态成员&#xff1a; 子类中的默认成员函数 构造函数/拷贝构造函数&#xff1a; 赋值重载函数&#xff…...