string类02

string类02

string类OJ
演示一下getline的作用体现在哪里
01
  • 因为,如果使用cin进行输入的话,cin在遇到空格的时候就会停止输入,空格之后的内容就会作为下一次输入的东西,所以我们在进行有空格的字符串的输入的时候,我们需要使用getline来进行输入,getline在遇到空格的时候,是不会停下来的
    在这里插入图片描述
  • 如果一行有多个字符串,多个字符串用空格隔开,现在要求,一次性将一行全部接收,那么,我们现在就只能使用getline来进行输入,不能使用cin,因为cin一旦遇到空格或者回车,就会停止接收,会将后续的内容作为下次的输入
#include<iostream>
#include<string>
using namespace std;
int main()
{
    string str;
    getline(cin,str);
    int count=0;
    int len=str.length();
    for(int i=len-1;i>=0;--i)
    {
        if(str[i]!=' ')
            count++;
        else
            break;
    }
    cout<<count<<endl;
}
#include<iostream>
#include<string>
using namespace std;
int main()
{
    string s;
    while(getline(cin,s))
    {
        cout<<s.substr(s.rfind(' ')+1).size();
    }
}
02

在这里插入图片描述

class Solution {
public:
    string reverseOnlyLetters(string S) 
    {
        int begin = 0,end=S.length()-1;
        while(begin<end)
        {
            if(!isalpha(S[begin]))
                begin++;
            if(!isalpha(S[end])) 
                end--;
            if(isalpha(S[begin])&&isalpha(S[end]))
                swap(S[begin++],S[end--]);
        }
        return S; 
    }
};
03

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int firstUniqChar(string s) 
    {
        unordered_map<int,int> mp;
        for(auto i : s)
        {
            mp[i]++;
        }
        for(int i=0;i<s.size();i++)
        {
            if(mp[s[i]]==1)
                return i;
        }
        return -1;
    }
};
04

在这里插入图片描述

class Solution {
public:
    bool isPalindrome(string s) 
    {
        //需要注意一下,空的字符串也是回文字符串
        if(s.empty())
            return true;
        //因为,所给出的字符串有可能有大写的字母,也可能有小写的字母
        //那么,我们现在需要把他们统一一下,也就是说,我们需要把大写字母转化成
        //小写字母,或者说把小写的字母转换成大写的字母,从而再去进行回文的判断
        for(auto &ch : s)
        {
            if(ch>='A'&&ch<='Z')
                ch+=32;
        }
        int begin=0;
        int end=s.size()-1;
        while(begin<end)
        {
            //begin从前往后找字母或者数字
            while(begin<end)
            {
                if(isalnum(s[begin]))
                {
                    break;    
                }
                begin++;
            }
            //end从后向前寻找
            while(begin<end)
            {
                if(isalnum(s[end]))
                {
                    break;    
                }
                end--;
            }
            if(s[begin]!=s[end])
                return false;
            else
            {
                begin++;
                end--;
            }
        }
        return true;
    }
};
05

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    string addStrings(string num1, string num2) {
        int i = num1.length() - 1, j = num2.length() - 1, add = 0;
        string ans = "";
        while (i >= 0 || j >= 0 || add != 0) {
            int x = i >= 0 ? num1[i] - '0' : 0;
            int y = j >= 0 ? num2[j] - '0' : 0;
            int result = x + y + add;
            ans.push_back('0' + result % 10);
            add = result / 10;
            i -= 1;
            j -= 1;
        }
        // 计算完以后的答案需要翻转过来
        reverse(ans.begin(), ans.end());
        return ans;
    }
};
简单模拟首先string类
  • 为了防止我们自己模拟的string类和编译器里面自己所带的string类冲突,所以我们把自己所模拟的string类放在bite这个命名空间的下面,为了防止引起冲突
    在这里插入图片描述
    在这里插入图片描述
  • 其实也可以把string类理解成一个动态的顺序表,所以底层肯定是需要有一段空间来进行支持的
#include<iostream>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		//其实下面的这个构造函数是合并了两种构造之后的结果,一种是没有参数的构造
		//另一种是有参数的构造函数,所以把这两种情况其实是合并在一起了
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char;

				//空的字符串只包含了一个\0
				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}


	private:
		char* _str;   //让字符指针接收用户所提供过来的字符串
	};
}
int main()
{
	string s1;
	string s2("1111");	
	string s3(nullptr);

	return 0;
}
  • 先看一下上面所给出的string类的构造函数和析构函数有没有问题,其实,上面所给出的构造函数和析构函数是存在问题的,因为之前说过,new和delete要匹配起来使用,才可以,上面有进行单个空间的申请,也有进行连续空间的申请,但是在析构的时候,我们却同意使用的是连续空间的释放方式,所以说,上面的代码其实是有问题的
  • 那么其实最方便的修改方式,就是我们把单个空间的申请方式也改成连续空间的申请方式,只不过是我们给成一个字符的连续申请就可以达到我们的目的了,就可以匹配起来使用了
//模拟实现string类
#include<iostream>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char[1];

				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}


	private:
		char* _str;
	};
}
int main()
{
	string s1;
	string s2("1111");
	string s3(nullptr);

	return 0;
}
  • 其实,还有像下面这种方式更为简单的方式,就是在一开始就给他一个有效的空间就可以了
    在这里插入图片描述
//模拟实现string类
#include<iostream>
#include<string>
namespace bite
{
	class string
	{
		//为了防止和标注库中的内容引起冲突,我们采用bite命名空间
	public:
		//首先给出string类的构造
		/*string()
		{

		}

		string(const char* str)
		{
			if (str == "")
				_str = nullptr;
		}*/

		上面的两种方式是可以合并的
		//string(const char* str = "")
		//{
		//	if (str == nullptr)
		//		//我们就去构造一个空的字符串
		//		//也是一个有效的字符串,只不过是字符串中只包含一个\0
		//	{
		//		_str = new char[1];
		//		*_str = '\0';
		//	}
		//	else
		//	{
		//		//如果传递过来的东西不是空字符串的话
		//		_str = new char[strlen(str) + 1];
		//		strcpy(_str, str);
		//	}
		//}

		//当然,还有简单的构造方式
		string(const char* str)
		{
			if (nullptr == str)
			{
				str = "";
			}
			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
		}

		//给出析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}

	private:
		//要实现string类,首先需要给出一个指针,指向我所要存放字符串的那一块空间
		char* _str;
	};

}
int main()
{
	return 0;
}
  • 像下面这样写代码的话,s1和s2就可以正确的构造和析构出来了,没有任何的问题
//模拟实现string类
#include<iostream>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char[1];

				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}


	private:
		char* _str;
	};
}
void TestString()
{
	bite::string s1;  //我们先去构造一个空的字符串
	bite::string s2("hello");

}
int main()
{
	TestString();

	return 0;
}
  • 现在,再来看一下拷贝构造函数,如果代码写成下面这个样子的话,其实是有问题的,最终的结果会造成代码的的崩溃
#include<iostream>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char[1];

				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}


	private:
		char* _str;
	};
}
void TestString()
{
	bite::string s1;  //我们先去构造一个空的字符串
	bite::string s2("hello");
	bite::string s3(s2);

}
int main()
{
	TestString();

	return 0;
}
  • 造成崩溃的原因如下所示:
  • 说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s2构造s3时,编译器会调用默认的拷贝构造。最终导致的问题是,s3、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝
  • 浅拷贝就是原封不动的进行拷贝的操作
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 相当于是同一块空间被释放两次,所以最终会造成代码崩溃的问题出现
    在这里插入图片描述
  • 在有些情况下,我们可能还需要用一个对象给另一个对象进行赋值的操作,那么,如果用户没有显示给出赋值运算符的重载的话,那么其实赋值运算符同样存在着浅拷贝的问题,下面的这种代码,也是会引起代码崩溃的
    在这里插入图片描述
  • 不仅代码会崩溃,同时,之前s1的空间并没有被释放,所以会造成内存泄漏的问题出现
  • 崩溃的同时造成了内存泄漏的问题
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 像下面的string类的模拟实现就是有问题的实现
  • 拷贝构造和赋值运算符重载的部分是有问题的
  • 原本的空间也找不到了,会造成内存泄漏的问题产生
    在这里插入图片描述
#include<iostream>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char[1];

				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}

		//下面的两个都是有问题的函数形式,都是会造成代码崩溃的
		//这个相当于是编译器所给出的拷贝构造函数的形式
		string(const string& s)
			:_str(s._str)
		{
			
		}

		//编译器所给出的赋值运算符的重载的形式
		//不仅代码崩溃,内存还会泄露
		string operator=(const string& s)
		{
			_str = s._str;
			return *this;
		}

	private:
		char* _str;
	};
}
void TestString()
{
	bite::string s1;  //我们先去构造一个空的字符串
	bite::string s2("hello");
	//bite::string s3(s2);

	s1 = s2;
}
int main()
{
	TestString();

	return 0;
}
  • 那么问题要怎么解决呢?
  • 让每个对象有自己独立的内存空间就是可以的
    在这里插入图片描述
  • 深拷贝
    在这里插入图片描述
  • 下面的代码就是完全没有问题的,不会出现代码崩溃以及内存泄漏问题的出现
#include<iostream>
#include<string>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char[1];

				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}

		string(const string& s)
			:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str, s._str);
		}

		string& operator=(const string& s)
		{
			//看一看是不是自己给自己赋值
			//如果是自己给自己赋值的话,其实就没什么必要了
			if (this != &s)
			{
				char* temp = new char[strlen(s._str) + 1];
				strcpy(temp, s._str);
				delete[] _str;
				_str = temp;
			}
			return *this;
		}
	private:
		char* _str;
	};
}
void TestString()
{
	bite::string s1;  //我们先去构造一个空的字符串
	bite::string s2("hello");
	bite::string s3(s2);

	s1 = s2;
}
int main()
{
	TestString();

	return 0;
}
  • 上面那种深拷贝的方式,其实来说应该是属于传统意义上的深拷贝的方法,其实深拷贝还有一种稍微简洁一点的书写方式,如下所示:
#include<iostream>
#include<string>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   //用C类型的字符串来对string类类型的对象来进行初始化
		//给出一个默认的参数相当于是兼具了没有参数的构造函数
		{
			if (nullptr == str)
			{
				//在这个位置,很多人可能有疑问
				//上面既然已经给出了一个模拟参数了,那么这个地方为什么还需要
				//进行是不是空的判断,关键在于
				//有些人在传递参数的时候,很有可能跟你传递一个nullptr过来
				//所以在这个位置需要判断一下字符串是不是空的
				//所以我们给出判空操作,如果我们判断传进来的字符串是空的字符串的话
				//那么,我们就去构造一个空的字符串就可以了
				_str = new char[1];

				//然后将\0放置到字符空间中
				//通过解引用放在那一部分空间里面
				*_str = '\0';

				//或者说上面的两句话,其实可以用下面的一句话来进行概括
				//_str = new char('\0');
			}
			else
			{
				//多申请一个空间的目的在于存放\0
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}

		string(const string& s)
			:_str(nullptr)
		{
			string strTemp(s._str);
			swap(_str, strTemp._str);
		}

		string& operator=(string s)
		{
			//看一看是不是自己给自己赋值
			//如果是自己给自己赋值的话,其实就没什么必要了
			swap(_str, s._str);
			return *this;
		}
	private:
		char* _str;
	};
}
void TestString()
{
	bite::string s1;  //我们先去构造一个空的字符串
	bite::string s2("hello");
	bite::string s3(s2);

	s1 = s2;
}
int main()
{
	TestString();

	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 赋值运算符
  • 下面这种方式并没有传递引用,而是以值得方式来进行拷贝的操作,因为,代码内部实在进行交换的操作,如果传递引用的话,实际上实相当于把s2和s3的空间进行了一个交换,和我们所希望达到的目的是不一样的
  • 既然是以值得方式来进行传递得,那么在这个过程中,肯定会创建一个临时的对象
  • 第一步是需要创建临对象,我首先需要把s2赋给s,然后去拷贝构造一个临时对象s,其实s就是s2的一个副本,s到时候也是会有自己的空间的
  • 先会去调用拷贝构造函数,把s创建出来,然后接下来进入函数,进行交换的操作,把地址进行交换了一下
  • 交换完成了之后,就需要返回当前的对象,那么在返回当前的对象的时候,刚刚创建的临时对象是需要进行销毁的操作的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 但是,像上面的那种复制操作的效率其实还是有点低的,我们还是可以对其进行进一步的修改的,代码可以修改为如下所示的形式:
string& operator=(const string& s)
{
	//一进来还是判断一下是不是自己给自己进行符指的操作
	if (this != &s)
	{
		string strTemp(s);
		swap(_str, strTemp._str);
	}
	return *this;
}
  • 向上面这样进行代码的书写的话,传参的效率会稍微提高一些,但是其实还是需要去创建一个临时的对象,只是和上面那种代码的书写区别在于,上面的代码是由编译器来调用拷贝构造函数来创建一个临时的对象,下面的这种方式是由用户自己来调用拷贝构造函数来创建按一个临时的对象,两种代码的原理其实是一样的
对于浅拷贝的解决方式,还是一种解决方式就是写时拷贝
写时拷贝
  • 浅拷贝形成的原因是多个对象共用同一块内存空间,在结束的时候,进行销毁的操作,会导致同一份资源被多次释放从而导致代码崩溃的问题,那么我们如果要避免浅拷贝问题出现的话,那么我们就可以在上述形成浅拷贝的原因的地方,做出一些调整
  • 我们做出的调整就是,其实还是多个对象共用同一快内存空间,只不过是,我们在进行销毁的时候,只让那一份资源销毁一次,就可以完美的避开浅拷贝所给我们带来的问题。
  • 写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
  • 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
    在这里插入图片描述
  • 如果一个对象试图去更改公用空间中的内容的话,那么我们就在他准备要去修改空间中的内容的时候,给他分配一块新的内存空间,那么,这个样子的话,他对空间中的内容进行修改的时候,就不会影响到别的对象对这块空间的使用了,这种机制其实就是写时拷贝技术
    在这里插入图片描述
string类的模拟实现
#include<iostream>
#include<string>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
	public:
		//构造函数
		string(const char* str = "")   
		{
			if (str == nullptr)
			{
				str = "";
			}
			_size = strlen(str);
			//标准库中string类容量是没有算\0的空间的
			_capacity = _size < 15 ? 15 : strlen(str);
			//+1是因为要存放\0的空间的
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_capacity = 0;
				_size = 0;
			}
		}

		string(const string& s)
			:_str(nullptr)
		{
			string strTemp(s._str);
			this->swap(strTemp);
		}

		//string& operator=(string s)
		//{
		//	//看一看是不是自己给自己赋值
		//	//如果是自己给自己赋值的话,其实就没什么必要了
		//	swap(_str, s._str);
		//	return *this;
		//}

		string& operator=(const string& s)
		{
			//一进来还是判断一下是不是自己给自己进行符指的操作
			if (this != &s)
			{
				string strTemp(s._str);
				this->swap(strTemp);
			}
			return *this;
		}


		///
		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

		bool empty()const
		{
			return 0 == _size;
		}

		void reserve(size_t newCapacity)
		{
			size_t oldCapacity = capacity();
			if (newCapacity > oldCapacity)
			{
				//只有大于的时候才会去进行扩容的操作,如果是小于
				//就忽略此次的扩容操作
				char* temp = new char[newCapacity + 1];
				strcpy(temp, _str);
				delete[] _str;
				_str = temp;
				_capacity = newCapacity;
			}
		}

		void resize(size_t newsize, char ch)
		{
			/*
			size_t oldsize = size();
			if (newsize <= oldsize)
			{
				_size = newsize;
				_str[_size] = '\0';
			}
			else if (newsize < capacity())
			{
				//直接填充
				memset(_str+_size, ch, newsize - oldsize);
				_size = newsize;
				_str[_size] = '\0';
			}
			else
			{
				//先去开辟空间
				reserve(newsize);
				memset(_str + _size, ch, newsize - oldsize);
				_size = newsize;
				_str[_size] = '\0';
			}
			*/
			//但是上面的这种代码需要分情况去讨论,代码写起来其实有一些冗余
			//那么,其实我们是有更简洁的一种写法的,如下所示

			size_t oldsize = size();
			if (newsize > oldsize)
			{
				if (newsize > capacity())
				{
					reserve(newsize);
				}
				memset(_str + _size, ch, newsize - oldsize);
			}
			_size = newsize;
			_str[_size] = '\0';
		}

		void resize(size_t newsize)
		{
			//调用上面的方法
			//多出来的空间用0进行填充
			resize(newsize, 0);
		}

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);

		}

	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	};
}
int main()
{
	return 0;
}

在这里插入图片描述

#include<iostream>
#include<string>
#include<assert.h>
using namespace std;
namespace bite
{
	//string类其实相当于是动态类型的字符顺序表
	class string
	{
		friend ostream& operator<<(ostream& _cout, const bite::string& s);
	public:
		//是string类的迭代器
		typedef char* iterator;
	public:
		//构造函数
		string(const char* str = "")
		{
			if (str == nullptr)
			{
				str = "";
			}
			_size = strlen(str);
			//标准库中string类容量是没有算\0的空间的
			_capacity = _size < 15 ? 15 : strlen(str);
			//+1是因为要存放\0的空间的
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		string(const string& s, size_t pos, size_t n = npos)
		{
			size_t len = s.size();
			if (pos > len)
				pos = len;
			if (n > len - pos)
				n = len - pos;
			_str = new char[n + 1];
			strncpy(_str, s.c_str() + pos, n);
			_size = n;
			_capacity = n;
			_str[_size] = '\0';
		}

		//析构
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_capacity = 0;
				_size = 0;
			}
		}

		string(const string& s)
			:_str(nullptr)
		{
			string strTemp(s._str);
			this->swap(strTemp);
		}

		//string& operator=(string s)
		//{
		//	//看一看是不是自己给自己赋值
		//	//如果是自己给自己赋值的话,其实就没什么必要了
		//	swap(_str, s._str);
		//	return *this;
		//}

		string& operator=(const string& s)
		{
			//一进来还是判断一下是不是自己给自己进行符指的操作
			if (this != &s)
			{
				string strTemp(s._str);
				this->swap(strTemp);
			}
			return *this;
		}


		///
		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

		bool empty()const
		{
			return 0 == _size;
		}

		void reserve(size_t newCapacity)
		{
			size_t oldCapacity = capacity();
			if (newCapacity > oldCapacity)
			{
				//只有大于的时候才会去进行扩容的操作,如果是小于
				//就忽略此次的扩容操作
				char* temp = new char[newCapacity + 1];
				strncpy(temp, _str, _size);
				delete[] _str;
				_str = temp;
				_capacity = newCapacity;
			}
		}

		void resize(size_t newsize, char ch)
		{
			/*
			size_t oldsize = size();
			if (newsize <= oldsize)
			{
				_size = newsize;
				_str[_size] = '\0';
			}
			else if (newsize < capacity())
			{
				//直接填充
				memset(_str+_size, ch, newsize - oldsize);
				_size = newsize;
				_str[_size] = '\0';
			}
			else
			{
				//先去开辟空间
				reserve(newsize);
				memset(_str + _size, ch, newsize - oldsize);
				_size = newsize;
				_str[_size] = '\0';
			}
			*/
			//但是上面的这种代码需要分情况去讨论,代码写起来其实有一些冗余
			//那么,其实我们是有更简洁的一种写法的,如下所示

			size_t oldsize = size();
			if (newsize > oldsize)
			{
				if (newsize > capacity())
				{
					reserve(newsize);
				}
				memset(_str + _size, ch, newsize - oldsize);
			}
			_size = newsize;
			_str[_size] = '\0';
		}

		void resize(size_t newsize)
		{
			//调用上面的方法
			//多出来的空间用0进行填充
			resize(newsize, 0);
		}

		void clear()
		{
			_size = 0;
			*_str = '\0';
		}

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		//下标引用操作符
		char& operator[](size_t index)
		{
			assert(index < _size);
			return _str[index];
		}

		const char& operator[](size_t index)const
		{
			assert(index < _size);
			return _str[index];
		}

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_size * 2);   //扩容到原先容量的2倍
			}
			_str[_size++] = ch;
			_str[_size] = '\0';
		}

		string& operator+=(const char* s)
		{
			首先计需要计算出来两个字符串拼接在一起需要多少内存空间
			//size_t leftsize = _size;
			//size_t rightsize = strlen(s);
			//char* temp = new char[leftsize + rightsize + 1];
			//strcpy(temp, _str);
			//strcpy(temp + leftsize, s);
			//delete[] _str;
			//_str = temp;
			//_size = leftsize + rightsize;
			//_capacity = _size;
			//return *this;

			//如果觉得上面的那种方法不好理解的话,可以使用下面的这种写法
			int len = strlen(s);
			reserve(_capacity + len);
			strcpy(_str + _size, s);
			_size += len;
			return* this;
		}

		//特殊操作

		//返回一个C语言格式的字符串
		const char* c_str()const
		{
			return _str;
		}

		size_t find(char ch, size_t pos)const
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}

		size_t rfind(char ch, size_t pos = npos)
		{
			for (int i = pos != npos ? pos : _size - 1; i >= 0; --i)
			{
				if (_str[i] == ch)
					return i;
			}
			return npos;
		}

		//从pos的位置开始截取n的字符构成一个新的字符串进行返回操作
		string substr(size_t pos = 0, size_t n = npos)const
		{
			//保证不越界
			if (pos > _size)
				pos = _size;
			if (n == _size - pos)
				n = _size - pos;
			return string(*this, pos, n);
		}

	private:
		char* _str;
		size_t _capacity;   //容量没有将\0的空间计算进去的,但是在底层开辟空间必须要预留出\0的位置
		size_t _size;

		static size_t npos;
	};

	ostream& operator<<(ostream& _cout, const bite::string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			if (s[i] == '\0')
				cout << ' ';
			else
				cout << s[i];
		}
		return _cout;
	}

	size_t string::npos = -1;
}



void TestBiteString1()
{
	bite::string s1("hello");
	bite::string s2(s1);

	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	/*for (auto e : s2)
		cout << e << " ";
	cout << endl;*/

	//或者,对s2的话,可以换一种打印的方式

	//如果把上述中iterator的方法屏蔽掉的话,那么其实基于范围的for循环也就不能用了
	//原因在于对于自定义类型,基于范围的for循环实际上是转换为
	//begin和end来进行一系列的操作的
	//所以上面的基于范围的for循环和下面的iterator的方法实际上就是同一种方法
	auto it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
		++it;
	}
}
void TestBiteString2()
{
	bite::string s1("hello");
	s1.push_back(' ');
	s1.push_back('w');
	s1.push_back('o');
	s1.push_back('r');
	s1.push_back('l');
	s1.push_back('d');

	cout << s1 << endl;


	s1.resize(10, '!');
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	s1.resize(20, '$');
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	s1.resize(6);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	s1.clear();
	if (s1.empty())
		cout << "空字符串" << endl;
	else
		cout << "非空字符串" << endl;
}

void TestBiteString3()
{
	bite::string s("2020,12,17.cpp");
	bite::string fix = s.substr(s.rfind('.') + 1);
	cout << fix;
}

int main()
{
	//TestBiteString1();

	//TestBiteString2();

	TestBiteString3();

	return 0;
}
  • vs的string是深拷贝的方式还是写时拷贝的方式实现的
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值