系列文章
http://t.csdnimg.cn/u80hL
前言——标准库中的string
string——为效率而生
在C++中C++为提高编译者对字符和字符串的管理效率而生。
string类——编译器自带
它由发明者根据行业标准制作,其成员函数包含了日常生活中的常用函数。
string——功能齐全
string支持了很多函数,这些函数围绕字符和字符串设计实现。
一、reserve——改变容量(string对象所占有的容量)
- 改变对象的capacity:他会请求拓展或缩小对象所占有的空间,但reserve只能操作对象未利用的空间,对于对象已利用的空间它不能动其分毫;
- reserve存在意义为:为对象提前开辟足够的空间以供使用,避免动态反复开空间消耗资源;
- reserve实际开辟的空间大小会大于等于我们要求的数值,编译器会自行选择要不要多给一些空间以符合该编译器的特性,如g++会开辟等于我们要求的数值大小的空间、visual私自开辟给要求数值1.5倍大小的空间;
- 如果reserve的空间小于对象占有的的空间,则会有两种情况:
情况1:请求的新空间大于等于对象=已利用的空间大小=且小于=对象所占的空间大小=,此时会保留对象已经利用的空间不受干扰,去释放未利用的空间;
情况2:请求的新空间小于对象已经利用的空间大小,此时对象占有的空间会等于对象已利用空间的大小;
如代码所示,请求开辟较大的空间、请求缩小空间(visual环境下):
#include<iostream>
using namespace std;
int main()
{
//reserve
string s1; //定义一个对象
s1 = "0123456789";
cout << s1 << endl;
cout << s1.size() << endl; //已经利用10字节空间
cout << s1.capacity() << endl; //占有了15字节空间,visual环境下,多开了一点空间
cout << endl;
s1.reserve(30); //给这个对象请求更大的空间使用权
cout << s1 << endl;
cout << s1.size() << endl; //已经利用10字节空间
cout << s1.capacity() << endl; //占有了31字节空间(visual环境下,多开了一点空间,编译器认为没有必要开太大)
cout << endl;
s1.reserve(15); //请求给对象更小的使用权
cout << s1 << endl;
cout << s1.size() << endl; //已经利用10字节空间
cout << s1.capacity() << endl; //缩小字节
return 0;
}
ps. 在实践时我发现,编译器会优化我们的 reserve()请求,编译器会根据具体的情况决定要不要执行我们的开拓和缩小请求。
二、resize——剪裁或拓展空间使用权(定义对象利用的空间大小)
一般来讲,string对象的size大小会和它所存储的字符、字符串所占空间保持一致,但在一些情况下,我们要求一些对象一定要有一个确定大小的空间使用权,此时resize就派上用场了;
- 通过resize我们可以强行自定义对象利用的空间大小权;
- resize的操作会有3种情况:
情况1:resize的空间大于原size和capacity,此时原size会被扩大到新的size大小,那些没有实质内容的空间可以指定一种字符去填充,如果没有指定则会自动用’\0’填充;
而capacity会自动扩容直到可以包括下新的size;
情况2:resize的空间大于原size且小于capacity,此时原size会被扩大到新的size大小,那些没有实质内容的空间可以指定一种字符去填充,如果没有指定则会自动用’\0’填充;
capacity保持不变;
情况3:resize的空间小于原size,此时编译器会舍去不在新size存储空间里面的信息,而capacity不做改变;
简而言之,capacity会配合resize完成其功能。
如下代码,使用resize()扩大和缩小对象空间使用。
void test2()
{
string s2; //定义一个对象
s2 = "0123456789";
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
s2.resize(20);
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
s2.resize(5);
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
}
三、push_back——尾插字符、append——尾插字符串、+=运算符重载
在string字符串利用空间的尾部插入一个字符或字符串。
string有一套针对字符和字符串的插入函数设计。
void push_back(char c) //尾插字符
{
//首先检查是否需要扩容
if (_capacity == _size)
{
reserve(_capacity == 0 ? _capacity == 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
void append(const char* str) //尾插字符串
{
//检查是否需要扩容
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str +_size, str); //在_str的尾部插入一个字符串
_size +=len;
}
void test4() //试验尾插
{
cout << endl;
cout << endl;
cout << "试验尾插" << endl;
string s1("123abc");
cout << s1 << endl;
s1.push_back('d');
cout << s1 << endl;
s1.append("456def");
cout << s1 << endl;
}
基于上述的两个函数,我们可以以此实现+=运算符的重载;
//string+=的本质就是push_back和append
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void text5()
{
cout << endl;
cout << endl;
cout << "试验push_back()、append()和+=" << endl;
string s1("a");
cout << s1 << endl;
s1.push_back('b');
cout << s1 << endl;
s1.append("cd");
cout << s1 << endl;
s1 += 'e';
cout << s1 << endl;
s1 += "fg";
cout << s1 << endl;
}
四、insert——插入函数(在字符串中pos位置插入字符或字符串)
针对字符串的插入字符或字符串。
在pos位置上插入字符c,并返回该字符的位置
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = c;
++_size;
return *this;
}
在pos位置上插入字符串str,并返回该字符的位置
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
//扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
size_t end = _size+len;
while (end>pos+len-1)
{
_str[end] = _str[end-len];
end--;
}
//插入字符串
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
void test11()
{
cout << endl;
cout << endl;
cout << "试验insert()函数" << endl;
string s1("abcde");2
cout << s1 << endl;
s1.insert(1, 'c');
cout << s1 << endl;
s1.insert(1, "abc");
cout << s1 << endl;
}
五、erase——删除字符串中的一部分
删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len=npos) //用一个npos来配合实现全部删除
{
assert(pos < _size);
//若将pos后的数字全部删除
if (pos + len > _size || len == npos)
{
_str[pos] = '\0';
_size = pos;
}
//如将pos后有限的字符删除,剩下的拼接在一起
else
{
//我们通过直接覆盖来实现这一点
size_t begin = pos + len;
while (begin < _size)
{
_str[pos] = _str[begin];
begin++;
pos++;
}
_size -= len;
}
return *this;
}
void test10()
{
cout << endl;
cout << endl;
cout << "试验erase" << endl;
string s1("abcde");
cout << s1 << endl;
s1.erase(2,1);
cout << s1 << endl;
s1.erase(1);
cout << s1 << endl;
}
六、关系运算符重载
字符和字符串的重载,一般是比较第一个字符的ascll码的大小。
relational operators
bool operator<(const string& s) const
{
return _str[0] < s._str[0];
}
bool operator==(const string& s) const
{
return _str[0] == s._str[0];
}
bool operator<=(const string& s) const
{
return *this<s||*this==s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
void text7()
{
cout << endl;
cout << endl;
cout << "试验关系运算符" << endl;
cout << "s1=s2时" << endl;
string s1("1");
string s2("1");
cout << "<" << " " << (s1 < s2) << endl;
cout << ">" << " " << (s1 > s2) << endl;
cout << "==" << " " << (s1 == s2) << endl;
cout << "<=" << " " << (s1 <= s2) << endl;
cout << ">=" << " " << (s1 >= s2) << endl;
cout << "s3>s4时" << endl;
string s3="2";
string s4="1";
cout << "<" << " " << (s3 < s4) << endl;
cout << ">" << " " << (s3 > s4) << endl;
cout << "==" << " " << (s3 == s4) << endl;
cout << "<=" << " " << (s3 <= s4) << endl;
cout << ">=" << " " << (s3 >= s4) << endl;
cout << "s5<s6时" << endl;
string s5="1";
string s6="2";
cout << "<" << " " << (s5 < s6) << endl;
cout << ">" << " " << (s5 > s6) << endl;
cout << "==" << " " << (s5 == s6) << endl;
cout << "<=" << " " << (s5 <= s6) << endl;
cout << ">=" << " " << (s5 >= s6) << endl;
}
七、流插入、流提取
字符串相关的流提取<<,可以看作是一种遍历;
字符串相关的流插入>>,需要调用C语言就有的in.get()函数,其插入的内在机制基本与C语言一致,其表现形式有一些差异;
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s._size; i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//in >> ch;
ch = in.get();
s.reserve(128);
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
八、迭代器
非const对象可以调用常函数,也能调用非常函数。但是常对象只能调用常函数,不能调用非常函数(常对象也包括常指针和常引用)。为了应对常性有关的权限扩大问题,我们设计了两种迭代器,一个是为一般对象服务,一个是专门为常性对象服务。
//iterator ——实现string类的迭代器
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const //这里需要注意我们的迭代器常性和非常性名字最好不同,因为我们在实际编写时经常忘记常性,我们检查时也不方便检查常性,所以最好从名字上做区分。
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
九、find()——寻找字符串中的字符和子串
返回字符串c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == c)
{
return pos;
}
pos++;
}
return -1;
}
返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
const char* p = strstr(_str + pos, s);
if (p)
{
return p - _str;
}
else
{
return npos;
}
}
void test12()
{
cout << endl;
cout << endl;
cout << "测试find()" << endl;
string s1("abcdef");
cout << s1.find('c') << endl;
cout << s1.find("ef") << endl;
}
十、构造函数——对象源头
构造函数为标准库的各个成员的现代写法提供了实现的可能;
string(const char* str = "") //构造函数,我们要新造一个string对象去存储str内容。
:_size(strlen(str)),
_capacity(_size)
{
_str = new char[_capacity + 1]; //生成新的str要放在函数体里面,防止成员声明顺序被修改后出现成员变量赋予随机值。
strcpy(_str, str);
}
十一、拷贝构造
关于拷贝构造有了 现代写法和传统学法之分,传统写法是符合人第一直觉的信息赋值,现代写法是较为高级的逻辑复用。
就操作而言,现代写法利用构造函数创造出了一个与被拷贝对象信息一致的临时对象,然后交换临时对象与this的内容;
就思路而言,现代写法用拷贝构造完成了信息复制,用交换地址确定了信息归属。
拷贝构造_传统写法
//string(const string& s)
//{
// _size = s._size;
// _capacity = s._capacity;
// _str = s._str;
//}
//拷贝构造——现代写法
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
十二、析构函数
~string() //析构函数,我们调用delete[]释放空间,对象的变量置零,然后指针置空即可
{
delete[] _str; //释放空间
_str = nullptr; //指针置空
_size = _capacity = 0; //成员变量归零
cout << "~string" << endl;
}
十三、赋值运算符重载
赋值运算符也有传统和现代写法之分,其区分点与拷贝构造的区分类似,也是用构造函数完成了信息复制,最后换地址完成了信息归属。
赋值运算符重载_传统写法
//string& operator=(const string& s) //这里的=作用是:赋值
//{
// _capacity = s._capacity;
// _size = s._size;
// strcpy(_str, s._str);
// return *this;
//}
//赋值运算符重载_现代写法
string& operator=( string tmp) //这里的=作用是:赋值
{
swap(tmp);
return *this;
}