STL简介
STL(standard template libaray - 标准模板库)是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL的一些版本
- 原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本即所有STL实现版本的始祖。
- P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
- RW版本
由Rouge Wage公司开发,继承自HP版本,不能公开或修改,可读性一般。
- SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版本。被GCC采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。
STL的6大组件
STL的一些缺陷
- STL库的更新太慢了。上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
- STL不支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
- STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
- STL的使用会有代码膨胀的问题,比如使用vector<int> / vector<double> / vector<char> 这样会生成多份代码,当然这是由于模板语法本身导致的。
——截至2023-8-9
string
string的使用
用法参考网址:cplusplus.com/reference/string/string/
string常用API - 用法示例
- part1:构造与赋值
string s1; // 无参构造
string s2(10, 65); // 有参构造,开局10个'A'
string s3("hello world"); // 拷贝构造
s1 = s3; // operator =
string s4(s3, 2, 5);
const char* str = "abcdefg";
string s5(str);
string s6(str, 5);
- part2:迭代器
begin():指向容器第一个元素的位置(可读可写)
rbegin():指向容器最后一个元素的位置(可读可写)
cbegin():指向容器第一个元素的位置(只读)
crbegin():指向容器最后一个元素的位置(只读)
end():指向容器最后一个元素的下一个位置(可读可写)
rend():指向容器第一个元素的前一个位置(可读可写)
cend():指向容器最后一个元素的下一个位置(只读)
crend():指向容器第一个元素的前一个位置(只读)
// 正向非常量迭代器,可以正常读写
string str1("hello world");
string::iterator beg = str1.begin();
while (beg != str1.end())
{
cout << ++ * beg << " ";
beg++;
}
cout << endl;
// 反向非常量迭代器,反方向正常读写
string str2("hello world");
string::reverse_iterator rbeg = str2.rbegin();
while (rbeg != str2.rend())
{
cout << ++ * rbeg << " ";
rbeg++;
}
cout << endl;
// 正向常量迭代器,只能读不能写
string str3("hello world");
string::const_iterator cbeg = str3.cbegin();
while (cbeg != str3.end())
{
//cout << ++*cbeg << " "; // error
cout << *cbeg << " ";
cbeg++;
}
cout << endl;
// 反向常量迭代器,反方向只读不写
string str4("hello world");
string::const_reverse_iterator crbeg = str4.crbegin();
while (crbeg != str4.crend())
{
//cout << ++*crbeg << " "; // error
cout << *crbeg << " ";
crbeg++;
}
cout << endl;
- part3:容量相关
string str = "hello world";
// size方法:Return length of string(不包含最后的'\0')
cout << "str_size: " << str.size() << endl;
// length方法与size方法效果相同
cout << "str_length: " << str.length() << endl;
// max_size方法:Return maximum size of string(没有实际的参考性)
cout << "str_max_size: " << str.max_size() << endl;
// resize方法:改变size,但不改变容量大小
// capacity方法:获取string的容器大小
str.resize(5);
cout << str << endl;
cout << "str_size: " << str.size() << endl;
cout << "str_capacity: " << str.capacity() << endl;
// reverse方法:尝试更改容量大小(只有当变大的时候生效)
str.reserve(5);
cout << "change_5_capacity_result: " << str.capacity() << endl;
str.reserve(20);
cout << "change_20_capacity_result: " << str.capacity() << endl;
// clear方法:清空string
str.clear();
// empty方法:判空
cout << str.empty() << endl;
// shrink_to_fit方法:尝试缩容
// 函数原型:void shrink_to_fit();
// This function has no effect on the string length and cannot alter its content.
str = "123456789";
cout << "size: " << str.size() << "capacity: " << str.capacity() << endl;
str.shrink_to_fit();
cout << "size: " << str.size() << "capacity: " << str.capacity() << endl;
part4:数据访问
string str = "hello world";
cout << str[3] << endl; //operator[] - 越界触发断言
cout << str.at(7) << endl; // at方法 - 越界抛异常
cout << str.back() << endl; // back方法:访问最后一个字符
cout << str.front() << endl; // front方法:访问第一个字符
part5:数据操作
string s1 = "hello ";
string s2 = "world";
// operator+=
s1 += s2;
cout << s1 << endl;
// append:在字符串尾添加字符串或单个字符
s2.append(" good");
cout << s2 << endl;
// push_back:尾插单个字符
s1.push_back('!');
cout << s1 << endl;
// assign:赋值,效果与operator=一样
string s3;
s3.assign(s2);
cout << s3 << endl;
// insert:在任意位置插入(下标从0开始)
s3.insert(5, "*******");
cout << s3 << endl;
// erase:按下标/迭代器的位置删除n个
s3.erase(5, 3); // 从下标5开始,删除3个
cout << s3 << endl;
// replace:将指定位置的n个字串替换为另一段字符串
s3.replace(5, 3, " *-* ");
cout << s3 << endl;
// swap:交换两个string对象。对应的size和capacity也原封不动的交换
s3.swap(s2);
cout << s2 << endl;
// pop_back:尾删单个字符
s3.pop_back();
cout << s3 << endl;
string注意事项
- 注意resize方法和reverse方法,区分size和capacity:size返回的是字符串长度,而capacity返回的是容器的容量大小。同理。resize调整的是字符串长度,而reverse是尝试调整容量的大小。(reverse并不会改变字符串的内容)
- string和其它容器不同。string实际上是basic_string的char型的具体模板类: 所以这就是为什么string每次使用时不需要显示的指定类型,而其它容器(vector、list等)都需要显示的指定数据类型的原因了。
- 其实不光有string一种字符串容器,根据需要我们还可以选择u16string、u32string、wstring等。
模拟实现string
模拟实现string有助于我们更好的理解string的相关功能,更好的体会STL容器的设计。如下是我自己写的部分string功能:
// 输出输出流的重载
ostream& ytc::operator<<(ostream& _cout, const ytc::string& s)
{
if (s.empty()) //避免cout一个已释放的string
return _cout << "";
return _cout << s._str;
}
istream& ytc::operator>>(istream& _cin, ytc::string& s)
{
s.clear();
char get = _cin.get();
while (get != '\n'/* || get != ' '*/) //可以选择读不读空格
{
if (s._size == s._capacity)
s.reserve((s._capacity + 1) * 2);
s._str[s._size++] = get;
get = _cin.get();
}
return _cin;
}
// 构造和析构
ytc::string::string(const char* str) : _size(strlen(str)), _capacity(strlen(str))
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
ytc::string::string(const ytc::string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
ytc::string& ytc::string::operator=(const string& s)
{
// 容量检查
size_t len = strlen(s._str);
reserve(_size + len);
// 拷贝内容
strcpy(_str, s._str);
_size = s._size;
return *this;
}
ytc::string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// iterator
ytc::string::iterator ytc::string::begin()
{
return _str;
}
ytc::string::iterator ytc::string::end()
{
return _str + _size;
}
// modify
void ytc::string::push_back(char c)
{
// 容量检查
if (_size == _capacity)
reserve((_capacity + 1) * 2);
_str[_size++] = c;
_str[_size] = '\0';
}
ytc::string& ytc::string::operator+=(char c)
{
push_back(c);
return *this;
}
void ytc::string::append(const char* str)
{
// 容量检查
size_t len = strlen(str);
reserve(_size + len);
// 追加内容
strcat(_str, str);
_size += len;
}
ytc::string& ytc::string::operator+=(const char* str)
{
append(str);
return *this;
}
void ytc::string::clear()
{
memset(_str, 0, sizeof(char) * _size);
_size = 0;
}
void ytc::string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
const char* ytc::string::c_str()const
{
return _str;
}
// capacity
size_t ytc::string::size()const
{
return _size;
}
size_t ytc::string::capacity()const
{
return _capacity;
}
bool ytc::string::empty()const
{
return _size == 0;
}
void ytc::string::resize(size_t n, char c)
{
// n > _size,尾插字符
if (n > _size)
{
// 一步到位的扩容
reserve(n + 1);
// 尾插字符
int times = n - _size;
while (times--)
{
// 每次添加字符检查一下的扩容
/*if (_size == _capacity)
{
_str[_size] = '\0'; //最后加个'\0',防止拷贝的数组比实际的空间大的问题
reserve(2 * (_capacity + 1));
}*/
_str[_size++] = c;
}
_str[_size] = '\0';
}
// n < _size,直接截断
else if (n < _size)
{
_str[n] = '\0';
_size = n;
}
}
void ytc::string::reserve(size_t n)
{
if (n > _capacity)
{
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
}
// access
char& ytc::string::operator[](size_t index)
{
// 先将*this转换为const对象,然后调用const对象的operator[]
// 然后再将其返回的const char转换为char
return const_cast<char&>(
static_cast<const ytc::string&>(*this)[index]);
}
const char& ytc::string::operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
//relational operators
bool ytc::string::operator<(const string& s)
{
return strcmp(_str, s._str) < 0;
}
bool ytc::string::operator<=(const string& s)
{
return _str < s._str || _str == s._str;
}
bool ytc::string::operator>(const string& s)
{
return strcmp(_str, s._str) > 0;
}
bool ytc::string::operator>=(const string& s)
{
return _str > s._str || _str == s._str;
}
bool ytc::string::operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool ytc::string::operator!=(const string& s)
{
return !(_str == s._str);
}
// 返回c在string中第一次出现的位置(下标)
size_t ytc::string::find(char c, size_t pos) const
{
if (pos < _size)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
}
return npos;
}
// 返回子串s在string中第一次出现的位置
size_t ytc::string::find(const char* s, size_t pos) const
{
const char* res = strstr(_str + pos, s);
if (res != nullptr)
{
return (res - _str) / sizeof(_str[0]);
}
return npos;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
ytc::string& ytc::string::insert(size_t pos, char c)
{
// 如果pos传的过大了,那么就直接在最后插入
if (pos > _size)
pos = _size;
// 容量检查
if (_size == _capacity)
reserve((_capacity + 1) * 2);
// 字符后移
size_t index = _size + 1;
while (index >= pos && index + 1 != 0) //后面的条件是控制0位置的特殊情况
{
_str[index] = _str[index - 1];
index--;
}
_str[pos] = c;
_size++;
// 最后返回
return *this;
}
ytc::string& ytc::string::insert(size_t pos, const char* str)
{
// 如果pos传的过大了,那么就直接在最后插入
if (pos > _size)
pos = _size;
// 容量检查
size_t len = strlen(str);
reserve(_size + len);
size_t index = _size;
// 字符后移
while (index >= pos && index + 1 != 0) //后面的条件是控制0位置的特殊情况
{
_str[index + len] = _str[index];
index--;
}
strncpy(_str + pos, str, len);
_size += len;
// 最后返回
return *this;
}
// 删除pos位置上的元素,并返回该元素的下一个位置
ytc::string& ytc::string::erase(size_t pos, size_t len)
{
if (pos < _size)
{
if (len > _size || pos + len > _size)
{
len = _size - pos + 1;
}
size_t index = pos;
while (index < _size)
{
_str[index] = _str[index + len];
index++;
}
_str[index] = '\0';
_size -= len;
}
else
{
cout << "删除位置错误!" << endl;
}
return *this;
}