网站后台换qq/看seo
目录
1.什么是STL
1.1概念
1.2.STL的六大组件
2.string类的基本概念和使用
2.1string类的介绍
2 1. string类对象的常见构造
2.2 . string类对象的析构
2.3. string类对象的容量操作
2.3.1容量
2.3.2扩容
2.4string类中operator[]
2.5string类与迭代器
2.5.1正向迭代器
2.5.2反向迭代器
2.5.3使用迭代器的意义
2.5.4其他遍历方式
2.6string类的增删
2.6.1增
2.6.2查与匹配
2.7string类中自定义插入与删除
3.string的模拟实现
3.1简易的string类的实现
3.1.1构造函数
3.1.2析构函数
3.1.3拷贝构造
**浅拷贝
**深拷贝
3..1.4=运算符重载
3.1.5简易的string实现
3.1.6简易string的现代写法
3.2 string类的模拟实现
3.2.1迭代器的实现
3.2.2容量操作
*reserve
*resize
3.2.3字符串的插入函数
*push_back
*append
*insert
*运算符+=的重载
3.2.4字符串的删除
3.3.5字符串的查找
3.3.6字符串的输入输出
3.3.7其他函数
3.2完整的string类
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定义为15string 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;//显示容量,一般都是15s1.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码+1cout << 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');//在末尾插入字符acout << s1 << endl;s1.append("bcd");//在末尾插入字符串bcdcout << s1 << endl;s1 += 'e';//在末尾插入字符ecout << s1 << endl;s1 += "hello linux";//在末尾插入字符串hello linuxcout << 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和resizevoid 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;//有效字符,不算\0size_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;}