为什么广告不集中建设广告网站,珠海网站制作报价,北京设计网站,关于网站建设的合同协议文章目录1. STL的介绍1.1 STL的六大组件1.2 STL的版本1.3 STL的缺陷2. string的使用2.1 为什么要学习string类#xff1f;2.2 常见构造2.3 Iterator迭代器2.4 Capacity2.5 Modifiers2.6 String operations3. string的模拟实现3.1 构造函数3.2 拷贝构造函数3.3 赋值运算符重载和…
文章目录1. STL的介绍1.1 STL的六大组件1.2 STL的版本1.3 STL的缺陷2. string的使用2.1 为什么要学习string类2.2 常见构造2.3 Iterator迭代器2.4 Capacity2.5 Modifiers2.6 String operations3. string的模拟实现3.1 构造函数3.2 拷贝构造函数3.3 赋值运算符重载和析构函数3.4 常用接口(c_str、[ ]、迭代器、size和capacity)3.5 关系运算符的重载3.6 插入、删除和容量操作(reserve和resize)3.7 swap、find和clear3.8 流插入和流提取运算符的重载整体源码1. STL的介绍 STL(standard template libaray-标准模板库) 是C标准库的重要组成部分不仅是一个可复用的组件库而且是一个包罗数据结构与算法的软件框架。 1.1 STL的六大组件 1.2 STL的版本 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本本着开源精神他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖。P. J. 版本 由P. J. Plauger开发继承自HP版本被Windows Visual C采用不能公开或修改缺陷可读性比较低符号命名比较怪异。RW版本 由Rouge Wage公司开发继承自HP版本被C Builder 采用不能公开或修改可读性一般。SGI版本 由Silicon Graphics Computer SystemsInc公司开发继承自HP版 本。被GCC(Linux)采用可移植性好可公开、修改甚至贩卖从命名风格和编程 风格上看阅读性非常高。我们后面学习STL要阅读部分源代码主要参考的就是这个版本。 1.3 STL的缺陷 STL库的更新太慢了。这个得严重吐槽上一版靠谱是C98中间的C03基本一些修订。C11出来已经相隔了13年STL才进一步更新。STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。STL极度的追求效率导致内部比较复杂。比如类型萃取迭代器萃取。STL的使用会有代码膨胀的问题比如使用vector/vector/vector这样会生成多份代码当然这是模板语法本身导致的。 2. string的使用
2.1 为什么要学习string类
C语言中字符串是以’\0’结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列的库函数但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户自己管理稍不留神可能还会越界访问。
string类的注意事项 string是表示字符串的字符串类该类的接口与常规容器的接口基本相同再添加了一些专门用来操作string的常规操作。string在底层实际是basic_string模板类的别名typedef basic_stringchar, char_traits, allocatorstring;不能操作多字节或者变长字符的序列。 2.2 常见构造 int main()
{string s1;string s2(hello world);string s3 hello world;string s4(s3, 6, 3);cout s4 endl;string s5(s3, 6, 13);cout s5 endl;string s6(s3, 6);cout s6 endl;string s7(hello world, 5);cout s7 endl;string s8(10, *);cout s8 endl;//对于string的访问我们可以直接使用[],因为重载了[],//所以string类可以像数组一样访问当然也可以用范围for循环for (size_t i 0; i s2.size(); i){s2[i];}cout s2 endl;for (auto e : s2){cout e ;}cout endl;return 0;
}这个构造函数的意思是从str中的第pos个位置开始取len个字符来初始化目标对象但我们可以看到这里的第三个参数用到了缺省参数npos如果第三个参数的大小超过了str的长度会直接取到str的末尾下面我们来看一下这里的npos指的是多少 这里的npos指的是无符号的-1表示的是4294967295因为我们的字符串最大的长度是不可能超过这么长的所以当我们第三个参数默认不给的时候他会从pos位置一直取到字符串的结尾。 2.3 Iterator迭代器 迭代器和指针非常类似它可以是需要的任意类型通过迭代器可以指向容器中的某个元素如果需要还可以对该元素进行读/写操作。虽然迭代器像指针但它不一定是指针而所有的容器都可以使用迭代器来进行遍历和修改。 正向迭代器 int main()
{string s1(hello world);string::iterator it s1.begin();while (it ! s1.end()){cout *it ;it;}cout endl;//用迭代器修改容器中的内容it s1.begin();while (it ! s1.end()){*it 1;cout *it ;it;}return 0;
}这里我们还需要注意的是迭代器的区间是左闭右开的这里的begin()指向的是字符串中的第一个字符而end()指向的是最后一个字符后面的’\0’。 反向迭代器 int main()
{string s1(hello world);string::reverse_iterator it s1.rbegin();while (it ! s1.rend()){cout *it ;it;}return 0;
}const迭代器
当我们创建一个const修饰的对象时就必须要用到const迭代器了const迭代器是只读的不能够修改容器中的内容。当然const迭代器既有正向迭代器又有反向迭代器。
int main()
{const string s1(hello world);string::const_iterator it1 s1.begin();while (it1 ! s1.end()){cout *it1 ;it1;}cout endl;string::const_reverse_iterator it2 s1.rbegin();while (it2! s1.rend()){cout *it2 ;it2;}cout endl;return 0;
}这里还有一点我们需要注意的是所有的迭代器都可以使用auto去自动识别因为auto是可以根据后面对象的类型自动去识别迭代器类型的。但是这样写的话可读性也会变的很差。
int main()
{const string s1(hello world);auto it1 s1.begin();while (it1 ! s1.end()){cout *it1 ;it1;}cout endl;auto it2 s1.rbegin();while (it2 ! s1.rend()){cout *it2 ;it2;}cout endl;return 0;
}2.4 Capacity 这里我们可以先写一个简单的程序来观察一下string是怎么扩容的
在vs下的扩容情况
int main()
{string s;size_t sz s.capacity();cout making s grow:\n;cout capacity changed: sz \n;for (int i 0; i 100; i){s.push_back(c);if (sz ! s.capacity()){sz s.capacity();cout capacity changed: sz \n;}}return 0;
}在g下的扩容情况 这里我们可以看到在不同的编译器下string的扩容效果是不同的。下面我们来看一下reserve和resize的区别 int main()
{// 扩容string s1(hello world);s1.reserve(100);cout s1.size() endl;cout s1.capacity() endl;// 扩容初始化string s2(hello world);s2.resize(100, x);cout s2.size() endl;cout s2.capacity() endl;// 比size小删除数据保留前5个s2.resize(5);cout s2.size() endl;cout s2.capacity() endl;return 0;
}这里的reserve主要是为字符串预留空间只会影响capacity的大小。而resize则是将字符串的个数改为n个多出的空间使用字符c来填充。它会影响size和capacity的大小。一般情况下我们可以提前使用reserve开空间避免频繁的扩容。 2.5 Modifiers insert和erase 对于insert/erase我们不推荐经常使用能少用就少用因为他们可能都存在要挪动数据效率低下。下面我们举几个例子来看一下他们的用法
int main()
{string s1(world);s1.insert(0, hello);cout s1 endl;cout -------------------- endl;//s1.insert(5, 1, );//s1.insert(5, );s1.insert(s1.begin()5, );cout s1 endl;cout -------------------- endl;string s2(hello world);//s2.erase(5, 1);s2.erase(s2.begin() 5);cout s2 endl;cout -------------------- endl;//s2.erase(5, 30);s2.erase(5);cout s2 endl;return 0;
}replace和swap int main()
{string s1(hello world);s1.replace(5, 1, %%d);cout s1 endl;string s2(I love you!);string s3(Wake up bro!);cout s2: s2 endl;cout s3: s3 endl;cout ------------------- endl;s2.swap(s3);cout s2: s2 endl;cout s3: s3 endl;return 0;
}2.6 String operations c_str
在某些场景中只支持对C形式的字符串即字符数组进行操作比如网络传输、fopen而不支持对C中的 string 对象进行操作所以 string 提供了c_str用于返回C形式的字符串。 find和rfind find函数用于返回字符在string中首次出现的位置从前往后找rfind函数用于返回字符在string中首次出现的位置从后往前找。 find_first_of first_first_of函数用于返回在string中寻找与字符/字符数组/string中任意一个字符匹配的元素的位置。
int main()
{std::string str(Please, replace the vowels in this sentence by asterisks.);//把str中包含abcdv中任何一个的字符替换成*std::size_t found str.find_first_of(abcdv);while (found ! std::string::npos){str[found] *;found str.find_first_of(abcdv, found 1);}std::cout str \n;return 0;
}substr 将string中第pos个位置开始往后的n个字符构造成一个新的string对象并返回。 3. string的模拟实现
3.1 构造函数
我们在写构造函数的时候一般会有有参构造和无参构造两种构造函数。
//无参构造和有参构造的错误写法
string():_str(nullptr),_size(0),_capacity(0)
{}
string(const char* str):_str(str),_size(strlen(str)),_capacity(strlen(str))
{}我们可以将构造寒素写成有参和无参两种形式但是因为char *str被const修饰了所以传参的时候会导致权限的放大所以会报错因此我们只能将私有成员变量_str用const修饰但是这势必会导致无法修改_str指向的内容。而且当我们使用无参构造去创建一个对象的时候由于_str指向的是一个空指针如果这个对象后续没有开空间调用析构函数所以delete的时候必定会导致问题的出现。
基于上面的种种原因我们可以在初始化的时候直接使用缺省参数构造一个空字符串注意不能是nullptr因为使用strlen的时候会出问题如果没有传参就使用缺省值当然如果传入的是空字符串。我们需要将空间开大一些因为后期使用push_back需要第一次就扩容的时候使用二倍扩容会出问题。
//构造函数
string(const char *s ):_size(strlen(s))
{_capacity _size 0 ? 3 : _size;_str new char[_capacity 1];strcpy(_str, s);
}3.2 拷贝构造函数
string(const string s):_size(s._size), _capacity(s._capacity)
{_str new char[_capacity 1];strcpy(_str, s._str);
}
//拷贝构造现代写法s2(s1)
string(const string s):_str(nullptr),_size(0),_capacity(0)
{string tmp(s._str);swap(tmp);
}对于拷贝构造函数如果我们不写编译器会自动默认生成对于内置类型编译器的默认拷贝构造函数能够很好的处理但是如果内置的成员变量具有资源的申请就会导致浅拷贝问题的出现。所以我们只能重写深拷贝构造函数。
当我们使用拷贝构造的现代写法时一定要走初始化列表如果不走初始化列表s2的数据将会是随机值随机指向一块空间将tmp和s2的数据交换后tmp指向的空间将会被销毁那么随即指向的空间将会被delete掉程序奔溃。 3.3 赋值运算符重载和析构函数
//赋值运算符重载(传统写法)
string operator(const string s)
{if (this s)return *this;char* tmp new char[strlen(s._str) 1];strcpy(tmp, s._str);delete[] _str;//这里一定要记住先释放原来的空间避免造成内存泄露_str tmp;_size s._size;_capacity s._capacity;return *this;
}
//赋值运算符重载现代写法1
string operator(const string s)
{if (this ! s){string tmp(s);swap(tmp);}return *this;
}
//赋值运算符重载现代写法2
string operator(string s)
{swap(s);return *this;
}
~string()
{delete[] _str;_str nullptr;_capacity _size 0;
}赋值运算符重载也属于类的六大默认成员函数之一如果我们不写编译器也会自动默认生成。但是它和编译器自动生成的默认拷贝构造函数一样都是按字节拷贝同样会导致浅拷贝问题的出现。
为了解决这个问题我们只能重新写赋值运算符重载函数如果是对象自己赋值自己的话直接返回对象本身就可以了。这里我们先开一块临时的内存空间将要拷贝的数据放进去这里我们一定要注意先释放原来对象所指向的空间避免造成内存泄漏。
赋值运算符重载的现代写法有两种第一种写法是先拷贝构造一个临时对象然后在调用swap对象将本对象的数据与其做交换。第二种写法是直接利用形参所拷贝构造的对象进行进行交换。 3.4 常用接口(c_str、[ ]、迭代器、size和capacity) c_str的模拟实现
//模拟实现c_str()
const char* c_str()
{return _str;
}重载[ ]
//重载[]
char operator[](int pos)
{return _str[pos];
}
const char operator[](int pos)const
{return _str[pos];
}因为普通对象是可读可写的但是const对象是只可读不可写的所以我们需要对[ ]的重载一份针对于const版本的。 size和capacity
//获取size()
int size()const
{return _size;
}
//获取capacity
int capacity()
{return _capacity;
}3.5 关系运算符的重载
//比较两个对象关系的运算符重载
bool operator(const string s)const//重载
{return strcmp(_str, s._str) 0;
}bool operator(const string s)const//重载
{return strcmp(_str, s._str) 0;
}bool operator(const string s)const//重载
{return *this s || *this s;
}bool operator(const string s)const//重载
{return !(*this s);
}bool operator(const string s)const//重载
{return !(*this s);
}bool operator! (const string s)const//重载!
{return !(*this s);
}3.6 插入、删除和容量操作(reserve和resize) reserve的实现
//reserve的实现(扩容)
void reserve(size_t n)
{if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}
}因为reserve是只能扩容的所以我们需要先判断一下需要扩的容量是否大于原来的容量。 resize的实现
//resize的实现
void resize(size_t n, char ch)
{if (n _size){_size n;_str[n] \0;}else{if (n _capacity){reserve(n);}int i _size;while (i n){_str[i] ch;i;}_size n;_str[_size] \0;}
}push_back的实现
//push_back的实现
void push_back(char ch)
{if (_size 1 _capacity){reserve(_capacity * 2);}_str[_size] ch;_str[_size] \0;
}append的实现
//append的实现
void append(const char* s)
{size_t len strlen(s);if (_size len _capacity){reserve(_capacity len);}strcpy(_str _size, s);_size len;
}重载
//重载
string operator(char ch)
{push_back(ch);return *this;
}
string operator(const char* s)
{append(s);return *this;
}insert的实现
//insert的实现
string insert(size_t pos, char ch)
{assert(pos _size);if (_size 1 _capacity){reserve(_size * 2);}size_t end _size 1;while (end pos){_str[end] _str[end - 1];end--;}_str[pos] ch;_size;return *this;
}
string insert(size_t pos, const char* str)
{assert(pos _size);size_t len strlen(str);if (_size len _capacity){reserve(_size len);}size_t end _size len;while (end pos len - 1){_str[end] _str[end - len];end--;}/*size_t end _size;for (size_t i 0; i _size - pos 1; i){_str[end len] _str[end];--end;}*/strncpy(_str pos, str, len);_size len;return *this;
}erase的实现
//erase的实现
string erase(size_t pos 0, size_t len npos)
{assert(pos _size);if (len npos || len pos _size){_str[pos] \0;_size pos;}else{strcpy(_str pos, _str pos len);_size - len;}return *this;
}3.7 swap、find和clear swap的实现
//swap的实现
void swap(string str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}find的实现
//find的实现
size_t find(const char* str, size_t pos 0)
{assert(pos _size);char* tmp strstr(_str pos, str);if (tmp nullptr ){return npos;}return tmp - _str;
}size_t find(char ch, size_t pos 0)
{assert(pos _size);for (size_t i pos; i _size; i){if (ch _str[i]){return i;}}return npos;
}clear的实现
void clear()
{_str[0] \0;_size 0;
}3.8 流插入和流提取运算符的重载
//重载运算符
ostream operator(ostream out,const string str)
{for (auto e : str){out e;}return out;
}//重载运算符
istream operator (istream in, string str)
{str.clear();size_t i 0;char tmp[128];char ch in.get();while (ch ! ch ! \n){tmp[i] ch;if (i 127){tmp[i] \0;str tmp;i 0;}ch in.get();}if (i ! 0){tmp[i] \0;str tmp;}return in;
}流插入和流提取运算符因为他们需要和流对象和对象抢占左操作数所以我们需要将他们重载成全局函数但是如果我们的重载函数不需要访问私有成员那么我们就不需要将他们声明为类的友元函数。 cin和scanf一样只要遇到空格或者\n就会被忽略掉所以我们需要用get函数一个字符一个字符的获取当然了如果原来的对象中有有内容的话我们需要先将原来对象中的内容清空然后再重新输入新的内容。所以这里我们在每一次调用流提取重载函数时先将原来对象中的内容使用clear函数清除所有的数据。流插入运算符的重载因为这里比较简单则不再叙述。 整体源码
#includecassert
#includecstring
#includeiostream
using namespace std;namespace cjl
{class string{public://迭代器的模拟实现typedef char* iterator;//普通迭代器typedef const char* const_iterator;//const迭代器iterator begin(){return _str;}iterator end(){return _str _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str _size;}//构造函数string(const char *s ):_size(strlen(s)){_capacity _size 0 ? 3 : _size;_str new char[_capacity 1];strcpy(_str, s);}//拷贝构造string(const string s):_size(s._size), _capacity(s._capacity){_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 operator(const string s){if (this s)return *this;char* tmp new char[strlen(s._str) 1];strcpy(tmp, s._str);delete[] _str;//这里一定要记住先释放原来的空间避免造成内存泄露_str tmp;_size s._size;_capacity s._capacity;return *this;}//赋值运算符重载现代写法1/*string operator(const string s){if (this ! s){string tmp(s);swap(tmp);}return *this;}*///赋值运算符重载现代写法2/*string operator(string s){swap(s);return *this;}*///析构函数~string(){delete[] _str;_str nullptr;_capacity _size 0;}//模拟实现c_str()const char* c_str(){return _str;}//重载[]char operator[](int pos){return _str[pos];}const char operator[](int pos)const{return _str[pos];}//获取size()int size()const{return _size;}//获取capacityint capacity(){return _capacity;}//比较两个对象关系的运算符重载bool operator(const string s)const//重载{return strcmp(_str, s._str) 0;}bool operator(const string s)const//重载{return strcmp(_str, s._str) 0;}bool operator(const string s)const//重载{return *this s || *this s;}bool operator(const string s)const//重载{return !(*this s);}bool operator(const string s)const//重载{return !(*this s);}bool operator! (const string s)const//重载!{return !(*this s);}//reserve的实现(扩容)void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}//resize的实现void resize(size_t n, char ch){if (n _size){_size n;_str[n] \0;}else{if (n _capacity){reserve(n);}int i _size;while (i n){_str[i] ch;i;}_size n;_str[_size] \0;}}//push_back的实现void push_back(char ch){if (_size 1 _capacity){reserve(_capacity * 2);}_str[_size] ch;_str[_size] \0;}//append的实现void append(const char* s){size_t len strlen(s);if (_size len _capacity){reserve(_capacity len);}strcpy(_str _size, s);_size len;}//重载string operator(char ch){push_back(ch);return *this;}string operator(const char* s){append(s);return *this;}//insert的实现string insert(size_t pos, char ch){assert(pos _size);if (_size 1 _capacity){reserve(_size * 2);}size_t end _size 1;while (end pos){_str[end] _str[end - 1];end--;}_str[pos] ch;_size;return *this;}string insert(size_t pos, const char* str){assert(pos _size);size_t len strlen(str);if (_size len _capacity){reserve(_size len);}size_t end _size len;while (end pos len - 1){_str[end] _str[end - len];end--;}/*size_t end _size;for (size_t i 0; i _size - pos 1; i){_str[end len] _str[end];--end;}*/strncpy(_str pos, str, len);_size len;return *this;}//erase的实现string erase(size_t pos 0, size_t len npos){assert(pos _size);if (len npos || len pos _size){_str[pos] \0;_size pos;}else{strcpy(_str pos, _str pos len);_size - len;}return *this;}//swap的实现void swap(string str){std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}//find的实现size_t find(const char* str, size_t pos 0){assert(pos _size);char* tmp strstr(_str pos, str);if (tmp nullptr ){return npos;}return tmp - _str;}size_t find(char ch, size_t pos 0){assert(pos _size);for (size_t i pos; i _size; i){if (ch _str[i]){return i;}}return npos;}void clear(){_str[0] \0;_size 0;}private:char* _str;int _size;int _capacity;//static const size_t npos -1;//只有const静态整形变量才能够在类内提供初始值//如果是static const double/float则不可以在类内提供初始值。static const int/short可以在类内提供初始值。//static const size_t npos;static size_t npos;};//const size_t string::npos -1;size_t string::npos -1;//重载运算符ostream operator(ostream out,const string str){for (auto e : str){out e;}return out;}//重载运算符istream operator (istream in, string str){str.clear();size_t i 0;char tmp[128];char ch in.get();while (ch ! ch ! \n){tmp[i] ch;if (i 127){tmp[i] \0;str tmp;i 0;}ch in.get();}if (i ! 0){tmp[i] \0;str tmp;}return in;}
}