设计个企业网站网页咋弄,唐河企业网站制作哪家好,简单网站页面设计,室内装修装饰设计培训班文章目录 前言一、写实拷贝#xff08;了解#xff09;二、string类常用接口实现2.1 成员变量2.2 默认构造函数2.3 拷贝构造函数2.4 operator2.5 operator[]2.6 c_str2.7 size()2.8 capacity() 三、迭代器的实现3.1 begin()和end()3.2 范围for 四、string类增删查改4.1 reser… 文章目录 前言一、写实拷贝了解二、string类常用接口实现2.1 成员变量2.2 默认构造函数2.3 拷贝构造函数2.4 operator2.5 operator[]2.6 c_str2.7 size()2.8 capacity() 三、迭代器的实现3.1 begin()和end()3.2 范围for 四、string类增删查改4.1 reserve()增容函数4.2 push_back()尾插字符4.3 append()追加字符串4.4 operator4.5 insert4.6 erase4.7 resize4.8 find4.9 substr 五、string类运算符重载5.1 operator !5.2 operator5.3 operator 前言 个人主页小沈YO. 小编介绍欢迎来到我的乱七八糟小星球 专栏C 心愿便利店 本章内容探索string底层实现 记得 评论 点赞 收藏 关注哦~ 提示以下是本篇文章正文内容下面案例可供参考
一、写实拷贝了解
写时拷贝就是一种拖延症是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数用来记录资源使用者的个数。在构造时将资源的计数给成1每增加一个对象使用该资源就给计数增加1当某个对象被销毁时先给该计数减1然后再检查是否需要释放资源如果计数为1说明该对象时资源的最后一个使用者将该资源释放否则就不能释放因为还有其他对象在使用该资源。
二、string类常用接口实现
2.1 成员变量
class string
{
public:private:char* _str;size_t _size;size_t _capacity;const static size_t npos;——————————————————————————const static size_t npos-1;//静态的成员变量是不可以给缺省值必须在类外面进行初始化但是const静态的整形可以特例
};
const size_t string::npos -1;//支持在类外面初始化2.2 默认构造函数
string():_str (new char[1]{\0}),_size(strlen(0)),_capacity(0)
{}
//常量字符串规定后面默认就有\0
//strlen 计算的是字符串中有效字符的个数不算 \0而常量字符串的结尾默认有一个 \0用 new开辟空间的时候需要多开一个用来存储结尾的 \0
string(const char* str):_size(strlen(str))//0,_capacity(_size)//0
{_str new char[_capacity 1];//1strcpy(_str, str);
}对于上述代码中形参上必须加 const 修饰这样才能用 C 语言中的常量字符串来初始化 string 类对象上面两种初始化的方式都可以一个是无参的一个是有缺省值的形参的的缺省值直接给一个空字符串即可如上述要注意初始化列表是按照声明的顺序来初始化的。_capacity表示的是可以存储有效字符的容量而字符串结尾默认的 ‘\0’ 并不算作有效字符因此最初的 _capacity 就是形参 str 的长度。 对于为什么不可以用 ‘\0’ “\0” 和nullptr当缺省值 答案
首先对于’\0’str是一个char*类型而’\0’是一个char类型的 类型不匹配其次对于给缺省值nullptrstrlen是不会去检查空的它是一直找到 \0为止的也就相当于直接对这个字符串进行解引用了这里的字符串又是空所以会引发空指针问题。最后对于\0它表示该字符串有一个字符 ‘\0’ 它的结尾还有一个默认的 ‘\0’因此有两个 ‘\0’
2.3 拷贝构造函数
//传统写法
string(const string s)//拷贝构造
{_str new char[s._size1];strcpy(_str,s._str);_size s._size;_capacity s._capacity;
}上述我们称为传统写法下面这种我们称为现代写法对于现代写法不需要亲自去申请空间初始化而是调用构造函数去完成。最后再将初始化好的 tmp 交换过来 还要注意一定要通过初始化列表对 *this 进行初始化不然交换给 tmp 后里面都是随机值不同编译器有的会处理有的不会最终出了作用域 tmp 去销毁的时候就会出问题。
//现代写法下面代码是两种不同的现代写法
不过要注意如果 string 对象中有 \0只会把 \0 前面的字符拷贝过去
void swap(string s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string(const string s):_str(nullptr)//拷贝构造,_size(0),_capacity(0)//不处理tmp里面是随机值析构就会发合适呢个错误
{string tmp(s._str);//构造swap(tmp);
}
___________________________________________________________________________________________
void swap(string s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string(const string s)//拷贝构造
{string tmp(s._str);swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);
}2.4 operator
//传统写法
string operator(const string s)
{if (this ! s){char* tmp new char[s._capacity 1];strcpy(tmp, s._str);delete[] _str;_str tmp;_size s._size;_capacity s._capacity;}return *this;
}对比于上述代码写法下述这种写法通过调用拷贝构造来申请空间在利用局部对象出了作用就会被销毁的特点将需要释放的资源通过 swap 交换给这个局部变量让这个局部变量帮我们销毁。
//现代写法这里不能直接用 swap 交换两个 string 类对象会导致栈溢出
void swap(string s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string operator(const string s)
{if (this ! s){//string tmp(s);//调用拷贝构造string tmp(s._str);//调用构造swap(tmp);//这里s2换给了tmp本来s2要析构现在tmp出了作用域调用析构也就意味着原始的s2的空间释放了}return *this;
}下述这种写法不用我们去调用构造或者拷贝构造直接通过形参去调用传值传参会调用拷贝构造tmp是它的实参调用拷贝构造构造的一个一摸一样的空间。
//现代写法的优化版本
void swap(string s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string operator(string tmp)
{swap(tmp);return *this;
}2.5 operator[]
这两个运算符重载函数构成函数重载对象在调用的时候会找最匹配的非const对象会调用非const版本const 对象会调用const版本。
//可读可写版本
char operator[](size_t pos)
{assert(pos _size);return _str[pos];
}
//只可以读版本
const char operator[](size_t pos) const
{assert(pos _size);return _str[pos];
}2.6 c_str
返回的是一个const char*的数组指针只读不写这个数组包含的字符序列与string对象的值相同另外还包含一个以空字符(‘\0’)结尾的字符串加上 const这样普通的 string 类对象可以调用const 类型的 string 类对象也可以调用普通对象来调用就是权限的缩小。
const char* c_str()
{return _str;
}2.7 size()
size_t size()const
{return _size;
}2.8 capacity()
size_t capacity()const
{return _capacity;
}三、迭代器的实现
iterator 是 string 类的内嵌类型也可以说是在 string 类里面定义的类型在一个类里面定义类型有两种方法typedef 和 内部类。
3.1 begin()和end()
//非const调用
typedef char* iterator;//string 类的 iterator 是通过typedef来实现的
iterator begin()
{return _str;
}
iterator end()
{return _str_size;
}
——————————————————————————————————————————————————————————
//const调用
typedef const char* const_iterator;
const_iterator begin()const
{return _str;
}
const_iterator end()const
{return _str _size;
}3.2 范围for
支持范围for写法范围for的底层是迭代器实现的但是范围for不是万能的范围for遇上const类型的对象会报错因此要提供const迭代器typedef const char* const_iterator;
string s1(hello world);
for (auto ch : s1)
{ch;cout ch ;
}
cout endl;四、string类增删查改
4.1 reserve()增容函数
reserve 函数不会进行缩容因此在扩容前要先进程判断只有当形参 n 大于当前容量的时候才扩容。
void reserve(size_t n)
{if (n _capacity){char* tmp new char[n 1];//开n1个是因为n个有效字符另一个是\0strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}
}4.2 push_back()尾插字符
对于尾插首先检查是否需要增容如果需要就调用我们上面实现的 reserve 函数来进行扩容选择2倍扩容 扩容后将ch加到str上然后 _size 最后手动添加一个新的 \0 。
void push_back(char ch)
{if (_size _capacity){//reserve(_capacity * 2);//对于这里可以采用三目来判断_capacity是否为0若不进行判断空串的_capacity是0进行扩容0*00就会发生越界访问reserve(_capacity0?4:_capacity * 2);}_str[_size] ch;_size;_str[_size] \0;
}4.3 append()追加字符串
void append(const char * str)
{size_t len strlen(str);if (_size len _capacity){reserve(_sizelen);//这里就不需要担心_capacity为0的情况}strcpy(_str _size, str);//strcpy会把\0也拷贝过去size len;
}4.4 operator
要有返回值返回*this
//追加一个字符复用push_back()
string operator(char ch)
{push_back(ch);return *this;
}//追加一个字符串复用append()
string operator(const char* str)
{append(str);return *this;
}4.5 insert
在pos位置插入字符
void 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] _str[end-1];--end;}_str[pos] ch;_size;
}注意上述代码挪动数据时的判断条件中end 和 pos 都是 sizt_t 类型例如当 pos 0 的时候 end posend–,一直减到end-1但是end是一个无符号整形所以循环条件一直成立还可以进入循环 所以下面有两种修改方式:
为了防止整型提升有以下两种写法
void insert(size_t pos, char ch)
{assert(pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}size_t end _size1;while (end pos){_str[end] _str[end-1];--end;}_str[pos] ch;_size;
}
——————————————————————————————————————————————————————————————————————————————————
void insert(size_t pos, char ch)
{assert(pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}int end _size;换成有符号也会报错因为操作符的两边两个数据的类型不同时会发生类型提升end变成有符号类型也会被提升到无符号类型因为pos是无符号类型所以可以强转pos-(int)poswhile (end (int) pos){_str[end] _str[end-1];--end;}_str[pos] ch;_size;
}在pos位置插入字符串
void 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 _sizelen;while (end poslen){_str[end] _str[end-len];--end;}strncpy(_str pos, str,len);_size len;}
}4.6 erase
void erase(size_t pos, size_t lennpos)
{assert(pos _size);if (len npos||poslen_size){_str[pos] \0;_size pos;}else{size_t begin pos len;while (begin _size){_str[begin - len] _str[begin];begin;}_size - len;}
}4.7 resize
size_t find(char ch,size_t pos0)//这里给了一个半缺省
{for (size_t i pos; i _size; i){if (_str[i] ch){return i;}}return npos;
}4.8 find
size_t find(const char* str, size_t pos 0)//半缺省
{const char* p strstr(_strpos, str);//strstr找到返回所在位置指针否则返回空if (p){return p - _str;//返回下标}else{return npos;}
}4.9 substr
这里就表明我们一定要手写一个拷贝构造编译器默认生成的拷贝构造是一个浅拷贝会发生调用两次析构的问题所以要手写一个深拷贝
string substr(size_t pos, size_t len npos)
{string s;size_t end pos len;if (len npos || pos len _size){len _size - pos;end _size;}s.reserve(len);for (size_t i pos; i pos len; i){s _str[i];}return s;//s是一个浅拷贝出了作用域s销毁
}五、string类运算符重载
5.1 operator !
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);
}5.2 operator
因为类函数有this指针传参数容易发生错误匹配原因和运算符重载要写在类外面 无论是形参还是返回值只要涉及到 ostream 或 istream 都必须要用引用
//有以下两种写法
ostream operator(ostream out,const string s)
{for (size_t i 0; i s.size(); i){out s[i];}return out;
}
_______________________________________________________________________
范围for(这里使用范围for要调用const迭代器)
ostream operator(ostream out,const string s)
{for (auto ch : s)//s是一个const对象要用const迭代器out ch;return out;
}5.3 operator
空格符 ’ ’ 和换行符 \n不能直接用 istream 对象来读取的in ch 是读不到空格符和换行符。需要借助 get() 成员函数才能读取到空格符和换行符。
istream operator(istream in, string s)
{s.clear();//清掉原始数据不然就变成尾插了char ch;//in ch;//拿不到空格或者换行例如sacnf拿不到空格所以出现了getcharch in.get();while (ch! ch!\n){s ch;ch in.get();}return in;
}对于上面这种写法在输入的字符串很长的情况下会多次调用 reserve 进行扩容所以可以采用下述优化版本来实现先开辟一个数组将输入的字符存储到数组中然后从数组中拷贝到string对象中数组出了作用域就会销毁
istream operator(istream in, string s)
{s.clear();//清掉数据不然就变成尾插了char buff[128];size_t i 0;char ch;ch in.get();while (ch! ch!\n){buff[i] ch;if (i 128){buff[i] \0;s buff;i 0;}}if (i ! 0){buff[i] \0;s buff;}return in;
}