当前位置:   article > 正文

【C++】STL-string模拟实现_gcc4.7 stl string实现

gcc4.7 stl string实现

驼峰法命名

1.函数,类名 单词首字母大写

  • 例如: PushBack
  1. 变量 第一个单词小写,后面的单词首字母大写
  • 例如: valueOver

在stl,linux源码中,所有都是小写,单词和单词之间用_分割


面试题:写一个简洁版的string

  • 1.考察的实现string的四个默认成员函数,深浅拷贝的问题
  • 2.可以写传统写法,也可以写现代写法

string成员变量

一个char*类型的指针


构造函数

注意:

  • 构造函数的缺省值不能为NULL,查文档也可得知无参时默认的参数时空串""
  • 我们要多开辟一个空间存放\0
//默认参数为空字符串,不能为NULL,不然strlen会报错! 要给空字符串
string(const char* str = "")
	:_str(new char[strlen(str)+1])   //要多开辟一个空间用来存放\0
{
    strcpy(_str, str);//\0也会拷贝过去
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

析构函数

~string()
{
    delete[] _str;//释放_str指向的空间  ->new[] 要和delete[]配合使用
    _str = nullptr;//指针置空
}
  • 1
  • 2
  • 3
  • 4
  • 5

拷贝构造函数

image-20220204120746823


传统写法

  • 为了减少拷贝,传引用
  • 思路:开辟和要拷贝的对象的字符串大小一样的空间 ->使用库函数strcpy拷贝字符串
//s2(s1)
string(const string& s)
	:_str(new char[strlen(s._str)]+1) //要多开辟一个空间用来存放\0
{
   strcpy(_str, s._str);//字符串拷贝
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

现代写法

  • 为了减少拷贝,传引用
  • 先构造一个临时对象,然后临时对象和this指向的对象 进行字符串交换
//现代写法
string(const string& s)
	:_str(nullptr)//要初始化_str,否则为随机值,交换后,tmp._str就指向随机的空间,析构的时候释放非法空间
{
    //调用的是构造函数创建tmp,这里的参数是字符串!不是对象
    //string tmp(s)  ->这样是拷贝构造,s是对象,而s._str才是对象的内容->字符串
    string tmp(s._str);
    swap(_str, tmp._str);//调用std域里面的swap交换函数,交换两个字符串
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意: string tmp(s) ->这样才是拷贝构造,s是对象,s._str是对象的内容-字符串, 如果用的是拷贝构造来构造tmp,就是错误的! 不断的复用拷贝构造,陷入死循环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6OZM9tJm-1671703499128)( https://mangoimage.oss-cn-guangzhou.aliyuncs.com/image-20220204162145388.png)]


获取C形式的字符串 c_str

返回类型:const char*

//获取C形式的字符串
const char* c_str() const	//函数后加const修饰,const的对象也能调用
{
    return _str;
}
  • 1
  • 2
  • 3
  • 4
  • 5

赋值重载 operator=

传统写法

  • 防止自己给自己赋值 比较地址是否一致
  • 减少拷贝->传引用
  • 为了可以连续赋值,所以要有返回值, 由于*this这块空间出了作用域不销毁,可以传引用返回 (this是局部变量指针,出了作用域就销毁了,是*this不销毁)
//case1:s1 = s2  case2:s1 = s1  case3:s1 = s2 = s3
string& operator=(const string& s)
{
    //防止自己给自己赋值 s1=s1,比较地址是否一致
    if (this != &s) 
    {
        delete[] _str;//先释放原来空间的内容
        _str = new char[strlen(s._str) + 1]; //多开辟一个空间保存\0
        strcpy(_str, s._str);
    }
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意:由于不会对对象s进行修改,所以参数可以传引用 + 用const修饰


现代写法

注意:此时的参数是传值,且不能加const修饰!

原因:要跟原对象进行交换,所以不能传引用,否则导致原对象改变! 也不能加const,否则不能被修改

// 现代写法
//s1 = s2 ->先用s2拷贝构造一个对象s,s和s2就有相同大小的空间和值,然后再拿s和s1交换
string& operator=(string s)
{
    swap(_str, s._str);//s是临时对象,出了作用域就销毁
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

由于s是临时对象,出了作用域自动调用析构函数销毁

如果这里是自己给自己赋值,会导致s1的本身空间地址发生改变,因为二者指向的空间发生了交换


这里调用swap函数,这里不仅需要引用#include<algorithm>函数,还需要指定类域 std::swap(tmp1,tmp2)

当然,我们也可以选择自己实现:

//注意!!这里要传引用!!,否则临时对象tmp析构,会导致交换后的空间也被释放
void swap(char*& str)
{
    char* tmp = str;
    str = _str;
    _str = tmp;
}
//函数内部调用swap只需传一个参数:
swap(tmp._str);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

简易版代码:

#include<string.h>
#include<algorithm>
namespace Mango
{
class string
{
public:
	string(const char* str = "")
		:_str(new char[strlen(str)+1])
	{
		strcpy(_str, str);
	}
	~string()
	{
		delete[] _str;
		_str = nullptr;
	}
	//传统写法拷贝构造
	string(const string& s)
	{
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}
	
    /*
	//现代写法的拷贝构造函数
	string(const string s)
	{ 
		_str = nullptr;
		string tmp(s._str);
		swap(tmp._str);
	}
	*/
	void swap(char*& str)
	{
		char* tmp = str;
		str = _str;
		_str = tmp;
	}
	const char* c_str() const
	{
		return _str;
	}
	string& operator=(const string& s)//为了支持连续赋值,所以返回引用
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str,s._str);
		}
		return *this;
	}
	
	//现代写法
	/*string& operator=(string s)
	{
		swap(s._str);
		return *this;
	}
	*/
private:
	char* _str;
};
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

string的改造 ->支持增删查改

接口总览

#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
//不包含上面的文件,下面会报错
namespace Mango
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");         //构造函数
		string(const string& s);              //拷贝构造函数
		string& operator=(const string& s);   //赋值运算符重载函数
		~string();                            //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size();
		size_t capacity();
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		bool empty()const;

		//修改字符串相关函数
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len);
		void clear();
		void swap(string& s);
		const char* c_str()const;

		//访问字符串相关函数
		char& operator[](size_t i);
		const char& operator[](size_t i)const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;
		size_t rfind(char ch, size_t pos = npos)const;
		size_t rfind(const char* str, size_t pos = 0)const;

		//关系运算符重载函数
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		size_t _capacity; //记录字符串当前的容量
		static const size_t npos; //静态成员变量(整型最大值)
	};
	//静态成员变量在类外初始化
	const size_t string::npos = -1;

	//<<和>>运算符重载函数
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
	istream& getline(istream& in, string& s);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

string :管理字符串的数组,可以增删查改 , 字符串数组的结尾有\0

string成员变量

private:
    char* _str;//指向动态开辟的数组
    size_t _size;//字符串有效字符个数
    size_t _capacity;//容量
  • 1
  • 2
  • 3
  • 4

构造函数

image-20220204164422890

\0不是有效字符是标识字符串结尾的字符

  • _capacity表示的时能存储有效字符的个数,多开辟的一个空间是给\0的
//默认参数为空字符串,不能为NULL,不然strlen会报错! 要给空字符串
string(const char* str = "")
{
    _size = strlen(str);//如果没传参数,默认str为空串,大小为0
    _capacity = _size;
    _str = new char[_capacity + 1];//多开辟一个空间给\0
    strcpy(_str, str);//字符串拷贝,\0也会被拷贝
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

交换

注意:先去局部域去找swap函数,所以要指定域, :: 域限定符 左边没有写,默认在全局域中找

//s1.swap(s2)
void swap(string& s)
{
    ::swap(_str, s._str);
    ::swap(_size, s._size);
    ::swap(_capacity, s._capacity);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

传引用是为了减少拷贝,因为传值传参会调用拷贝构造

image-20220204205108483

//C++98,c++11增加了右值引用的移动语义,优化了swap函数模板
::swap(s1,s2);//调用库的
s1.swap(s2);//自己写的 this->swap(s2)
  • 1
  • 2
  • 3

拷贝构造

容量和size都可以不初始化,但是_str要初始化为空指针,不然就是随机值!交换的时候就会指针非法访问!

注意:拷贝构造和赋值重载两个函数,如果我们不写,编译器会帮我们自动生成一份,对呀内置类型完成的时浅拷贝,对于自定义类型会调用它自己的拷贝构造函数,如果使用默认的拷贝构造就会出现问题:

  • s2(s1) 如果是浅拷贝,s2和s1指向同一块空间,更改s1的内容会导致s2也受影响,析构的时候,会对这块空间析构两次

image-20220614114354245

所以我们需要实现深拷贝,传统写法:动态开辟空间,把数据拷贝过来 或者现代写法:先构造一个对象,然后交换

传统写法:

//拷贝构造 s2(s1)
string(const string& s)
    :_size(strlen(s._str))//写法2:_size(s._size)
    ,_capacity(s._capacity)
    {
		_str = new char[_capacity+1];//多开辟一个空间存放\0
        strcpy(_str,s._str);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现代写法:

复用含参数的构造函数构造对象tmp,然后交换两个对象

image-20220614140724930

  • tmp是临时对象,出了作用域调用析构函数销毁, 如果tmp指向一个随机空间,析构的时候会崩溃
  • new/malloc出来的空间才能被释放,但是释放空指针没有问题free(nullptr);//没问题
//s2(s1)
string(const string& s)
	:_str(nullptr),_capacity(0),_size(0)   //要初始化_str,不然就是随机值
{
    string tmp(s._str);//先用s构造一个临时对象 注意:这里的参数是字符串!!!
    swap(tmp);  //this->swap(tmp)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

由于修改对象s,所以可以传引用!


赋值重载operator=

传统写法: 开辟新空间,把数据拷贝过来

同样的,如果我们不实现,默认生成的就是浅拷贝

image-20220614135212418


所以我们需要自己实现一份深拷贝,先释放旧空间,然后开辟新空间,把数据拷贝过来

  • 注意:要防止自己给自己赋值,如:s1=s1, 这样会导致错误,把原空间销毁了
  • 有可能是连续赋值,所以要有返回值,由于*this这块空间出了作用域不销毁,可以传引用返回
//可能存在Bug的代码
string& operator=(const string& s)
{
    if(this != &s)
    {
        delete[] _str;//先释放旧空间
        _str = new char[s._capacity+1];//开辟和s一样大小的空间
        strcpy(_str,s_str);
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上述可能存在的问题是什么?

new空间可能会失败,会直接抛出异常,而我们之前先把_str的空间释放了,所以就会导致_str变为野指针,所以可以稍微换一种写法

  • 先用临时变量接收这块空间的地址,然后拷贝内容到这块空间上,然后再处理
string& operator=(const string& s)
{
    if(this != &s)
    {
       char* tmp = new char[s._capacity+1];//为\0也要预留空间
       strcpy(tmp,s._str);
       delete[] _str;//释放原空间
       _str = tmp;
        
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

现代写法: 创建一个对象出来进行交换

  • 防止自己给自己赋值 比较地址是否一致
  • 由于*this这块空间出了作用域不销毁,可以传引用返回 (this是局部变量地址(对象的地址),出了作用域就销毁了,而*this是对象,出了作用域不销毁)
  • 此处是传值传参! 不可以传引用,否则会对原对象进行修改!
/*写法1:依旧传引用,但是在内部构造临时对象交换
		string& operator=(const string& s)
		{
			if(this != &s)
			{
                string tmp(s._str);
                swap(tmp);
			}
			return *this;
		}
*/
//写法2:传值,然后直接交换

string& operator=(string s)
{
    //防止自己给自己赋值
	if(this != &s)
    {
        swap(s); //this->swap(s)
    }
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

此时参数的s是局部变量,出了作用域会调用析构函数销毁

如果此时不防止自己给自己赋值是否可行?

此时虽然不会发生误销毁原空间的情况,但是会导致地址发生改变

image-20220614142126081


析构函数

~string()
{
    delete[] _str;// 释放数组空间
    _str = nullptr;//指针置空
    _size = 0;
    _capacity = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

返回元素个数

元素个数不可能为负数 所以返回类型为size_t

size_t size()  const
{
    return _size;
}
  • 1
  • 2
  • 3
  • 4

加const修饰函数,这样普通对象和const对象都能调用


返回容量

容量不可能为负数 所以返回类型为size_t

size_t capacity()  const
{
    return _capacity;
}
  • 1
  • 2
  • 3
  • 4

加const修饰函数,这样普通对象和const对象都能调用


遍历:

at()作用和operator[]类似 ,不同点是:at()越界抛异常,[]越界直接报错

operator[]重载

返回第i个位置的字符

普通对象 :可读可写

返回下标为i的字符的引用,出了作用域空间还在->用引用返回

//operator[]
//可读可写,所以返回引用
char& operator[](size_t i)
{
    assert(i < _size);//保证i的合法性
    return _str[i];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

const对象:只读

出了作用域空间还在->用引用返回

//只读
const char operator[](size_t i) const
{
    assert(i < _size);//保证i的合法性
    return _str[i];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

迭代器

分为const迭代器和普通迭代器, const对象调用函数,优先选择最匹配的const成员函数

变量定义:
public:
	typedef char* iterator;
	typedef const char* const_iterator;
  • 1
  • 2
  • 3
begin() 和end()
image-20220204210250639

begin() :_str位置 end() :_str+_size位置

//普通迭代器 - 普通对象调用
iterator begin()
{
    return _str;
}
iterator end()
{
    return _str+_size;
}

//const迭代器 -const对象调用
const_iterator begin() const
{
    return _str;
}
const_iterator end() const
{
    return _str + _size;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

有了迭代器自然就支持了范围for了


迭代器遍历对象

void test()
{
    string s1 = "hello world";
    string::iterator it = s1.begin();//调用string的迭代器
    while (it != s1.end())
    {
        cout << *it << " ";
        it++;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

范围for

看起来很神奇,但是原理很简单,范围for会被编译器替换成迭代器形式,也就是说范围for是有迭代器支持的

for(auto ch:s1)
{
    cout<<ch<<" ";//ch本质就是s的每一个字符
}
  • 1
  • 2
  • 3
  • 4

注意:迭代器的begin和end函数名要小写!不然使用范围for会报错


容量

容量为n,实际空间为n+1个,因为有一个空间是用来存放\0的, _size是有效字符个数,当_size == _capacity 空间就满了

reserve

开辟空间 ->只改变capacity 不改变size

  • 由于容量不为负数,所以参数类型:size_t
  • 先开辟n+1个空间,然后把原空间的内容拷贝到新空间,再释放原空间,指针指向新空间
//开空间 capacity变,size不变
void reserve(size_t n)
{
    //判断是否是扩容
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];//多开辟一个空间存放\0
        //strcpy(tmp, _str);//把原内容拷贝到新空间,使用strcpy可能存在问题我!
        //把_str的内容拷贝到tmp,共拷贝_size+1个字节
        strncpy(tmp,_str,_size+1);//+1是为了把原空间的最后位置的\0也拷贝
        delete[] _str;//释放原空间

        _str = tmp;//_str指向新空间
        _capacity = n;//容量改为n  _size不变
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注意:不能使用strcpy函数,因为如果有效字符包含\0,则会发生错误! 所以应该使用strncpy,直接根据有效字符的个数拷贝

image-20220205100519827


resize

开辟空间 ->改变capacity && 改变size

默认用\0初始化

image-20220204215826439


image-20220204220305210

if(n<_size)
{
    _size = n;//减少有效字符个数为n个
    _str[_size] = '\0';//提前终止
}
  • 1
  • 2
  • 3
  • 4
  • 5

针对case2和case3:可以写成一种情况: 都需要填充字符

//开空间+初始化 capacity和size都要改变
//默认填充字符为\0
void resize(size_t n, char val = '\0')
{
    //case1
    if (n < _size)
    {
        _size = n;//减少有效字符个数为n个
        _str[_size] = '\0';//提前终止
    }
    else
    {
        //判断是否是增容 -针对case3
        if (n > _capacity)
        {
            reserve(n);//扩容成n个空间,然后下面再填充数据
        }
        //case2和case3
        //填数据,从原字符串\0位置填,填到字符个数为n个
        for (size_t i = _size; i < n; i++)
        {
            _str[i] = val;
        }
        _str[n] = '\0';//最后在下标为n位置放\0
        _size = n;//字符个数变为n个
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

push_back

要注意最后要存放\0

//插入字符-尾插
void push_back(char ch)
{
    //先判断容量够不够
    if (_size == _capacity)
    {
        //两倍扩容
        //如果一开始构造的是空串,_capacity = 0,*2之后还是0,reserve(0),没有开辟空间,然后下面对_size位置访问->就是非法访问,err
       // reserve(_capacity * 2); ->err,
        reserve(_capacity==0?4:_capacity*2);
    }
    //插入字符 + 处理\0
    _str[_size] = ch;//在原来\0位置插入字符
    _str[_size + 1] = '\0';//在后面放\0
    _size++;//有效字符+1
    
    //上面三行代码也可以简写为	: _str[_size] = ch ,_str[++_size] = '\0'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

append
  • 不宜两倍扩容,因为两倍扩容也不知道够不够,直接把容量扩成插入之后的总长度
//尾插入字符串 -> 相当于在_size位置往后插入,可以复用insert函数
void append(const char* str)
{
    //计算插入之后总长度
    size_t len = _size + strlen(str);//原有长度+插入字符串长度
    //如果len=_capacity刚好能存放,有\0
    if (len > _capacity)
    {
        //扩容
        reserve(len);//复用reserve函数,reserve内部处理了\0,把\0也拷贝到新空间了
    }
    strcpy(_str + _size, str);//在原字符串末尾(\0位置)插入字符串,str中的\0也会被拷贝
    _size = len;//长度变为插入后总长度
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

image-20220204214834795

如果使用strcat(追加函数):需要找到原字符串\0位置,然后再插入,需要遍历前面的字符,效率低.

此处可以直接算出\0位置在哪里,用strcpy更高效


operator+=
插入字符

复用push_back函数

返回插入之后的对象,出了作用域还在!所以可以返回引用

//s1+='x'
string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

插入字符串

复用append函数

返回插入之后的对象,出了作用域还在!所以可以返回引用

//s1+="xxxx"
string& operator+=(const char* str)
{
    append(str);
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Insert
在pos位置插入一个字符
  • 检查pos位置的合法性
  • 检查容量够不够
  • 把pos位置及其后面的数据往后移动 //pos位置的字符也要往后移动
  • 在pos位置插入字符
  • 有效字符++
  • 返回插入之后的对象,由于出了作用域不销毁,返回引用

image-20220204224606685

Bug1: 若最初为空串:reserve(0) ,没有开辟空间出来,但是后序却访问了_str[pos]位置->非法访问


image-20220204224625822

//Insert
string& insert(size_t pos, char ch)
{
    //检查pos的合法性
    //可以在_size位置插入(原来字符串的\0位置)
    assert(pos<=_size);
    //先检查容量
    if (_size == _capacity)
    {
        reserve(_capacity==0?4:_capacity*2);
    }
    //解决办法1:使用指针
    /*
		char* end = _str + _size;//指向的是\0的位置
		//从后往前移动数据
		while (end >= _str + pos)
		{
			*(end + 1) = *end;
			--end;
		}
	*/
    /*解决办法2:强转pos的类型为int
		int end = _size;//最后一个有效字符的下一个位置,即\0位置
		while(end>=(int)pos)
		{
			_str[end+1]=_str[end];
			--end;
		}
	*/

    //解决办法3:让end和pos不能相等
    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];//从后往前拷贝
        --end;
    }
    _str[pos] = ch;//把字符插入到pos位置
    _size++;//数据个数+1
    //返回插入数据后的字符串
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

在pos位置之前插入字符串
  • 检查pos位置的合法性
  • 检查插入之后的容量够不够
  • 把pos位置及其后面的数据往后移动len个位置(len:插入字符串的长度)
  • 在pos位置开始往后拷贝字符串,注意:不能用strcpy!!! 因为strcpy会拷贝\0
  • 有效字符+len
  • 返回插入之后的对象,由于出了作用域不销毁,返回引用

移动数据时,需要注意上面insert的Bug2情况!

//在pos位置插入字符串
string& insert(size_t pos, const char* str)
{
    //保证pos位置的合法性
    //可以在_size位置插入,
    assert(pos <= _size);
    size_t len = strlen(str);

    //插入字符串之后容量是否够
    //如果len + _size = _capacity刚好能放满,因为_capacity多开了一个空间存放\0
    if (len + _size > _capacity)
    {
        //扩容 -开辟 原来字符串长度+插入字符串长度 个空间
        reserve(_size+len);
    }

    //挪动数据,从\0位置开始移动
    char* end = _str + _size;
    //pos位置的字符也要往后移动,尽管是在最后一个位置插入,也能保证插入后最后一个位置是\0,
    while (end >= _str + pos)
    {
        *(end + len) = *end;//每个字符往后移动len个位置
        --end;
    }
    //插入字符串
    //strcpy(_str+pos,str)//不能用strcpy,因为strcpy会把插入字符串的\0也拷贝过去,可能导致提前结束
    //这里我们要根据字节数拷贝
    strncpy(_str + pos, str, len);//从_str+pos位置开始把str拷贝过去,只拷贝str中的len个字符(不含\0)
    _size += len;//有效字符个数+len

    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

注意:插入字符串的时候使用strncpy,不能使用strcpy,否则会将待插入的字符串后面的’\0’也插入到字符串中,


简化push_back
void push_back(char ch)
{
    insert(_size,ch);//尾插一个字符 ->在_size位置插入一个字符
}
  • 1
  • 2
  • 3
  • 4

简化append
void append(const char* str)
{
    insert(_size,str);//尾插一个字符串 ->在_size位置插入一个字符串
}
  • 1
  • 2
  • 3
  • 4

erase

从pos位置开始删除len个字符

情况分析:

情况1:删除位置后面的字符个数 < 要删除的个数

image-20220613104925734


情况2:删除位置后面的字符个数 >= 要删除的个数

image-20220613105041352


//从pos位置开始删除len个字符
//len的缺省值为npos,size_t类型,其值为42亿多,字符串没有这么长
//如果不传len,即删除pos位置后面的所有字符
string& erase(size_t pos, size_t len = npos)
{
    //pos不能为\0位置,保证pos的合法性
    assert(pos < _size);
    size_t sz = _size - pos;//计算pos位置后面剩余的字符个数
    //case1:剩余的字符长度<=要删的长度 ->后面的全删完
    if (len >= sz) //len=npos的时候,也会进入这里
    {
        //把pos位置搞成\0,后面的直接忽略
        _str[pos] = '\0';
        _size = pos;//有效字符变成pos个
    }
    //case2:剩余的字符长度大于要删的长度
    else
    {
        //把_str+pos+len后面的字符往前覆盖到_str+pos位置
        strcpy(_str+pos, _str + pos + len);
        _size -= len;//删去了len个字符
    }
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

npos

静态成员变量->在类里面声明,在类外面定义赋值! 可以不加static

image-20220205003431795

find

查找一个字符

从pos位置开始查找字符,找到了返回下标,没有找到返回npos

//默认从0位置开始找
size_t find(char ch, size_t pos = 0)
{
    //不能在\0位置开始往后找
    assert(pos < _size);
    //从pos位置遍历查找
    for (size_t i = pos; i < _size; i++)
    {
        if (_str[i] == ch)
        {
            return i;
        }
    }
    //找不到
    return npos;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

查找一个字符串

从pos位置开始查找字符串,找到了返回下标,没有找到返回npos

  • 查找子串,复用strstr函数
//默认从0位置开始找
size_t find(const char* str, size_t pos = 0)
{
    //不能在\0位置开始往后找
    assert(pos < _size);
    //从pos位置开始找子串str首次出现的位置,找到了返回指针
    const char* ret = strstr(_str + pos, str);
    if (ret != nullptr)
    {
        //找到了,要返回下标 
        //指针-指针就是相距的距离 ==> 距离起始位置的下标
        return ret - _str;
    }
    else
    {
        //找不到
        return npos;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

rfind

和find基本一致,只不过是从后往前找

思路:

先把字符串进行逆序, 然后复用find的逻辑

image-20220205092024921

查找pos位置的字符/字符串 ->复用find函数,找逆置之后的字符串的pos'位置

注意:最后要返回的是pos位置


查找一个字符
  • 首先我们需要用对象拷贝构造一个临时对象tmp,因为我们并不希望调用rfind函数后原对象被修改

  • 将tmp对象的字符串逆置,然后将所给pos镜像对称一下再调用find函数

  • 将从find函数接收到的返回值镜像对称一下作为rfind函数的返回值返回即可,

//默认从npos位置开始找
//从后往前找
size_t rfind(char ch, size_t pos = npos) const
{
    string tmp(*this); //拷贝构造
    reverse(tmp.begin(), tmp.end()); //调用reverse逆置对象tmp的字符串
    //pos大于字符串有效长度
    if (pos >= _size) 
    {
        pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
    }
    pos = _size - 1 - pos; //将pos改为pos'位置
    size_t ret = tmp.find(ch, pos); //复用find函数
    if (ret != npos)
        return _size - 1 - ret; //找到了,返回pos位置
    else
        return npos; //没找到,返回npos
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

注:rfind函数规定,当所给的pos大于等于字符串的有效长度时,看作所给pos为字符串最后一个字符的下标,


查找一个字符串
  • 用对象拷贝构造一个临时对象tmp,然后将tmp对象的C字符串逆置,

  • 需要拷贝一份待查找的字符串,将其逆置,

  • 将所给pos对称一下,改为pos’位置再调用find函数,

注意:通过find函数得到pos'位置后,得到的是待查找字符串的最后一个字符在对象C字符串中的位置

而我们需要返回的是待查找字符串在对象C字符串中的第一个字符的位置

所以还需做进一步调整后才能作为rfind函数的返回值返回

//默认从npos位置开始找
size_t rfind(const char* str, size_t pos = npos) const
{
    string tmp(*this); //拷贝构造
    reverse(tmp.begin(), tmp.end()); //调用reverse逆置tmp对象的字符串
    size_t len = strlen(str); //待查找的字符串的长度
    char* arr = new char[len + 1]; //开辟arr字符串(用于拷贝str字符串),多给一个空间是给\0的
    strcpy(arr, str); //拷贝str给arr
    size_t left = 0, right = len - 1; //设置左右指针
    //逆置字符串arr
    while (left < right)
    {
        ::swap(arr[left], arr[right]);//使用的是std全局域的swap函数,
        left++;
        right--;
    }
    if (pos >= _size) //所给pos大于字符串有效长度
    {
        pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
    }
    pos = _size - 1 - pos; //将pos改为镜像对称后的位置pos'
    size_t ret = tmp.find(arr, pos); //复用find函数从pos'位置开始找字符串arr
    delete[] arr; //销毁arr指向的空间,避免内存泄漏
    if (ret != npos)
        return _size - ret - len; //找到了,返回ret镜像对称后再调整的位置
    else
        return npos; //没找到,返回npos
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

比较

比较操作只需写两个,其他的复用即可

比较函数可以重载为类的成员函数也可也重载为全局的函数, 重载为成员函数的好处是:不必借助友元就可以访问类的成员,如果写在全局,如果要访问类的私有/保护成员,就要在类种写成友元函数

//字符串比较定义为全局的比较好,看着不别扭,要注意定义的顺序,因为要复用函数!
//可以使用内联->直接在调用的地方展开,效率高
inline bool operator<(const string& s1, const string& s2) 
{
	return strcmp(s1.c_str(), s2.c_str()) <0;
}
inline bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str())== 0;
}
inline bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}
inline bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}
inline bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}
inline bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

如果我们想不使用strcmp函数,我们也可以自己遍历两个字符串比较字符

bool operator<(const string& s1, const string& s2)
{
	size_t i = 0;
	size_t j = 0;
	while (i < s1.size() && j < s2.size())
	{
		//挨个字符比较
		if (s1[i] < s2[j])
		{
			return true;
		}
		else if (s1[i] > s2[j])
		{
			return false;
		}
		else //s1[i] == s2[j],比较下一个字符
		{
			i++;
			j++;
		}
	}
	//有一个字符串遍历结束了 ||两个字符串都遍历结束了
	return s1.size() < s2.size() ? true : false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

如果写在类里面:只需要传一个参数,因为第一个参数默认是this指针

加const修饰函数,这样普通对象和const对象都能调用

//>运算符重载
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 strcmp(_str, s._str) < 0;
	return !(*this >= s);//复用>= (*this)->operator=(s) ,*this是string对象,this是地址
}
//<=运算符重载
bool operator<=(const string& s)const
{
    //return (*this < s) || (*this == s);
	return !(*this > s);//(*this)->operator>(s) ,*this是string对象,this是地址
}
//!=运算符重载
bool operator!=(const string& s)const
{
	return !(*this == s); //(*this)->operator==(s) ,*this是string对象,this是地址
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

clear()

清空已有内容

void clear()
{
    _size = 0;
    _str[0] = '\0';
}
  • 1
  • 2
  • 3
  • 4
  • 5

empty()

判断字符串是否为空

有空间但是没有数据 -> 也是空

#include<string>
int main()
{
	string s1;
	s1.reserve(10);//只开空间
	cout << s1.empty() << endl;//1
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

//判空
bool empty() 
{
    //判断方式1:return _size == 0; 
	return strcmp(_str, "") == 0;//判断字符串是否是空串
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:两个字符串相比较不能用 == ,要使用strcmp函数 也可以直接判断_size是不是0


流提取和流插入运算符重载

注意:下面两个函数重载的函数都是写在类外的

原因:如果写在类内部,则函数的第一个成员默认是this指针, 流对象和对象抢占左操作数的位置,调用的时候就会看起来很奇怪, 所以要重载成全局的函数

问:需要重载成友元函数吗?

看该函数是否需要访问私有成员

>>

重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入,输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到空格或是’\n’便停止读取

标准库里面也是先清空再写入的:

image-20220614151834118


err代码:

原因:如果原来对象的字符串中有内容,就会在原来的内容上增加,就不符合逻辑

>>会自动跳过空格和换行,所以不能使用>> ->使用get

//in是cin的别名,返回引用是为了支持连续赋值:  cin >> s1 >> s2
istream& operator>>(istream& in, string& s)
{
    //先读取一个字符
	char ch;
	in >> ch;
	//读取字符插入,读取到空格或是’\n’便停止读取,
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		in >> ch;//继续读取下一个字符
	}
	return in;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

正确写法: cin.get()可以用来接收字符 要清空原有内容

cin是流对象,get是它的方法

istream& operator>>(istream& in, string& s)
{
	//先清空已有内容,否则会出错
	s.clear();
	char ch;
	ch = in.get();//类似getchar(),读取一个字符
	//读取字符插入,读取到空格或是’\n’便停止读取
    //注意:用的是&&  遇到空格或者\n都停止!!!
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();//继续读取下一个字符
	}
	return in;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

<<

重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印,实现时我们可以直接使用范围for对对象进行遍历即可,

注意:s对象不修改!所以可以传引用和const修饰 out就是cout的别名,为了可以输出多个对象(支持连续输出),所以要返回out的引用

//out是cout的别名,返回引用是为了支持连续输出: cout << s1<<s2<<endl;
ostream& operator<<(ostream& out, const string& s)
{ 
    for (auto ch : s) 
    {
        out << ch;
    }
    /*//我们关注的是size,而不是\0
    for(size_t i = 0;i<s.size();i++)
    {
    	out << s[i];
    }
    */
    return out;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注意:不能直接使用out<<s.c_str()<<endl; 或者 out << s._str<<endl;的方式打印

我们可以使用标准库的string进行验证:

因为以字符串形式输出,关注的是\0,而我们输出关注的是_size,

image-20220614151544143


getline

getline函数用于读取一行含有空格的字符串,实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符,

istream& getline(istream& in, string& s)
{
	//先清空已有内容
	s.clear();
	char ch;
	ch = in.get();//类似getchar(),读取一个字符
	//读取字符插入,读取到空格或是’\n’便停止读取,
	while (ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

总结

string是一个管理字符数组的类,要求这个字符数组结尾用\0标识

1.拷贝构造和赋值重载实现深拷贝

2.增删查找的相关接口(跟顺序表类似)

3.重载了一些常见的运算符 如: > < >> << []

4.迭代器


image-20220205110734833


image-20220205110749293


具体要不要加const要看接口的功能性质

1.只读接口函数 -> 加const

2.只写接口函数 ->不加const

3.可读可写接口函数 ->分为加const版本和不加const版本


关于string的深入讨论

C++标准只规定了string要实现的接口功能,具体如何实现,看各个库的实现人自己决定的

  • 1.vs系列的编译器,由微软工程师实现
  • 2.gcc/g++GNU的c & c++编译器 由开源组织实现的
  • 3.Clang编译器,Clang是一个C语言,C++,Objective-C语言的轻量级编译器

虽然它们的结构不一样,实现也就不一样,但是整体增删查改的逻辑大同小异

image-20220205112503889

关于string的深浅拷贝

image-20220205112751808

计数为1时,才是独享这块空间!


vs下的深拷贝:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mS46X4kU-1671703499146)( https://mangoimage.oss-cn-guangzhou.aliyuncs.com/image-20220205112840504.png)]

一开始两个对象占据的是不同一块空间, 二者不相影响


Linux版本下的深拷贝 ->写时拷贝

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vcf54Isi-1671703499146)( https://mangoimage.oss-cn-guangzhou.aliyuncs.com/image-20220205112902496.png)]

一开始二者占据的是同一块空间, 更改某一个对象时,二者的地址发生改变.


string支持一个const char*的构造函数

Mango::string s2 = "hello";//先构造一个临时对象"hello",然后拿这个临时对象拷贝构造s2, 连续的构造,会被编译器优化,直接用"hello"构造s2
  • 1

如果不想发生这种隐式类型转化:加explicit关键字

//构造函数
explicit string(const char* str = "")
{
    _size = strlen(str);//如果没传参数,str为空串,大小为0
    _capacity = _size;
    _str = new char[_capacity + 1];//多开辟一个空间给\0
    strcpy(_str, str);//字符串拷贝,\0也会被拷贝
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

String.h

  • 为了防止命名冲突 ->写在自己定义的命名空间内
  • 整个模拟实现的代码都是写在.h文件下的, 不可以.h放声明, .cpp放定义 因为模板不支持分离编译
  • string标准库中提供了一个swap交换函数,而全局std域也提供了一个swap的模板函数, 二者都是对两个对象的成员进行交换
    • 而string类中的swap仅仅是对3个成员变量进行交换,效率更高
    • 如果选择全局域std中的swap,则要进行3次深拷贝(拷贝构造+赋值重载)
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
#pragma warning(disable:26495)
namespace Mango
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];//为\0开辟空间
			strcpy(_str, str);
		}

		//拷贝构造函数
		//s2(s1)
		//现代写法:
		/*
		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(tmp);
		}
		*/
		
		//传统写法:
		string(const string& s)
		{
			_size = s._size;
			_capacity = s._capacity;
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}

		//赋值运算符重载函数
		//s1 =s2
		// 
		//传统写法:
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;
				_size = s._size;
				_capacity = s._capacity;
				_str = new char[_capacity + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}
		//现代写法:
		/*
		string& operator=(const string& s)
		{
			if(this != &s)
			{
				string tmp(s._str);//调用构造函数,构造临时对象
				swap(tmp);
			}
			return *this;
		}
		*/
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		//迭代器相关函数
		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()
		{
			return _size;
		}
		size_t capacity()
		{
			return _capacity;
		}
		//开空间 ->只修改_capacity 不修改_size
		void reserve(size_t n)
		{
			//增容才处理
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//多开一个空间给\0
				//strcpy(tmp, _str);//不建议使用strcpy,建议使用strncpy按字节拷贝
				strncpy(tmp, _str, _size + 1);
				_capacity = n;
				_str = tmp;
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			//根据n的大小决定怎么调整
			if (n < _size)
			{
				_str[n] = '\0';//直接缩小到n位置
				_size = n;
			}
			else
			{
				//需要填充字符
				if (n > _capacity)
				{
					reserve(n);
				}
				//从_size位置填充到n位置
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
				_size = n;
			}
		}
		bool empty() const
		{
			return _size == 0;
		}

		//修改字符串相关函数
		void push_back(char ch)
		{
			//判断是否需要增容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			//插入字符 + 处理\0
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = _size + strlen(str);//插入后的总长度
			if (len > _capacity)
			{
				reserve(len);//扩容成len个空间
			}
			//在尾部追加
			strcpy(_str + _size, str);
			_size = len;
		}
		string& operator+=(char ch)
		{
			//insert(_size,ch);
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			//insert(_size,str);
			append(str);
			return *this;
		}
		string& insert(size_t pos, char ch)
		{
			assert(pos <=_size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			//[pos,_size]的字符往后移动,\0也后移
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end+1) = *end ;
				end--;
			}
			//插入到pos位置 
			_str[pos] = ch;
			_size++;
			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len+_size > _capacity)
			{
				reserve(len + _size);
			}
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				//所有字符往后挪len个长度
				*(end + len) = *end;
				end--;
			}
			//在pos位置插入该字符串 -> strcpy
			strncpy(_str + pos, str, len);//从_str+pos位置开始把str拷贝过去,只拷贝len个字符(不含\0)
			_size += len;//修改字符串长度
			return *this;
		}
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			size_t sz = _size - len;//剩余字符个数
			//从pos位置要删除的长度>剩余字符个数
			if (len >= sz)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				//把_str + _pos+len后面的字符往前拷贝覆盖
				strcpy(_str + pos, _str + pos + len);
			}
			return *this;
		}
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		const char* c_str()const
		{
			return _str;
		}

		//访问字符串相关函数
		char& operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}
		const char& operator[](size_t i)const
		{
			assert(i < _size);
			return _str[i];
		}
		size_t find(char ch, size_t pos = 0)const
		{
			assert(pos < _size);
			//遍历查找
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		//找字符串
		size_t find(const char* str, size_t pos = 0)const
		{
			assert(pos < _size);
			const char* ret = strstr(_str+pos, str);
			if (ret)
			{
				return ret - _str;
			}
			else
			{
				return npos;
			}
		}
		//默认从npos位置开始找
		//从后往前找
		size_t rfind(char ch, size_t pos = npos) const
		{
			string tmp(*this); //拷贝构造
			reverse(tmp.begin(), tmp.end()); //调用reverse逆置对象tmp的字符串
			//pos大于字符串有效长度
			if (pos >= _size)
			{
				pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
			}
			pos = _size - 1 - pos; //将pos改为pos'位置
			size_t ret = tmp.find(ch, pos); //复用find函数
			if (ret != npos)
				return _size - 1 - ret; //找到了,返回pos位置
			else
				return npos; //没找到,返回npos
		}
		//默认从npos位置开始找
		size_t rfind(const char* str, size_t pos = npos) const
		{
			string tmp(*this); //拷贝构造
			reverse(tmp.begin(), tmp.end()); //调用reverse逆置tmp对象的字符串
			size_t len = strlen(str); //待查找的字符串的长度
			char* arr = new char[len + 1]; //开辟arr字符串(用于拷贝str字符串),多给一个空间是给\0的
			strcpy(arr, str); //拷贝str给arr
			size_t left = 0, right = len - 1; //设置左右指针
			//逆置字符串arr
			while (left < right)
			{
				::swap(arr[left], arr[right]);//使用的是std全局域的swap函数,
				left++;
				right--;
			}
				if (pos >= _size) //所给pos大于字符串有效长度
				{
					pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
				}
			pos = _size - 1 - pos; //将pos改为镜像对称后的位置pos'
			size_t ret = tmp.find(arr, pos); //复用find函数从pos'位置开始找字符串arr
			delete[] arr; //销毁arr指向的空间,避免内存泄漏
			if (ret != npos)
				return _size - ret - len; //找到了,返回ret镜像对称后再调整的位置
			else
				return npos; //没找到,返回npos
		}

		//关系运算符重载函数
		bool operator>(const string& s)const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator>=(const string& s)const
		{
			//*this就是string对象,两个string对象比较
			return ((*this) > s) || ((*this) == s);
		}
		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 strcmp(s._str, _str) == 0;
		}
		bool operator!=(const string& s)const
		{
			return !((*this) == s);
		}
	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		size_t _capacity; //记录字符串当前的容量
		static const size_t npos; //静态成员变量(整型最大值)
	};
	//静态成员变量在类外初始化
	const size_t string::npos = -1;

	//<<和>>运算符重载函数
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();//先读取一个字符
		while (ch != '\n' && ch != ' ')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		//范围for遍历输出即可,本质是迭代器
		for (auto ch : s)
		{
			out << ch ;
		}
		/*
		for(int i = 0;i<_size;i++)
		{
			cout << s[i];
		}
		*/
		return out;
	}
	istream& getline(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();//先读取一个字符
		while (ch != '\n' )
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}
	//测试反向查找
	void testString8()
	{
		string tmp("Mango Hello world");
		cout << tmp.rfind('e') << endl;
		cout << tmp.rfind("He") << endl;
	}
	//测试现代写法的成员函数
	void testString1()
	{
		string s0;
		string s1("Always");
		string s2(s1);
		cout << s2.c_str() << endl;
		string s3("more than words");
		s3 = s1;
		cout << s3.c_str() << endl;
		s3 = s3;
	}
	//测试流插入流提取运算符重载
	void testString7()
	{
		//string s;
		string s("Hello Mango");
		cin >> s;
		cout << s << endl;
		// 不能以字符串形式输出,测试标准库
		string s1("more than");
		s1 += '\0';
		s1 += "words";
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	}
	// 测试比较大小运算符重载
	void testString6()
	{
		string s1("abcd");
		string s2("abcd");
		cout << (s1 <= s2) << endl;
		string s3("abcd");
		string s4("abcde");
		cout << (s3 <= s4) << endl;
		string s5("abcde");
		string s6("abcd");
		cout << (s5 <= s6) << endl;
	}
	// 测试insert和erase
	void testString5()
	{
		string s(" Mango Hello");
		s.insert(0, "Lemon");
		cout << s.c_str() << endl;
		s.insert(5, '!');
		cout << s.c_str() << endl;
		s.erase(0, 7);
		cout << s.c_str() << endl;
		s.erase(6);
		cout << s.c_str() << endl;
	}
	// 测试查找
	void testString4()
	{
		string s("Mango");
		cout << s.find('m') << endl;
		cout << s.find("max") << endl;
	}
	// 测试resize
	void testString3()
	{
		string s("Mango"); // capacity - 12
		s.resize(5);
		cout << s.c_str() << endl;
		s.resize(7, '!');
		cout << s.c_str() << endl;
		s.resize(20, '~');
		cout << s.c_str() << endl;
	}
	// 测试尾插字符及字符串push_back/append,同时测试reserve
	void testString2()
	{
		string s("more than words");
		s.push_back('~');
		s.push_back(' ');
		cout << s.c_str() << endl;
		s.append("zhabuduodele");
		cout << s.c_str() << endl;
		s += '~';
		s += "yiyandingzhen";
		cout << s.c_str() << endl;
	}
}
/*
bool operator<(const string& s1, const string& s2)
{
	size_t i = 0;
	size_t j = 0;
	while (i < s1.size() && j < s2.size())
	{
		//挨个字符比较
		if (s1[i] < s2[j])
		{
			return true;
		}
		else if (s1[i] > s2[j])
		{
			return false;
		}
		else //s1[i] == s2[j],比较下一个字符
		{
			i++;
			j++;
		}
	}
	//有一个字符串遍历结束了 ||两个字符串都遍历结束了
	return s1.size() < s2.size() ? true : false;
}
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/486152
推荐阅读
相关标签
  

闽ICP备14008679号