c++ String 《string的模拟实现》《string类中的深浅拷贝》《string的常用接口说明》《为什么要学习string》

一。为什么我们要学习string?
1.C语言中的字符串
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

2.string类的常用接口说明,

(1)string类对象的常用构造

函数名称                                                      功能说明
string()                                        构造空的string类对象,即空字符串
string(const char* s)                           用C-string来构造string类对象
string(size_t n, char c)                        string类对象中包含n个字符c
string(const string&s)                               拷贝构造函数
string(const string&s, size_t n)                用s中的前n个字符构造新的string类对象



void TestString()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(10, 'a'); // 用10个字符'a'构造string类对象s3
string s4(s2); // 拷贝构造s4
string s5(s3, 5); // 用s3中前5个字符构造string对象s5
}

(2)string类对象的容量操作

函数名称                                               功能说明
    size_t size() const                         返回字符串有效字符长度
    size_t length() const                       返回字符串有效字符长度
    size_t capacity ( ) const                       返回空间总大小
    bool empty ( ) const                检测字符串释放为空串,是返回true,否则返回false
    void clear()                                    清空有效字符
    void resize ( size_t n, char c )     将有效字符的个数该成n个,多出的空间用字符c填充
    void resize ( size_t n )             将有效字符的个数改成n个,多出的空间用0填充
    void reserve ( size_t res_arg=0 )             为字符串预留空间

#include<string>   //这个后没有没有<.h>
#include<iostream>
using namespace std;
#include<cstring>

void TestString()
{
	string s("Hello world!!");
	cout << s.length() << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;   //string类直接支持cin和cout;
	cout << s << endl; //没有与这个相同的类型;
    s.clear();//把这里面的内容清空之后,长度与字符个数清空,但是底层容量没有大的变化。
	cout << s.length() << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	s.resize(10, 'a'); //把s中有效字符数增加到15个,多余的用a补上
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;   //为什么打印不出来?
s.resize(15);     //把s中的有效字符增加15个,多余的用\0补上;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
s.resize(5);     //把s中的有效字符减少到5个;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

}

这几个的代码实现很简单,而且容易看懂,不难,下面我就把实现过的代码exe看一下;
在这里插入图片描述

void  TestString2()
{
	//测试reserve是否影响string的有效个数
	string s("hello world");
	s.reserve(100);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
     //判断缩小reserve的大小,是否会改变容量的大小;
	s.reserve(50);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
//我们可以由测试可以看见内容是不会改变的,且缩小容量,对容量的大小也不会改变;
}

在这里插入图片描述
这写代码你没事了可以敲一敲,就可以理解了;

注意:

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一
    致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小。
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于
    string的底层空间总大小时,reserver不会改变容量大小。

(3)string类对象的访问操作

函数名称                                                 功能说明
char& operator[] ( size_t pos )           返回pos位置的字符,const string类对象调用
const char& operator[] ( size_t pos )  const返回pos位置的字符,非const string类对象调用

void  TestString3()
{
	string s1("hello bit");
	const string s2("hello world");
	cout << s1 << " " << s2 << endl;
	s1[0] = 'H';
	cout << s1 << endl;
for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
//s2[0] = 'H';  //编译失败,因为const类型不能被修改;
}

(4)string类对象的修改操作

函数名称                                                          功能说明
void push_back(char c)                                       在字符串后尾插字符c
string& append (const char* s);                            在字符串后追加一个字符串
string& operator+=(const string& str)                   	在字符串后追加字符串str
string& operator+=(const char* s) 							在字符串后追加C个数字符串
string& operator+=(char c) 									在字符串后追加字符c
const char* c_str( )const 											返回C格式字符串
size_t find (char c, size_t pos = 0)const             从字符串pos位置开始往后找字符c,
                                                         返回该字符在字符串中的位置
size_t rfind(char c, size_t pos = npos)              从字符串pos位置开始往前找字符c,
                                                     返回该字符在字符串中的位置
string substr(size_t pos = 0, size_t n  = npos)const  在str中从pos位置开始,截取n个字符,
                                                          然后将其返回
void  TestString4()
{
	string  str;
	str.push_back(' ');
	str.append("hello");
	str.push_back(' ');
str += 'b';
	str += 'it';
	cout << str << endl;
	//cout << c_str()<<endl;这个为什么会报错,我实在是想不通;
                     //获取文件的后缀<.cpp>
	string file("test.cpp");    
	size_t pos = file.rfind('.');
	string dpf(file.substr(pos, file.size() - pos));   //关键使用"substr"接口
	cout << dpf << endl;
             //取出dpf中的域名
	string dpf1("http://www.cplusplus.com/reference/string/string/npos/");
	cout << dpf1 << endl;
	size_t start = dpf1.find('://');
	if (start == string::npos)
	{
		cout << "dpf1" << endl;
		return;
	}
	start += 2;  //这里为什么会是+3,而不是别尼?
	size_t finsh = dpf1.find('/', start);    
	string address = dpf1.substr(start ,finsh - start);
	cout << address << endl;    //这里打印的就是去掉前面的http后面的域名;
                      //删除dpf1的协议前缀;
	pos = dpf.find("://");
	dpf1.erase(0, pos + 5);
	cout << dpf1 << endl;
}

在这里直接使用,练习接口的实现,就可以完成对string类的书写,下面就来看一下生成的程序。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
根据这几张图片的实现,我们是否就可以知道其中接口的使用。

//利用reserve提高效率,避免开空间带来的开销:

void Testpushback()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s group\n";
	for(int i = 0 ; i < 100; i++)
	{
		s += 'c';
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity change = " << sz << endl;
		}
	}
}
void Testpushback_p()
{
	string s;
	s.reserve(100);
	size_t sz = s.capacity();
	cout << "making s group\n";
	for (int i = 0; i < 100; i++)
	{
		s += 'c';
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity change = " << sz << endl;
		}
	}
}

这两个一个是使用reserve的,一个是没有使用的,我们看一下生成的程序;
在这里插入图片描述
这个图片是第一段代码的,它在不停的自己开辟空间,而且没有用的直接浪费了我,下面我们看一下reserve的使用生成程序:
在这里插入图片描述
我们可以清楚的看到,reserve它只是调用了一个应该生成的大小,而且没有开辟多余的空间。

注意:

  1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
    2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

二,string类的模拟操作

(1)经典的string类问题

class String
{
public:
	    String(const char* str = "")
	    {
	  	  // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
		    if(nullptr == str)
		    {
		    assert(false);
		    return;
		    }
		    _str = new char[strlen(str) + 1];
		    strcpy(_str, str);
	    }
~String()
{
    if(_str)
    {
	    delete[] _str;
	    _str = nullptr;
    }
}
private:
char* _str;
};
// 测试
void TestString()
{
    String s1("hello bit!!!");
    String s2(s1);
}

试一试上面的代码,通过编译,我们可以知道这段代码是编译不过的,那是因为s1与s2公用一块内存,当释放一块时,另一块也就释放掉了,导致程序崩溃,这样的方式叫做《浅拷贝》。
(2)浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以 当继续对资源进项操作时,就会发生发生了访问违规。要解决浅拷贝问题,C++中引入了深拷贝
(3)深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

下面我们就可以看一下string的最基本的四大接口实现:
先是普通版:

class String
{
public:
	String(const char* str = "")   //构造
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str)+1];
		strcpy(_str, str);
	}
String(const String& s)    //拷贝构造
		:_str(new char[strlen(s._str)+1])
	{
		strcpy(_str, s._str);
	}
String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* pstr = new char[strlen(s._str) + 1];
			strcpy(pstr, s._str);
			delete[]_str;
			_str = pstr;
		}
		return *this;
	}
~String()    //析构
	{
		if (_str)
		{
			delete[]_str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

下面是现代版的string类:
int main()
{
String s1(“hello world”);
String s2(s1);
return 0;
}

现代版string的基本接口实现;

class String
{
public:
	String(const char* str = " ")
	{
		if (str != nullptr)
			str = "";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
String(const String& s)
		:_str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
//String& operator=(const String& s)  // 在这里交换函数怎么实现不了;
	//{
	//	swap(_str, s._str);
	//	return *this;
	//}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			/*char* pstr = new char[strlen(s.str) + 1];
			strcpy(pstr, s._str);
			delete[]_str;
			_str = pstr;*/
			String strTmp(s);
			swap(_str, strTmp._str);
		}
		return *this;
	}
	~String()
	{
		if (_str)
		{
			delete[]_str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

其实,我感觉是没有什么改变的,就是《拷贝构造》和《赋值运算》哪里有一点不一样。

第三点还有写实拷贝,但是这个我们还是了解一下就可以了。

写实拷贝:https://coolshell.cn/articles/12199.html

四 ,string类的模拟实现

#include<string>   //这个后没有没有<.h>
#include<iostream>
using namespace std;
#include<cstring>
#include<assert.h>

namespace bit
{
	class String
	{
	public:
		typedef char* iterator;
iterator begin()
		{
			return _str;
		}
iterator end()
		{
			return _str + _size;
		}
	String(char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
	         strcpy(_str, str);
		}

	// copy(s1)
	String(const String& s)
		:_str(NULL)
		, _size(0)
		, _capacity(0)
	{
		String tmp(s._str);
		this->Swap(tmp);
	}
	// copy = s2;
	String& operator=(String s)
	{
		this->Swap(s);
		return *this;
	}

	void Swap(String& s)
	{
		swap(_str, s._str);
		swap(_size, s._size);
		swap(_capacity, s._capacity);
	}


	~String()
	{
		if (_str)
		{
			delete[] _str;
			_size = 0;
			_capacity = 0;
		}
	}

	bool operator<(const String& s)
	{
		int tmp = 0;
		while (tmp < _size && tmp < s._size)
		{
			if ((*_str + tmp) != (*s._str + tmp))
			{

				if ((*_str + tmp) >= (*s._str + tmp))
				{
					return false;
				}
				else
					return true;
			}
			tmp++;
		}
		if (_size < s._size)
		{
			return true;
		}
		else
		{
			cout << "tem:" << tmp << endl;
			return false;
		}
	}
	bool operator>(const String& s)
	{
		return !(*this < s || *this == s); //因为this是一个指针;

	}
	bool operator<=(const String& s)
	{
		return !(*this > s);
	}
	bool operator>=(const String& s)
	{
		return !(*this < s);
	}
	bool operator==(const String& s)
	{
		int tmp = 0;
		if (_size != s._size)
		{
			return false;
		}
		while (tmp != _size)
		{
			if ((*_str + tmp) != (*s._str + tmp))
			{
				return false;
			}
			tmp++;
		}
		return true;
	}
	bool operator!=(const String& s)
	{
		return !(*this == s);
	}

	void Reserve(size_t n)
	{
		if (n == _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[]_str;
			_str = tmp;
			_capacity = n;
		}
	}

	void PushBack(char ch)
	{
		if (_size >= _capacity)
		{
			Reserve(_capacity * 2);
		}
		_str[_size] = ch;
		++_size;
		_str[_size + 1] = '\0';

		cout << _str << endl;
	}

	void Append(const char* str)
	{
		size_t n = strlen(str);
		if (_size + n > _capacity)
		{
			Reserve(_size + n);
		}
		strcpy(_str + _size, str);
		_size += strlen(str);
		cout << _str << endl;
	}

	String& operator+=(char ch);
	String& operator+=(const char* str);
	void Insert(size_t pos, char ch)    // 在pos位置上插入字符c/字符串str,并返回该字符的位置
	{
		if (_size == _capacity)
		{
			Reserve(_capacity * 2);
		}
		String s(*this);
		char* tmp = s._str + pos;
		_str[pos] = ch;
		strcpy(_str + pos + 1, tmp);

		cout << _str << endl;
	}
	void Insert2(size_t pos, const char* str)  // 在pos位置上插入字符c/字符串str,并返回该字符的位置
	{
		_size += strlen(str);
		while (_size == _capacity)
		{
			Reserve(_capacity * 2);
		}
		int tmp = _size;
		while (tmp != pos - 1)
		{
			_str[tmp + strlen(str)] = _str[tmp];
			--tmp;
		}
		int tmp2 = 0;
		while (tmp2 < strlen(str))
		{
			_str[pos + tmp2] = str[tmp2];
			tmp2++;
		}
		/*int i=strlen(ch);
		String s(*this);
		char* tmp = s._str + pos;
		strcpy(_str+pos, ch);
		strcpy(_str+pos+i,tmp);*/


		cout << _str << endl;

	}

	void Erase(size_t pos, size_t len);
	size_t Find(char ch, size_t pos = 0)  // 返回c在string中第一次出现的位置
	{
		int i = 0;
		for (i = 0; i< _size; ++i)
		{
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				else
					pos++;
			}
			return -1;
		}
		//cout<<<<endl;
	}
	size_t Find2(const char* str, size_t pos = 0)    // 返回子串str在string中第一次出现的位置
	{
		if (strlen(str) > _size)
		{
			return -1;
		}
		int tmp = 0;
		while (pos < _size)
		{
			tmp = 0;
			if (_str[pos] == str[0])
			while (tmp <= strlen(str))
			{
				if (_str[pos + tmp] != str[tmp])
				{
					break;
				}
				tmp++;
				if (tmp == strlen(str))
				{
					return pos;
				}
			}
			pos++;
		}

	}
char* c_str()
		{
			return _str;
		}
char& operator[](size_t pos)
		{
			//assert(pos < _size);
		return _str[pos];
	}

	size_t Size()
	{
		return _size;
	}

	size_t Capacity()
	{
		return _capacity;
	}

private:
	char* _str;
	size_t _size;
	size_t _capacity;

	static size_t npos;
};

size_t String::npos = -1;

void TestString1()
{
	String s1("hello");
	String s2("word");
	s1.PushBack('h');
	s1.Append("hidasfa");
	s1.Insert2(3,"fdsa");
	//cout << s1.Find2("ll", 0);
	cout<<(s1 < s2)<<endl;
	cout<<(s1 == s2)<<endl;
	cout<<(s1 <= s2)<<endl;
	String copy(s1);
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	cout << copy.c_str() << endl;

	copy = s1 = s2;

	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	cout << copy.c_str() << endl;

	String::iterator it = s1.begin();
	while (it != s1.end())
	{
	cout << *it << " ";
	++it;
	}
	cout << endl;


	}
}
int main()
{
	bit::TestString1();
	return 0;
}

下面我们看一下生成的可执行程序:
在这里插入图片描述
当然,我这里的接口不是最完整的,但是我们常用的接口都已经实现了,仅供参考,如果你还有想实现别的接口,自己可以实现一下;

完成了以上的代码,你也可以写一篇博客,记录一下《我们相互关注一下,哈哈》;

猜你喜欢

转载自blog.csdn.net/dpfxaca6/article/details/88809459