目录
1.什么是STL
网上说学到了STL,才算是开始学习c++,如果学完了c++你说你会不用STL,你都不敢说你会c++,所以说STL在c++中的作用可以说是举足轻重的。
1.1概念
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架。
1.2.STL的六大组件
2.string类的基本概念和使用
2.1string类的介绍
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
4. 不能操作多字节或者变长字符的序列。
2 1. string类对象的常见构造
1.string() (重点) 构造空的string类对象,即空字符串
2.string(const char* s) (重点) 用C-string来构造string类对象
3.string(size_t n, char c) string类对象中包含n个字符c
4.string(const string&s) (重点) 拷贝构造函数
string s1; //定义出来什么都不弄,但是里面的容量自动capacity定义为15
string s2("hello world");//定义s2给它赋值
string s3(s2);
string s4(s2, 2, 6);//只取a[2]~a[6]的字符串
cout << s4 << endl;
string s5(s2, 2, 10000);//取a[2]~到后一百的字符串,但是到了‘/0’就自动停止
cout << s5 << endl;
string s6("hello world", 3);//只取前三个字符
cout << s6 << endl;
2.2 . string类对象的析构
自动调用,不用管
2.3. string类对象的容量操作
1. size(重点) 返回字符串有效字符长度
2.length 返回字符串有效字符长度
3.capacity 返回空间总大小
4.empty (重点) 检测字符串释放为空串,是返回true,否则返回false
5.clear (重点) 清空有效字符
6.reserve (重点) 为字符串预留空间
7.resize (重点) 将有效字符的个数该成n个,多出的空间用字符c填充
2.3.1容量
string s1("hello world");
cout << s1.length()<<endl;//以‘/0’结束,它计算的是有效字符的长度
cout << s1.size()<< endl;
cout << s1.capacity() << endl;//显示容量,一般都是15
s1.clear();//清除s1中的有效字符
cout << s1 << endl;
2.3.2扩容
string s1("hello world");
cout << s1.capacity() << endl;
s1.reserve(100);//申请100个空间
cout << s1.capacity() << endl;
这里我们发现它申请并不是直接就申请了100个,而是在原来的字符串的有效长度增加了100个空间
string s1;
s1.reserve(100);//这里就是申请100个空间
string s2;
s2.resize(100, 's');//这里不仅是申请了空间而且还初始化了
这里的resize()可以初始化,如果原来的字符串有字符再进行扩容初始化,那么原来串里面的数据不会被覆盖。
如果我们开辟的空间小于字符串的本身呢?s1.resize(2),那么久只保存前面两个字符,如果是s1.reserve(2)那么它的值是不会变的。
2.4string类中operator[]
operator[] (重 点) 返回pos位置的字符,const string类对象调用
我们可以用s[i]进行读和写或者来进行其他操作。
string s1("hello world");
cout << s1 << endl;
//遍历字符串
for (size_t i = 0; i <s1.size(); ++i)
{
cout << s1[i] << " ";
}
for (size_t i = 0; i <s1.size(); ++i)
{
s1[i] += 1;//给字符串的每个字符的ASCII码+1
cout << s1[i] << " ";
}
2.5string类与迭代器
2.5.1正向迭代器
begin+ end
begin()获取一个字符串开始的位置 + end()获取最后一个字符下一个位置的
rbegin + rend
rbegin()获取最后一个字符 + rend()获取开头一个字符上一个位置
迭代器我们可以认为就是指针,使用的函数是 iterator
string s1("hello world");
cout << s1 << endl;
string::iterator it = s1.begin();
//auto it= s1.begin();//这里的auto可以自动识别类型
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
//修改
it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
这里的it相当于就是指向字符‘h’这个位置的指针,然后往后面遍历。
2.5.2反向迭代器
函数是reverse_iterator
string s1("hello world");
//string::const_iterator it = s.begin();
auto it = s1.begin();
while (it != s1.end())
{
//*it -= 1;
cout << *it << " ";
++it;
}
cout << endl;
//auto rit = s.rbegin();
string::const_reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
//*rit = 'A';
cout << *rit << " ";
//这里的rit取的最后一个字符的地址,我们主观的以为要rit--,其实是rit++。
++rit;
}
cout << endl;
注意:我们定义的反向迭代器rit之后,在进行遍历的时候虽然是从后往前,但是rit还是需要++操作。
2.5.3使用迭代器的意义
我们看了之前的 [] 操作符,感觉比迭代器简单太多了,为什么还要有迭代器呢?
这是因为对于string类来说,下标[]就足够的好用,但是其他的容器,比如list,map,set是不支持下标[]遍历的,因为像链表这些东西来说,它的物理内存不是连续的,所以就不能用下标[]。
2.5.4其他遍历方式
范围for
//范围for,自动往后迭代,遇到‘/0’结束
for (auto& i : s1)//auto自动识别类型
{
i += 1;
cout << i;
}
cout << endl;
范围for会把s1中的每个字符都赋给i,自动往后迭代,自动判断结束。
s.at()
//给字符串的每个字符的ASCII码+1;这里的s1[i]=s1.at(i),
for(size_t i = 0; i <s1.size(); ++i)
{
s1.at(i) += 1;
}
这里的效果和上面相同
2.6string类的增删
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= (重点) 在字符串后追加字符串str c_str(重点) 返回C格式字符串
find + npos(重点) 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回
2.6.1增
string s1("hello world");
s1.push_back('a');//在末尾插入字符a
cout << s1 << endl;
s1.append("bcd");//在末尾插入字符串bcd
cout << s1 << endl;
s1 += 'e';//在末尾插入字符e
cout << s1 << endl;
s1 += "hello linux";//在末尾插入字符串hello linux
cout << s1 << endl;
如果原字符串的空间满了,再push_back或者append,那么他会自动扩容。
注意:这里还是推荐+=来尾插。
2.6.2查与匹配
string s("bit.txt");
//这里用rfind()是从后面开始找,找到了'.'这个位置的下标,这里也可以用find(因为只有一个‘.’)
size_t pos1 = s.rfind('.');
//这里的npos的值是49亿多,这里就是验证得到的pos是否有效
if (pos1 != string::npos)
{
//这里就是substr()就是从pos位置开始取s1.size()-pos个字符,就是后缀的长度。
string suffix = s.substr(pos1, s.size() - pos1);
cout << suffix<<endl;
}
find会返回第一次出现匹配的位置,如果匹配失败会返回nops(无符号-1,表示一个极大的数)。
substr是字符串截取函数,意思是截取pos位置与file.size()-pos位置的字符串。同上,我们也可以使用rfind进行倒着查找。
string url("http://www.cplusplus.com/reference/string/string/find/");
size_t pos2 = url.find(':');//从0开始找到‘:’
string s1 = url.substr(0, pos2);
cout << s1<<endl;
size_t pos3 = url.find('/',pos2+3);//从pos2+3的位置开始找
string s2 = url.substr(pos2+3,pos3-(pos2+3));
cout << s2 << endl;
string s3 = url.substr(pos3 + 1);//从pos3+1位置找到结束
cout << s3 << endl;
我们都知道网址分为三部分:协议,域名与网络路径。下面使用find与substr来进行截取操作。
2.7string类中自定义插入与删除
string s1("hello woeld");
s1 += "++++";//在末尾插入字符串"++++"
s1 += '-';//在末尾插入字符'-'
cout << s1 << endl;
//头插的三种方法(两个是插字符,一个是插字符串),尽量少用,效率低
s1.insert(0,2,'s');//在第0个位置插入两个字符’s‘
cout << s1 << endl;
s1.insert(s1.begin(), 's');
cout << s1 << endl;
s1.insert(0,"xxx");
cout << s1<<endl;
//中间插入
s1.insert(4, "sss");
cout << s1 << endl;
在使用erase时,如果不给值就会删完,尽量少使用头部和中间的删除,因为要挪到数据,效率较低。
3.string的模拟实现
3.1简易的string类的实现
3.1.1构造函数
写简易的string类必须写构造函数,拷贝构造,析构函数,运算符重载。
class string
{
public:
string(const char* _s1)
:_str(new char[strlen(_s1)+1])
{
strcpy(_str,_s1);
}
private:
char* _str;
};
void test1()
{
string s1("hello world");
}
这样写构造函数运行起来没有什么问题,但是不排除以后人这样定义
string s4;
那么构造函数就要改一下。
string(const char* _s1="")
:_str(new char[strlen(_s1)+1])
{
strcpy(_str,_s1);
}
3.1.2析构函数
~string()//析构函数
{
delete[] _str;//释放空间
_str = nullptr;//指针指向空,防止野指针
}
3.1.3拷贝构造
string s2(s1); //因为这里在类里没有合适的拷贝构造函数,会调用系统生成的,
//但是系统生成的拷贝构造是浅拷贝
这样定义那么我们写的构造函数就不够用了,那么可以用编译器自己实现的吗?
我们发现是可以的,值是拷贝上了,但是他们两个的地址是却是一样的,那么这就会造成一个问题,他们会析构两次。
我们看到就会发生奔溃。string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,其中s2先析构,s1的值就别成了随机值,然后再析构就奔溃了。
**浅拷贝
为什么系统自己生成的拷贝构造就不行呢?
因为编译器生成的拷贝构造函数属于是浅拷贝,他对自定义类型会进行按字节序拷贝,所以它就把地址也拷贝下来了,对于很多个对象指向同一块空间,如果其中的一个销毁了,其他的对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
**深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情 况都是按照深拷贝方式提供。就是重新开一个和s1相同的空间,然后把s1里的值拷贝给s2
string(const string& _s2)//这里就是进行的深拷贝,就是重新开一个和s1相同的空间,
//然后把s1里的值拷贝给s2
:_str(new char[strlen(_s2._str) + 1])
{
strcpy(_str, _s2._str);
}
我这时就可以看到,虽然值相同但是空间不相同,所以析构时候也不会有什么问题。
3..1.4=运算符重载
string s1("hello world");
string s3("travis Scott");
s3 = s1;
string &operator=(const string&_s3)
{
if (this != &_s3)
{
delete[]_str;
_str = (new char[strlen(_s3._str) + 1]);
strcpy(_str, _s3._str);
return *this;
}
return *this;
}
这个由于之前讲过的,所以就不多介绍了。
3.1.5简易的string实现
class string
{
public:
string(const char* _s1="")
:_str(new char[strlen(_s1)+1])
{
strcpy(_str,_s1);
}
string(const string& _s2)//这里就是进行的深拷贝,就是重新开一个和s1相同的空间,
//然后把s1里的值拷贝给s2
:_str(new char[strlen(_s2._str) + 1])
{
strcpy(_str, _s2._str);
}
string &operator=(const string&_s3)
{
if (this != &_s3)
{
delete[]_str;
_str = (new char[strlen(_s3._str) + 1]);
strcpy(_str, _s3._str);
return *this;
}
return *this;
}
~string()//析构函数
{
delete[] _str;//释放空间
_str = nullptr;//指针指向空,防止野指针
}
private:
char* _str;
};
3.1.6简易string的现代写法
3.2 string类的模拟实现
3.2.1迭代器的实现
其他的迭代器可能实现起来比较困难,但是string类的迭代器算是简单的。首先我们要对迭代器重命名,因为我们是要自己写。
typedef char* iterator;
typedef const char* conts_iterator;
然后就算我们的迭代器的函数。
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
const iterator begin()const
{
return _str;
}
const iterator end()const
{
return _str+_size;
}
size_t size()const
{
return _size;
}
我们需要注意的是cont修饰的this指针是不可修改的。
3.2.2容量操作
库里还有两个容量函数resize和reserve
*reserve
就是申请空间不做处理
void reserve(size_t newcapcity)
{
//如果新空间大于原来的,就从新创建一个
if (newcapcity > _capacity)
{
char *temp = new char[newcapcity];
strcpy(temp, _str);
//释放原来的空间,使用新的
delete[] _str;
_str = temp;
_capacity = newcapcity;
}
}
如果传的值比原串的capacity大
如果是小呢?
是没有什么变化的,这和我们平时调用的普通的string是一样的,但是这个增容的大小还是有点区别的。
*resize
void resize(size_t newsize,char ch='\0')
{
if (newsize > _size)
{
/ 如果newSize大于底层空间大小,则需要重新开辟空间
if (newsize>_capacity)
{
reserve(newsize);
}
memset(_str+_size,ch,newsize-_size);
}
_size = newsize;
_str[newsize] = '\0';
}
先resize检查容量够不够,不够就复用reserve函数,如果够这里可以看到的是如果我们增容的100个空间,后面新增的空间也被初始化成了l。
这里增容的空间比原字符串小,那么就会保存前几个字符。
3.2.3字符串的插入函数
插入函数分为尾部插入单个字符(push_back),尾部插入字符串(append),任意位置插入字符串(insert),运算符重载(+=)。
*push_back
void push_back(char ch)
{
if (_size == _capacity)
{
//三目运算符
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size++] = ch;
_str[_size] = '\0';//字符串默认结尾有‘\0’
}
直接在后面插入字符时,还要加‘\0’。
*append
void append(const char *s)
{
int len = strlen(s);
if (_size + len > _capacity)//说明空间不够,要扩容
{
reserve(_size + len+1);//必须还要给‘\0’开一个空间,不然析构的时候会出错,所以要+1
}
strcpy(_str + _size, s);
_size += len;
}
//重载一个string类型
//void append(const string& s)
//{
// if (_size + s._size > _capacity)//说明空间不够,要扩容
// {
// reserve(_size + s._size);
// }
// strcpy(_str + _size, s._str);
// _size += s._size;
//}
这里一共写了两个版本,完成的功能都是一样的。
*insert
string& insert(size_t pos,char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)//说明空间不够,要扩容
{
reserve(_size + len+1);//必须还要给‘\0’开一个空间,不然析构的时候会出错,所以要+1
}
size_t end= _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
string s1("hello world");
s1.insert(0, "lol");//在0这个位置插入lol
但是insert的效率特别的低,因为你要找到你要插入的位置,把后面的字符串往后移,然后再覆盖。
*运算符+=的重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
string s1("hello world");
s1 += "hello ";
3.2.4字符串的删除
string& erase(size_t pos=0,size_t len=-1)
{
if (len == -1 || pos+len >= _size)
{
_str[pos] = '\0';
_size = pos;
return *this;
}
strcpy(_str + pos, _str +pos+len);
_size -= len;
return *this;
}
string s1("hello world");
s1.erase(0, 2);
3.3.5字符串的查找
size_t find(char ch)
{
for (size_t i = 0; i < _size; ++i)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* s, size_t pos = 0)
{
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr -_str;
}
}
3.3.6字符串的输入输出
这里的内容上一节已经说过,这里就不多说了,就是要在类外定义
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
iostream& operator>>(iostream& in,string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
3.3.7其他函数
bool operator<(const string& s)
{
return strcmp(this->_str, s._str) < 0;
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator>(const string& s)
{
return strcmp(this->_str, s._str) > 0;
}
bool operator<=(const string& s)
{
return !(*this > s);
}
bool operator==(const string& s)
{
return strcmp(this->_str, s._str) == 0;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
//以上是比较运算符重载
//其它操作
const char* c_str()const//返回char*字符串
{
return _str;
}
void swap(string& s)//交换两个字符串类
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
bool empty()const//判空
{
return _size == 0;
}
void clear()//清楚字符串
{
_size = 0;
_str[_size] = '\0';
}
3.2完整的string类
class string
{
public:
/
//迭代器
typedef char* iterator;
typedef const char* conts_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin()const
{
return _str;
}
const iterator end()const
{
return _str + _size;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
//reserve和resize
void reserve(size_t newcapcity)
{
//如果新空间大于原来的,就从新创建一个
if (newcapcity > _capacity)
{
char *temp = new char[newcapcity];
strcpy(temp, _str);
//释放原来的空间,使用新的
delete[] _str;
_str = temp;
_capacity = newcapcity;
}
}
void resize(size_t newsize, char ch = '\0')
{
if (newsize > _size)
{
/ 如果newSize大于底层空间大小,则需要重新开辟空间
if (newsize > _capacity)
{
reserve(newsize);
}
memset(_str + _size, ch, newsize - _size);
}
_size = newsize;
_str[newsize] = '\0';
}
///
//插入函数
void push_back(char ch)
{
if (_size == _capacity)
{
//三目运算符
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size++] = ch;
_str[_size] = '\0';//字符串默认结尾有‘\0’
}
void append(const char *s)
{
int len = strlen(s);
if (_size + len > _capacity)//说明空间不够,要扩容
{
reserve(_size + len + 1);//必须还要给‘\0’开一个空间,不然析构的时候会出错
}
strcpy(_str + _size, s);
_size += len;
}
//重载一个string类型
//void append(const string& s)
//{
// if (_size + s._size > _capacity)//说明空间不够,要扩容
// {
// reserve(_size + s._size+1);
// }
// strcpy(_str + _size, s._str);
// _size += s._size;
//}
string& insert(size_t pos, char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)//说明空间不够,要扩容
{
reserve(_size + len + 1);//必须还要给‘\0’开一个空间,不然析构的时候会出错,所以要+1
}
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
//删除
string& erase(size_t pos = 0, size_t len = -1)
{
if (len == -1 || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
return *this;
}
strcpy(_str + pos, _str + pos + len);
_size -= len;
return *this;
}
//查找
size_t find(char* substr, size_t pos = 0)
{
const char* ans = strstr(_str + pos, substr);
if (ans)
{
return ans - _str;
}
return -1;
}
//
//创建对象
string(const char* _s1 = "")
:_str(new char[strlen(_s1) + 1])
, _size(strlen(_s1))
, _capacity(strlen(_s1))
//, _capacity(_size)
{
strcpy(_str, _s1);
}
string(const string& _s2)
:_str(new char[strlen(_s2._str) + 1])
, _size(strlen(_s2._str))
, _capacity(strlen(_s2._str))
//, _capacity(_size)
{
strcpy(_str, _s2._str);
}
string &operator=(const string&_s3)
{
if (this != &_s3)
{
delete[]_str;
_str = (new char[strlen(_s3._str) + 1]);
strcpy(_str, _s3._str);
_capacity = _size = _s3._size;
return *this;
}
return *this;
}
~string()//析构函数
{
delete[] _str;//释放空间
_str = nullptr;//指针指向空,防止野指针
_size = _capacity = 0;
}
bool operator<(const string& s)
{
return strcmp(this->_str, s._str) < 0;
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator>(const string& s)
{
return strcmp(this->_str, s._str) > 0;
}
bool operator<=(const string& s)
{
return !(*this > s);
}
bool operator==(const string& s)
{
return strcmp(this->_str, s._str) == 0;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
//以上是比较运算符重载
//其它操作
const char* c_str()const//返回char*字符串
{
return _str;
}
void swap(string& s)//交换两个字符串类
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
bool empty()const//判空
{
return _size == 0;
}
void clear()//清楚字符串
{
_size = 0;
_str[_size] = '\0';
}
private:
char* _str;
size_t _capacity;//有效字符,不算\0
size_t _size;
};
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out <<s[i];
}
return out;
}
iostream& operator>>(iostream& in,string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}