目录
1. 为什么要学习string类
1.1 C语言中的字符串
1.2 两个面试题(暂不做讲解)
2. 标准库中的string类
2.1 string类(了解)
2.2 string类的常用接口说明(注意下面我只讲解最常用的接口)
1. string类对象的常见构造
2. string类对象的容量操作
3. string类对象的访问及遍历操作
4. string类对象的修改操作
5. string类非成员函数
6. 牛刀小试
3. string类的模拟实现
3.1 经典的string类问题
3.2 浅拷贝
3.3 深拷贝
3.3.1 传统版写法的string类
3.3.2 现代版写法的string类
3.3 写时拷贝(了解)
3.4 string类的模拟实现
4. 扩展阅读
string - C++ Reference
1. string 是表示字符串的字符串类2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。3. string 在底层实际是: basic_string 模板类的别名, typedef basic_stringstring; 4. 不能操作多字节或者变长字符的序列。在 使用 string 类时,必须包含 #include 头文件以及 using namespace std ;
函数名称 |
功能说明 |
string()(重点) | 构造空的string类对象,即空字符串 |
string(const char*s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s)(重点) | 拷贝构造函数 |
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
函数名称 | 功能说明 |
string::size - C++ Reference(重点) | 返回字符串有效字符长度 |
string::length - C++ Reference | 返回字符串有效字符长度 |
string::capacity - C++ Reference | 返回空间总大小 |
string::empty - C++ Reference(重点) | 检测字符串释放为空串,是返回true,否则返回false |
string::clear - C++ Reference(重点) | 清空有效字符 |
string::reserve - C++ Reference(重点) | 为字符串预留空间** |
string::resize - C++ Reference(重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
// size/clear/resize
void Teststring1()
{
// 注意:string类对象支持直接用cin和cout进行输入和输出
string s("hello, bit!!!");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此时s中有效字符个数已经增加到15个
s.resize(15);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
}
//================================================================================
====
void Teststring2()
{
string s;
// 测试reserve是否会改变string中有效元素个数
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
s.reserve(50);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
// 利用reserve提高插入数据的效率,避免增容带来的开销
//================================================================================
====
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';
}
}
}
void TestPushBackReserve()
{
string s;
s.reserve(100);
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';
}
}
}
1. size() 与 length() 方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size() 。2. clear() 只是将 string 中有效字符清空,不改变底层空间大小3. resize(size_t n) 与 resize(size_t n, char c) 都是将字符串中有效字符个数改变到 n 个,不同的是当字符个数增多时:resize(n) 用 0 来填充多出的元素空间, resize(size_t n, char c) 用字符 c 来填充多出的元素空间。注意:resize 在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。4. reserve(size_t res_arg=0) :为 string 预留空间,不改变有效元素个数,当 reserve 的参数小于string的底层空间总大小时, reserver 不会改变容量大小。
函数名称 | 功能实现 |
string::operator[] - C++ Reference(重点) | 返回pos位置的字符,const string类对象调用 |
string::begin - C++ Reference+string::end - C++ Reference | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
string::rbegin - C++ Reference+string::rend - C++ Reference | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
void Teststring()
{
string s1("hello Bit");
const string s2("Hello Bit");
cout << s1 << " " << s2 << endl;
cout << s1[0] << " " << s2[0] << endl;
s1[0] = 'H';
cout << s1 << endl;
// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
void Teststring()
{
string s("hello Bit");
// 3种遍历方式:
// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
// 另外以下三种方式对于string而言,第一种使用最多
// 1. for+operator[]
for (size_t i = 0; i < s.size(); ++i)
cout << s[i] << endl;
// 2.迭代器
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << endl;
++it;
}
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
cout << *rit << endl;
// 3.范围for
for (auto ch : s)
cout << ch << endl;
}
三种遍历方式
int main()
{
string s1;
string s2("hello bit");
// 三种遍历
// 1、下标+[]
for (size_t i = 0; i < s2.size(); ++i)
{
// s2.operator[](i)
s2[i] = 'x';
}
cout << endl;
for (size_t i = 0; i < s2.size(); ++i)
{
// s2.operator[](i)
cout << s2[i] << " ";
//cout << s2.at(i) << " ";
}
cout << endl;
//s2[10]; // 越界断言报错
try
{
s2.at(10); // 越界抛异常
}
catch (exception& e)
{
cout << e.what() << endl;
}
// 2、迭代器
// [begin(), end() ) end()返回的不是最后一个数据位置的迭代器,返回是最后一个位置下一个位置
// 也要注意的是,C++中凡是给迭代器一般都是给的[)左闭右开的区间
// 迭代器是类似指针一样东西,具体是什么我们讲了底层实现才能知道
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 迭代器意义:像string、vector支持[]遍历,但是list、map等等容器不支持[]
// 我们就要用迭代器遍历,所以迭代器是一种统一使用的方式
vector v = { 1, 2, 3, 4 };
vector::iterator vit = v.begin();
while (vit != v.end())
{
cout << *vit << " ";
++vit;
}
cout << endl;
list lt = { 1, 2, 3, 4 };
list::iterator ltit = lt.begin();
while (ltit != lt.end())
{
cout << *ltit << " ";
++ltit;
}
cout << endl;
// 反向迭代器
string s3("123456");
string::iterator it3 = s3.begin();
while (it3 != s3.end())
{
*it3 += 5;
++it3;
}
cout << endl;
string::reverse_iterator rit = s3.rbegin();
while (rit != s3.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
// 3、C++11 提供 范围for, 特点:写起来简洁
// 依次取容器中的数据,赋值给e,自动判断结束
//for (auto& e : s3)
for (char& e : s3)
{
e += 1;
}
cout << endl;
for (auto e : s3)
{
cout << e << " ";
}
cout << endl;
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
for (auto x : lt)
{
cout << x << " ";
}
cout << endl;
return 0;
}
函数名称 | 功能说明 |
string::push_back - C++ Reference |
在字符串后尾插字符 c
|
string::append - C++ Reference |
在字符串后追加一个字符串
|
string::operator+= - C++ Reference(重点) | 在字符串后追加字符str |
string::c_str - C++ Reference(重点) | 返回C格式字符串 |
string::find - C++ Reference+string::npos - C++ Reference(重点) |
从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的位置
|
string::rfind - C++ Reference |
从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的位置
|
string::substr - C++ Reference |
在 str 中从 pos 位置开始,截取 n 个字符,然后将其返回
|
void Teststring()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'b'; // 在str后追加一个字符'b'
str += "it"; // 在str后追加一个字符串"it"
cout << str << endl;
cout << str.c_str() << endl; // 以C语言的方式打印字符串
// 获取file的后缀
string file1("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size() - pos));
cout << suffix << endl;
// npos是string里面的一个静态成员变量
// static const size_t npos = -1;
// 取出url中的域名
sring url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t start = url.find("://");
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
start += 3;
size_t finish = url.find('/', start);
string address = url.substr(start, finish - start);
cout << address << endl;
// 删除url的协议前缀
pos = url.find("://");
url.erase(0, pos + 3);
cout << url << endl;
}
int main()
{
string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.append("world");
cout << s1 << endl;
string s2("!!!!");
//s1.append(s2);
s1.append(s2.begin(), s2.end());
cout << s1 << endl;
// 实际中最喜欢用这个+=
s1 += ' ';
s1 += "比特";
s1 += s2;
cout << s1 << endl;
// 尽量少用insert,因为底层实现是数组,头部或者中间插入需要挪动数据
s1.insert(0, "x");
cout << s1 << endl;
s1.insert(3, "yyyy");
cout << s1 << endl;
s1.insert(0, "yyyy");
cout << s1 << endl;
//s1.insert(300, "yyyy");
cout << s1 << endl;
s1.erase(0, 1);
cout << s1 << endl;
s1.erase(0, 3);
cout << s1 << endl;
s1.erase(3, 10);
cout << s1 << endl;
//s1.erase(3, 100);
s1.erase(3);
cout << s1 << endl;
s1.erase();
cout << s1 << endl;
return 0;
}
c_str用于配合其他函数接口
假设要求取出文件名的后缀
// 假设要求取出文件名的后缀
string filename = "test.txt.zip";
size_t pos = filename.rfind('.');//实现只取最后一个后缀
if (pos != string::npos)
{
//string suff(filename, pos, filename.size() - pos);
//string suff(filename, pos);
//string suff = filename.substr(pos, filename.size() - pos);
string suff = filename.substr(pos);
cout << suff << endl;
}
string GetDomain(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
size_t start = pos + 3;
size_t end = url.find('/', start);
if (end != string::npos)
{
return url.substr(start, end - start);
}
else
{
return string();
}
}
else
{
return string();
}
}
string GetProtocol(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
return url.substr(0, pos - 0);
}
else
{
//string s;
//return s;
return string();//返回一个匿名对象
}
}
string url1 = "http://www.cplusplus.com/reference/string/string/rfind/";
string url2 = "https://tower.im/users/sign_in";
string url3 = "tower.im/users/sign_in";
cout << GetDomain(url1) << endl;
cout << GetProtocol(url1) << endl;
cout << GetDomain(url2) << endl;
cout << GetProtocol(url2) << endl;
cout << GetProtocol(url3) << endl;
1. 在 string 尾部追加字符时, s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多,一般情况下string 类的 += 操作用的比较多, += 操作不仅可以连接单个字符,还可以连接字符串。2. 对 string 操作时,如果能够大概预估到放多少字符,可以先通过 reserve 把空间预留好。
函数 | 功能说明 |
operator+ (string) - C++ Reference |
尽量少用,因为传值返回,导致深拷贝效率低
|
operator>> (string) - C++ Reference(重点) |
输入运算符重载
|
operator<< (string) - C++ Reference(重点) | 输出运算符重载 |
getline (string) - C++ Reference(重点) | 获取一行字符串 |
relational operators (string) - C++ Reference(重点) | 大小比较 |
上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。
见博客
class string
{
public:
/*string()
:_str(new char[1])
{*_str = '\0';}
*/
//string(const char* str = "\0") 错误示范
//string(const char* str = nullptr) 错误示范
string(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void Teststring()
{
string s1("hello bit!!!");
string s2(s1);
}
上述 string 类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用 s1 构 造 s2 时,编译器会调用默认的拷贝构造。最终导致的问题是, s1 、 s2 共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃 ,这种拷贝方式,称为浅拷贝
class string
{
public:
string(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
class string
{
public:
string(const char* str = "")
{
if (nullptr == str)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
: _str(nullptr)
{
string strTmp(s._str);
swap(_str, strTmp._str);
}
// 对比下和上面的赋值那个实现比较好?
string& operator=(string s)
{
swap(_str, s._str);
return *this;
}
/*
string& operator=(const string& s)
{
if(this != &s)
{
string strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
C++面试中string类的一种正确写法 | 酷 壳 - CoolShell
STL 的string类怎么啦?_haoel的博客-CSDN博客