string类中的常用接口
#include<string>
- 构造与析构
// 构造
string();
string(const string& str);
string(const string& str, size_t pos, size_t len = npos);
string(const char* s);
string(const char* s, size_t n);
string(size_t n, char c);
template<class InputIterator>
string(InputInterator first, InputIterator last);
// 析构
~string();
例一:构造函数的使用
void Test1()
{
string s1; // 构造一个空字符串 里面有一个\0
string s2("hello"); // 用C串构造string对象
string s3("world", 4); // 用C串的前4个字符构造
string s4(10, 'a'); // 用10个字符a构造
string s5(s2); // 拷贝构造
string s6(s2, 0, 3); // 用s2对象从0号位置开始的3个字符构造
string s7(s2.begin(), s2.begin() + 3); // 区间构造 左闭右开
}
- 容量相关操作
size_t size()const;
size_t length()const;
void resize(size_t n);
void resize(size_t n, char c);
size_t capacity()const;
void reserve(size_t n = 0);
void clear();
bool empty()const;
- 元素访问
char& operator[](size_t pos);
const char& operator[](size_t pos);
char& back();
const char& back()const;
char& front();
const char& front()const;
char& at(size_t pos);
const char& at(size_t pos)const;
- 迭代器
iterator begin();
const iterator begin()const;
iterator end();
const iterator end()const;
reverse_iterator rbegin();
const_reverse_iterator rbegin()const;
reverse_iterator rend();
const_reverse_iterator rend()const;
- 修改
// 增加
// 1. operator+=
string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);
// 2. append
string& append(const string& str);
string& append(const string& str, size_t subpos, size_t sublen);
string& append(const char* s);
string& append(const char* s, size_t n);
string& append(size_t n, char c);
template<class InputIterator>
string& append(InputIterator first, InputIterator last);
// 3. push_back
void push_back(char c);
// 4. insert
string& insert(size_t pos, const string& str);
string& insert(size_t pos, const string& str, size_t subpos, size_t sublen);
string& insert(size_t pos, const char* s);
string& insert(size_t pos, const char* s, size_t n);
string& insert(size_t pos, size_t n, char c);
void insert(iterator p, size_t n, char c);
iterator insert(iterator p, char c);
template <class InputIterator>
void insert(iterator p, InputIterator first, InputIterator last);
// 删除
// 1. erase
string& erase(size_t pos = 0, size_t len = npos);
iterator erase(iterator p);
iterator erase(iterator first, iterator last);
// 2. pop_back
void pop_back();
// assign:用新字符串替换旧字符串
string& assign(const string& str);
string& assign(const string& str, size_t subpos, size_t sublen);
string& assign(const char* s);
string& assign(const char* s, size_t n);
string& assign(size_t n, char c);
template <class InputIterator>
string& assign(InputIterator first, InputIterator last);
// replace:替换字符串的一部分
string& replace(size_t pos, size_t len, const string& str);
string& replace(iterator i1, iterator i2, const string& str);
string& replace(size_t pos, size_t len, const string& str, size_t subpos, size_t sublen);
string& replace(size_t pos, size_t len, const char* s);
string& replace(iterator i1, iterator i2, const char* s);
string& replace(size_t pos, size_t len, const char* s, size_t n);
string& replace(iterator i1, iterator i2, const char* s, size_t n);
string& replace(size_t pos, size_t len, size_t n, char c);
string& replace(iterator i1, iterator i2, size_t n, char c);
template <class InputIterator>
string& replace (iterator i1, iterator i2, InputIterator first, InputIterator last);
void swap(string& str);
- 特殊操作
const char* c_str()const;
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(const char* s, size_t pos, size_t n)const;
size_t find(char c, size_t pos = 0)const;
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(const char* s, size_t pos, size_t n) const;
size_t rfind(char c, size_t pos = npos) const;
string substr(size_t pos = 0, size_t len = npos) const;
int compare(const string& str) const;
int compare(size_t pos, size_t len, const string& str) const;
int compare(size_t pos, size_t len, const string& str,size_t subpos, size_t sublen) const;
int compare(const char* s) const;
int compare(size_t pos, size_t len, const char* s) const;
int compare(size_t pos, size_t len, const char* s, size_t n) const;
- 成员常量
static const size_t npos = -1;
- 非成员函数重载
istream& getline(istream& is, string& str, char delim);
istream& getline(istream& is, string& str);
// std::swap(string)
void swap(string& x, string& y);
元素遍历方式
// 法一:
for(int i = 0; i < str.size(); i++
{
cout << str[i] << " ";
}
cout << endl;
// 法二:
for(auto e : str)
cout << e << " ";
cout << endl;
// 法三:
string::iterator it = str.begin();
while(it != str.end())
{
cout << *it << " ";
++it;
}
cout << endl;
深度理解resize()
假设resize前string对象的_size为oldsize,_capacity为oldcapacity,resize后string对象的_size为oldsize,_capacity为newcapacity
- newsize <= oldsize
将有效元素个数减少到newsize个,_capacity不改变 - newsize > oldsize
1)newsize <= oldcapacity :在原空间后加
如果是void resize(size_t n);
,在原字符串后追加 (newsize - oldsize)个0;
如果是void resize(size_t n, char c);
,在原字符串后追加 (newsize - oldsize)个字符c
2)newsize > oldcapacity
要进行以下操作:
① 开辟新空间
② 将旧空间元素拷贝到新空间
③ 释放旧空间
④ 让_str指向新空间
⑤ _capacity = newsize
⑥ 在新空间后加:如果是void resize(size_t n);
,在原字符串后追加 (newsize - oldsize)个0;如果是void resize(size_t n, char c);
,在原字符串后追加 (newsize - oldsize)个字符c
深度理解reserve()
- newcapacity <= oldcapacity
1)newcpacity <= 15
_capacity 最终变为15
2)newcpacity > 15
reserve直接忽略 - newcapacity > oldcapacity
vs:1.5倍 Linux:2倍
std::swap和std::string::swap的区别
void Test1()
{
string s1("hello world");
string s2("hahahah");
//重新开辟空间,交换s1,s2的值
std::swap(s1, s2);
//交换两个指针指向的不同空间
s1.swap(s2);
}
迭代器失效场景
-
所有可能引起容量修改的操作
push_back / insert / operator+= / append
resize() / reserve()
其他:erase / clear / swap -
解决迭代器失效的办法:在可能会引起迭代器失效的操作之后对迭代器重新赋值
浅拷贝和深拷贝
-
当一个类中没有显式给出拷贝构造函数或者赋值运算符重载,编译器会生成默认的拷贝构造函数以及赋值运算符重载,但是它们都是浅拷贝
-
浅拷贝:将一个对象中的内容按字节拷贝至另一个对象,两个对象使用同一份资源
浅拷贝存在的问题:当类中未涉及到资源管理时,不会出现任何问题;但是当类中涉及到资源管理时,如果用户不显式定义拷贝构造函数或赋值运算符重载函数,当多个对象销毁时,会将同一份资源释放多次而引起代码崩溃 -
浅拷贝解决方式
1)深拷贝:即每个对象各自拥有一份资源
2)写时拷贝:浅拷贝+引用计数
引用计数:记录该份资源被多少个对象在使用,保证了多个对象销毁时资源只释放一次
传统版:
class String
{
public:
String(const char* str = "")
{
if (str == nullptr)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
#if 0
if (str != nullptr)
{
_str = new char[strlen(str) + 1];
strcpy(_str, str); // 拷贝包括\0
}
else // 如果传参为nullptr 将其当成空字符串处理
{
// 这种写法,构造string对象时用的是new,析构函数释放时要用delete
// 实现起来不方便
_str = new char;
*_str = '\0';
}
#endif
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
/*
编译器默认生成的拷贝构造函数是浅拷贝
浅拷贝:两个对象指向同一块空间
浅拷贝在释放资源时,会将同一块空间释放两次导致程序崩溃
所以,涉及到资源管理要显式定义拷贝构造函数
深拷贝:两个对象各自拥有一块空间
*/
String(const String& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
/*
编译器默认生成的赋值运算符重载存在两个问题:
一是浅拷贝,二是被赋值对象原来的空间被覆盖,导致没有释放,存在内存泄漏
赋值运算符重载同拷贝构造一样,涉及到资源管理,一定要显式定义
注意:如果被赋值空间 < 赋值对象空间 那么strcpy会出错
所以,先开辟一段和赋值对象空间一样大的临时空间,然后将赋值对象中的内容拷贝到临时空间
然后释放赋值对象的旧空间,让其指针指向临时空间
*/
String& operator=(const String& s)
{
if (this != &s)
{
char* temp = new char[strlen(s._str) + 1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
return *this;
}
private:
char* _str;
};
现代版:
class String
{
public:
String(const char* str = "")
{
if (str == nullptr)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
/*
该拷贝构造如果不写初始化列表会出问题
Test2没问题,但是Test3释放资源会出问题
*/
String(const String& s)
:_str(nullptr)
{
// 用构造函数构造一个临时变量
String sTemp(s._str);
// 交换this指针指向对象的_str和临时对象的_str的地址
std::swap(_str,sTemp._str);
}
// 因为赋值时,自己给自己赋值的情况很少,所以采用以下做法
/*
这种写法的优势,巧妙的使用了传值构造的临时对象
传参时,对象传参要调用拷贝构造函数先创建一份临时变量
将临时变量的_str和this指针指向对象的_str的地址交换
临时对象在函数执行完要销毁,调用析构函数,将被赋值对象原来的空间释放掉
*/
String& operator=(String s)
{
std::swap(_str, s._str);
return *this;
}
// 下面代码依然重复度高
/*String& operator=(const String& s)
{
if (this != &s)
{
String sTemp(s._str);
swap(_str, sTemp._str);
}
return *this;
}*/
private:
char* _str;
};
/*
Test2中调用没有初始化列表的拷贝构造函数,最终调用析构函数没有问题
*/
void Test2()
{
String s1("hello");
String s2(s1);
}
// Test3中调用没有初始化列表的拷贝构造函数,最终调用析构函数会程序中断
String Test3()
{
String s1("sad");
return s1;
}
void Test4()
{
String s1("sad");
String s2("happy");
s1 = s2;
}