STL六大组件:容器、算法、迭代器、仿函数、空间配置器、配接器
stl可以参考微软的文档:
https://learn.microsoft.com/zh-cn/cpp/standard-library/map-class?view=msvc-170
这是容器map文档
stl中的线性结构容器
string —— 动态顺序表,专门用来存储和管理字符串
vector —— 动态顺序表,可以存储任意类型数据
list —— 带头结点的双向循环链表
deque —— 动态二维数组结构
array —— 静态类型的顺序表
forward_list —— 带头结点的循环单链表
string类
string类
一提起字符串,首先想到的是C语言的字符串。
C语言是没有字符串类型的,所有的字符串都是用char[]数组结尾加 ‘\0’,来表示,操作起来较为不便。
在stl标准模版库中有实现string类。那么就可以使用类去构造对象然后进行操作,在C++语言中用内置类型定义一个变量,和用类创建一个类对象的代码是一样的:
int num;
string str;//感觉就好像string也是自带类型一样
string类的方法:
构造:
总结一下一共7个方法:
空,string拷贝,子串构造,C串构造,C子串构造,字符填充,迭代器构造
常用的就是前四种。
C串构造:string( const char* s) 是挺重要的,这样在传参时传递C串给string也不会出问题,因为会进行C串构造成string ;注意子串构造是:从pos拷贝len长度构造
迭代器:
begin() , end()
容量
常用的就是size,resize ,clear,empty
for(int i = 0; i < str.size(); i++ )
str.clear();
str.resize(10);
if( str.empty() )
对于string容量,要理解字符串需要的是一块连续的内存,所以对于字符串的扩容,增删,插入操作的效率其实不高,因为要涉及元素的移动。
对字符串操作,insert或者+=,会使原串长超过reserve容量,此时就需要扩容,扩容一般来讲是1.5倍或两倍扩容,这样就不会造成频繁的扩容,元素复制,提高效率。
元素访问:
可以用下标运算符 [ ] 来获取某一位置字符,如果越界访问会assert,程序中断崩溃
str.at(i) 使用效果和【】是一样的,越界会抛异常out of range
修改:
修改一般来讲就是:增、删、替换、交换、
增:+=,append(添加整串),push_back(尾插字符),insert(任意位置插入任意数量字符)
删:erase(全部删除),pop_back(尾删)
改:assign(重新分配),replace(替换),swap
str1+=str2;
//string& append (const string& str, size_t subpos, size_t sublen);
str.append( str2, 0 ,10);
str.puck_bash(c);
//string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
str.insert( pos, str2, 0, 10);//把str2串的0到10字符插入pos位置之前
//string& erase (size_t pos = 0, size_t len = npos);
str.earse();//这样就全删了,因为默认参数是0到,npos--可以理解为字符串结尾,
//一般用i != string::npos来判断限制,进行循环终止条件
str.earse(c);//也可以删除单个字符
str.assign( str, 9)//实现substr相同功能,当然substr重载的形式更丰富
string& replace (size_t pos, size_t len, const string& str);
要替换的区间位置,[pos , len),替换的新串str
看文档中这些方法,一般都有很多重载形式:关注不同重载的返回值类型、参数的类型和作用,便于掌握这些函数的用法。
比如用const string&修饰的参数,那必然是作为输入参数;不带const有可能只是作为输出参数,也用可能是输入输出参数;insert方法参数较多,首先明确插入肯定要先指明插入位置pos,之后就是要操作的str和范围;
字符串操作:
种类比较多,一般就是用个find,substr,compare,c_str
str.c_str();
find方法要找的可以是字符串,也可以是字符,并且可以设置从某一位置开始找,返回值是找到的元素的下标
当返回值为string::npos时说明要找的不存在
成员常量:string::npos 用来表示字符串末尾
static const size_t npos = -1;
实际是
4294967295
这个值,当用作字符串成员函数中的len(或sublen)参数的值时,表示“直到字符串的末尾”。
非成员函数重载:
这里只列出我常用的
istream& getline (istream& is, string& str);
//不是成员函数,不用str.getline()
getline(cin,str);//获取一行输入
istream& is 可以不仅仅是cin,也可以是文件流等等
还有这些转换函数,要注意转换时有数据范围限制的
stoi("123")
to_string(123);
这里为什么string转其他内置类型有这么多方法,而转成stirng只有to_string一个。
这就是重载的妙用,重载是同名函数但参数列表不同,返回值是不能区别重载的,这些方法都是用返回值来实现功能
总结
string可以说是使用起来很方便的一个类,也非常常用,较之于C字符串,可以说是string是很强大的,方法有很多,所以日常中药多加练习,才真正能够随心所用,并且string是stl容器其中一只,stl容器中很多类的方法都是比较相似的如:push_back,earse,find,[],begin,end,insert,当然针对不同的容器,还是有一些特有的方法。
模拟实现string
//#include<iostream>
//using namespace std;
//#include<string>
//class String{
//private:
// char* _str;
//public:
// String(const char* str = "")//带参构造
// {
// if (nullptr == str)
// str = "";
// _str = new char[strlen(str) + 1];
// cout << &_str << endl << &str << endl;
//
// strcpy(_str, str);
// }
// char* c_str(){
// return _str;
// }
//
// String(const String& s)//拷贝构造
// :_str(new char[strlen(s._str)+1])
// {
// strcpy(_str, s._str);//进行深拷贝,申请新的空间,复制
// }
//
// String(const String* this, const String& s)//成员函数是有this指针隐式传参的
// //String(const String& s)//也可以这样实现
// // :_str(nullptr)
// //{
// // String temp(s._str);//用s传入的字符串给tmp拷贝构造(这里用的拷贝构造就是上面写的)
// // /*在出了作用域后temp就会销毁,则调用delete[]删除其的str,删除的是交换了的之前this的str,而this的_str是未定义的指针,
// // 所以最好在构造函数的初始化列表中加上:_str(nullptr)*/
// // swap(_str, temp._str);//把this的_str,和temp._str交换
// //}
//
// /*String& operator=(const String& s)
// {
// if (this != &s)
// {
// char* temp = new char[strlen(s._str) + 1];
// strcpy(temp, s._str);
// delete[] _str;
// _str = temp;
// }
// return *this;
// }*/
// String& operator=(String s)//值传参,生成临时拷贝构造的对象
// {
// swap(_str, s._str);
// return *this;
// }
//
// /*
// 对于浅拷贝的问题,主要是释放资源析构实时delete两次,delete释放过的内存就会报错
// 解决:采用浅拷贝+引用计数
// 每次析构只是计数-1,当计数=0时才释放资源
// 若遇到要修改时才进行深拷贝,申请空间,因为这些字符串是共享一块char[]内存
// */
//
// friend ostream& operator<<(ostream& os, const String& s);
//
// ~String(){
// if (_str){
// delete[] _str;
// _str = nullptr;
// }
// }
//};
friend
//ostream& operator << (ostream& os, const String& s)//友元函数
//{
// cout << s._str << endl;
// return os;
//}
//
//void test01()
//{
// String s1("sajdh");
// cout << s1 << endl;
// //String s2(s1);//实际调用的是编译器的浅拷贝,而没有调用String(const char* str)实现的深拷贝
// /*
// 解决方式:1.new新空间给this->_str;
// */
// String s3(s1);
// cout << s3 << endl;
// String s4 = "120938109283";
// s4 = s3;
// cout << s4;
//}
//
//int main()
//{
// test01();
// _CrtDumpMemoryLeaks();
// return 0;
//}
#include<iostream>
#include<cassert>
#include<algorithm>
namespace gyx
{
class string
{
private:
char*_str;
size_t _size;
size_t _capacity;
friend std::ostream& operator<<(std::ostream&, const string&);
public:
const static size_t npos = -1;
//构造
string(const char* str = "")
{
if (nullptr == str)
str = "";
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& str)
:_str(nullptr)
/*
一定要给_str初始化nullptr,这里实现的拷贝构造,是要生成临时对象temp,然后把temp的内容(_str,size,capacity)和this交换,
而this是没有初始化的,它的_str指向一个未知的地址,可以说是野指针,交换后临时对象出作用域就会被析构,而析构delete[] _str就会释放野指针,程序崩溃
*/
{
string temp(str._str);//调用上面的C串构造
this->swap(temp);
}
string(size_t n, char c)
{
_str = new char[n + 1];
memset(_str, c, n);
_str[n] = '\0';
_size = n;
_capacity = n;
}
string& operator+(string s)
{
this->swap(s);
return *this;
}
string& operator=(string s)
{
this->swap(s);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_capacity = 0;
_size = 0;
}
}
//迭代器
typedef char* iterator;//迭代器不是指针,这里只是简单实现,功能相近
iterator begin(){ return _str; }
iterator end(){ return _str + _size; }
//容量
size_t size()const{ return _size; }
size_t capacity()const { return _capacity; }
bool empty()const{ return 0 == _size; }
void clear(){ _size = 0; }
void resize(size_t newsize, char c)
{
size_t oldsize = size();
//resize可以增多,也可减少size
if (newsize <= oldsize)
{
_size = newsize;
_str[_size] = '\0';
}
else
{
//增多
if (capacity() < newsize)
{
reserve(newsize);
}
memset(_str + _size, c, newsize - _size);
_size = newsize;
_str[_size] = '\0';
}
}
void resize(size_t newsize)
{
resize(newsize, '\0');
}
void reserve(size_t newcapacity)
{
size_t oldcapacity = capacity();
if (newcapacity > oldcapacity)
{
char* newstr = new char[newcapacity + 1];
strncpy(newstr, _str,_size);//只拷贝size个元素,别拷贝超了
delete[] _str;
_str = newstr;
_capacity = newcapacity;
_str[_size] = '\0';
}
}
//元素访问
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
char& at(size_t pos)
{
//越界会抛异常out of range
return _str[pos];
}
char& at(size_t pos)const{ return _str[pos]; }
//字符串操作
void push_back(char c)
{
*this += c;
}
string& operator+=(char c)
{
if (_size == _capacity)
reserve((size_t)_capacity*1.5 + 3);//避免capacity=0的情况
_str[_size] = c;
++_size;
_str[_size] = '\0';
return *this;
}
string& operator+=(const char* str)
{
size_t needspace = strlen(str);
size_t nowfreespace = capacity() - size();
if (needspace > nowfreespace)
reserve(capacity()+needspace);
strcat(_str, str);
_size += needspace;
_str[_size] = '\0';
return *this;
}
string& operator+=(const string& s)
{
*this += s.c_str();
return *this;
}
string& apend(const char* str)
{
*this += str;
return *this;
}
string& append(const string& s)
{
*this += s;
return *this;
}
string& insert(size_t pos, const string& str)
{
size_t needspace =str.size();
size_t nowfreespace = capacity() - size();
if (nowfreespace < needspace)
reserve(capacity()+needspace+1);
for (size_t i = size(); i>=pos; i--)
{
_str[i + needspace] = _str[i];
}
for (int j = 0; j < str.size(); j++)
_str[pos + j] = str[j];
_size += needspace;
_capacity += needspace;
return *this;
}
string& earse(size_t pos=0, size_t len=npos)
{
if (pos > _size)//不删
return *this;
len = std::min(len, len-pos);
for (size_t i = pos; i <+len+_size; i++)
{
_str[i] = _str[i + len];
delete &_str[i + len];
}
_size -= len;
_capacity -= len;
return *this;
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* c_str()const{ return _str; }
size_t find(char c, size_t pos = 0)const
{
if (pos >= _size)
return npos;
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == c)
return i;
}
return npos;
}
string substr(size_t pos = 0, size_t len = npos)const
{
if (pos >= _size)
return string();
//pos+len的不能越界,最多截取到字符串尾
len = std::min(len, _size - pos);
string temp(len+1, '\0');
for (size_t i = pos,j=0; i<pos+len; ++i)
temp[j++] = _str[i];
return temp;
}
friend std::ostream& operator<<(std::ostream&, const string&);
};
std::ostream& operator<<(std::ostream& os, const string& s)
{
for (size_t i = 0; i<s.size(); i++)
os << s[i];
return os;
}
}
using namespace gyx;
void test()
{
string s1;
string s2("ni hao");
string s3(s2);
s1 = s2;
std::cout << s2 << '\n';
std::cout << s1;
s1.apend("wqjeiwq");
s1.insert(3, "111");
std::cout << "s1=" << s1<<'\n';
string sub = s1.substr(3, 3);
std::cout << sub;
sub.reserve(100);
sub.resize(100);
std::cout << sub.find('a');
int pos = sub.find('a');
if (pos != string::npos)
std::cout << "找到了!!!\n";
}
int main()
{
test();
_CrtDumpMemoryLeaks();
return 0;
}
拷贝构造的实现要给底层char[ ]数组指针传nullptr初始化
模拟实现其他的方法首先要明确方法的作用,目的,参数设置的顺序,比如:insert()首先肯定要有插入的位置pos,以及要插入的内容,还有就是底层实现是char的顺序表,顺序表在增删是会有大量的元素移动,所以要注意越界问题,以及结尾的\0