文章目录
1.实现最简单mystring类
class mystring{
public:
//构造函数
mystring(const char* str)
//初始化列表
:_str(new char[strlen(str)+1])
{
strcpy(_str,str);
}
/*
拷贝构造函数
拷贝构造必须传引用;因为传参是拷贝构造,拷贝构造需要传参。。。
如果不传引用,最后会发生死递归。
*/
mystring(const mystring&s)
//初始列表
:_str(new char[strlen(s._str)+1])
{
strcpy(_str,s._str);
}
const char* c_str()const
{
return _str;
}
char& operator[](int pos){
return _str[pos];
}
//析构函数
~mystring(){
if(_str){
delete[] _str;
}
}
int size(){
return strlen(_str);
}
//重载=
mystring& operator=(const mystring&s){
/*
如果两个mystring类相等,那么两个指针指向的位置为同一位置
如果执行delete[] _str;那么就会删除掉自己的内容
如果再进行拷贝,就会拷贝随机值;因此mystring类相等的情况需要额外处理
*/
if(&s==this){ //if(s._str==_str)
return *this;
}
/*
由于两个mystring的空间大小不同,为了防止空间不够或者空间过大造成浪费
考虑先删除原来的空间
*/
delete[] _str;
char* tmp=new char[strlen(s._str)+1];
strcpy(tmp,s._str);
_str=tmp;
return *this;
}
private:
char* _str;
};
测试
int main(){
mystring s1("hello world");
mystring s2("hello c++");
mystring s3(s2);
mystring s4("hello ");
s4=s2;
cout<<s1.c_str()<<endl;
cout<<s2.c_str()<<endl;
cout<<s3.c_str()<<endl;
cout<<s4.c_str()<<endl;
cout<<s1.size()<<endl;
return 0;
}
输出:
1.1.深浅拷贝
浅拷贝问题:指针指向堆区同一位置;
浅拷贝会发生的问题:(1).同一块空间析构两次,发生错误;(2)指针指向同一位置,改变其中一个,另外一个也会该边。
比如mystring类的拷贝构造函数只是将指针赋值
mystring(const mystring&s)
//初始化列表
:_str(s._str)
{}
int main(){
if(1)
{
mystring s1("hello world");
mystring s2(s1);
}
}
error:析构了两次空间
2.对mystring类进行完善
class mystring{
//注意:初始化列表进行初始化的顺序为:成员变量在类中声明的顺序
public:
/*
构造函数
全缺省:
如果mystring s1;那么就创建了一个空的字符串,切记不是nullptr
在字符串中:"\0"---->\0\0 ""----->\0 '\0'---->对应ASCII码0,因此这里的缺省值为""
*/
mystring(const char* str="")
//初始化列表
:_size(strlen(_str)),
capacity(_size) //capacity为存储有效字符的大小
{ _str=new char[capacity+1]; //需要预留\0的位置
strcpy(_str,str);
}
mystring(const mystring&s)
//初始列表
:size(strlen(s._str)),
capacity(_size)
{
_str=new char[capacity+1]
strcpy(_str,s._str);
}
const char* c_str()const
{
return _str;
}
const char& operator[](int pos) const
{
return _str[pos];
}
char& operator[](int pos){
return _str[pos];
}
//析构函数
~mystring(){
if(_str){
delete[] _str;
}
}
int size() const
{
return _size;
}
int capacity() const
{
return _capacity;
}
//重载=
mystring& operator=(const mystring&s){
if(&s==this){ //if(s._str==_str)
return *this;
}
delete[] _str;
char* tmp=new char[strlen(s._str)+1];
strcpy(tmp,s._str);
_str=tmp;
return *this;
}
private:
char* _str;
int _size; //有效字符的个数
int _capacity; //存储有效字符的空间大小
const static int npos=-1;//c++的语法糖式
};
mystring:: const static npos=-1;
测试
int main(){
mystring s1("hello world");
mystring s2("hello c++");
mystring s3(s2);
mystring s4("hello ");
s4=s2;
cout<<s1.c_str()<<endl;
cout<<s2.c_str()<<endl;
cout<<s3.c_str()<<endl;
cout<<s4.c_str()<<endl;
cout<<s1.size()<<endl;
return 0;
}
3.增删查改
3.1resize和reserve
reserve(int n)的作用:
如果n比mystring的容量大,就对mystring进行扩容
如果n比mystring的容量小,不做处理
resize(int n,char ch=‘\0’)的作用:
- 如果n>mystring的容量,扩容并且将剩余的空间初始化为ch
- 如果n<mystring的容量
- 如果n>_size:将有效字符的个数变为n,并且初始化为ch
- 如果n<_size:将有效字符的个数变为n
void reserve(size_t n){
if(n>_capacity){
char* tmp=new char[n+1];
strcpy(tmp,s_tr);
delete[] _str;
_str=tmp;
_capacity=n;
}
}
void resize(size_t n,char ch='\0'){
if(n<_size){
_size=n;
_str[n]='\0';
}
else{
if(n>_capacity){
reserve(n);
}
for(int i=_size;i<n;i++){
_str[i]=ch;
}
_str[n]='\0';
_size=n;
}
}
3.2重载push_back
void push_back(char ch){
if(_size==_capacity){
reverse(_capacity==0?4:2*_capacity);
}
_str[_size]=ch;
_size++;
_str[_size]='\0';
}
3.3.重载append
void append(const char*str){
if(_size+strlen(str)>_capacity){
reverse(_size+strlen(str));
}
//拷贝
strcpy(_str+_size,str);
_size=_size+strlne(str);
}
3.4重载+=
+=有两种情况:
- 一种是加上一个单独的字符: s1+=‘a’
- 二是加上一个字符串:s1+=“hello c++”
mystring& operator+=(char ch){
push_back(ch);
return *this;
}
mystring& operator+=(const char*str){
append(str);
return *this;
}
3.5迭代器模拟
//迭代器的模拟实现,string类的迭代器本质上是原生指针
iterator begin() {
return _str;
}
iterator end() {
return _str + _size;
}
const_iterator begin()const //这里的const修饰this指针
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
3.6范围for原理
底层是封装的迭代器,需要成员函数begin()和end()函数支持。
int main() {
mystring s1("hello world");
mystring s2("welcome c++");
for (auto i : s1) {
cout<<i<<" "
}
cout << endl;
}
//如果没有成员函数begin()和end(),那么编译无法通过。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lCmUz8IL-1656869748091)(https://s2.loli.net/2022/07/03/COUIbEWRVvwAnTt.png)]
3.7insert随机位置插入
//需要将\0一并移动过去
//pos的类型是无符号型的,如果写出end=_size
string& insert(size_t pos,constychar ch_) {
assert(pos < _size);
int end = _size+1;
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
/*
此时的结束条件为end>=pos
如果pos为0,即实现头插,那么结束时end的大小为-1
-1和无符号类型的整数进行比较时,-1会自动转换为无符号类型的整数
就会出现死循环
*/
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size + +;
return *this;
}
改进
mystring& insert(size_t pos,char ch_) {
assert(pos < _size);
//_size的位置是\0
int end = _size+1;
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size + +;
return *this;
}
mystring& insert(size_t pos char* str) {
int len = strlen(str);
assert(pos + len < _size);
if (_size + len > _capacity) {
reverse(_size + len);
}
int end = _size + len;
while (end > pos + len) {
_str[end] = _str[end - len];
end--;
}
//插入
int i = 0;
strncpy(_str + len, str,len);
_size += len;
return *this;
}
3.8erase随机位置删除
void erase(size_t pos,size_t len=npos){
if(len==npos||pos+len>=_size){
_str[pos]='\0';
_size=pos;
}
else{
int start=pos+len;
while(start<=_size){
_str[pos++]=_str[start++];
}
_size=pos;
}
}
3.9 find查找函数
size_t find(char ch,size_t pos=0){
for(;pos<_size;pos++){
if(_str[pos]==ch){
return pos;
}
}
return npos;
}
//strstr算法
//BM算法
//kmp算法
size_t find(const char*str,size_t pos){
const char* p=strstr(_str,str);
if(p==nullptr)
{
return npos;
}
return p-_str;
}
3.101输入输出流重载
ostream& operator<<(ostream& os, mystring& s) {
os << s.c_str() << endl;
return os;
}
/*
上面写法的缺点是如果s中存在多个\0,那么就无法打印所有的元素
*/
ostream& operator<<(ostream&os,mystring&s){
for(auto ch:s){
os<<ch;
}
os<<endl;
}
istream&operator>>(istream&in, mystring& s) {
//在流插入的过程中,遇见空格' '或者\0就停止
//一开始往缓冲区写,遇见空格和\0再从缓冲区读取数据
char buf[256] = {'\0'};
char ch = in.get();
int i = 0;
while (ch != ' ' && ch != '\n') {
buf[i++] = ch;
//需要为\0留一个位置
if (i == 255) {
i = 0;
s += buf;
memset(buf, '\0', sizeof(buf));
}
}
}
3.10getline()
//getline()和cin的区别是getline遇见空格不会停下,遇见\n才会停下。
istream& getline(istream& in, mystring& str) {
char buf[256] = { '\0' };
char ch = in.get();
int i = 0;
while (ch != '\n') {
buf[i++] = ch;
if (i == 256) {
i = 0;
str += buf;
memset(buf, '\0', sizeof(buf));
}
ch = in.get();
}
str += buf;
return in;
}
3.11比较运算符重载
bool operator<(mystring& s1, mystring& s2) {
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(mystring& s1, mystring& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(mystring& s1, mystring& s2) {
return s1 < s2 || s1 == s2;
}
bool operator>(mystring& s1, mystring& s2)
{
return !(s1 <= s2);
}
bool operator>=(mystring& s1, mystring& s2)
{
return !(s1 < s2);
}
4.string类模拟的现代写法
void swap(mystring&str){
std::swap(_str,str._str);
std::swap(_size,str._size);
std::swap(_capacity,str._capacity);
}
4.1拷贝构造的现代写法
mystring(const mystring&str)
//拷贝构造必须先对自己的区域的资源解析处理;
:_str(nullptr),_size(0),_capacity(0)
{
mystring tmp(str.c_str());
swap(tmp);
}
4.2赋值的现代写法
//这里的s是传值拷贝,属于临时变量,出了作用域就被销毁
mystring& operator=(mystring s) {
swap(s);
return *this;
}