容器——string篇
- STL简介
- String常见接口函数
- 深度了解String
- 构造函数
- 拷贝构造
- 赋值重载
- 析构函数
- 运算符重载
- 查找
STL简介
STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统 称。现在主要出现在 c++中,但是在引入 c++之前该技术已经存在很长时间了。 STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。
STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。 算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte. 迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。 仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template 。适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。 空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.
STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
String常见接口函数
参考标准库中接口函数了解参数及使用使用场景可以更好上手[string]。对于String的接口函数是比较冗余的。在每一个接口函数中实现的函数重载比较繁多。因为早期实现时的参考不足,作为字符串的有关类函数。实现接口函数就要考虑是字符还是字符串。因为是String的底层存储空间是连续的,可以理解为一个操作字符串的顺序表。所以函数的接口十分类似。
字符串相加
算法:数学加式实现,决定字符串从尾向前,对于结果保存和进位保存。加法进位后,保存结果需要转换成字符串保存,便于返回。最后需要进行进位检查。翻转字符串。
class Solution {
public:string addStrings(string num1, string num2) {int len1=num1.size()-1;int len2=num2.size()-1;int flag=0;string str="";while(len1>=0||len2>=0){int ret1=len1>=0?num1[len1--]-'0':0;int ret2=len2>=0?num2[len2--]-'0':0;int ret=ret1+ret2+flag;if(ret>9){flag=ret/10;ret%=10;}else{flag=0;}str+=(ret+'0');}if(flag) str+='1';reverse(str.begin(),str.end());return str;}
};
通过该题的算法实现。使用到的STL中的容器(string)、迭代器、算法。可以看出String使用场景大多遍历字符串,修改字符,翻转字符……;和C语言中字符函数(str)功能有着异曲同工。所以对于string大多就是掌握字符串查找修改。
深度了解String
熟悉函数常见的接口函数及其使用场景,就可以按照这样的套路进行更深层次的了解。可以参考C语言的str函数来进行接口函数的功能封装。
对于类的模拟实现需要实现他的6个默认成员函数。虽然规带默认的成员函数会如果没有编写,编译器会自动生成。但是对于默认生成的拷贝构造函数会有实现时深浅拷贝问题。编译器直接生成的进行内置类型的传递赋值会出现浅拷贝问题。浅拷贝结构示意图:
对于空间会自己指向同一个内存空间地址。在进行析构函数释放空间时,对于同一个空间释放2次。编译器会自己检查空间的释放过程。
构造函数
string(const char* str = ""):_size(strlen(str)){_capacity = _size == 0 ? 3 : _size;//避免扩容时容量为0_str = new char[_capacity + 1];strcpy(_str, str);}
拷贝构造
string(const string& s):_capacity(s._capacity),_size(s._size){//'\0'_str = new char[_capacity+1];strcpy(_str, s._str);}
赋值重载
string& operator=(const string& s){if(*this!=s){char* tmp = new char[s._capacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}
析构函数
~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
运算符重载
对于字符串的比较字符串大小,进行字符的ascll码比较。就是运算符的重载实现。其余运算符重载可以进行复用和逻辑与、逻辑或操作实现。
bool operator<(const string& s)const{return strcmp(_str, s._str)>0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}
查找
实现的方法就是类似于数据结构中的顺序表。实现插入时需要实现字符串的出入。对于空间的开辟,C++中通常是搭配使用管理内存,扩容是对于C语言中的realloc不会使用,如果对于空间开辟和释放不配套使用。编译器会报错处理,析构函数实现也不友好。String自己实现函数reverse。
void reserve(size_t n){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
对于String中涉及内存相关的,都是开空间,拷贝数据,释放空间。
在返回值的接受时使用引用接受返回值,参数传递时也可以使用引用传参减少拷贝带来的消耗提升效率。写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
string& erase(size_t pos, size_t len){assert(pos < _size);//长度大于字符长度,pos位置后直接删除if (pos == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{//长度在字符串长度之内,直接用pos位置后字符串覆盖strcpy(_str + pos, pos + len + _str);_size -= len;}return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_capacity < len + _size){reserve(len + _size);}size_t end = _size + len;
//挪动数据 while (end >= pos){_str[end] = _str[end - len];end--;}//拷贝数据strncpy(_str+pos, str, len);_size += len;return *this;}