C++ - mock implementation of string class

  We want to imitate the string in the STL library and basically implement some commonly used operations.

content

member function interface

default member function

Constructor

copy constructor

destructor

operator=assignment operator overloading

Capacity related functions 

size( ) function

capacity() function

empty() function

reserve() function

resize() function 

clear() function

Access subscript related functions

operator[ ] function

Find related functions

find() character

find() string

Iterator related functions

Related functions for inserting characters

insert() a character

insert() string

Tail insert push_back( )

Append the string append( )

operator+= plus equals one character

operator+= plus equals a string

erase delete function

Relational operator overloaded functions

operator< less than 

operator== is equal to 

operator<= less than or equal to 

operator> greater than

operator>= greater than or equal to 

operator!= is not equal to 

Stream insert stream extract function overload

stream extraction 

stream insertion 

other functions

Swap( ) swap function

string in c form


member function interface

#pragma once
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
using namespace std;
namespace sjj//自定义一个命名空间
{
	class string
	{
	public:
		typedef char* iterator;//string的迭代器就是原生指针
		typedef const char* const_iterator;
		//构造函数
		//string("hello");
		string(const char* str = "");

		//拷贝构造函数
		//s2(s1)
		string(const string& s);

		//赋值重载
		//s2=s1
		string& operator=(string s);//传值传参也是拷贝构造

		//析构
		~string();

		//容量大小相关的函数//
		size_t size()const;

		size_t capacity()const;

		bool empty()const;

		void clear();

		void reserve(size_t n);//对容量进行改变,让容量到n

		void resize(size_t n, char ch = '\0');//改变size的大小

		//访问下标相关的函数//
		char& operator[](size_t pos);

		const char& operator[](size_t pos)const;

		//查找相关的函数//
		//正向查找一个字符
		size_t find(char c, size_t pos = 0);

		//正向查找一个字符串
		size_t find(char* s, size_t pos = 0);

		///迭代器相关的函数/
		iterator begin();

		iterator end();

		const_iterator begin()const;

		const_iterator end()const;

		///插入字符串相关的函数/

		//尾插
		void push_back(char c);

		//追加字符串
		void append(const char* str);

		//加等于
		string& operator+=(char c);

		const string& operator+=(char* str);

		//插入一个字符
		string& insert(size_t pos, char c);

		//插入字符串
		string& insert(size_t pos, const char* s);

		//删除相关的函数
		//删除pos位置开始的n个字符
		string& erase(size_t pos, size_t len = npos);

		//其他函数//
		//自己写的交换函数
		void Swap(string& s);
		//C形式的字符串
		const char* c_str()const;
	private:
		char* _str;//字符串数组
		size_t _size;//有效字符的个数 不含\0
		size_t _capacity;//能存储有效字符的空间,不包含\0
		static const size_t npos;//静态成员变量,在类外部初始化
	};
	const size_t string::npos = -1; //初始化

	///关系运算符重载//
	bool operator<(const string& s1, const string& s2);

	bool operator==(const string& s1, const string& s2);

	bool operator<=(const string& s1, const string& s2);

	bool operator>(const string& s1, const string& s2);

	bool operator>=(const string& s1, const string& s2);

	bool operator!=(const string& s1, const string& s2);


	//  operator<<   operator>>
	//  cout<<s1 --> operator<<(out,s1);
	///流插入 流提取 重载///
	ostream& operator<<(ostream& out, const string& s);
	//ostream&是系统的类型,它能够实现输出内置类型的数据

	istream& operator>>(istream& in, string& s);
}

default member function

Constructor

A null character is given as the default value. When no parameter is passed, an empty string is constructed. When a parameter is passed in, the passed parameter is used.

string(const char* str = "")//给缺省值""空字符串
	:_size(strlen(str))
	, _capacity(_size)
{
	_str = new char[_capacity + 1];//  +\0
	strcpy(_str, str);//char *strcpy( char *strDestination, const char *strSource );
}

copy constructor

First of all, you must understand the knowledge related to deep and shallow copying, please click here!

The first way of writing: traditional writing

//传统写法     //要用自己写的
string(const string& s)
	:_size(0)
	, _capacity(0)
{
	_str = new char[s._capacity + 1];//_str申请一块和s._str一样大的空间
	strcpy(_str, s._str);//将两个指针指向交换
	_size = s._size;
	_capacity = s._capacity;
}

We do a deep copy honestly, first new a space as large as the original, and then deep copy the data, so that the two spaces will not affect each other 

The second: modern writing (recommended writing)

//现代写法————投机取巧
string(const string& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);//复用构造函数,构造一个tmp对象出来
	this->Swap(tmp);//相当于Swap(this,tmp)
}

Summary :

  The modern writing method is opportunistic, reuse the constructor, let him open a temporary object, out of the scope, the local temporary object is automatically destroyed, we don't need to care about it, we only need to exchange their pointers in the end, the code is very concise .

destructor

Clean up some dynamic resources

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

operator=assignment operator overloading

If we don't write it, it will also be generated by the compiler by default, but it is also a shallow copy, which does not meet our usage requirements.

We know that to deep copy, then we must first consider the capacity of the two spaces :

① The number of data in s3 is greater than the number of data in s1, we need to consider the expansion of s1

②The space of s1 is much larger than the number of data of s3, we need to consider the problem of shrinkage

In this way, we might as well make the problem simpler and rude, directly release the space of s1, re-open a space as large as s3, and finally copy the data there

 The first: traditional writing

string& operator=(const string& s)
{
	/*if (this != &s)//防止自己给自己赋值s3=s3
	{
		delete[] _str;//释放掉s1 默认有个隐藏的this指针,实际上是这样: this->_str
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}*/
	//优化版本
	//new可能会失败,但是却先释放s1了,我们可以先new
	if (this != &s)//防止自己给自己赋值s3=s3
	{
		char* tmp = new char[strlen(s._str) + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
	}
	return *this;//返回s1
}

 We can see through the debug window:

The second: modern writing (very clever)

//更NB的写法
//s1=s3 这里传值传参也是一种拷贝,s充当的就是tmp
string& operator=(string s)
{
	this->Swap(s);
	return *this; //返回左值,支持连续赋值
}

Summary : The modern writing used here is very clever, using copy by value, which is equivalent to a call to the copy constructor.

Capacity related functions 

size( ) function

We can directly return _size, excluding \0

const size_t size()const
{
	return _size;
}

capacity() function

size_t capacity()const
{
	return _capacity;
}

empty() function

You can use the strcmp function to compare two string pointers.

//判断是否为空
bool empty()
{
	return strcmp(_str, "") == 0;
}

reserve() function

To adjust the capacity to n, we first create a new space of n+1, then copy the original data to the new space, release the original space, exchange the pointers of the two pointers, and set _capacity to n That's it.

Let's take a look at the animation demo:

void reserve(size_t n)//对容量进行改变,让容量到n
{
	if (n > _capacity)
	{
		char* tmpstr = new char[n + 1];
		strcpy(_str, tmpstr);
		delete[] _str;
		_str = tmpstr;
		_capacity = n;
	}
}

resize() function 

void resize(size_t n, char ch = '\0')//改变size的大小
{
	if (n <= _size)//缩小的情况
	{
		_size = n;
		_str[n] = '\0';
	}
	else//要插入数据的情况
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		//void *memset( void *dest, int c, size_t count );
		memset(_str + _size, ch, n - _size);
		_size = n;
		_str[n] = '\0';
	}
}

There are two situations in the resize function:

The first: the value n of the given size is less than _size, in this case the data will be truncated, and only the first n will be retained

The second type: the value n of the given size is greater than the capacity, and the expansion problem must be considered

clear() function

Setting the _size position to a slash 0 is equivalent to emptying the string.

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

Access subscript related functions

operator[ ] function

Operator[ ] is the most familiar way   to access elements by subscripting , and it is very simple and convenient. It is implemented so that we can access strings like arrays . We can implement the normal version and the const object version.

char& operator[](size_t pos)
{
	assert(pos < _size);//防止越界
	return _str[pos];//返回pos位置的引用,相当于就是把那个字符给返回了
}
//const版本
const char& operator[](size_t pos)const
{
	assert(pos < _size);//防止越界
	return _str[pos];//返回pos位置的引用,相当于就是把那个字符给返回了
}

Find related functions

find() character

  npos is a static member variable of the string class, and its value is the maximum value of the integer.

  By traversing, we search for the matching string from the beginning to the end, find the return subscript, and return npos if not found.

//正向查找一个字符
size_t find(char c, size_t pos = 0)
{
	assert(pos < _size);
	for (int i = 0; i < _size; ++i)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;//没有找到目标字符,返回npos
}

find() string

  The strstr string search function is used, the pointer to the first character of the returned string is found, and NULL is not found.

Having found the string, we can determine the position of the target string by the pointer difference.

//正向查找一个字符串
size_t find(char* s, size_t pos = 0)
{
	assert(pos < _size);

	//const char * strstr ( const char * str1, const char * str2 );
	const char* ptr = strstr(_str + pos, s);//调用strstr字符串查找函数
	if (s)
	{
		return ptr - _str;//返回找到第一个字符的下标
	}
	else
	{
		return npos;
	}
}

Iterator related functions

  The iterator in the string class is equivalent to a native pointer, but here it is a typedef, so here we just treat it as a pointer.

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin()const
{
	return _str;
}

const_iterator end()const
{
	return _str + _size;
}

Related functions for inserting characters

Let's first take a look at the insertion principle of the animation demonstration:

insert() a character

  The insert function needs to insert a character at any position. First, check the validity of the subscript, and then determine whether it needs to be expanded. For expansion, you can use the reserve function, and then move the pos position and the characters behind it by one place, leaving the characters to be inserted. out position, then insert the character into the string

//插入一个字符
string& insert(size_t pos, char c)
{
	assert(pos <= _size);
	if (_size == _capacity)//判断是否需要增容
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//从后往前挪动数据,以免被覆盖
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = c;
	++_size;
	return *this;
}

insert() string

The principle is similar to inserting a character

//插入字符串
string& insert(size_t pos, const char* s)
{
	assert(pos <= _size);//检查有效位置
	size_t len = strlen(s);//记录要插入的字符串长度
	if (_size + len > _capacity)
	{
		reserve(_size + len);//按需扩容
	}

	size_t end = _size + len;
	while (end > pos + len)//挪动数据
	{
		_str[end] = _str[end - len];
		--end;
	}
	strncpy(_str + pos, s, len);//拷贝数据到pos位置
	_size += len;
	return *this;
}

Tail insert push_back( )

  push_back is to splicing characters at the end of a string. We must first consider whether the space is enough. If it is not enough, we can use the reserve function to expand by 2 times, (not necessarily 2 times), then insert the data, and finally fill in at the end. \0 is enough.

void push_back(char c)
{
	if (_size == _capacity)//扩容
	{
        //这里要注意,刚开始初始化给定的_capacity=0时
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//再插入
	_str[_size] = c;
	++_size;
	_str[_size] = '\0';
}

You can also reuse the insert function directly. Tail insertion means inserting at the position where the pos position is _size.

//尾插字符
void push_back(char c)
{
	insert(_size, c); //在字符串末尾插入字符ch
}

Append the string append( )

  Append is actually similar to push_back, except that append is splicing a string. The problem here is that the string is doubled? Expand 1.5 times? Not very sure. What we need here is the number of incoming strings. Our space must be at least _size+len, and the space is just enough to satisfy all characters. After opening the space, use strcpy to copy the data in the string.

//追加字符串
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//扩容
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

  It is also possible to reuse the insert function, which can be inserted at the end of the original string.

//尾插字符串
void append(const char* str)
{
	insert(_size, str); //在字符串末尾插入字符串str
}

operator+= plus equals one character

  Add equals is also a function we use more, because it is convenient and concise, and it is easy to understand at a glance. We can reuse push_back, pay attention to returning a new object.

//加等于一个字符
string& operator+=(char c)
{
	//复用push_back
	push_back(c);
	return *this;
}

operator+= plus equals a string

reuse append

//加等于一个字符串
const string& operator+=(char* str)
{
	//复用append
	append(str);
	return *this;
}

erase delete function

We want to delete len characters starting at position pos

We need to consider two questions:

The first one: delete all positions after the pos position, put \0 in the pos position, _size-=pos

  The second one: Only a part of the characters after the pos position is deleted, and the remaining part is to be filled in the previous empty space.

//删除pos位置开始的n个字符
string& erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if (len == npos || pos + len > _size)//删除pos位置之后的所有字符
	{
		_size = pos;
		_str[_size] = '\0';
	}
	else//删除pos位置之后的部分字符
	{
		strcpy(_str + pos, _str + pos + len);//用后面的剩余字符填充空位
		_size -= len;
	}
	return *this;//返回新的对象
}

Relational operator overloaded functions

operator< less than 

  The strcmp() function compares not the length of the string, but the size of the characters at the corresponding position in the string (that is, the ASCII code value is compared, and it is also case-sensitive, the incoming c_str is a non-empty pointer

bool operator<(const string& s1, const string& s2)
{
	//	size_t i1 = 0;
	//	size_t i2 = 0;
	//	while (i1 < s1.size() && i2 < s2.size())
	//	{
	//		if (s1[i1]<s2[i2])
	//		{
	//			return true;
	//		}
	//		else if (s1[i1]>s2[i2])
	//		{
	//			return false;
	//		}
	//		else
	//		{
	//			++i1;
	//			++i2;
	//		}
	//	}
	//	//while的条件是 且
	//	//一个一个的字符按照ASCII码值去比较
	//	//现在是有一个字符串已经走完了
	//	// "abcd"   "abcd"    false
	//	// "abcd"   "abcde"   true   
	//	// "abcde"  "abcd"    false
	//	return i2 < s2.size() ? true : false;

	//c_str是一个内容为字符串指向字符数组的临时指针
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

operator== is equal to 

bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;

}

operator<= less than or equal to 

  Implementing the above less than and equal to, the following operators can be reused already implemented.

reuse less than or equal to

bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}

operator> greater than

Reuse the inverse of less than or equal to


bool operator>(const string& s1, const string& s2)
{
	return !(s1 < s2&& s1 == s2);
}

operator>= greater than or equal to 

reuse less than

bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}

operator!= is not equal to 

multiplexing is equal to

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

Stream insert stream extract function overload

stream extraction 

  It should be noted that the ostream and istream classes are the base classes in the C++ standard output stream. It can output built-in types. We can write it as a global function or overload it as a friend, but the friend destroys the encapsulation of the program. ,Not recommended.

ostream& operator<<(ostream& out, const string& s)
//ostream&是系统的类型,它能够实现输出内置类型的数据
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

stream insertion 

We should pay attention to cleaning up the s object first, so as not to give an initial value when initializing the object.

istream& operator>>(istream& in, string& s)
{
    s.clear();//防止对象初始化已经给了值
	char ch = in.get();
	while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止
	{
		s += ch;
		ch = in.get();//再获取下一个
	}
	return in;
}

other functions

Swap( ) swap function

  Because the swap function is used in many places, and the implementation is similar, it may be encapsulated as a member function.


//模拟string类里面的交换函数
void Swap(string& s)
{
	std::swap(_str, s._str);//用C++库里面自带的交换函数
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

  We can query, there is a swap function in the C++ global library, and there is also a swap function in the string class. These two functions can be exchanged

The swap function in the std library:

The swap function in the string class:

int main()
{
	std::string s1("hello");
	std::string s2("C++");
	s1.swap(s2);//调用string类里面的swap函数
	swap(s1,s2);//调用全局的swap函数
	return 0;
}

Which of the two is more efficient?

  Answer : The special exchange function in the string class is more efficient, because it only exchanges member variables; while the std library is implemented with templates, which will create temporary objects and perform three deep copies, which is more expensive! 

string in c form

returns a non-null pointer

//C语言形式的字符串
const char* c_str()const
{
	return _str;
}

Thanks for watching!

Guess you like

Origin blog.csdn.net/weixin_57675461/article/details/123800470