C++6: STL 시뮬레이션 구현 문자열

 문자열은 STL의 템플릿 라이브러리 중 하나로 문자열을 특별히 처리하는 데이터 구조와 유사합니다. 구현을 시뮬레이션하고 구성의 독창성을 논의하기 전에 STL이 무엇인지 간단히 이해합시다.

목차

 STL이란 무엇입니까?

STL의 탄생

문자열에 대해

문자열의 아날로그 구현

생성자와 소멸자

간단한 문자열 인쇄 구현

 복사 생성자

복사 구조를 실현하기 위해 일하는 사람을 흔드는 것

 할당 연산자 오버로딩

크기 조정 및 예약

예약하다

크기 조정

 푸시백

 append 추가

+= 연산자 오버로딩

끼워 넣다

삭제

찾다

<<화>>

<<

>>

문자열 반복자


 STL이란 무엇입니까?

 STL의 전체 이름: standard template libaray-standard template library

STL의 탄생

HP Labs의 두 전문가인 Alexander Stepanov와 Meng Lee는 오픈 소스 정신을 위해 출마하고 있습니다. 소스 코드 이것은 STL의 소스 코드입니다. 조상 버전

STL의 후속 버전: PJ 버전, RW 버전, SGI 버전, 다양한 버전의 차이점과 장단점은 설명하지 않을 것입니다. 프로그램 작성.


문자열에 대해

사실 STL은 개발 과정에서 매우 편리한 툴킷에 가깝습니다.어떤 데이터 구조를 구현해야 하는 C 언어에 비해 STL은 많은 데이터 구조를 지원합니다.우리는 몇 개의 헤더 파일만 포함하면 되며, 예를 들어 다음에 구현해야 하는 문자열과 같이 사용하기 매우 편리합니다.

일반적으로 C 언어 단계에서 문자열을 조작하는 것은 우리에게 골칫거리이지만 STL을 사용하면 매우 편리합니다.

예를 들어, 단순히 문자열을 인쇄하고 다른 문자열과 연결하고 싶습니다.

 문자열을 사용하면 문자열 인쇄를 트래버스하기 위해 for 루프를 작성할 필요가 없을 뿐만 아니라 += 연산을 사용하여 문자열 추가를 실현할 수 있습니다! 매우 편리하다고 할 수 있지만 그것이 무엇인지 알고 그것이 왜 의미가 없는지 모르기 때문에 단순히 이런 것을 사용하는 문턱은 매우 낮으며 무언가를 얻으려면 여전히 그 원리를 이해해야합니다.


문자열 인터페이스 설명 및 일화 공유

문자열은 템플릿 라이브러리 중 하나입니다.생성된 지 오래되었기 때문에 많은 중복 인터페이스가 포함되어 있으며 물론 다양한 유용한 인터페이스가 있습니다.확인이 필요한 경우 다음 웹 사이트로 이동할 수 있습니다:https: // cplusplus.com/reference/

 물론 문자열에도 더 흥미로운 점이 있습니다.Chen Hao 선배는 이 질문에 대해 말했습니다: STL의 문자열 클래스에 어떤 문제가 있습니까? _haoel의 블로그-CSDN 블로그

사용자가 개발하기 편리한 우수한 코드 마스터로서 STL의 중요성과 기타 특성에 대해 다른 글의 작성자가 저보다 더 잘 작성해야 한다고 생각하므로 더 이상 언급하지 않겠습니다.

문자열의 아날로그 구현

기본 멤버 변수

class mystring
{
   public:
    private:
		char* _str;
		size_t _size;
		size_t _capacity;
}

문자열 클래스의 구현을 시뮬레이션하는 것은 가장 기본적인 멤버 함수를 벗어날 수 없습니다.결국 우리는 힙에 공간을 열어야 하므로 생성자를 직접 구현해야 합니다.

생성자와 소멸자

 생성자는 실제로 시퀀스 테이블과 유사하지만 new를 사용하면 전체 코드가 훨씬 짧아집니다.

		
	//构造函数,字符串初始化版本,求长度,算容量,开空间
	mystring( const	char* str="")
	{
		_size = strlen(str);
		_str = new char[_size + 1];//留一个给斜杠零

			
		strcpy(_str, str);//拷贝数据到新空间
		_capacity = _size;
	}

소멸자는 해당 형식으로 삭제할 수 있습니다.

//析构函数
~mystring()
{
	delete[] _str;
    _str = nullptr;
	_size = _capacity = 0;
}

간단한 문자열 인쇄 구현

STL의 스팅을 사용하면 인쇄가 매우 편리합니다.문자열은 cout에 직접 적용되지만 현재로서는 간단한 인쇄 기능만 필요합니다.스트림 삽입 및 스트림 추출 연산자를 오버로드하는 특정 작업은 나중에 설명합니다.시작하겠습니다. 간단한 것, 구현하는 것.

먼저 전체 문자열을 역참조한 후 출력해야 하는데, 문자열 자체가 문자열의 배열이기 때문에 []의 역참조 연산을 지원한다.

void Print()
	{
		for (int i = 0; i < _size; ++i)
		{
				cout << _str[i];
		}
	}

그런 다음 내부에 문자열을 채워 효과를 확인합니다.

 물론 랜덤 액세스도 지원해야 합니다. 즉, str의 [] 역참조 기능을 지원해야 하므로 매우 간단합니다. [] 연산자를 직접 오버로드합니다.

오버로딩은 어렵지 않지만 추가 const 버전을 구현하는 것을 잊지 마십시오.

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

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

멤버 함수의 const 버전에 대해서는 여기에서 검토할 예정인데 멤버 함수의 const 버전을 구현해야 하는 이유는 무엇입니까?

const 수정 멤버 함수 

const에 의해 수정된 멤버 함수는 본질적으로 함수가 아니라 현재 클래스 개체의 멤버 함수에 있는 this 포인터입니다.

위 멤버 함수의 const 버전을 예로 들어 보겠습니다.

const char& operator[](size_t index)const

사실 실제 모습은

const char& operator[](const mystring *const this  ,size_t index)

const는 현재 This 포인터를 수정합니다. this 포인터 자체는 const를 전달합니다. 함께 제공되는 const는 this 자체를 보호하는 데 사용되지만 이 포인터가 가리키는 객체를 보호하지 않는 반면 const 멤버 함수는 가리키는 객체를 보호합니다. 에.객체.

const 버전의 멤버 함수를 구현하면 const 유형의 클래스 개체가 멤버 함수를 성공적으로 호출할 수 있습니다.

그렇지 않은 경우 const 클래스 개체는 쓸모가 없습니다.

그러나 const 버전의 모든 멤버 함수가 구현되는 것은 아니며 다음 조건이 충족되는 경우에만 구현이 필요합니다.

1. 멤버를 수정하지 않는 함수는 모두 const 유형으로 선언해야 합니다.

2. const 멤버 함수는 개체의 데이터를 수정할 수 없습니다.

 복사 생성자

 복사 생성자는 참조로 전달되어야 합니다. strcpy를 사용하기 위해 기본 문자열 헤더 포인터를 가져오려면 추가 c_str도 구현해야 합니다.

mystring(const mystring& str )
{
	_size = str._size;
	_str = new char[str._capacity+1];
	_capacity = str._capacity;

	strcpy(_str, str.c_str());

}

char* c_str()
{
	return _str;
}
char* c_str()const
{
	return _str;
}

 이 방법은 비교적 간단하고 소박합니다. 우리는 더 "현대적인" 쓰기 방법을 구현하여 사람들을 흔들어 일하게 합니다.

복사 구조를 실현하기 위해 일하는 사람을 흔드는 것

//拷贝构造现代写法,摇人打工
mystring(mystring& str)
	:_str(nullptr), _size(0), _capacity(0)
	{
		mystring tmp(str._str);
		swap(str);

	}

우리는 생성자를 사용하여 임시 변수 tmp를 생성한 다음 tmp를 현재 this와 교환합니다. .

물론 알고리즘 라이브러리 내부의 스왑 효율이 낮기 때문에 스왑을 다시 작성해야 합니다.

 주제에서 벗어난 것: T c(a); 이것은 생성자처럼 보입니다. 내장 유형을 사용할 수 없기 때문입니까?
 

C++는 템플릿을 생성한 후 내장 유형을 관리하기 위해 내장 유형의 생성자와 유사한 초기화 메소드도 제공합니다.

예를 들어:

int i(10);
int j = int();

 따라서 생성자를 호출하는 이 방법은 내장 유형에도 효과적이므로 걱정하지 마십시오.

		void swap(mystring& str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);

		}

 할당 연산자 오버로딩

 할당, 문자열을 전달하고 현재 문자열을
전달된 문자열로 바꿉니다. 생성자와 비슷하지만 연속 할당 문제에 주의해야 합니다. str1 = str2 = str3은
까다롭습니다. 직접 할당하세요.

mystring& operator=(const mystring& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);

		delete[] _str;

		_str = tmp;

		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

크기 조정 및 예약

 resize와 reserved 두 인터페이스 함수는 스팅의 공간을 제어하는 ​​데 상당히 유용하므로 다음에 구현해 보겠습니다.

예약하다

예비 기능에 대해서는 원래 코드를 참조하고 간단히 말해서 실현 기능은 용량을 재설정하지만 여전히 용량 축소 문제에 주의해야 합니다. 즉, 전달된 매개변수의 크기가 현재 용량보다 작으면 실행할 수 없으며 그렇지 않으면 축소됩니다.

	void reserve(size_t n = 0)
	{
		if (n > _capacity)
		{
            //新空间,之所以是n+1则是为了斜杠零而留出的空间
			char* tmp = new char [n + 1];

            //拷贝
			strcpy(tmp, _str);

            //更新容量
			_capacity = n;
            
            //销毁原空间
			delete[]_str;
			_str = tmp;
		}
	}

크기 조정

원래 코드의 설명을 읽으십시오. 크기 조정은 두 개의 매개 변수를 전달해야 합니다. 하나는 크기 값을 업데이트하는 것이고 다른 하나는 N이 현재 크기 값보다 클 때 빈 부분을 문자로 채울지 여부를 선택할 수 있습니다. 따라서 여기서는 기본값 '\0'을 직접 제공합니다.

	void resize(size_t  n, char c = '\0')
	{
		if (n > _size)
		{
			reserve(n);
			for (size_t i = _size; i < n; ++i)
			{
				_str[i] = c;
			}
			_size = n;
			_str[_size] = '\0';

		}
		else
		{
			_size = n;
			_str[_size] = '\0';
		}
	}

 푸시백

 우리는 모두 push_back 기능에 매우 익숙하지만 여전히 처리해야 할 몇 가지 세부 사항이 있습니다.

size의 위치는 strcpy에 의해 직접 덮어쓰여집니다. 크기를 확장할 뿐만 아니라 '\0'을 추가해야 합니다.

void push_back(char c)
	{
		//尾插,如满,扩容
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;

			reserve(newcapacity);
		}

		_str[_size] = c;
		++_size;

		//strcpy从\0处开始覆盖,需要在后面加上\0
		_str[_size] = '\0';

	}

 append 추가

 이 함수는 현재 문자열 뒤에 문자열을 추가하는 데 사용됩니다. 오버로드된 버전이 여러 개 있습니다. 여기에서 구현된 가장 간단한 것은 문자열을 추가하는 것입니다.


		//字符串追加
		void append(const char* str)
		{
			size_t  size = strlen(str);

			if (_size + size > _capacity)
			{
				reserve(_capacity + size);
			}
			strcpy(_str + _size, str);

			//只是重置了容量大小,size还没有更新。
			_size += size;


		}

+= 연산자 오버로딩

추가를 구현한 후 +=는 매우 간단하며 직접 재사용할 수 있습니다.

		mystring& operator+= (const mystring& str)
		{
			append(str.c_str());

			return *this;
		}

끼워 넣다

string& insert(size_t pos, const char* str)
{

	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	size_t end = _size+len ;
	while (end > pos + len-1)
	{
		_str[end] = _str[end -len];
		--end;
	}
	//用strncpy是因为直接strcpy会把\0也拷过去,读的时候就直接断层了,用strncpy的话可以规避掉\0
	strncpy(_str + pos, str,len);
	_size += len;


	return *this;
}

삭제

//删除
//给个npos的缺省值,当没有传递需要删除的个数的时候全部删除
//还需要额外处理,当len<size- pos的时候正常删除,如果后面还有剩余的字符串,直接挪动过去覆盖就好,没有的话就
//在POS位置放一个\0
string& erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if (len < _size - pos)
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	else if (len == npos || len >=_size + pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	return *this;
}

찾다

		size_t find(const char* str, size_t pos = 0) const
		{
			assert(pos < _size);

			char* ptr = strstr(_str+pos, str);

			if (ptr != nullptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}

		}

<<화>>

<<

스트림 삽입 및 스트림 추출 문제에 대해서는 전제 조건이 있는데 이 두 오버로드는 멤버 함수라고 할 수 없습니다.

그런 다음 전역 함수로 작성합니다.

여기서 작은 문제가 있는데 클래스와 객체 단계에서 접한 <<는 접근 한정자에 의해 제한을 받는데 친구 멤버 함수로 작성하니 오버로드된 << 함수가 친구여야 한다. ?

멤버 변수에 직접 액세스할 수 없으며 간접적으로 액세스하는 함수를 작성할 수 있습니다.

	ostream& operator<<(ostream& out,const mystring& str)
	{
		for (size_t i = 0; i < str.size(); ++i)
		{
			out << str[i];
		}
		return out;
	}

반환 값은 ostream을 반환 값 개체로 사용하여 연속 cout과 같은 작업을 지원합니다.

>>

추출된 함수는 어렵지 않으나 버퍼 문제에 주의가 필요함

먼저 istream의 get 함수를 사용하여 현재 버퍼 내부의 문자를 가져오는 간단한 구현을 해 보겠습니다. get의 동작은 get과 매우 유사하지만 C와 C++ 사이에 차이점을 만드는 것이 좋습니다.

	istream& operator>>(istream& in, mystring& str)
	{
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = in.get();
		}

		return in;
	}

현재 버전은 공백과 줄바꿈 사이의 문자만 가져올 수 있습니다.

 물론 일회성 수집도 가능하며 조건만 수정하면 됩니다.

 그러나 라이브러리의 구현은 이와 같습니다. 우리는 라이브러리를 따라 달성합니다. 그러나 여기에는 약간의 효율성 문제가 있는데, 짧은 문자열 += 연산을 처리하는 것은 가능하지만 문자열이 길어지면 효율성이 떨어집니다.

그런 다음 일괄 처리할 수 있습니다.

	istream& operator>>(istream& in, mystring& str)
	{
		str.clear();

		char ch = in.get();
		char buff[128] = { '\0' };
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			//已满,+=数据
			if (i == 127)
			{
				str += buff;			
				//i置零
				i = 0;
			}

			buff[i++] = ch;
			ch = in.get();

		}

		//已跳出循环,遇到'\n'或' '已取完当前缓冲区分割区域内的数据,
		//由于上逻辑为满127才+=数据,那么一定会有剩余情况发生,在这里额外处理

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}

		return in;
	}

문자열 반복자

 반복자의 사용이 포인터와 유사하다는 것을 알고 있으므로 간단하게 구현할 수 있습니다.

	//迭代器:
	
	typedef char* iterator;

	iterator begin()
	{
		return _str;
	}
		
	iterator end()
	{
		return _str + _size;
	}

 


이 시점에서 기본 기능이 포함된 교반이 시뮬레이션 및 구현되었습니다.

읽어주셔서 감사합니다 조금이나마 도움이 되셨으면 합니다

 

추천

출처blog.csdn.net/m0_53607711/article/details/128727435