【字符串探秘:手工雕刻的String类模拟实现大揭秘】

【本节目标】

  • 1. string类的模拟实现

  • 2.C++基本类型互转string类型

  • 3.编码表 :值 --- 符号对应的表

  • 4.扩展阅读

1. string类的模拟实现

1.1 经典的string类问题

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己 来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以 下string类的实现是否有问题?为了防止和库里面的string类发生冲突,我们在这里使用命名空间来限制我们写的string类。

构造函数

namespace yu
{class string{public:string(const char* str):_str(str){}private:char* _str;size_t _size;size_t _capacity;};
}

上面的代码有什么问题吗?

我们之前提到权限可以缩小,可以平移,但是就是不能放大,那我们下面的写法还有错误吗?

namespace yu
{class string{public:string(const char* str):_str(str){}private:const char* _str;size_t _size;size_t _capacity;};void test(){string str("hello world");}
}

这里也是不可以的,因为常量字符串存在代码区,只能可读,不能写,那我们上面就只能完成一个打印输出的工作,不能完成扩容,修改等其他增删改操作。所以我们可以开辟一个同样的空间

namespace yu
{class string{public:string(const char* str)//strlen求取'\0'之前字符的个数:_str(new char[strlen(str)+1]),_size(strlen(str))//capacity是存储有效字符的个数,不包括'\0',_capacity(strlen(str)){}private:char* _str;size_t _size;size_t _capacity;};void test(){string str("hello world");}
}

但是上面strlen这个需要计算3次,而且strlen的实践复杂度是O(N),所以我们写成下面的形式。

namespace yu
{class string{public:string(const char* str):_size(strlen(str))//capacity是存储有效字符的个数,不包括'\0',_capacity(_size)//strlen求取'\0'之前字符的个数,_str(new char[_capacity + 1]){}private:char* _str;size_t _size;size_t _capacity;};void test(){string str("hello world");}
}

随后我们写一下c_str函数,看看是否打印输出成功。

namespace yu
{class string{public:string(const char* str):_size(strlen(str))//capacity是存储有效字符的个数,不包括'\0',_capacity(_size)//strlen求取'\0'之前字符的个数,_str(new char[_size + 1]){strcpy(_str, str);//拷贝}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};void test(){string str("hello world");cout << str.c_str() << endl;}
}
int main()
{yu::test();return 0;
}

此时我们的程序发生了崩溃,因为初始化的顺序和声明的顺序一致,所以程序会先执行_str(new char[_capacity + 1]),但是此时_capacity还没有初始化,此时编译器可能给了随机值或者0。

那么此时开的空间就只有1个字符的空间,开空间小导致拷贝时程序报错。

那怎么解决呢?我们可以初始化的顺序和声明的顺序一致。

但是这样的写法不好,我们这里可以不使用初始化列表,可以使用函数体内初始化。

namespace yu
{class string{public:string(const char* str){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);//拷贝}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};void test(){string str("hello world");cout << str.c_str() << endl;}
}
int main()                                         
{yu::test();return 0;
}

我们再来来实现一下析构函数

~string()
{delete[] _str;_str = nullptr;_capacity = 0;_size = 0;
}

string类里面还提供了无参的构造函数

namespace yu
{class string{public:string():_str(nullptr),_size(0),_capacity(0){}string(const char* str){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);//拷贝}~string(){delete[] _str;_str = nullptr;_capacity = 0;_size = 0;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};void test(){string str;cout << str.c_str() << endl;}
}
int main()                                         
{yu::test();return 0;
}

我们这里程序又崩溃了,为什么?cout在识别到char *类型的时候,会认为当前输出的是字符串,会进行解引用行为,这里报错就是空指针解引用的原因。所以我们这里可以设置一个空间存储'\0'

string():_str(new char[1]),_size(0),_capacity(0){_str[0] = '\0';}

但是实践上我们一般写成全缺省构造函数,不分别写有参和无参两种形式。

//string(const char* str = nullptr)//error:strlen(nullptr)会报错
//string(const char* str = '\0')//error:char不能给char*
string(const char* str = "")//常量字符串默认结尾是\0
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);//拷贝
}

再来实现一下size和[ ]操作符重载,库中我们还实现了cosnt[ ]操作符重载形式,这种形式函数内部未对对象(*this)作出改变,所以可以加上const。

//不包括'\0'
size_t size() const
{return _size;
}//返回pos位置值的引用
//	1.减少拷贝
//  2.修改返回值
char& operator[](size_t pos) 
{//这里可以=因为\0处也有空间//hello world\0//\0位置处的下标就是_sizeassert(pos <= _size);return _str[pos];
}
const char& operator[](size_t pos) const
{//这里可以=因为\0处也有空间//hello world\0//\0位置处的下标就是_sizeassert(pos <= _size);return _str[pos];
}

函数内部未对对象(*this)作出改变,所以可以加上const,我们可以验证一下。

void test()
{string str("hello world");for (size_t i = 0; i < str.size(); i++){str[i]++;}cout << str.c_str() << endl;
}

运行结果:

除了上面的[ ]可以遍历和修改,迭代器也可以修改,我们来模拟实现一下。

typedef char* iterater;
iterater begin()
{//第一个字符位置是beginreturn _str;
}
iterater end()
{//\0位置就是endreturn _str + _size;
}

我们来测试一下

void test()
{string str("hello world");string::iterater it = str.begin();while (it != str.end()){cout << *it;it++;}
}

运行结果:

但是上面这种写法只适合底层空间连续,后面遇到不连续的我们就要修改写法,除了上面的打印工作,我们还有范围for。

for (auto ch : str)
{cout << ch;
}

其实范围for底层也是用的迭代器,通过反汇编我们可以看到。

如果我们上面把begin变成Begin,此时范围for就会报错,因为范围for是傻瓜式的替换成迭代器,只有我们自定义写的迭代器没有按照规则命名,范围for就不能使用。

我们再来实现一下打印输出的工作

void print_str(const string& s)
{for (size_t i = 0; i < s.size(); i++){s[i]++;}cout << s.c_str() << endl;
}

但是我们发现我们的代码出现错误了,为什么?

因为我们上面的size和[ ]操作符重载传入的对象是非const类型的,而我们的打印输出是const类型的,这里会存在权限放大的方法,所里这里会报错,所以size和c_str函数内部未对对象(*this)作出改变,所以可以加上const。而[ ]操作符重载可以使用cosnt版本的。 

void print_str(const string& s)
{for (size_t i = 0; i < s.size(); i++){//s[i]++;//此时是const,也就不能修改cout << s[i];}cout << endl;
}

运行结果:

我们再将迭代器放入刚刚的输出打印函数,我们发现也出现了同样的问题。

所以这里要使用const迭代器,所以我们要实现一下。

typedef const char* const_iterater;
const_iterater begin() const
{return _str;
}
const_iterater end() const
{return _str + _size;
}

因此我们的程序就可以正常输出,但是此时指针指向的内容不可被改变。

我们再来实现一下string类的增删查改。字符串的增加操作必定都要开空间,对于字符串追加的函数,我们这里不能实现每次开2倍的空间操作,如果要追加的字符串的长度过长,开辟的空间必定不够,因此这里我们先实现reserve函数,解决空间开辟的问题。

void reserve(size_t n)
{if (n > _capacity){//扩容步骤/*1.开辟空间2.拷贝数据3.释放旧空间4.指向新空间*/char* tmp = new char[n + 1];//多开一个给'\0'的位置strcpy(tmp, _str);//会拷贝'\0'delete[] _str;_str = tmp;_capacity = n;}//不缩容return;
}

现在预备条件已经写好了,我就可以开始写轮子了。

void append(const char* str)
{size_t len = strlen(str);//这里都不包含'\0',因此可以不用处理//而且我们开空间都给\0开好了位置//空间永远都比capacity多一个if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;//这里插入的str字符串已经拷贝过来\0,就不需要单独处理了
}
void push_back(char ch)
{if (_size == _capacity){//当上面构造函数没有传参时,capacity值为0,这里需要单独处理一下size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';//处理\0
}
//这里我们就可以复用上面的接口
string& operator+=(const char* str)
{append(str);return *this;
}
string& operator+=(char ch)
{push_back(ch);return *this;
}

验证一下

void test()
{string str("hello world");str += '!';str += "!!";print_str(str);
}

运行结果:

我们再来实现一下插入函数

void insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){//当上面构造函数没有传参时,capacity值为0,这里需要单独处理一下size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}size_t end = _size;while (end >= pos){//依次向后挪动_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size;//挪动的时候挪动了'\0'//_str[_size] = '\0';这里不用处理
}void test()
{string str("hello world");str += '!';str += "!!";print_str(str);cout << endl;str.insert(5, '*');str.insert(5, '*');str.insert(5, '*');print_str(str);
}

运行结果:

我们看一下我们的代码有没有什么问题?我们试一下我们的头插:str.insert(0, '*');

 头插的时候,end减到为0,下次减减的时候减到-1,但是此时end为size_t,会变成整型的最大值,因此程序会一直运行,进入死循环,那我们将上面的end变量类型变为int呢?我们来看一下下面的程序。

上面的结果为什么是yes!当无符号整型和有符号整型比较时,有符号整型会隐式提升为无符号整型,-1此时就能转化为整型的最大值。所以将上面的end变量类型变为int也不行,因为我们的pos也是无符号整型,不过我们可以通过强制类型转换解决。

void insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){//当上面构造函数没有传参时,capacity值为0,这里需要单独处理一下size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}int end = _size;while (end >= (int)pos){//依次向后挪动_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size;//挪动的时候挪动了'\0'//_str[_size] = '\0';这里不用处理
}

或者也可以这样写,这样end变量的值只能减到0,就不会出现上面的错误。

void insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){//当上面构造函数没有传参时,capacity值为0,这里需要单独处理一下size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}size_t end = _size + 1;while (end > pos){//依次向后挪动_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;//挪动的时候挪动了'\0'//_str[_size] = '\0';这里不用处理
}

我们再来实现一下字符串的插入。

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

这里需要注意不能使用strcpy,因为它会拷贝'\0',会覆盖后面的'w'字符。接下来我们再实现一下删除,库里面接口为我们提供了缺省值npos,它属于静态成员变量,类里面定义,类外初始化。

private:char* _str;size_t _size;size_t _capacity;//不用给缺省值static size_t npos;//不会初始化列表,属于整个类,属于所有对象
};size_t yu::string::npos = -1;

但是我们发现库里面是这样写的。

我们发现当我们给双精度浮点型的时候程序报错了,error C2864: yu::string::x: 带有类内初始化表达式的静态 数据成员 必须具有不可变的常量整型类型,或必须被指定为“内联”。对于上面这种写法只能支持整型变量。上面的这种写法可以算是编译器的特殊处理,此时的npos只读,不能修改,不能加加减减。

void erase(size_t pos, size_t len = npos)
{assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}
}

测试一下:

void test()
{string str("hello world");print_str(str);cout << endl;str.erase(5, 1);print_str(str);cout << endl;str.erase(2);print_str(str);
}

运行结果:

我们看一下上面判断的条件,第二个条件是否可以覆盖第一个条件。这里是不能的,npos已经是最大值了,如果再加上pos就会溢出,程序就会有问题。我们再来写来写一下交换的成员函数。

void swap(string& str)
{//交换指向的内容即可std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);//std::swap(str, *this);效率较低,拷贝构造消耗大
}

我们来看一下库中这几个函数的区别,如果没有第二个swap函数,就只能调用第三个swap函数,第三个函数首先要拷贝,然后赋值,这里都是深拷贝,要开空间区拷贝,代价大。                         

我们再来实现一下find函数。

size_t find(char ch, size_t pos = 0)
{for (size_t i = pos; i < _size; i++){if (_str[i] == ch)return i;}return npos;
}
size_t find(const char* str, size_t pos = 0)
{//暴力匹配const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{//指针相减是元素之间的个数return ptr - _str;}
}

测试一下:

void test1()
{string str("hello world");cout << str.find('h') << endl;cout << str.find("lo") << endl;
}

运行结果:

我们再来实现一下substr函数。

string substr(size_t pos = 0, size_t len = npos)
{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];//会自动处理'\0'}return str;
}

我么们来看一下我们的代码有没有什么问题?

这里主要就是浅拷贝的问题,临时对象和str对象指向了同一块空间。

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构 造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

string(const string& s)//拷贝构造 - 深拷贝
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

通过深拷贝就可以解决问题指向同一块空间。

那赋值的深拷贝呢?

string& operator=(const string& s)//赋值 - 深拷贝
{if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}
}

此时也能解决问题。

我们上面都是通过C语言的形式中print_str或者str.c_str()输出我们的字符串的,我们实现一下直接使用C++流插入操作符来输出字符串。

//流插入和流提取重载在全局
ostream& operator<<(ostream& out, const string& s)
{for (auto ch :s){out << ch;}return out;
}

再来实现一下流提取。

// 这里会修改s,所以不用带上const
istream& operator>>(iostream& in, string& s)
{char ch;in >> ch;while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}
}

我们来测试一下

我们发现我们的程序无论是输入空格或者换行都不能结束。我们来测试一个数据,当什么输入"12345空格6",安装我们想要的逻辑,cin应该只会读取到”123345”。我们来一下我们的调式结果。

按照常理,输入到'5'字符后应该输入' ',但是上面的调式结果却跳过了空格,而是输出字符'6',ch没有拿到空格。

我们来看一下cin的特性,当我们连续往两个字符中写入'x',' '和'y',cin会将空格当为分隔符,会忽略这个空格,从而直接读取后续的字符,也就是读取'y','\n'也是如此,我们上面的程序是将字符串分为一个个字符,然后字符依次输入,此时就是cin输入单个字符,遇到空格或者换行就会被忽略掉,所以上面的程序无论输入什么都不会被结束,因为拿不到空格或者换行,我们可以使用getchar来获取单个字符的输入,但是我们今天学习的是C++语言,最好应用C++函数。

我们这里使用我们的C++istream提供的get函数,它通常不会跳过分隔符或空白字符,而是将其留在输入流中,因此我们就可以修改我们的流提取重载了。

// 这里会修改s,所以不用带上const
istream& operator>>(iostream& in, string& s)
{char ch = in.get();//in >> ch;//拿不到空格或者换行while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;
}

然后我们再来测试一下,输入"12345 678"。

结果确实被显示出来了,并且" 678"被输入到我们的缓冲区从而没有被显示在显示器上,但是我们发现我们的流提取重载并没有清空之前存在的字符串,因为我们实现的时候是使用了+=重载,如果要输入的字符串之前没有清空,那么后续输入的字符串就会在之前的字符串上追加。但是我们的库函数就是直接输入什么字符串就会显示什么字符串。

所以在输入字符串之前,如果之前的字符串还有内容,我们就要清空,所以我们要实现一下我们的clear函数。

void clear()
{//删除数据,但是不会释放空间_size = 0;_str[_size] = '\0';
}

注意:这里我们一定要将0位置处设置为'\0',否则就会出现错误。

然后再来改造一下我们的流提取重载。

// 这里会修改s,所以不用带上const
istream& operator>>(iostream& in, string& s)
{//清空历史数据s.clear();char ch = in.get();//in >> ch;//拿不到空格或者换行while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;
}

流提取重载这样就写好了,但是还是有一个小问题,如果我们要输入的字符非常长,那我们就要经过多次开辟空间,这样消耗很大,那我们可以用reserve提前开辟空间吗?不行,因为我们不确定用户要输入的字符到底有多长,同时我们也不能获取缓冲区输入字符的长度,这里就有人设计出了一个字符数组来解决这个问题,我们来看看是怎么设计的。

istream& operator>>(iostream& in, string& s)
{//清空历史数据s.clear();char buff[128];char ch = in.get();int i = 0;//in >> ch;//拿不到空格或者换行while (ch != ' ' && ch != '\n'){buff[i++] = ch;//该数组出了作用域就被销毁if (i == 127){//下标为127,此时数组就有128个元素buff[i] = '\0';s += ch;i = 0;}ch = in.get();}//i没有走到127的情况if (i > 0){buff[i] = '\0';s += buff;}return in;
}

1.2.浅拷贝问题

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共 享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为 还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就 你争我夺,玩具损坏。

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩 子都买一份玩具,各自玩各自的就不会有问题了。

1.3 深拷贝问题

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情 况都是按照深拷贝方式提供。

1.4.传统版写法的String类的拷贝构造和赋值拷贝

string(const string& s)//拷贝构造 - 深拷贝
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}
string& operator=(const string& s)//赋值 - 深拷贝
{if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}
}

1.5.现代版写法的String类的拷贝构造和赋值拷贝

string(const string& s)//拷贝构造 - 深拷贝
{string tmp(s.c_str());swap(tmp);
}

但是这里有些编译器可能会出现一个问题,s2和tmp交换,s2确实获得了tmp的数据,但是tmp交换之后指向谁呢?此时我们需要让他指向nullptr。

再来看一下赋值拷贝的现代写法。

// s1 = s3
string& operator=(string s)//赋值 - 深拷贝
{swap(s);return *this;
}

这里不能加引用,因为加上引用s就是s3的别名,此时就是交换是s1和s3。此时不带上引用,s对象是通过s3对象拷贝构造的,然后s再和s1交换,s交换后指向s1,当s出了作用域,就会调用析构函数释放空间。

注意:此时我们就不用考虑自己给自己赋值的问题,因为此时我们传参的时候已经拷贝构造了。如果要考虑的话就要这样写

string& operator=(const string& s)//赋值 - 深拷贝
{if (this != &s){string tmp(s);swap(tmp);}
}

使用上面的赋值现代写法不能兼容我们的substr函数,substr函数返回的是str的临时拷贝,临时拷贝具有常属性,而赋值现代写法参数是非cosnt,这里会存在权限放大的原因,所以会出现错误。

2.C++基本类型转string函数

3.编码表 :值 --- 符号对应的表

计算机中数据都是由二进制存储的,那我们怎么通过这些01序列分辨出我们的数据是什么呢?计算机中为我们提供了ASCII表。

比如今天我们要存储"apple!",实际上计算机存储的就是97 112 112 108 101 33 0这几个序列,我们来验证一下。

 此时的计算机只能存储显示英文,那怎么显示其他国家的语言呢?以我们国家为例。也要对应的值和对应的符号相对于起来,但是中国文化上下五千年文明,博大精深,如果我们国家也用8个比特位,一个字节表示一个符号,也就是256中符号肯定不能存储文明国家所有的文字,所有我们国家就用两个字节到四个字节表示一个文字,一般常见的汉字可以考虑用两个字节对应一个汉字,所以这样就有256*256个表示情况,微软平台使用的都是GBK编码。

我们来看一下内存中是怎么样的,通过两个字节去编码表找对应的汉字。

我们可以再来看一下编码表的顺序。

编码表不是乱编的,是按照一定顺序编码的,将同音字编码在一起。我们国家的编码表是兼容SASCII表的,但是当同时出现中文和英文,我们国家的编码表怎么识别呢?它是当成两个字节去国家的编码去寻找呢?还是当成一个字节去寻找呢?在双字节编码表中,英文字符会占用一个字节,而中文字符会占用两个字节。在处理文本时,系统可以通过检查字节的高位信息来确定是一个英文字符还是一个中文字符,然后再在编码表中找到对应的字符。但是其他国家语言呢?还需要兼容其他国家的编码表,太繁琐了,于是就衍生出来万国码。

        "万国码" 广泛指的是 Unicode(统一码),而不是特指某一种具体的编码。Unicode 是一种用于文本字符的国际化标准,目的是为了能够涵盖全球范围内的所有语言和符号。

编码方式:

  • UTF-8: 是一种可变长度编码方式,使用1到4个字节来表示字符。对于ASCII字符,使用一个字节表示,对于其他字符,使用更多的字节。

  • UTF-16: 使用16位(2字节)来表示一个字符。基本多文本平面(BMP)上的字符使用16位表示。

  • UTF-32: 是一种固定长度编码,每个字符使用32位(4字节)表示,不论字符在Unicode中的位置。

Linux下一般都使用UTF-8编码。

C++标准库提供了多个字符串类型(stringwstringu16stringu32string)以适应不同的字符编码需求。这些字符串类型是为了支持不同的字符集和编码方式:

  1. string

    • std::string 是标准 C++ 中用于存储单字节字符的字符串类型。它使用了默认的字符集(通常是 ASCII 或 UTF-8)。
  2. wstring

    • std::wstring 是宽字符字符串类型,在 Windows 平台上通常使用。它使用 wchar_t 类型存储字符,这个类型在不同的编译器和平台上可能占据不同的字节大小(例如,Windows 上通常是 2 字节,而在 Linux 上可能是 4 字节)。
  3. u16string

    • std::u16string 是存储 UTF-16 编码的字符串类型,每个字符通常占用 2 个字节。
  4. u32string

    • std::u32string 是存储 UTF-32 编码的字符串类型,每个字符通常占用 4 个字节。

        这些字符串类型的选择取决于需要处理的文本数据的特定要求。在多语言环境中,特别是处理 Unicode 字符时,选择适当的字符串类型非常重要。例如,如果需要处理表情符号、不同语言的字符集或者需要支持各种语言的国际化应用程序,那么使用宽字符或者 UTF-16/UTF-32 编码的字符串类型可能更为适合。

4.扩展阅读

面试中string的一种正确写法

STL中的string类怎么了?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/186174.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.网络编程基础知识 - 基础概念、TCP网络通信、UDP网络通信

网络编程 文章目录 网络编程一、概念1.1 网络1.2 IP地址1.2.1 IPv4 介绍1.2.2 IPv6 介绍1.2.3 查看IP地址 1.3 域名和端口1.4 网络协议1.5 TCP与UDP1.6 InetAddress类1.7 Socket 二、TCP网络通信编程2.1 介绍2.2 案例2.2.1 字节流编程案例12.2.2 字节流编程案例22.2.3 字符流编…

5. 文件属性和目录

5. 文件属性和目录 1. Linux 系统的文件类型1.1 普通文件1.2 目录文件1.3 字符设备文件和块设备文件1.4 符号链接文件1.5 管道文件1.6 套接字文件 2. stat 系统调用2.1 struct stat 结构体2.2 st_mode 变量2.3 struct timespec 结构体 3. fstat 和 lstat 函数3.1 fstat 函数3.2…

python中的序列

文章目录 序列类型标准类型运算符标准类型运算符序列类型运算符字符串 序列类型 字符串 列表 元组 由元组构成的列表 标准类型运算符 &#xff08;1&#xff09;按字符串大小比较 标准类型运算符 序列类型运算符 序列类型转换内建函数 注&#xff1a; &#xff08;1&#xff…

深入理解MySQL索引底层数据结构与算法

索引的本质 索引是帮助MySQL高效获取数据的排好序的数据结构 索引的数据结构 二叉树红黑数Hash表B-Tree MySQL索引底层为啥不用二叉树 如图&#xff0c;对单边增长的数据&#xff0c;索引效率没有什么提升 MySQL索引底层为啥不用红黑数 红黑数&#xff1a;二叉平衡树 随…

计算虚拟化之内存

有了虚拟机&#xff0c;内存就变成了四类&#xff1a; 虚拟机里面的虚拟内存&#xff08;Guest OS Virtual Memory&#xff0c;GVA&#xff09;&#xff0c;这是虚拟机里面的进程看到的内存空间&#xff1b;虚拟机里面的物理内存&#xff08;Guest OS Physical Memory&#xf…

STM32CubeIDE(CUBE-MX hal库)----定时器

系列文章目录 STM32CubeIDE(CUBE-MX hal库)----初尝点亮小灯 STM32CubeIDE(CUBE-MX hal库)----按键控制 STM32CubeIDE(CUBE-MX hal库)----串口通信 文章目录 系列文章目录前言一、定时器二、使用步骤三、HAL库实验代码三、标准库代码 前言 STM32定时器是一种多功能外设&#…

[iOS开发]UITableView的性能优化

一些基础的优化 &#xff08;一&#xff09;CPU 1. 用轻量级对象 比如用不到事件处理的地方&#xff0c;可以考虑使用 CALayer 取代 UIView CALayer * imageLayer [CALayer layer]; imageLayer.bounds CGRectMake(0,0,200,100); imageLayer.position CGPointMake(200,200…

Python基础学习之包与模块详解

文章目录 前言什么是 Python 的包与模块包的身份证如何创建包创建包的小练习 包的导入 - import模块的导入 - from…import导入子包及子包函数的调用导入主包及主包的函数调用导入的包与子包模块之间过长如何优化 强大的第三方包什么是第三方包如何安装第三方包 总结关于Python…

智能优化算法应用:基于生物地理学算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于生物地理学算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于生物地理学算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.生物地理学算法4.实验参数设定5.算法结果…

【matlab程序】matlab利用工具包nctool读取grib2、nc、opendaf、hdf5、hdf4等格式数据

【matlab程序】matlab利用工具包nctool读取grib2、nc、opendaf、hdf5、hdf4等格式数据 引用&#xff1a; B. Schlining, R. Signell, A. Crosby, nctoolbox (2009), Github repository, https://github.com/nctoolbox/nctoolbox Brief summary: nctoolbox is a Matlab toolbox…

时间序列预测实战(二十一)PyTorch实现TCN卷积进行时间序列预测(专为新手编写的自研架构)

一、本文介绍 本篇文章给大家带来的是利用我个人编写的架构进行TCN时间序列卷积进行时间序列建模&#xff08;专门为了时间序列领域新人编写的架构&#xff0c;简单不同于市面上大家用GPT写的代码&#xff09;&#xff0c;包括结果可视化、支持单元预测、多元预测、模型拟合效…

【docker系列】docker实战之部署SpringBoot项目

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于SpringBoot房产销售系统

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于房产销售系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了房产销售系统&#xff0c;它彻底改变了过去传统的…

【MySQL】事务(事务四大特性+四种隔离级别+MVCC)

事务 前言正式开始事务的四大特性为什么会出现事务事务的版本支持事务提交方式事务常见操作方式启动事务回滚演示提交事务事务的异常autocommit 事务的隔离性隔离级别查看隔离级别修改隔离级别验证四种隔离级别读未提交(read uncommitted) —— 缩写为RU读提交(read committed)…

3款厉害的小工具,小黑子都在用!

大家好&#xff0c;我是 Javapub。 程序员与普通人最大的区别是什么&#xff0c;当然是会使用工具。基于一些同学经常问我的问题&#xff0c;接下来给大家分享几款我经常使用的工具&#xff0c;主打一个提升效率。 第一款 Everything 用 windwos 的同学都体会过&#xff0c;…

ERP软件对Oracle安全产品的支持

这里的ERP软件仅指SAP ECC和Oracle EBS。 先来看Oracle EBS&#xff1a; EBS的认证查询方式&#xff0c;和数据库认证是一样的。这个体验到时不错。 结果中和安全相关的有&#xff1a; Oracle Database VaultTransparent Data Encryption TDE被支持很容易理解&#xff0c;…

指针数组以及利用函数指针来实现简易计算器及typedef关键字(指针终篇)

文章目录 &#x1f680;前言&#x1f680;两段有趣的代码✈️typedef关键字 &#x1f680;指针数组&#x1f680;简易计算器的实现 &#x1f680;前言 基于阿辉前两篇博客指针的基础篇和进阶篇对于指针的了解&#xff0c;那么今天阿辉将为大家介绍C语言的指针剩下的部分&#…

2021年9月15日 Go生态洞察:TLS加密套件的自动排序机制

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Linux CentOS7 fdisk

Centos7的磁盘管理包括添加磁盘、查看磁盘信息、磁盘分区、格式化、挂载和卸载&#xff0c;逻辑卷管理等。 对分区后的磁盘格式化比较简单&#xff0c;执行mkfs命令即可&#xff1b;而挂载可以使用的分区执行mount命令很方便地完成。本文仅讨论新添加磁盘的分区操作。 一、添…

C++学习之继承中修改成员权限细节

看看下面的代码 这是错误的 class A { public:int x 10; }; class B :public A {using A::x;int x 100; };看看函数 class A { public:void fun(){cout << "uuuu" << endl;} }; class B :public A { public:using A::fun;void fun(){cout << …