【与C++的邂逅】--- string容器使用

news2024/11/13 10:06:22

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     与C++的邂逅   


本篇博客我们将来了解string容器本身以及接口的使用。


string是串,本质是一个字符数组,可以对其进行增删查改。

🏠 string构造

//常用
string s1;
string s2("hello world");
string s3(s2);

//不常用
string s4(s2,3,5); // string (const string& str, size_t pos, size_t len = npos)
string s5(s2,3);
string s6(s2,3,30);
string s7("hello world",5) //string (const char* s, size_t n)
string s8(10,'x'); //string (size_t n, char c);

对于string的构造,我们常用的是它的无参构造,拷贝构造等。注意:在库中pos通常代表位置,n代表的是个数。

  • npos

由库中文档说明,我们可以知道 :

1. npos是string里的一个const静态成员变量,类型是size_t,缺省值给的是-1(整形最大).

2. 当使用substring的这个构造时,从str的pos位置开始拷贝构造,如果要构造的长度len大于后面的长度,则有多少拷贝多少直到拷贝到结尾 .

3. 缺省参数npos是整形最大值,一定大于后面的长度,不传第三个参数默认拷贝到结尾.

string s2("hello world");
// string (const string& str, size_t pos, size_t len = npos)
string s4(s2,3,5); //拷贝5个字符 
string s5(s2,3); //拷贝到结尾
string s6(s2,3,30);//拷贝到结尾
  • 隐式类型转换在构造中的应用
string s1("hello world");
//隐式类型转换
string s2 = "hello world";
const string& s3 = "hello world";

//以及后面其他接口也可以使用...
push_back("hello world");

说明 :

1. 对于s2是先调用常量字符串的构造生成一个临时对象,再调用拷贝构造将临时对象拷贝给s2.

2. 对于s3,它引用的是临时对象,而临时对象具有"常性",因此需要是const引用.

🏠 元素访问方式

📌 operator[ ]

      char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;
  • 引用返回
int main()
{
	string s = "hello";

	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s.operator[](i) << " "; //显式[]+下标访问对象元素
		cout << s[i] << " ";//隐式[]+下标访问对象元素
	}

	for (size_t i = 0; i < s.size(); i++)
	{
		s[i]++; //修改访问元素内容
	}
	return 0;
}

由于string类重载了[ ]运算符,所以我们可以直接利用[ ] + 下标访问对象中的元素;同时由于是引用返回,所以我们可以通过[ ] + 下标修改对应位置的元素内容,也就是可读可写.

class string
{
/...
private:
  char* _str;
  size_t _size;
  int _capacity; 
}

char& operator[](size_t pos)
{
   assert(i < _size);
   return _str[i]; 
}

string类一般是动态开辟内存来储存字符元素,所以实现operator[ ]一般返回的是在堆上的空间,因此我们不用担心传引用返回时返回的是一个局部对象.

传引用返回优势 : 

1. 能修改返回对象.

2. 减少拷贝.

  • string类重载的[]运算符还会执行严格的越界检查,只要下标超过size或小于0就报错.
  • opertaor[ ]有两个版本,一个是非const,返回对象可修改,是典型的可读可写;另一个是const版本,返回对象内容不可修改.
const string s2("hello");
s2[0] = 'x'; //不可修改 调用const版本的operator[]

string s1("hello");
s1[0] = 'x';//调用非const版本的opeator[]

1. const版本重载的[ ]主要是给const对象用的,毕竟const对象无法调用非const版本,这是权限的扩大.

2. 对于const和非const对象其实都能调用const版本的重载,但却重载出两个版本,这是因为各自有不同的需求(比如只有一个const版本的operator[ ],对于普通string对象就不能修改了,不符合我们的要求,如果重载了两个版本则调用更匹配的,普通对象调用普通版本,const对象调用const版本).

3.具体实践中重载[]操作符看具体需求.

📌 迭代器

迭代器目前我们可以理解为具有和指针类似行为的东西,但实际上不一定是指针.

string::iterator it = s.begin();
while (it != s.end())
{
    cout << *it << endl; //类似指针解引用
    ++it;//类似指针++
}
auto it = s.begin();

迭代器访问元素是所有容器(除stack,queue外)通用的访问方式,他们通用的将迭代器命名为iterator,头元素位置的迭代器和尾元素位置的迭代器都命名为begin和end,它们都是定义在类域里的成员函数.

注 : C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型.

for (auto ch : s)
  cout << ch << endl;

C++11之后我们更喜欢用范围for来访问string类元素,它底层也是利用的迭代器.

  • begin与end

对于begin返回的是开始位置,对于end返回的是最后一个字符的下一个位置,在string类中大概就是\0的位置.

  • 迭代器的版本
const string s1("hello");
string::const_iterator it1 = s1.begin();
while (it1 != s1.end())
{
  cout << *it1 << endl;//无法修改
}

迭代器分为iterator(可读可写)和const_iterator(只读),对于const对象匹配的是返回const_iterator的begin().下面我们来区分const iterator , iterator 和 const_iterator.

类型特点类比指针
iterator可读可写int* p1
const_iterator只读 迭代器指向数据不能写const int* p2
const iterator迭代器本身不能写int* const p3
  • 反向迭代器

string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{
	*it2 += 3;
	++it2;
}

反向迭代器是从后往前遍历,rbgein就是返回的原来正向最后一个元素位置(\0前面),rend返回的是原来正向第一个元素的前一个位置.

注 : string类通过运算符重载改变了反向迭代器++和--的行为,底层是封装的正向迭代器.

总结 : 迭代器一共有四种:1. iterator的const和非const版本 以及 reverse_iterator的const和非const版本.

注 : 库里面其实还有cbegin和cend,他们其实就是类似iterator的const版本.

📌 at

string s1("hello");
try
{
  s1[10];
}
catch (const exception& e)
{
  cout << e.what() << endl;
}

string s("hello world");
for (size_t i = 0; i < s.size(); i++)
{
	//at(pos)访问pos位置的元素
	cout << s.at(i);
}
cout << endl;
for (size_t i = 0; i < s.size(); i++)
{
	//at(pos)访问pos位置的元素,并对其进行修改
	s.at(i) = 'x';
}
cout << s << endl; //xxxx

at是访问pos位置的字符,它也是引用返回,可读可写;与operator[ ]不同的是,operator[ ]对于下标是暴力检查,而at是抛异常,两者底层处理越界的方式不同.

总结:

string类元素遍历方式主要有4种:

1. [ ] + 下标

2. 使用at访问对象中的元素

3. 迭代器遍历

4.范围for

我们实践中主要用的是[]以及范围for.

🏠 修改

📌 尾部插入和尾部删除

📒 尾部插入

  • push_back()
void push_back (char c);
//应用
string s("hello");
s1.push_back('x');

我们可以使用push_back来尾部插入字符。

  • append()
string& append (const string& str);
string& append (const char* s);
string& append (size_t n, char c);

//应用
string s1("hello");
string s2(" world");

//append(string)完成两个string对象的拼接
s1.append(s2); //hello world
//append(str)完成string对象和字符串str的拼接
s1.append(" C++"); //hello world C++

//append(n, char)将n个字符char拼接到string对象后面
s1.append(3, '!'); //hello world C++!!!
	
cout << s1 << endl; //hello world C++!!!

我们可以使用append来实现尾部插入string对象/字符串/多个相同字符;这里也体现了vector与string的不同:vector<char>一次只能进一个char字符,而string对象一次可能进一个串。

  • operator +=

string类重载的+=可以+=一个字符/串/string对象,我们在实践中更多使用+=来尾部插入。

string s1("hello");
string s2(" world");

s1 += s2;     //+=string
s1 += "C++" ; //+=字符串
s1 += '!';    //+=字符

📒 尾部删除

void pop_back();

string str ("hello world!");
str.pop_back();
cout << str << '\n';  //hello world

我们可以用pop_back来删除string对象的尾部字符。

📌 insert和erase

  • insert
string& insert (size_t pos, const string& str);
string& insert (size_t pos, const char* s);
iterator insert (iterator p, char c);
template <class InputIterator>
void insert (iterator p, InputIterator first, InputIterator last);

insert可以帮助我们在pos位置(下标/迭代器)插入字符,可以插入一个string对象/字符串/字符。注意是在pos位置插入!

    string s("C"); //C

	//insert(pos, str)在pos位置插入字符串str
	s.insert(1, "H"); //CH

	//insert(pos, string)在pos位置插入string对象
	string t("I");
	s.insert(2, t); //CHI

	//insert(pos, char)在pos位置插入字符char
	s.insert(s.end(), 'N'); //CHIN
	
    //nsert (iterator p, InputIterator first, InputIterator last)
    //在p位置插入另一个迭代区间
    string h("A");
    s.insert(s.end(),h.begin(),h.end());

	cout << s << endl; //CHINA

注 : 

1. 使用insert时,我们要注意传入区间和下标的合法性。

2. 一次insert的时间复杂度是O(N),一段程序中不断对string对象调用insert会有较大的时间消耗,因此在实践中我们要慎用。

3. insert其实可以复用来实现尾插头插。

  • erase
string& erase (size_t pos = 0, size_t len = npos);
iterator erase (iterator p);
iterator erase (iterator first, iterator last);

erase可以帮助我们实现在指定位置删除后面指定长度len,同样地,如果不传npos或传的len大于剩下的字符串长度,则有多少删多少。

string s("hello");
s.erase(0, 1);
cout << s << endl; //输出ello

s.erase(0, 20);
cout << s << endl;//输出空串

注 : erase也是一样要慎用效率不高,也是O(N)

📌 assign

string& assign (const string& str);
string& assign (const char* s);

//应用
//const char* str
string s1("hello world");
s1.assign("yes");
cout << s1 << endl; //输出yes
//string
string s2("ha");
s1.assign(s2);
cout << s1<< endl; //输出ha 

assign其实是变相的赋值,把原来的内容清空再赋值成新的内容。

📌 replace

string& replace (size_t pos,  size_t len,  const string& str);
string& replace (size_t pos,  size_t len,  const char* s);
string& replace (size_t pos,  size_t len,  size_t n, char c);

replace接口的作用是把原串的某一段内容替换位为字符/字符串。

//string& replace (size_t pos,  size_t len,  const string& str);
string s("hellea");
s.replace(4,1,"oworld");// helloworlda
cout << s << endl;

// string& replace (size_t pos,  size_t len,  size_t n, char c);
s = "hellea";
s.replace(4,1,2,'o');
cout << s << endl;//hellooa

注 : 

1. len表示的是从pos位置开始要替换的字符个数。

2. replace如果替换字符多的话效率不高,也需要慎用。

string s("hello world hello bit");
for (int i= 0; i < s.size() ;)
{
	if (s[i] == '.')
	{
		s.replace(i, 1, "%20");
		i += 3;
	}
	else
		i++;
}

我们可以用replace实现替换空格,但是这样效率不高,我们可以利用一个新的string对象提高效率,这样不用挪数据到新串里

string s("hello world hello bit");
string s1;//空串
for(auto ch : s)
{
   if(ch != ' ')
     s1 += ch; 
   else
     s1 += "%20";
}

📌 operator + 

string类中对+运算符进行了重载,重载后的+运算符支持以下几种类型的操作:
1.string类 + string类
2.string类 + 字符串
3.字符串 + string类
4.string类 + 字符
5.字符 + string类
它们相加后均返回一个string类对象

    string s;
	string s1("super");
	string s2("man");
	char str[] = "woman";
	char ch = '!';

	//string类 + string类
	s = s1 + s2;
	cout << s << endl; //superman

	//string类 + 字符串
	s = s1 + str;
	cout << s << endl; //superwoman

	//字符串 + string类
	s = str + s1;
	cout << s << endl; //womansuper

	//string类 + 字符
	s = s1 + ch;
	cout << s << endl; //super!
	
	//字符 + string类
	s = ch + s1;
	cout << s << endl; //!super

注 :operator+重载,参数必须要有一个自定义类型,因此不能字符串+字符串。

🏠 容量

📌 size()与length()

string s1 = "hello";
cout << s1.size() << endl; //输出5
cout << s1.length() << endl;

说明 :

1. 对于size和length都是求字符串的长度.length是主要针对串这个容器而设计的,而size是面对大多数容器设计的表示"容器元素个数"的接口.

2. 对于size和length,两个算出的字符串长度都是不算'\0'的 ! 

📌 capacity

size_t capacity() const;

使用capacity函数获取当前对象所分配的存储空间的大小。

string s("CSDN");
cout << s.capacity() << endl; //15

关于扩容方式的探讨:

void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

vs2019环境输出结果:

capacity changed:15
making s grow:
capacity changed:31
capacity changed:47
capacity changed:70
capacity changed:105
capacity changed:157
capacity changed:235

g++环境输出结果:

capacity changed:
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
capacity changed: 256

注意:

1. 严格来说capacity比实际空间少一个,有一个多的是预留给'\0'的。

2.如何扩容,C++标准并没有规定,取决于编译器的实现。比如在vs2019环境下,第一次是二倍扩容,后面都是1.5倍扩容;而g++环境下,一直是2倍扩容。

📌 max_size

size_t max_size() const;

使用max_size函数获取string对象对多可包含的字符数。

string s("CSDN");
cout << s.max_size() << endl; //4294967294

📌 clear

void clear();

clear的作用是把数据全部清理掉,它只是把string有效字符清空成为空串,只剩下\0,而不改变底层空间的大小。

//vs2019环境
string s1 = "hello";
cout << s1.size() << endl;//5
cout << s1.capacity() << endl;//15
s1.clear();
cout << s1.size() << endl; //输出 0
cout << s1.capacity() << endl;// 15

📌 empty

bool empty() const;

使用empty判断对象是否为空。

string s("CSDN");
cout << s.empty() << endl; //0

//clear()删除对象的内容,该对象将变为空字符串
s.clear();
cout << s.empty() << endl; //1

📌 reserve和resize

  • 使用resize改变当前对象的有效字符的个数
void resize (size_t n);
void resize (size_t n, char c);

resize注意事项:

1. resize会改变对象的capacity和size

2.当n大于对象当前的size时,将size扩大到n,扩大的字符为c,若c未给出,则默认为’\0’。

3. 当n小于对象当前的size时,将size缩小到n;缩不缩容取决于编译器,一般空间不变

4. 若给出的n大于对象当前的capacity,则capacity也会根据自己的增长规则进行扩大

string s1("hello");
s1.resize(6); //hello\0\0
s1.resize(20,'x');//helloxxxxxxxxxxxxxxx

//扩大
cout << s1.size() << endl; //20
cout << s1.capacity() << endl; //31 

//缩小
s1.resize(10);
cout << s1.size() << endl; //10
cout << s1.capacity() << endl; //31
  • 使用reserve改变当前对象的容量大小
void reserve (size_t n = 0);
string s("hello");
cout << s.capacity()<<endl;  //15
//扩容
s.reserve(100);
cout << s.capacity()<<endl; //111
//缩容
s.reserve(20);
cout << s.capacity()<<endl; //111
s.reserve(10);
cout << s.capacity()<<endl;//10

我们可以得到在vs环境下扩容是会多扩的,而缩容传参数比capacity小是默认不会缩容的,小于15才会缩。

string s("hello");
cout << s.capacity()<<endl;  //5
//扩容
s.reserve(100);
cout << s.capacity()<<endl; //100
//缩容
s.reserve(20);
cout << s.capacity()<<endl; //20
s.reserve(10);
cout << s.capacity()<<endl;//10

linux环境下,reserve扩容是要多少扩多少;缩容是要缩多少就缩到多少。

注 :

1. reserve改变的是capacity,而不能改变size

2.reserve改变空间(为string预留空间),但不改变size(有效元素个数)要注意越界问题

string s1("hello");
s1.resize(200);
s1[200]; //越界

3.由于扩容和缩容在不同平台的不确定性,所以我们最好用reserve来提前开好我们需要的空间,因此reserve的使用场景是知道需要提取开多少空间。

📌 shrink_to_fit

void shrink_to_fit();
string s1(100,'x');
cout << s1.capacity() << endl;//111
cout << s1.size()<< endl;//100
s1.resize(10);
s1.shrink_to_fit();
cout << s1.capacity() << endl;//15
cout << s1.size() << endl;//10

shirink_to_fit可以使size和capacity同步变化,但不要经常调用,因为实际的缩容是开新空间释放旧空间,以时间换空间代价较大。

🏠 算法与子串的提取

📌算法

📒 sort算法

库里的排序算法传参数是随机迭代器区间(左闭右开),它可以对大部分容器(不能对list排序)进行排序,相当于是一个函数模板,利用迭代器这个桥梁将算法与容器连接起来。

string s1("hello");

//按字典序排序 全部排
sort(s1.begin(),s1.end());  //ehllo
//第一个和最后一个不参与排序
sort(++s1.begin(),--s1.end()); //hello
//前4个排序
sort(s1.begin(),s1.begin()+4);  //ehllo

注 : 对于string容器使用sort排序是按字典序排序,同时它可以对整个数据处理,也可以对容器的一部分数据处理。

📒 reverse算法

算法库里的reverse也是一个函数模板,能对容器里的数据实现逆置,参数是迭代器区间。

string s("hello");
reverse(s.begin(),s.end());
cout << s << endl; //olleh

注 : 它也可以实现完全逆置和部分逆置,但是它和sort都需要包含<algorithm>这个头文件。

📌 子串的提取

  • 使用substr函数提取string中的子字符串。
string substr (size_t pos = 0, size_t len = npos) const;

注 : substr是pos位置开始生成len长度的子串,len过大或不传就是后面剩下的字符都变成子串,采取的也是左闭右开的迭代器区间。

string s1("abcdef");
string s2;

//substr(pos, n)提取pos位置开始的n个字符序列作为返回值
s2 = s1.substr(2, 4);
cout << s2 << endl; //cdef
  • 使用copy函数将string的子字符串复制到字符数组中。
size_t copy (char* s, size_t len, size_t pos = 0) const;
string s("abcdef");
char str[20];

//copy(str, n, pos)复制pos位置开始的n个字符到str字符串
size_t length = s.copy(str, 4, 2);
//copy函数不会在复制内容的末尾附加'\0',需要手动加
str[length] = '\0';
cout << str << endl; //dcef

注 :

1. copy返回值是赋值的len。

2.copy函数不会在复制内容的末尾附加'\0',需要我们手动加上。

🏠 查找

📌 find

使用find函数正向搜索第一个匹配项,可以在string对象里查找一个字符/字符串/string对象,返回的是第一个匹配的位置,未找到则返回-1.

size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
size_t find (char c, size_t pos = 0) const;
string s1("http://www.cplusplus.com/reference/string/string/find/");

//find(string)正向搜索与string对象所匹配的第一个位置
string s2("www");
size_t pos1 = s1.find(s2);
cout << pos1 << endl; //7

//find(str)正向搜索与字符串str所匹配的第一个位置
char str[] = "cplusplus.com";
size_t pos2 = s1.find(str);
cout << pos2 << endl;  //11

//find(char)正向搜索与字符char所匹配的第一个位置
size_t pos3 = s1.find(':');
cout << pos3 << endl; //4

📌rfind

使用rfind函数反向搜索第一个匹配项,rfind是倒着找一个字符/字符串/string对象,如果存在,返回匹配的第一个位置,否则返回-1.

size_t rfind (const string& str, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos = npos) const;
size_t rfind (char c, size_t pos = npos) const;
string s1("http://www.cplusplus.com/reference/string/string/find/");

//rfind(string)反向搜索与string对象所匹配的第一个位置
string s2("string");
size_t pos1 = s1.rfind(s2);
cout << pos1 << endl; //42

//rfind(str)反向搜索与字符串str所匹配的第一个位置
char str[] = "reference";
size_t pos2 = s1.rfind(str);
cout << pos2 << endl;  //25

//rfind(char)反向搜索与字符char所匹配的第一个位置
size_t pos3 = s1.rfind('/');
cout << pos3 << endl; //53

📌查找系列

size_t find_first_of (const string& str, size_t pos = 0) const;
size_t find_first_of (const char* s, size_t pos = 0) const;
//其他类似...

std::string str ("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
while (found!=std::string::npos)
{
  str[found]='*';
  found=str.find_first_of("aeiou",found+1);
}
std::cout << str << '\n';

🏠 输入与输出

📌 operator << 和 operator >> 

string类中也对>>和<<运算符进行了重载,这就是为什么我们可以直接使用>>和<<对string类进行输入和输出的原因。

ostream& operator<< (ostream& os, const string& str);
istream& operator>> (istream& is, string& str);
string s;
cin >> s; //输入
cout << s << endl; //输出

📌 getline

istream& getline (istream& is, string& str, char delim);
istream& getline (istream& is, string& str);

如果我们想输入一串含有空格的字符串到string对象中,使用cin是不行的,因为cin默认规定空格或换行是多个值之间分割,也就是当>>读取到空格便会停止读取。

string s;
getline(cin, s); //输入:hello CSDN
cout << s << endl; //输出:hello CSDN

getline函数将从is中提取到的字符存储到str中,直到读取到换行符’\n’为止

string s;
getline(cin, s, 'D'); //输入:hello CSDN
cout << s << endl; //输出:hello CS

getline函数将从is中提取到的字符存储到str中,直到读取到分隔符delim或换行符’\n’为止

🏠 string与其他数据类型转换

  • to_string : 其他数据类型转为字符串

库中提供了将其他数据类型转化为字符串的函数,但有时要注意数据溢出转换失败的风险。

int x,y = 0;
cin >> x >> y; //输入 20 30
string str = to_string(x+y);
cout << str << endl; //输出50
  • sto.. : 字符串转化为其他数据类型。

注 : 注意数据溢出的风险。

🏠 string与字符串的转化

  • 将字符串转换为string
//方式一
string s1("hello world");

//方式二
char str[] = "hello world";
string s2(str);

cout << s1 << endl; //hello world
cout << s2 << endl; //hello world
  • 使用c_str或data将string转换为字符串

1. c_str作用是获取底层的ptr指针,它是为了更好的与c语言兼容。

2.data与c_str类似,就跟size与length的关系。

3.在C++98中,c_str()返回 const char* 类型,返回的字符串会以空字符结尾;在C++98中,data()返回 const char* 类型,返回的字符串不以空字符结尾

4. 在C++11版本中,c_str()与data()用法相同

string s("hello world ");
const char* str1 = s.data();
const char* str2 = s.c_str();

cout << str1 << endl;
cout << str2 << endl;

//
string file("Test.cpp");
cout << file.c_str() << endl;

🏠 vs和g++下string结构的不同

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

  • vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

1.当字符串长度小于16时,使用内部固定的字符数组来存放。

2.当字符串长度大于等于16时,从堆上开辟空间。

union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情。

故总共占16+4+4+4=28个字节。

  • g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
1. 空间总大小

2.字符串有效长度

3.引用计数

4.指向堆空间的指针,用来存储字符串

struct _Rep_base
{
  size_type _M_length;
  size_type _M_capacity;
  _Atomic_word _M_refcount;
};

关于引用计数和写时拷贝:

对于浅拷贝会遇到两个问题:
1. 由于两个string对象指向同一块空间,因此在析构两个对象时,会释放两次空间。

2. 由于两个对象指向同一块空间,因此一个对象修改数据会影响另一个对象。

解决方法:

1. 引用计数 : 当每次为分配内存时,我们总是要多分配一个内存空间用来存放这个引用计数的值,只要发生拷贝构造和赋值时,这个内存值就会+1;当计数不为1时,不销毁空间;由最后一个析构的对象释放空间。

2. 写时拷贝:在内容修改时,string类查看这个引用计数是否为1,如果不为1,表示有人在共享这块内存,此时需要做一份深拷贝.也就是说,如果对这块内存不写就没有深拷贝只有浅拷贝,不修改就赚了,效率很高稳赚不赔。

🏠 relational operators 与 赋值

  • relational operators

string类中还对一系列关系运算符进行了重载,它们分别是==、!=、<、<=、>、>=。重载后的关系运算符支持string类和string类之间的关系比较、string类和字符串之间的关系比较、字符串和string类之间的关系比较。

string s1("abcd");
string s2("abde");
cout << (s1 > s2) << endl; //0
cout << (s1 < s2) << endl; //1
cout << (s1 == s2) << endl; //0

注意:这些重载的关系比较运算符所比较的都是对应字符的ASCII码值。

  • 赋值

string类中对=运算符进行了重载,重载后的=运算符支持string类的赋值、字符串的赋值以及字符的赋值。

string s1;
string s2("CSDN");

//支持string类的赋值
s1 = s2;
cout << s1 << endl; //CSDN

//支持字符串的赋值
s1 = "hello";
cout << s1 << endl;  //hello

//支持字符的赋值
s1 = 'x';
cout << s1 << endl; //x

完。

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

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

相关文章

Camtasia2024破解版本电脑屏幕录像编辑神器全新体验

&#x1f31f; 屏幕录像与编辑神器——Camtasia2024全新体验 大家好&#xff01;今天我要来和大家安利一款让我彻底摆脱视频制作烦恼的神器——Camtasia2024&#xff01;&#x1f389; &#x1f308; 功能升级&#xff1a;更智能&#xff0c;更便捷 得提的是Camtasia 2024在功…

python的常用模块,必能覆盖你的需求

1.Request 把python的提示信息做到精细且覆盖广泛 2.Numpy 非常重要的库&#xff0c;最初学Python&#xff0c;第一个使用的就是这个。为Python提供了很多高级的数学方式 3.SciPy 是Python的算法和数学工具车&#xff0c;把很多科学家从RUby吸引到了python 4. P…

【车载开发系列】ParaSoft安装步骤介绍

【车载开发系列】ParaSoft安装步骤介绍 【车载开发系列】ParaSoft安装步骤介绍 【车载开发系列】ParaSoft安装步骤介绍一. 前言二. 安装步骤1. 双击安装包2. 选择安装语言3. 选择许可协议4. 选择软件安装位置5. 选择开始菜单文件夹6. 选择安装时的附加任务7. 安装准备完毕8. 执…

【小沐学OpenGL】Ubuntu环境下glfw的安装和使用

文章目录 1、简介1.1 OpenGL简介1.2 glfw简介 2、安装glfw2.1 直接命令二进制安装2.2 源码安装 3、测试glfw3.1 测试1&#xff0c;glfwglew3.2 测试2&#xff0c;glfwglad3.3 测试3 结语 1、简介 1.1 OpenGL简介 OpenGL作为图形界的工业标准&#xff0c;其仅仅定义了一组2D和…

PhotoZoom9怎么样?图片模糊怎么办?

DeepZoomPix的前身。PhotoZoom是一款新颖的、技术上具有革命性的对数码图片进行放大的工具。通常的工具对数码图片进行放大时&#xff0c;总会降低图片的品质&#xff0c;而这款软件使用了S-SPLINE Max技术 一种申请过专利的&#xff0c;拥有自动调节、高级的插值算法的技术&am…

PCIe总线-Linux内核PCIe设备枚举流程分析(十三)

1.简介 当系统启动时或者有新的PCIe设备接入时&#xff0c;PCIe主机会扫描PCIe总线上的PCIe设备&#xff0c;读取设备配置空间信息&#xff0c;建立设备的拓扑关系&#xff0c;然后为设备分配资源&#xff08;如内存空间、I/O空间、中断、总线编号等&#xff09;&#xff0c;最…

网络安全AI大模型训练从入门到精通

前言 2022年下半年&#xff0c;国内安全圈内开始完chatGPT&#xff0c;当时在安全圈内小火了一把。大家纷纷注册去体验一把&#xff0c;希望chatGPT能帮助解决日常安服渗透问题。当时以为仅此而已&#xff0c;谁知年后大火&#xff0c;随后以chatGPT为代表的大语言模型&#x…

【老课推荐】基于LangChain和知识图谱的大模型医疗问答机器人项目

在当今数据驱动和人工智能主导的时代&#xff0c;大模型和知识图谱的结合是一个重要的研究和应用方向。大模型实战课程通过48课时&#xff0c;分为六个主要章节&#xff0c;涵盖了从基本概念到高级应用的多方面内容。学员将通过本课程学习如何使用LangChain和OpenAI进行开发&am…

Spring Boot:医疗排班系统开发的技术革新

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

数据分析:Python语言相关性对角矩阵计算

文章目录 介绍加载R包导入数据计算连续型变量相关系数展示显著性结果图总结介绍 下三角相关系数矩阵是指相关系数矩阵中,仅展示主对角线以下部分(不包括主对角线)的值。在相关系数矩阵中,主对角线上的元素都是1(因为任何变量与自身的相关系数都是完美的1),而上三角和下…

Java笔试面试题AI答之单元测试JUnit(2)

文章目录 7. 为什么JUnit只报告单次测试中的第一次失败&#xff1f;8. Java中&#xff0c;assert是一个关键字。 这不会与JUnit的assert&#xff08;&#xff09;方法冲突吗&#xff1f;9. 解释如何测试静态方法&#xff1f;一、直接调用测试二、隔离依赖三、使用Mock框架四、重…

助贷行业的三大严峻挑战:贷款中介公司转型债务重组业务

大家是否察觉到一种趋势&#xff1f;现如今&#xff0c;众多贷款辅助服务机构与专注于债务再构的公司之间形成了紧密的“联动”。有的选择将获取的贷款需求转介给债务重组方&#xff0c;有的则直接下场&#xff0c;动用自身资本参与债务重组业务。这一现象背后&#xff0c;究竟…

每日一练:合并区间

一、题目要求 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;in…

四.海量数据实时分析-Doris数据导入导出

数据导入 1.概述 Apache Doris 提供多种数据导入方案&#xff0c;可以针对不同的数据源进行选择不同的数据导入方式。 数据源导入方式对象存储&#xff08;s3&#xff09;,HDFS使用 Broker 导入数据本地文件Stream Load, MySQL LoadKafka订阅 Kafka 数据Mysql、PostgreSQL&a…

表格多列情况下,loading不显示问题

问题描述&#xff1a; 用element plus 做得表格&#xff0c;如下图&#xff0c;列数较多&#xff0c;且部分表格内容显示比较复杂&#xff0c;数据量中等的情况下&#xff0c;有一个switch 按钮&#xff0c;切换部分列的显示和隐藏&#xff0c;会发现&#xff0c;切换为显示的时…

单线程 TCP/IP 服务器和客户端的实现

单线程 TCP/IP 服务器和客户端的实现 文章目录 单线程 TCP/IP 服务器和客户端的实现通信流程服务端客户端 代码实现服务端客户端 运行结果 通信流程 服务端 socket&#xff1a;创建监听的文件描述符(socket) fd&#xff1b;bind&#xff1a;fd 和自身的 ip 和端口绑定&#x…

【Transformer】Positional Encoding

文章目录 为什么需要位置编码&#xff1f;预备知识三角函数求和公式旋转矩阵逆时针旋转顺时针旋转 原始Transformer中的位置编码论文中的介绍具体计算过程为什么是线性变换&#xff1f; 大模型常用的旋转位置编码RoPE基本原理Llama3中的代码实现 参考资料 为什么需要位置编码&a…

DPDK基础入门(五):报文转发

网络处理模块划分 Packet Input: 接收数据包&#xff0c;将其引入处理流程。Pre-processing: 对数据包进行初步处理&#xff0c;例如基本的检查和标记。Input Classification: 细化数据包的分类&#xff0c;例如基于协议或流进行分流。Ingress Queuing: 将数据包放入队列中进行…

【信息学奥赛题】

目录 一、计算机组成与工作原理 二、计算机信息表示 三、计算机软件系统 四、计算机网络基础 五、多媒体知识 六、数据结构 七、程序语言知识 八、知识性问题 一、计算机组成与工作原理 1&#xff0e;下列不属于冯诺依曼计算机模型的核心思想是&#xff08;D&#xff…

Spring源码(3)Aware接口、初始化和销毁方法、@Scope、@Primary

1、目标 本文的主要目标是学习Spring源码中Aware接口、初始化和销毁方法、Scope注解、Primary注解的使用 2、Aware接口 Component public class MyBeanAware implements BeanNameAware, ApplicationContextAware {Overridepublic void setBeanName(String name) {System.out…