容器——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;
}