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的思想实现的,但是最好能够掌握其发展过程中的不同设计思想。

深拷贝及string相关运算符重载

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

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

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

自定义String类实现
#include <iostream>
#include <string.h>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
// 自定义String类
class String{
public:
String()
:_pstr(new char[1]())
{
cout << "String()" << endl;
}
// String s1("hello");
// String s2 = "hello";
String(const char *pstr)
:_pstr(new char[strlen(pstr)+1]())
{
cout << "String(const char *)" << endl;
strcpy(_pstr,pstr);
}
// String s2(s1);
// String s3 = s1;
String(const String &rhs)
:_pstr(new char[strlen(rhs._pstr)+1]())
{
cout << "String(const String &)" << endl;
strcpy(_pstr,rhs._pstr);
}
~String()
{
cout << "~String()" << endl;
if(_pstr)
{
delete [] _pstr;
_pstr = nullptr;
}
}
// String s1("hello");
// String s2;
// s2 = s1;
String& operator=(const String &rhs)
{
cout << "String& operator=(const String &rhs)" << endl;
if(this != &rhs)
{
delete [] _pstr;
_pstr = nullptr;
_pstr = new char[strlen(rhs._pstr) + 1]();
strcpy(_pstr,rhs._pstr);
}
return *this;
}
// s1 = "hello";
String& operator=(const char * pstr)
{
cout << "String& operator=(const char * pstr)" << endl;
String tmp(pstr); //增量开发; 调用拷贝构造String(const char *)
*this = tmp;
return *this;
}
// s1 += s2;
String& operator+=(const String &rhs)
{
cout << "String& operator+=(const String &rhs)" << endl;
String tmp;
if(tmp._pstr)
{
delete [] tmp._pstr;
}
tmp._pstr = new char[strlen(_pstr) + 1]();
strcpy(tmp._pstr,_pstr);
delete [] _pstr;
_pstr = new char[strlen(_pstr) + strlen(rhs._pstr) + 1]();
strcpy(_pstr,tmp._pstr);
strcat(_pstr,rhs._pstr);
return *this;
}
// s1 += "hello";
String& operator+=(const char * pstr)
{
cout << "String& operator+=(const char * pstr)" << endl;
String tmp(pstr);
*this += tmp;
return *this;
}
// String s1("hello");
// s1[0];
char& operator[](std::size_t index)
{
if(index < size())
{
return _pstr[index];
}
else
{
static char nullchar = '\0';
return nullchar;
}
}
// const String s1("hello");
// s1[0];
const char& operator[](std::size_t index) const
{
if(index < size())
{
return _pstr[index];
}
else
{
static char nullchar = '\0';
return nullchar;
}
}
std::size_t size() const
{
return strlen(_pstr);
}
const char* c_str() const
{
return _pstr;
}
// 友元函数
friend bool operator==(const String &, const String &);
friend bool operator!=(const String &, const String &);
// 比较大小
friend bool operator<(const String &, const String &);
friend bool operator>(const String &, const String &);
friend bool operator<=(const String &, const String &);
friend bool operator>=(const String &, const String &);
// 输入输出运算符重载
friend std::ostream & operator<<(std::ostream &, const String &);
friend std::istream & operator>>(std::istream &, String &);
// 私有成员
private:
char* _pstr;
};
bool operator==(const String & lhs, const String & rhs)
{
return !strcmp(lhs._pstr,rhs._pstr);
}
bool operator!=(const String & lhs, const String & rhs)
{
return strcmp(lhs._pstr,rhs._pstr);
}
bool operator<(const String & lhs, const String & rhs)
{
return strcmp(lhs._pstr,rhs._pstr) < 0;
}
bool operator>(const String & lhs, const String & rhs)
{
return strcmp(lhs._pstr,rhs._pstr) > 0;
}
bool operator<=(const String & lhs, const String & rhs)
{
return strcmp(lhs._pstr,rhs._pstr) <= 0;
}
bool operator>=(const String & lhs, const String & rhs)
{
return strcmp(lhs._pstr,rhs._pstr) >= 0;
}
// cout << string("hello");
std::ostream & operator<<(std::ostream & os, const String & rhs)
{
if(rhs._pstr)
{
os << rhs._pstr;
}
return os;
}
std::istream & operator>>(std::istream & is, String & rhs)
{
if(rhs._pstr)
{
delete [] rhs._pstr;
rhs._pstr = nullptr;
}
vector buffer;
char ch;
while((ch = is.get()) != '\n')
{
buffer.push_back(ch);
}
rhs._pstr = new char[buffer.size() + 1]();
strncpy(rhs._pstr,&buffer[0],buffer.size());
return is;
}
// s1 = s2 + s3;
String operator+(const String & lhs, const String & rhs)
{
cout << "String operator+(const String &, const String &)" << endl;
String tmp(lhs);
tmp += rhs;
return tmp;
}
// s1 + "hello";
String operator+(const String & lhs, const char * pstr)
{
cout << "String operator+(const String &, const char *)" << endl;
String tmp(lhs);
tmp += pstr;
return tmp;
}
// "hello" + s1;
String operator+(const char * pstr, const String & rhs)
{
cout << "String operator+(const char *, const String &)" << endl;
String tmp(pstr);
tmp += rhs;
return tmp;
}
// 测试用例
void test0()
{
String s1; // 默认构造 String()
cout << "s1 = " << s1 << endl;
String s2("hello"); // 拷贝构造 String(const char *pstr)
String s3 = "world"; // 拷贝构造 String(const char *pstr)
String s4(s2); // 拷贝构造 String(const String &rhs)
String s5 = s3; // 拷贝构造 String(const String &rhs)
s1 = s2; // 赋值运算符重载 String &operator=(const String &rhs)
s1 = "hello,"; // 赋值运算符重载 String &operator=(const char *pstr)
s1 += s3; // String &operator+=(const String &rhs)
s1 += "!"; // String &operator+=(const char *pstr)
s1[0] = 'H'; // char &operator[](std::size_t index)
const String s6 = "C++";
cout << s6[0] << endl;
// s6[0] = 'B'; // 错误 const char &operator[](std::size_t index) const
// 比较字符串
String s7 = "hello";
String s8 = "hello";
String s9 = "he";
cout << (s7 == s8) << endl;
cout << (s8 <= s9) << endl;
// 输入输出运算符重载
cin >> s1;
cout << s1 << endl;
//
String s10("B");
String s11("C");
s1 = s10 + s11;
cout << s1 << endl;
s1 = s1 + "D";
cout << s1 << endl;
s1 = "A" + s1;
cout << s1 << endl;
}
int main()
{
test0();
return 0;
}
测试用例运行效果


写时复制原理探究

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

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

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



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

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

引用计数减到1,才真正回收堆空间

CowString代码初步实现

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

01_CoWString.cc
#include <iostream>
#include <string.h>
using std::cout;
using std::endl;
using std::ostream;
//
class CoWString{
public:
CoWString();
~CoWString();
CoWString(const CoWString & rhs);
CoWString(const char * pstr);
CoWString & operator=(const CoWString & rhs);
char & operator[](size_t idx);
const char * c_str() const{ return _pstr; }
size_t size() const{ return strlen(_pstr); }
int use_count(){ return *(int*)(_pstr - kRefCountLength); }
private:
char * malloc(const char * pstr = nullptr){
if(pstr){
return new char[strlen(pstr) + kRefCountLength + 1]() + kRefCountLength;
}else{
return new char[1 + kRefCountLength]() + kRefCountLength;
}
}
void initRefCount(){
*(int*)(_pstr - kRefCountLength) = 1;
}
void release(){
decreaseRefCount();
if(use_count() == 0){
delete [] (_pstr - kRefCountLength);
_pstr = nullptr;
cout << "delete heap" << endl;
}
}
void increaseRefCount(){
++*(int*)(_pstr - kRefCountLength);
}
void decreaseRefCount(){
--*(int*)(_pstr - kRefCountLength);
}
// 私有成员
static const int kRefCountLength = 4;
char * _pstr;
};
// 成员函数实现
CoWString::CoWString()
:_pstr(malloc())
{
cout << "CoWString()" << endl;
initRefCount();
}
CoWString::~CoWString()
{
release();
}
CoWString::CoWString(const CoWString & rhs)
:_pstr(rhs._pstr)
{
increaseRefCount();
cout << "CowString(const CowString&)" << endl;
}
CoWString::CoWString(const char * pstr)
:_pstr(malloc(pstr))
{
strcpy(_pstr,pstr);
initRefCount();
cout << "CoWString(const char *)" << endl;
}
CoWString & CoWString::operator=(const CoWString & rhs)
{
if(this != &rhs){ // 1.还需要考虑自复制
cout << "CowString& operator=(const CowString&)" << endl;
release(); // 2.判断是否回收,原本空间的引用计数-1
_pstr = rhs._pstr; // 3. 浅拷贝
increaseRefCount();
}
return *this;
}
char & CoWString::operator[](size_t idx)
{
if(idx < size())
{
return _pstr[idx];
}
else
{
cout << "out of range" << endl;
static char nullchar = '\0';
return nullchar;
}
}
ostream & operator<<(ostream & os, const CoWString & rhs)
{
os << rhs.c_str();
return os;
}
// 测试用例
void test()
{
CoWString str1;
cout << str1.use_count() << endl;
CoWString str2 = str1;
cout << "str1:" << str1 << endl;
cout << "str2:" << str2 << endl;
cout << str1.use_count() << endl;
cout << str2.use_count() << endl;
cout << "------------------------" << endl;
CoWString str3("hello");
cout << "str3:" << str3 << endl;
cout << str3.use_count() << endl;
CoWString str4 = str3;
cout << "str3:" << str3 << endl;
cout << "str4:" << str4 << endl;
cout << str3.use_count() << endl;
cout << str4.use_count() << endl;
cout << "-------------------------" << endl;
str1 = 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;
cout << "-------------------------" << endl;
//读操作不需要重新开辟空间
cout << str1[0] << endl;
//写操作应该重新开辟空间
//也就是写时复制
str1[0] = 'H';
cout << str1[0] << endl;
cout << str3[0] << endl;
cout << str4[0] << endl;
}
int main()
{
test();
return 0;
}
测试用例运行效果


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

如果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[]函数返回是这个新类型的对象,然后在这个新类型中对<<和=进行重载,让这两个运算符能够处理新类型对象,从而分开了处理逻辑。

见02_CoWString.cc

02_CoWString.cc
#include <iostream>
#include <string.h>
using std::cout;
using std::endl;
using std::ostream;
//
class CoWString{
class CharProxy{
public:
CharProxy(CoWString & self, size_t idx)
:_self(self),
_idx(idx)
{}
char & operator=(char ch);
friend
ostream & operator<<(ostream & os, const CharProxy & rhs);
private:
CoWString & _self;
size_t _idx;
};
public:
CoWString();
~CoWString();
CoWString(const CoWString & rhs);
CoWString(const char * pstr);
CoWString & operator=(const CoWString & rhs);
CharProxy operator[](size_t idx);
const char * c_str() const{ return _pstr; }
size_t size() const{ return strlen(_pstr); }
int use_count(){ return *(int*)(_pstr - kRefCountLength); }
friend
ostream & operator<<(ostream & os, const CharProxy & rhs);
private:
char * malloc(const char * pstr = nullptr){
if(pstr){
return new char[strlen(pstr) + kRefCountLength + 1]() + kRefCountLength;
}else{
return new char[1 + kRefCountLength]() + kRefCountLength;
}
}
void initRefCount(){
*(int*)(_pstr - kRefCountLength) = 1;
}
void release(){
decreaseRefCount();
if(use_count() == 0){
delete [] (_pstr - kRefCountLength);
_pstr = nullptr;
cout << "delete heap" << endl;
}
}
void increaseRefCount(){
++*(int*)(_pstr - kRefCountLength);
}
void decreaseRefCount(){
--*(int*)(_pstr - kRefCountLength);
}
// 私有成员
static const int kRefCountLength = 4;
char * _pstr;
};
// 成员函数实现
CoWString::CoWString()
:_pstr(malloc())
{
cout << "CoWString()" << endl;
initRefCount();
}
CoWString::~CoWString()
{
release();
}
CoWString::CoWString(const CoWString & rhs)
:_pstr(rhs._pstr)
{
increaseRefCount();
cout << "CowString(const CowString&)" << endl;
}
CoWString::CoWString(const char * pstr)
:_pstr(malloc(pstr))
{
strcpy(_pstr,pstr);
initRefCount();
cout << "CoWString(const char *)" << endl;
}
CoWString & CoWString::operator=(const CoWString & rhs)
{
if(this != &rhs){ // 1.还需要考虑自复制
cout << "CowString& operator=(const CowString&)" << endl;
release(); // 2.判断是否回收,原本空间的引用计数-1
_pstr = rhs._pstr; // 3. 浅拷贝
increaseRefCount();
}
return *this;
}
CoWString::CharProxy CoWString::operator[](size_t idx)
{
return CharProxy(*this,idx);
}
ostream & operator<<(ostream & os, const CoWString & rhs)
{
os << rhs.c_str();
return os;
}
char & CoWString::CharProxy::operator=(char ch)
{
if(_idx < _self.size())
{
if(_self.use_count() > 1)
{
// 原本空间的引用计数-1
_self.decreaseRefCount();
// 深拷贝
char * pTemp = _self.malloc(_self._pstr);
strcpy(pTemp,_self._pstr);
// 修改指向
_self._pstr = pTemp;
// 初始化新空间的引用计数
_self.initRefCount();
}
// 执行写操作
_self._pstr[_idx] = ch;
return _self._pstr[_idx];
}
else
{
cout << "out of range" << endl;
static char nullchar = '\0';
return nullchar;
}
}
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;
}
// 测试用例
void test()
{
CoWString str1("hello");
CoWString str2(str1);
CoWString str3(str1);
//读操作不需要重新开辟空间
cout << str1[0] << endl;
/* operator<<(cout,str1.operator[](0)); */
cout << str1.use_count() << endl;
cout << str2.use_count() << endl;
cout << str3.use_count() << endl;
//如果有多个对象共享了空间
//一个对象进行写操作应该重新开辟空间
//也就是写时复制
str1[0] = 'H';
/* (str1.operator[](0)).operator=('H'); */
cout << str1 << endl;
cout << str2 << endl;
cout << str3 << endl;
cout << str1.use_count() << endl;
cout << str2.use_count() << endl;
cout << str3.use_count() << endl;
str1[0] = 'X';
cout << str1 << endl;
}
int main()
{
test();
return 0;
}
测试用例运行效果


对于读操作,还可以给CharProxy类定义类型转换函数来进行处理。见03_CoWString.cc

03_CoWString.cc
#include <iostream>
#include <string.h>
using std::cout;
using std::endl;
using std::ostream;
//
class CoWString{
class CharProxy{
public:
CharProxy(CoWString & self, size_t idx)
:_self(self),
_idx(idx)
{}
char & operator=(char ch);
operator char()
{
cout << "operator char()" << endl;
return _self._pstr[_idx];
}
private:
CoWString & _self;
size_t _idx;
};
public:
CoWString();
~CoWString();
CoWString(const CoWString & rhs);
CoWString(const char * pstr);
CoWString & operator=(const CoWString & rhs);
CharProxy operator[](size_t idx);
const char * c_str() const{ return _pstr; }
size_t size() const{ return strlen(_pstr); }
int use_count(){ return *(int*)(_pstr - kRefCountLength); }
private:
char * malloc(const char * pstr = nullptr){
if(pstr){
return new char[strlen(pstr) + kRefCountLength + 1]() + kRefCountLength;
}else{
return new char[1 + kRefCountLength]() + kRefCountLength;
}
}
void initRefCount(){
*(int*)(_pstr - kRefCountLength) = 1;
}
void release(){
decreaseRefCount();
if(use_count() == 0){
delete [] (_pstr - kRefCountLength);
_pstr = nullptr;
cout << "delete heap" << endl;
}
}
void increaseRefCount(){
++*(int*)(_pstr - kRefCountLength);
}
void decreaseRefCount(){
--*(int*)(_pstr - kRefCountLength);
}
// 私有成员
static const int kRefCountLength = 4;
char * _pstr;
};
// 成员函数实现
CoWString::CoWString()
:_pstr(malloc())
{
cout << "CoWString()" << endl;
initRefCount();
}
CoWString::~CoWString()
{
release();
}
CoWString::CoWString(const CoWString & rhs)
:_pstr(rhs._pstr)
{
increaseRefCount();
cout << "CowString(const CowString&)" << endl;
}
CoWString::CoWString(const char * pstr)
:_pstr(malloc(pstr))
{
strcpy(_pstr,pstr);
initRefCount();
cout << "CoWString(const char *)" << endl;
}
CoWString & CoWString::operator=(const CoWString & rhs)
{
if(this != &rhs){ // 1.还需要考虑自复制
cout << "CowString& operator=(const CowString&)" << endl;
release(); // 2.判断是否回收,原本空间的引用计数-1
_pstr = rhs._pstr; // 3. 浅拷贝
increaseRefCount();
}
return *this;
}
CoWString::CharProxy CoWString::operator[](size_t idx)
{
return CharProxy(*this,idx);
}
ostream & operator<<(ostream & os, const CoWString & rhs)
{
os << rhs.c_str();
return os;
}
char & CoWString::CharProxy::operator=(char ch)
{
if(_idx < _self.size())
{
if(_self.use_count() > 1)
{
// 原本空间的引用计数-1
_self.decreaseRefCount();
// 深拷贝
char * pTemp = _self.malloc(_self._pstr);
strcpy(pTemp,_self._pstr);
// 修改指向
_self._pstr = pTemp;
// 初始化新空间的引用计数
_self.initRefCount();
}
// 执行写操作
_self._pstr[_idx] = ch;
return _self._pstr[_idx];
}
else
{
cout << "out of range" << endl;
static char nullchar = '\0';
return nullchar;
}
}
// 测试用例
void test()
{
CoWString str1("hello");
str1[0] = 'X';
cout << str1 << endl;
// CharProxy提供了类型转换函数 CharProxy->char
// <<处理不了CharProxy对象时会尝试进行转换
cout << str1[0] << endl;
std::string str2(5,str1[0]);
cout << str2 << endl;
}
int main()
{
test();
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;
};

最佳策略

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的底层实现的更多相关文章

  1. std::string的Copy-on-Write:不如想象中美好(VC不使用这种方式,而使用对小字符串更友好的SSO实现)

    Copy-on-write(以下简称COW)是一种很重要的优化手段.它的核心思想是懒惰处理多个实体的资源请求,在多个实体之间共享某些资源,直到有实体需要对资源进行修改时,才真正为该实体分配私有的资源. ...

  2. 【超值分享】为何写服务器程序需要自己管理内存,从改造std::string字符串操作说起。。。

    服务器程序为何要进行内存管理,管中窥豹,让我们从string字符串的操作说起...... new/delete是用于c++中的动态内存管理函数,而malloc/free在c++和c中都可以使用,本质上 ...

  3. string的底层实现

    String底层实现 string在C++也是一个重要的知识,但是想要用好它,就要知道它的底层是如何写的,才能更好的用好这个string,那么这次就来实现string的底层,但是string的接口功能 ...

  4. QString 和std::string互转

    std::string cstr; QString qstring; //****从std::string 到QString qstring = QString(QString::fromLocal8 ...

  5. std::string的split函数

    刚刚要找个按空格分离std::string的函数, 结果发现了stackoverflow上的这个问题. 也没仔细看, 直接拿来一试, 靠, 不对啊, 怎么分离后多出个空字符串, 也就是 "a ...

  6. could not deduce template argument for 'const std::_Tree<_Traits> &' from 'const std::string'

    VS2008, 写一个简单的demo的时候出现了这个: 1>------ Build started: Project: GetExportTable, Configuration: Relea ...

  7. 源码阅读笔记 - 3 std::string 与 Short String Optimization

    众所周知,大部分情况下,操作一个自动(栈)变量的速度是比操作一个堆上的值的速度快的.然而,栈数组的大小是在编译时确定的(不要说 C99 的VLA,那货的 sizeof 是运行时计算的),但是堆数组的大 ...

  8. CString std::string相互转换

    CString->std::string 例子: CString strMfc=“test“; std::string strStl; strStl=strMfc.GetBuffer(0); s ...

  9. 计算std:string的字节长度

    如果项目本身是使用 Unicode 字符集和utf8编码,std::string的length(),size()甚至是c的strLen取到的都是字节长度了,比如三个汉字,就是9, 以上情况不满足的话, ...

  10. 【原】error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'std::string'

    今天遇到一个非常难以排查的BUG,谷歌度娘都问过了依旧无解,最后自己重新尝试之后找到解决方案: 先看一下报错信息: 1>.\lenz.cpp(2197)  error C2679: binary ...

随机推荐

  1. 扎“芯”了——CP探针卡的国产替代道阻且长

    这是IC男奋斗史的第19篇原创 关注公众号[IC男奋斗史],让我们一起撸起袖子加油干! 本文3810字,预计阅读10分钟. 之前在<凤姐如何变冰冰?>这篇文章中杰哥有介绍过CP测试,也提到 ...

  2. 简单对比Linux、Harmony OS、Harmony OS NEXT与OpenHarmony LiteOS的内存管理原理(操作系统学习)

    简单对比Linux.Harmony OS.Harmony OS NEXT与OpenHarmony LiteOS的内存管理原理(操作系统学习) 内存管理是操作系统(OS)的核心组成部分,直接影响系统的性 ...

  3. 设备接入OneNET

    OneNET是由中国移动打造的PaaS物联网开放平台.平台能够帮助开发者轻松实现设备接入与设备连接,快速完成产品开发部署.但是对于传统电气工程师的传感器.串口设备.PLC等似乎连接到OneNET是一个 ...

  4. Oracle数据一致性与事务管理

    数据一致性和事务 Oracle中的数据一致性 当从A表取一条数据添加到B表时,需先删除A表数据,再新增B表数据, 如果第二条操作出异常时,就造成了数据不一致. Oracle中的事务 事务是保证数据一致 ...

  5. 论文笔记:AlphaEdit: Null-Space Constrained Knowledge Editing for Language Models(AlphaEdit)

      论文发表于人工智能顶会ICLR(原文链接).基于定位和修改的模型编辑方法(针对ROME和MEMIT等)会破坏LLM中最初保存的知识,特别是在顺序编辑场景.为此,本文提出AlphaEdit:   1 ...

  6. 【7】Tarjan学习笔记

    前言 WFLS 暑假集训 Day 5 Day 6 Day 8 Day 9 Tarjan 是个巨佬,快来膜拜他 orz. 长文警告:本文一共 \(1092\) 行,请合理安排阅读时间. 强连通分量 强连 ...

  7. Django+Celery 进阶:Celery可视化监控与排错

    一.Celery 命令行工具 Celery 命令行工具可用去查看Celery的运行状态.打开一个终端窗口,进入项目目录(与manage.py同级),运行以下命令 列出集群中在线的Celery Work ...

  8. iPaaS+MCP,赋能企业数智化转型,别让数据和AI“躺平”!

    现在企业都在喊"数智化转型",但是在真正落地的时候,却发现困难重重.数据分散.系统割裂.AI用不起来,这些问题让很多企业头疼不已.今天,咱们就来聊聊如何打破这些瓶颈,让数智化真正为 ...

  9. 使用ETLCloud实现MySQL数据库与StarRocks数据库同步

    在现代数据架构中,数据同步是保证数据一致性和分析准确性的关键步骤之一.本文将介绍如何利用ETLCloud技术实现MySQL数据库与StarRocks数仓数据库的高效数据同步,以及其在数据管理和分析中的 ...

  10. SciTech-BigDataAIML-ETL(Extract/Transform/Load): Airflow、Luigi、NiFi+Pandas 的 深度整合指南

    https://hot.dawoai.com/posts/2025/python-etl-practical-airflow-luigi-deep-integration-guide/ Python ...