前言
目录
- 1. STL简介
- (1)什么是STL
- (2)STL的版本
- (3)STL的六大组件
- 2. string的使用
- 2.1 npos
- 2.2 遍历字符串string的每一个字符
- 2.3 迭代器:
- 2.4 string的内存管理
- 2.5 string模拟实现
- 2.5.1 深拷贝:
- 3 .具体代码实现(string.hpp):
- 3.1 拷贝构造代码实现:
- 3.2 增删查改的代码实现:
- 3.3 String中的成员变量和全局重载
- 3.4 String中的运算符重载
- 3.5 Windows上库里面对String的实现
1. STL简介
(1)什么是STL
STL是(standard template libaray-标准模板库)的首字母缩写,是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
(2)STL的版本
- 原始版本:
由HP实验室完成的原始版本,主要是给Visual studio使用。 - P.J. 版本:
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。 - RW版本:
- SGR版本:
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。
我们后面学习STL要阅读部分源代码,主要参考的就是这个SGR版本。
(3)STL的六大组件
- 空间配置器–内存池
- 迭代器
- 配接器
- 容器(其实是数据结构)
- 仿函数
- 算法
六大组件之间的关系:
迭代器 主要是用来访问容器的。
Iterators特性: - 迭代器提供了通用的方法来访问容器。
- iterator像指针一样的类型,有可能就是指针(比如vector、String),也有可能不是指针(比如Map、set、 list是类来封装List迭代器),其用法像指针一样的东西。
- **iterator是个类型,**用这个类型可以定义一个对象
2. string的使用
String是类模板,string是被typedef 出来的,比STL产生的早,遵循STL的那一套.
- 使用string的时候,要包含头文件 #include< string >
- typedef basic_string string;
- 由于string这个类中有上百个成员函数的接口,我们要会用其中比较常见的接口,string的学习文档:链接: 传送门
- string是默认带\0 的,和C语言字符串一样。即:string s1;//只有一个\0
Operator at 和Operator [ ]的区别在于,Operator [ ]越界以后会抛异常。
2.1 npos
string 中的npos是静态成员变量,初始值是-1.
2.2 遍历字符串string的每一个字符
遍历字符串有三种方法:
- 第一种方式,下标 + [] – []是C++重载的运算符。
- 第二种方式,迭代器 – 迭代器是用来访问数据结构的。
- 第三种方式,范围for – 前提是:C++11才支持的语法。
void test_string3()
{//遍历string的每一个字符string s1("hello");cout << s1[0] << endl;s1[0] = 'x';cout << s1.size() << endl;//遍历一共有三种方式//·第一种方式,下标 + [] -- []是C++重载的运算符://size就是返回它有多少个字符,是不包含\0的for (size_t i = 0; i < s1.size(); i++){//s1.operator[](i);相当于调用这个函数cout << s1[i] << " ";//[]相当于函数调用}cout << endl;//编译器是看类型的,下面的运用和上面的类中调用成员函数完全不同//const char* s2 = "world";//s2[i];//*(s2 + i)//·第二种方式,迭代器 -- 迭代器是用来访问数据结构的://sting::iterator是个类型,用这个类型可以定义一个对象string::iterator it = s1.begin();//或者叫iter//begin是指向第一个位置//end不是结束位置,而是最后一个位置的下一个位置//如果end是最后一个位置的下一个的话就会访问不到最后一个位置//[ ) -- 左闭右开的结构 -- 方便遍历//写成小于 < 也是可以的,但是不建议,标准的地方就是写的不等于 != //统一用不等于 != while (it != s1.end()) {cout << *it << " ";it++;}cout << endl;//现阶段理解的迭代器:像指针一样的东西或者就是指针//·第三种方式,范围for -- 前提是:C++11才支持的语法//范围for的原理:替换成迭代器//自动取元素,赋值给ch,自动判断结束,自动++for (auto ch : s1){cout << ch << " ";}cout << endl;
}
现阶段理解的迭代器:像指针一样的东西或者指针。
iterator是个类型,用这个类型可以定义一个对象
- begin是指向第一个位置
- end不是结束位置,而是最后一个数据的下一个位置
- 如果end不是最后一个数据的下一个位置的话,循环条件中就会访问不到最后一个位置
- [ ) – 左闭右开的结构 – 方便遍历
范围for:
- 范围for又叫语法糖,因为它用起来很舒服很好用,省略了大量的代码
- 其实在底层编译器替代成了迭代器,只是上层个看起来厉害
- 大家可以通过看汇编代码来看底层实现的逻辑
- 范围for和迭代器底层并没有太大差异
2.3 迭代器:
四种迭代器分类:
- 第一种,普通的正向迭代器:const_iterator
- 第二种,反向迭代器:reverse_iterator
- 第三种,正向迭代器,能读不能写const_iterator
- 第四种,反向迭代器,能读不能写const_reverse_iterator
//不改变就加const保护
//普通迭代器是可读可写的
void Func(const string& rs)
{
//第三种,正向迭代器,能读不能写string::const_iterator it = rs.begin();while (it != rs.end()){//(*it) += 1;cout << *it << " ";it++;}cout << endl;//第四种,反向迭代器,能读不能写//string::const_reverse_iterator rit = rs.rbegin();//auto自动推导:auto rit = rs.rbegin();while (rit != rs.rend()){//(*rit) -= 1;cout << *rit << " ";rit++;}cout << endl;
}//四种迭代器:
void test_string5()
{
//第一种,普通的正向迭代器:string s("hello world");string::iterator it = s.begin();while (it != s.end()){(*it) += 1;cout << *it << " ";it++;}cout << endl;cout << s << endl;//第二种,反向迭代器:string::reverse_iterator rit = s.rbegin();while (rit != s.rend()){(*rit) -= 1;cout << *rit << " ";rit++;}cout << endl;cout << s << endl;Func(s);}
//iterator是终极方式,[] + 下标,是附带方式
2.4 string的内存管理
string的容量大小:Capacity
string的长度,既可以用length(),也可以用size();
reserve和resize都不会缩容量capacity,但是resize会让size降下来,只留size个。
Reserve只修改capacity大小,不修改size大小.
Resize既修改capacity大小,也修改size大小,会进行初始化
reserve和resize都不会缩容量capacity,但是resize会让size降下来,只留size个。
void test_string6()
{string s("hello world");//length产生的比size早cout << s.length() << endl;cout << s.size() << endl;cout << s.max_size() << endl;//容量要扩容cout << s.capacity() << endl;
}
2.5 string模拟实现
2.5.1 深拷贝:
浅拷贝的问题: 如果是栈的类,普通的浅拷贝(按字节拷贝),会出现问题,两个栈的str指针指向同一个地方,两个栈相互影响,我们并不希望这样,所以我们要学习一下深拷贝。
浅拷贝的效果:
深拷贝的效果:
3 .具体代码实现(string.hpp):
主要是增删查改和拷贝构造
3.1 拷贝构造代码实现:
#pragma once#include<iostream>
#include<assert.h>
#include<string>using namespace std;//class mystring
//class String//自己实现的封装在命名空间中防止冲突
namespace Joker
{//实现一个简单的string,只考虑资源管理深浅拷贝的问题//暂且不考虑增删查改//string需要考虑完善的增删查改和使用的stringclass string{public://const对象遍历访问是不可以被修改的,应该实现两种//两种迭代器的参数不同,函数重载typedef char* iterator;typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}iterator begin(){return _str;}iterator end(){return _str + _size;}//无参的构造函数/*string():_size(0),_capacity(0){_str = new char[1];_str[0] = '\0';}*///全缺省//1、"\0"这是有两个\0 //2、""这是有一个\0 //3、'\0'这里是**把\0的assic码值0**给了指针,实际是空指针**(注意:‘\0'的ascII为0 | ’0‘的ASCII值为48)**//而且如果支持c_str,cout的时候,解引用空指针会报错string(const char* str = "") //""是C语言默认常量字符串,后面有\0:_size(strlen(str)) //strlen()是不会判空指针的, _capacity(_size){_str = new char[_capacity + 1];//给'\0'多开一个strcpy(_str, str);}//·构造函数:
//·传统的写法:本分,老实,老老实实干活,该开空间开空间,该拷贝数据就自己拷贝数据//s2(s1); - 深拷贝//在类里面只要用string对象访问成员都是不受限制的//私有是限制在类外面使用对象去访问成员/*string(const string& s):_size(strlen(s._str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, s._str);}*///·现代写法:剥削,要完成深拷贝,自己不想干活,安排别人干活,然后窃取劳动成果//要初始化一下,不然有可能释放野指针string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);--调用构造函数swap(tmp);}
拷贝构造的现代写法:
- 先构造函数一个string类型的 temp 对象:使用传过来指针实例化一个所需一样的对象
- 再将tmp对象的内容和所要拷贝构造的对象的成员变量进行交换
- 在将这个拷贝函数结束之后,tmp对象的生命周期结束,自动调用其析构函数,释放掉空间
注意:
和temp交换的this 代表的_str指针是随机值,交换给temp之后,析构对随机指针释放是会报错的。
//赋值重载:
//传统写法://s1 = s3,如果不传引用返回,用传值返的话会深拷贝,代价太大了//new失败了之后会抛异常,用try捕获//string& operator = (const string& s)//{// if (this != &s)// {// //1、先释放:如果s1开空间失败了,之前的空间也被释放了// /*delete[] _str;// _str = new char[strlen(s._str) + 1];// strcpy(_str, s._str);*/// //2、先开空间:下面写法可以避免上述问题// char* tmp = new char[s._capacity + 1];// strcpy(tmp, s._str);// delete[] _str;// _str = tmp;// _size = s._size;// _capacity = s._capacity;// }// return *this;//}//现代写法:
//现代方法一:/*string& operator=(const string& s){if (this != &s){string tmp(s._str);--调用构造函数swap(tmp);}return *this;}*///现代方法二:-- 更简单,一行代码搞定,适用于所有深拷贝//s 就是 s1的深拷贝,先传参,传参就是拷贝构造string& operator=(string s) --调用拷贝构造{swap(s);return *this;}
赋值重载的现代写法:
- 赋值函数中,形参是string类型的对象,调用函数是值传参
- S对象已经是拷贝构造出来的对象,直接将s对象和所需要拷贝的对象交换就好。
~string()
{if (_str != nullptr){delete[] _str;_str = nullptr;_size = _capacity = 0;}
}const char* c_str() const
{return _str;
}//普通对象调用这个
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}//const修饰过的对象调用这个.(const引用中的const只是限定了不能通过此引用去修改变量的值)
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}
运算符[ ]的分析:
- 如果是传值返回的话,要是想对其进行修改的话,就不行
- 所以这里的话,必须是传的引用
3.2 增删查改的代码实现:
//s.size(&s)
//size_t size(cosnt string* const this)
size_t size() const
{return _size;
}
//加上const普通对象可以调用,const对象也可以调用
//不加的话const对象调用会有权限放大的风险size_t capacity() const
{return _capacity;
}//reserve扩容
void reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}//1、大于capacity
//2、小于capacity,大于size
//3、小于size
//resize的作用
//扩空间 + 初始化
//删除部分数据,保留前n个void resize(size_t n, char ch = '\0'){if (n < _size){_size = n;_str[_size] = '\0';}else{if (n > _capacity){reserve(n);}for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}//复用push_backstring& operator+=(char ch){push_back(ch);return *this;}//添加一个字符void push_back(char ch){//if (_size == _capacity)//{// reserve(_capacity == 0 ? 4 : _capacity * 2);//}//_str[_size] = ch;//_size++;//_str[_size] = '\0';insert(_size, ch);}//复用appendstring& operator+=(const char* str){append(str);return *this;}//添加一个字符串void append(const char* str){/*size_t len = _size + strlen(str);if (len > _capacity){ reserve(len);}strcpy(_str + _size, str);_size = len;*/insert(_size, str);}
//Insert一个字符string& insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);} /*size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];end--;}*///最好的一种方式size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size += 1;return *this;}//Insert一个字符串string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (len == 0){return *this;}if (_size + len > _capacity){reserve(_size + len);}//往后挪动len个位置size_t end = _size + len;//while(end >= pos + len) //-- 最好别这样写,怕别人给极端场景,pos,end都是0while (end > pos + len - 1){_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size += len;return *this;}//erase一个数值
string& erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];begin++;}_size -= len;}return *this;}
//求字串的函数
string substr(size_t pos,size_t len=npos)const
{assert(pos<_size);size_t reallen=len;if(len==npos||pos+len >_size){reallen=_size-pos;}string sub;for(size_t i=0;i<reallen;++i){ sub+=_str[pos+i];}return sub;
}size_t find(char ch, size_t pos = 0){for (; pos < _size; pos++){if (_str[pos] == ch){return pos;}}return npos;}size_t find(const char* str, size_t pos = 0){const char* p = strstr(_str + pos, str);//KMP,BM只做了解if (p == nullptr){return npos;}else{return p - _str;}}
全局Swap函数和 string::Swap( )成员函数的区别:
- 上述逻辑和之前C语言实现的逻辑并无二异
- 只是用了C++的语法
//交换 -- 用库里的交换还要调用拷贝构造
void swap(string& s)
{//调用库里的 -- 库中的(类模板)是全局的,也可以写std::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);
}
3.3 String中的成员变量和全局重载
private:char* _str;size_t _size; //有效字符个数 -- 不包含'\0'size_t _capacity; //存储有效字符的空间//静态不能给缺省值,强制要求在类外面定义const static size_t npos;
};//定义
const size_t string::npos = -1;//流插入
ostream& operator<<(ostream& out, const string& s)
{//中间有'\0'就不能将整个字符串打印完//out << s.c_str() << endl;//趋向于这样写 -- 这样能保证每个字符都能打印出来for (auto ch : s){out << ch;}return out;
}//流提取istream& operator>>(istream& in, string& s){//方法一://char ch;//in >> ch;//ch = in.get();//while (ch != ' ' && ch != '\n')//{// s += ch;// //in >> ch;// ch = in.get();//}//要清理掉之前的空间s.clear();char ch;ch = in.get();char buff[128] = { '\0' };size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){s += buff;memset(buff, 0, sizeof(char) * 128);i = 0;}ch = in.get();}s += buff;return in;}void clear(){_str[0] = '\0';_size = 0;}
3.4 String中的运算符重载
运算符重载不一定是成员函数,可以写成全局
bool operator<(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) < 0;}bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}
}
3.5 Windows上库里面对String的实现
总共有28个字节,成员变量如下:
private:
//好处就是对小字符操作更快,以空间换时间//<16 字符串存在buff数组中//>=16 存在_str指向的堆空间上char _buff[16];size_t _capacity;size_t _size;char* _str;
尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦