C++ STL string 详解
Posted DR5200
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ STL string 详解相关的知识,希望对你有一定的参考价值。
文章目录
- 一.string类的常用接口声明
- 二.string类的模拟实现
- string类接口
- 默认成员函数
- 容量相关的函数
- 修改字符串相关函数
- 迭代器相关函数
- 元素遍历相关函数
- String operations
- 非成员函数
一.string类的常用接口声明
(1).string类对象的常见构造
(1). string();
功能 : 构造空的string类对象,即空字符串
(2).string (const string& str);
功能 : 拷贝构造函数
(3).string (const string& str, size_t pos, size_t len = npos);
功能 : 拷贝str中从字符位置pos开始len个字符的部分(如果str太短或len是string::npos),则拷贝到str的末尾.
(4).string (const char* s);
功能 : 拷贝s所指的以空结尾的字符序列(C字符串).
(5).string (const char* s, size_t n);
功能 : 拷贝s所指的字符数组中前n个字符.
(6).string (size_t n, char c);
功能 : 用n个连续的字符c填充字符串.
// 1. 空字符串
string s1;
// 2. 拷贝s所指的以空结尾的字符序列
string s2("hello world");
string s3 = "hello world";
// 3. 拷贝构造函数
string s4(s3);
// 4. 拷贝str中从字符位置pos开始len个字符的部分(如果str太短或len是string::npos),则拷贝到str的末尾
string s5(s4,0,3);
string s6(s4,0,string::npos);
// 5. 拷贝s所指的字符数组中前n个字符
string s7("123456",3);
// 6. 用n个连续的字符c填充字符串
string s8(10,'a');
(2).string类对象的访问和遍历方式
构造出string类对象以后,我们想要遍历字符串要怎么做呢?
(1). 下标访问
(2). 迭代器
iterator begin();
const_iterator begin()const;
功能 : 返回指向字符串第一个字符的迭代器.如果string对象是const限定的,则函数返回一个const迭代器
iterator end();
const_iterator end()const;
功能 : 返回指向字符串最后一个字符的下一个位置的迭代器.如果string对象是const限定的,则函数返回一个const迭代器
(3).范围for
int main()
{
string s1("hello world");
// 1. 下标访问
for(size_t i = 0;i < s1.size();i++)
{
cout<<s1[i]<<" ";
// cout<<s1.at(i)<<endl;
}
cout<<endl;
// 2. 迭代器
// begin()返回的是第一个位置,end()返回的是最后一个位置的下一个位置
string::iterator it = s1.begin();
while(it != s1.end())
{
cout<<*it<<" ";
it++;
}
cout<<endl;
// 反向迭代器,逆序遍历
string::reverse_iterator rit = s1.rbegin();
while(rit != s1.rend())
{
cout<<*rit<<" ";
rit++;
}
cout<<endl;
// 3.范围for
for(auto& e : s1)
{
cout<<e<<" ";
}
cout<<endl;
}
(4).string类对象的修改操作
(1).void push_back(char c)
功能 : 将字符c追加到字符串的末尾,使其长度增加1.
(2).string& append (const char* s);
string& append (const string& str);
更多形式可查阅 append
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");
string s2("!!");
s1.append(s2);
}
(3).string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);
int main()
{
string s1;
s1 += ' ';
s1 += "hello ";
string s2("world");
s1 += s2;
}
(4). string& insert (size_t pos, const string& str);
string& insert (size_t pos, const char* s);
功能 : 在pos位置插入字符串
int main()
{
string s1("hello world");
s1.insert(0,"xx");
string s2("!!!!");
s1.insert(2,s2);
}
(5). string& erase (size_t pos = 0, size_t len = npos);
iterator erase (iterator p);
iterator erase (iterator first, iterator last);
(1). 删除字符串值中从字符位置pos开始并跨越len个字符的部分(如果内容太短或len是basic_string::npos,则删除到字符串末尾)。
请注意,默认参数会删除字符串中的所有字符(如成员函数clear).
(2). 删除p所指的字符.
(3). 删除[first,last]范围内的字符序列。
功能 : 删除基本字符串的一部分,减少其长度.
int main()
{
string s1("hello world");
s1.erase(0,3);
}
(6). const char* c_str() const;
功能 : 获取C字符串
返回一个指向数组的指针,该数组包含一个以null结尾的字符序列(即C字符串),表示string对象的当前值。此数组包含组成string对象值的相同字符序列,以及结尾处的附加终止空字符(’\\0’)。
string s1("hello world");
cout << s1 << endl; // 调用 operator<<(cout,s1)
cout << s1.c_str() << endl; // 调用 operator<<(cout,const char*)
s1.resize(20);
s1 += "!!!";
cout << s1 << endl;
cout << s1.c_str() << endl;
(7). 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;
功能 : 在字符串中搜索由其参数指定的序列的第一个匹配项 .
(8).string substr (size_t pos = 0, size_t len = npos) const;
功能 : 返回一个新构造的字符串对象,子字符串是对象的一部分,从字符位置pos开始,跨越len个字符(或直到字符串的结尾,以先到者为准).
// 获取域名
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);
}
else
{
return string();
}
}
string url = "http://www.cplusplus.com/reference/string/string/find/";
cout << GetDomain(url) << endl;
cout<<GetProtocol(url)<<endl;
(3).string类对象的容量操作
(1).size_t size() const;
功能 : 返回字符串的长度(以字节为单位).
(2).size_t capacity() const;
功能 : 返回当前为字符串分配的存储空间的大小(以字节为单位).
(3).bool empty() const;
功能 : 返回字符串是否为空(即其长度是否为0).
(4).void clear();
功能 : 删除字符串的内容,该字符串将变为空字符串(长度为0).
(5).void resize (size_t n);
void resize (size_t n, char c);
功能 : 将字符串大小调整为n个字符的长度。
如果n小于当前字符串长度,则当前值将缩短为其第一个n个字符,从而删除超过n个的字符。
如果n大于当前字符串长度,则通过在末尾插入尽可能多的字符以达到n的大小,来扩展当前内容。如果指定了c,则新元素将初始化为c,否则,为空字符.
(6).void reserve (size_type n = 0);
功能 : 请求对字符串容量进行调整,以适应计划的大小更改,长度最多为n个字符.
如果n大于当前字符串容量,则函数会使容器将其容量增加到n个字符(或更大)。
在所有其他情况下,它被视为收缩字符串容量的非绑定请求:容器实现可以自由地进行其他优化,并将基本字符串保留为大于n的容量。
string s1;
cout << "s1 size :"<<s1.size() << endl;
cout << "s1 capacity : "<<s1.capacity() << endl;
cout << s1 << endl;
s1.resize(20, 'x');
cout << "s1 size :" << s1.size() << endl;
cout << "s1 capacity : " << s1.capacity() << endl;
cout << s1 << endl;
string s2("hello world");
s2.resize(5);
cout << s2 << endl;
string s3;
cout << "s3 size :" << s3.size() << endl;
cout << "s3 capacity : " << s3.capacity() << endl;
s3.reserve(40);
cout << "s3 size :" << s3.size() << endl;
cout << "s3 capacity : " << s3.capacity() << endl;
二.string类的模拟实现
string类接口
namespace lyp
{
class string
{
public:
// 迭代器
typedef char* iterator;
// const迭代器
typedef const char* const_iterator;
// 默认成员函数
string(const char* str = "");
string(const string& str);
string& operator=(const string& str);
~string();
// 修改操作函数
void push_back(char c);
void append(const char* str);
string& operator+=(char c);
string& operator+=(const char* str);
string& insert(size_t pos,char c);
string& insert(size_t pos, const char* str);
string& erase(size_t pos, size_t len = npos);
void swap(string& str);
// 迭代器相关函数
iterator begin();
const_iterator begin()const;
iterator end();
const_iterator end()const;
// 容量相关函数
size_t size()const;
size_t capacity()const;
void resize(size_t n,char c = '\\0');
void reserve(size_t n);
void clear();
bool empty()const;
// 元素遍历函数
char& operator[](size_t index);
const char& operator[](size_t index)const;
// String operations
const char* c_str()const;
size_t find(char c,size_t pos = 0)const;
size_t find(const char* str,size_t pos = 0)const;
size_t rfind(char c, size_t pos = npos)const;
size_t rfind(const char* str, size_t pos = npos)const;
private:
char* _str;
size_t _size; // 有效字符的个数
size_t _capacity; // 能存储有效字符的最大个数
static const size_t npos; // 静态无符号成员变量
};
const size_t string::npos = -1;
// 非成员函数
// relational operators
bool operator<(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
// << >> 运算符重载
istream& operator>>(istream& in,string& str);
ostream& operator<<(ostream& out, const string& str);
// getline
istream& getline(istream& in, string& str);
}
默认成员函数
(1).构造函数
string(const char* str = "");
函数的默认参数为空字符串,当然我们也可以传递自己给定的字符串,创建的对象存储字符串的空间比参数字符串的长度多 1 ,目的是为了存储空字符,并将参数字符串的内容拷贝到对象字符串空间中_size 和 _capcity 都初始化为参数字符串的长度
string(const char* str = "")
:_str(new char[strlen(str) + 1])
,_size(strlen(str))
,_capacity(strlen(str))
{
strcpy(_str,str);
}
(2).拷贝构造函数
在实现我们的拷贝构造函数之前,我们要先了解一下深拷贝和浅拷贝
浅拷贝又称值拷贝,只是单纯的拷贝值而已,浅拷贝在某些情况下是不存在问题的,但是对于string类的构造函数如果使用浅拷贝,就会出现问题,因为浅拷贝之后两个对象的字符串指针指向同一块内存空间,在对象销毁时,这块内存空间就被析构了两次
而编译器默认生成的拷贝构造函数完成的是浅拷贝,所以我们自己需要去实现深拷贝,即拷贝构造出来的对象和源对象拥有不同的字符串空间
深拷贝的实现一般有两种方式,一种是传统方式,一种是现代方式
传统方式的步骤如下
(1).新开一块和源对象字符串空间一样大的空间
(2).将源对象字符串的内容拷贝到新空间
string(const string& str)
:_str(new char[strlen(str._str) + 1])
,_size(str._size)
,_capacity(str._capacity)
{
strcpy(_str, str._str);
}
现代方式的步骤如下
(1).利用我们上面写好的构造函数,用 str 构造出一个临时对象 tmp
(2).分别交换 创建的对象 和 tmp 对象的成员变量即可
注意 : 创建的对象_str 要初始化为空指针,否则创建的对象_str 为随机值,和 tmp 对象进行交换以后,tmp对象的_str为随机值,在tmp对象销毁时,调用析构函数,释放tmp对象的_str所指向的空间会出现问题
string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str); // 构造临时对象
std::swap(_str,tmp._str); // 交换
std::swap(_size,tmp._size); // 交换
std::swap(_capacity,tmp._capacity); // 交换
// 后面自己实现的swap函数
//swap(tmp);
}
(3).赋值运算符重载
编译器默认生成的赋值运算符重载完成的也是浅拷贝,因此我们仍需要自己去实现深拷贝,赋值运算符重载同样也有两种写法
传统写法步骤如下
(1).释放掉原空间,新开一块和 str 字符串空间一样大的空间
(2).将 str 的内容拷贝到新空间中
string& operator=(const string& str)
{
// 防止自己给自己赋值
if (this != &str)
{
delete[]_str; // 释放空间
_str = new char[strlen(str._str) + 1]; // 新开一块空间
strcpy(_str, str._str); // 将 str 的内容拷贝到新空间中
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
现代写法的步骤如下 :
注意 : 现代写法的参数并没有传引用,而是传值
(1). 因为是传值,所以会调用拷贝构造函数构造出 str
(2). 分别交换 创建的对象 和 str 对象的成员变量即可
string& operator=(string str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
// 后面自己实现的swap函数
//swap(str);
return *this;
}
但此方法有一个缺陷,就是当自己给自己赋值的时候,自己存储字符串的地址发生了改变,我们可以采取以下写法来避免该情况发生
string& operator=(const string& str)
{
// 防止自己给自己赋值
if (this != &str)
{
string tmp(str._str);
std::swap(_str, tmp._str)C++ STL详解