目录
STL简介:
什么是STL:
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个保罗数据结构和算法的软件框架。
STL六大组件:
string的使用:
为什么要学习string类:
c语言中,字符串是以‘\0’结尾的一些字符的集合,为了操作方便,c标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
了解string类:
1、string是表示字符串的字符串类
2、该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3、string再底层实际是:basic_string模板类的别名,typedef basic_string < char, char_traits, allocator > sting;
4、不能操作多字节或者变长字符的序列
注意:在使用string类时,必须包含#include头文件以及using namespace std。
常用接口:
1、构造(constructor)
①string():默认构造函数,得到一个长度为0的空串
②string(const string& str):这是一个拷贝构造函数,用已经存在的字符串str拷贝构造新的字符串。
③string(const string& str, size_t pos, size_t len = npos):使用字符串str,从pos下标开始,长度为len这一部分字符串来构造新的字符串(len是一个缺省参数,默认值为mpos,如果不给定,默认到字符串str的末尾)
④string(const char* s):使用c语言中的字符串来构造新的string对象
⑤string(const char* s, size_t n):使用c语言中的字符串s中前n个字符来构造string对象
⑥string(size_t n, char c):使用n个字符c来构造string对象。
⑦ string(Inputlterator first, Inputlterator last):使用迭代器给字符串的部分区间构造
operator=(赋值):
①string& operator = (const string& str);
②string& operator=(const char* s);
③string& operator=(char c);
2、析构(destructor)
~string();
每个对象在声明周期结束时会自动调用该析构函数。
3、迭代器
暂时理解是一个类似指针的东西
begin() && end()
rbegin() && rend()
都是左闭右开。
4、容量相关
①size_t size() const;
②size_ length() const:size()和length的功能完全一致,都是获取字符串的有效长度。
有两个的原因:length是因为沿用c原有的习惯保留下来的,string类最初只有length, 引入STL之后,为了兼容又加入了size, 它是作为STL容器属性存在的,便于符合STL的接口规则,以便用于STL的算法。
③size_t capacity() const:获取string对象在底层维护的顺序表的容量。
④void resize(size_t newsize) | void resize(size_t newsize, char c)
作用:resize将string对象现有的有效元素的个数更新为newsize个:
newsize <= oldsize:将原有的元素个数减小到newsize个,不会修改容量。
newsize > oldsize:
newsize <= capacity:多出来的元素直接用ch填充,如果没有给定ch,默认使用char()填充,char()等价于0;
newsize > capacity:1、开辟新空间。2、拷贝元素。3、释放旧空间
⑤、void reverse(size_t newcapacity = 0);
作用:扩容,只改变容量,不会修改有效元素的个数。
newcapacity <= oldcapacity:直接忽略。如果oldcapacity > 15, 会将底层的容量缩小为15,因为在string类的内部除了维护顺序表必须的三个成员变量以外,还有一个char array[BUF_SIZE],该数组的大小为16,由于字符串字符串是以‘\0’结尾,所以最后一位留给‘\0’,不会算在容量中。
newcapacity > oldcapacity:扩容。
验证:
newcapacity <= oldcapacity:
newcapacity < oldcapacity && oldcapacity > 15 && newcapacity <= 15 && newcapacity > size
在string的底层还有一个char array[BUF_SIZE]数组,为了提高效率,默认的情况下string中的内容存储在该数组中,只有在数组容量不够的时候,才会为其开辟新的空间。
newcapcity > oldcapacity:
在vs中,以1.5倍进行扩容;在linux中,是以2倍进行扩容的。
⑥void clear():清空有效元素,不改变空间的大小。
5、访问及遍历操作
①operator[]:访问pos位置的元素
char& operator[](size_t pos) | const char& operator[](size_t pos)const
②at:访问pos位置的元素。
注意:[] 和at在访问元素时效果一样;[]在访问越界时会触发assert断言,终止程序;at在访问越界,会抛出异常,用户可以对其进行捕获。
③front(C++11):获取string的第一个字符
④back(C++11):获取sring的最后一个字符
为什么这些函数都会提供const和非const两份代码?
因为const对象不能调用非const的成员函数,所以必须提供两份代码。
如下图,调用的就是const类型的函数:
6、元素遍历
①for循环 + [] 访问
②迭代器
③范围for
string s1("hello");
// for循环
for (int i = 0; i < s1.size(); i++) {
cout << s1[i];
}
cout << endl;
//迭代器
string::iterator begin = s1.begin();
while (begin != s1.end()) {
cout << *begin;
begin++;
}
cout << endl;
//范围for
for (auto& e : s1) {
cout << e;
}
cout << endl;
7、修改操作
1、operator+=
string& operator+=(const string& str):末尾追加string
string& operator+=(const char* s):末尾追加c中的字符串
string& operator+=(const char c):末尾追加字符
2、append
①string& append(const string& str):末尾追加一个str
②string& append(const string& str, size_t subpos, size_t sublen):将str从subpos开始,长度为sblen的字串加到末尾
③ string& append(const char* s):在末尾追加一个c中的字符串。
④ string& append(const char* s, size_t n):在末尾追加字符串从0号位置开始,长度为n的子串。
⑤ string& append(size_t n, char c):在末尾追加n个char类型的c
⑥string& append(InputIterator first, InputIlterator last):将迭代器firet到last区间的内容追加到字符串的末尾
3、push_back:末尾追加一个字符
4、insert:
①string& insert(size_t pos, const string& str):在pos处插入字符串str
②string& insert(size_t pos, const string& str, size_t subpod, size_t sublen):在pos处插入str从subpos开始,长度为sublen的子串
③ string& insert(size_t pos, const char* s):在pos中插入C中的字符串s
④ string& insert(size_t pos, const char* s, size_t n):在pos中插入字符串s的前n个字符
⑤ string& insert(size_t pos, size_t n, char c) | string& insert(iterator p, size_t n, char c):在pos处插入n个字符c
5、erase:
①string& erase(size_t pos = 0, size_t len = npos):删除从pos开始,长度为len的子串;这两个参数均为缺省参数,默认删除整个字符串
②irerator erase(iterator p):删除p位置的元素,返回下一个元素的位置
注意:在删除最后一个位置时,返回值时非法的,无法再次被使用
③iterator erase(iterator first, iterator last):删除first到last之间的元素。
6、swap
void swap(string& str):交换两个string类型的对象
string::swap效率比全局的swap要高,在交换字符串的时候强烈建议使用
8、其他
1、c_str
const char* c_str() const:以c语言的方式返回字符串
2、find
①size_t find(const string& str, size_t pos = 0)const:在目标字符串中查找str字符串出现的位置,默认从头开始查找。找到返回首个字符在目标字符串中的位置,找不到返回npos
②size_t find(const char* s, size_t pos = 0)const:功能与上面一个一致,唯一的区别是查询的是c中的字符串
③ size_t find(const char* s, size_t pos = 0, size_t n)const:查询c中字符串的前n个字符
④size_t find(char c, size_t pos = 0)const:查询字符c的位置,默认从0下标开始查询
npos是一个静态成员常量,用来表示size_t的最大值。该值在string中表示字符串的末尾定义:static const size_t npos = -1。
3、rfind
功能与find类似,只不过是从字符串的末尾向起始位置查找
4、find_first_of
与find类似,只不过是查找第一次出现的某一个字符或字符串
①size_t find_first_of(const string& str, size_t pos = 0)const:找第一次出现str的位置,默认从头开始找
②size_t find_first_of(const char* s, size_t pos = 0)const:找第一次出现c语言字符串s的位置,默认从头开始查找。
③size_t find_first_of(const char* s, size_t pos,size_t n)const:找第一次出现c语言字符串s从pos开始,长度为n的子串的位置
④size_t find_first_of(char c, size_t pos = 0)const:找第一次出现字符c的位置,默认从起始位置开始查找。
6、find_last_of
与rfind类似,只不过是查找第一次出现的某个字符或者字符串
7、substr
string& substr(size_t pos, size_t len = mpos) const:截取子串,pos为截取的起始位置,len表示截取的长度。
浅拷贝问题的解决:
浅拷贝的问题:
浅拷贝也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中有资源管理,最后就会导致多个对象共享同一份资源,在调用析构函数时就会释放两次导致崩溃。
上面这个程序就会崩溃,原因:编译器调用默认的拷贝构造函数,默认的拷贝构造函数只是将s1中的成员变量的内容原封不动的拷贝至s2;test()执行完毕后,分别调用析构函数,在释放的过程中一块相同的地址被释放了两次,所以程序崩溃。
下面这个程序也会崩溃:
原因:在赋值过程中,s2中的_str直接指向s1的空间,因此s2原来的空间就会丢失,造成内存泄漏。同时,s2、s1指向同一块内存空间,在函数结束时,对象调用调用析构函数,多次释放。
通过深拷贝的方式解决浅拷贝的问题:
本质:让每一个对象都拥有一份独立的资源
1、传统版解决方式(申请新空间):
解决拷贝构造:
myString(const myString& s) :_str(new char[strlen(s._str) + 1]){ strcpy(_str, s._str); }
解决赋值运算符重载:
myString& operator=(const myString& s) { if (this != &s) { char* temp = new char[strlen(s._str) + 1]; strcpy(temp, s._str); delete[] _str; _str = temp; } return *this; }
2、现代版解决方式(巧用构造函数)
拷贝构造:
myString(const myString& s) //_str必须初始化为nullptr,否则temp声明周期结束后调用析构函数,会崩溃 :_str(nullptr) { myString temp(s._str); //调用构造函数,内容为s对象的内容 swap(_str, temp._str); //通过swap函数将两个指针指向改变,_str指针指向上面申请的空间 }
赋值运算符:
①
myString& operator=(const myString& s) { if (this != &s) { myString temp(s._str); //与拷贝构造的思想一样。 swap(_str, temp._str); } return *this; }
②巧妙使用值传递
myString& operator=(myString s) { swap(_str, s._str); //巧妙使用值传递,因为在值传递的过程中会调用构造函数创建一个临时 return *this; //对象,效果和上面的方法一样。 }
通过写时拷贝解决浅拷贝问题:
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源计数给成1,每增加一个对象使用该资源,就给计数增加1;当某个对象被销毁时,先给该计数减一,然后再检查是否需要释放资源,如果计数为1,说明该对象是资源的最后一个使用者,将该资源释放,否则就不释放。
写时拷贝是在上面的基础上解决修改内容的问题的,假如s1和s2共用同一块内存空间,此时计数器为2,现在想要将s2的内容修改,具体步骤如下:
1、为s2对象开辟新空间
2、将原来的空间的计数器值减减
3、将s1的内容拷贝至s2新开辟的空间中,并将s2中计数器设置为1
4、s2再新空间中进行修改操作。