[C++] STL | 간단한 문자열을 얻기 위한 시뮬레이션

목차

1. 뼈대 구축

 2. 반복자의 구현

3. 문자열 복사 구성 및 할당(깊은 복사)

복사 공사

할당 구조

4. 문자열 추가, 삭제, 확인 및 수정

예약 인터페이스

인터페이스 크기 조정

푸시백 인터페이스

추가 인터페이스

연산자+=() 구현

 삽입 인터페이스

 인터페이스 지우기

인터페이스 찾기

substr 인터페이스

명확한 인터페이스

스트림 삽입 및 스트림 추출 

5. 비교를 위한 연산자 오버로드 함수

연산자<

연산자==

 기타 멀티플렉싱

6. 소스 코드 공유

마지막에 작성하십시오.


1. 뼈대 구축

먼저 프레임워크를 구축해야 합니다.

그런 다음 내용을 채웁니다.

프레임워크에는 주로 다음 사항이 포함됩니다.

1. 기본 기본 멤버 함수

2. 필수 멤버 변수

3. 일반적으로 사용되는 간단한 인터페이스(코드를 먼저 실행)

코드를 살펴보십시오.

#pragma once

#include <iostream>
#include <string>
#include <assert.h>

#include <string.h>

using namespace std;

namespace xl {
	class string {
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

	public:
		string(const char* str = "")
			: _size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

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

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

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

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

구현된 기능은 다음과 같습니다.

1. 생성자와 소멸자

2. 기본 [ ] 액세스

3. 변환 가능한 c_str

4. 용량과 관련된 크기 

순회를 먼저 실행할 수 있습니다.

#include "string.h"

int main()
{
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	for (int i = 0; i < s1.size(); i++) {
		cout << s1[i] << " ";
	}
	cout << endl;

	return 0;
}

산출:

 2. 반복자의 구현

반복자는 포인터일 수도 있고 아닐 수도 있습니다.

그러나 문자열에서 반복자는 포인터입니다.

표준 라이브러리의 iterator가 클래스에 존재하기 때문에 우리는 iterator를 클래스에 구현합니다.

클래스 필드를 통해 직접 액세스할 수 있습니다.

코드를 살펴보십시오.

public:
	typedef char* iterator;

	iterator begin() {
		return _str;
	}

	iterator end() {
		return _str + _size;
	}

이렇게 하면 직접 실행할 수 있습니다.

#include "string.h"

int main()
{
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	for (int i = 0; i < s1.size(); i++) {
		cout << s1[i] << " ";
	}
	cout << endl;

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

산출:

 하지만 아, 이것은 일반 객체의 반복자만 지원합니다.

const 개체도 있으므로 다른 개체를 구현해야 합니다.

public:
	typedef char* iterator;
	typedef const char* const_iterator;

	iterator begin() {
		return _str;
	}

	iterator end() {
		return _str + _size;
	}

	const_iterator begin() const {
		return _str;
	}

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

우리는 관찰할 수 있습니다,

const 반복자를 사용하는 것은 실제로 금지되어 있습니다.

 그러나 일반 반복자를 사용하면 가리키는 값을 수정할 수 있습니다.

void test2() {
	xl::string s1("hello");

	xl::string::const_iterator cit = s1.begin();
	while (cit != s1.end()) {
		//*cit += 1;
		cout << *cit << " ";
		cit++;
	}
	cout << endl;

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

산출:

3. 문자열 복사 구성 및 할당(깊은 복사)

복사 공사

새 공간을 열 필요:

string(const string& s) {
	_str = new char[s._capacity + 1];
	memcpy(_str, s._str, s.size() + 1);
	_size = s._size;
	_capacity = s._capacity;
}

할당 구조

오래된 공간을 삭제하고 새 공간을 열고 데이터를 복사하는 전략을 직접 채택합니다.

string& operator=(const string& s) {
	if (this != &s) {
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp;
	}
	return *this;
}

 위에서 언급한 잘 정돈된 방법을 우리는 전통적 글쓰기 방법이라고 부르며,

그렇다면 전통적인 글쓰기 방식과 물론 현대적인 글쓰기 방식이 있습니다. 다음과 같은 글쓰기 방식을 살펴보겠습니다.

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

// 现代写法
string& operator=(string tmp) {
	swap(tmp);
	return *this;
}

우리는 문자열 클래스에서 스왑을 구현했으며 복사 구성으로 형성된 tmp는 우리가 작업하는 데 도움이 될 것입니다.

그런 다음 스왑을 사용하여 tmp의 콘텐츠를 매춘할 수 있습니다.

나는 개인적으로 이 방법이 본질적으로 복사 구성의 재사용이라고 생각합니다.

사실 복사 구성은 현대적인 방식으로도 작성할 수 있습니다.

string(const string& s)
	: _str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);
	swap(tmp);
}

아니, 모사구성이라는 현대적 글쓰기 방식의 본질은 재사용이기도 하고,

그가 재사용하는 것은 우리가 구현한 생성자입니다. 

4. 문자열 추가, 삭제, 확인 및 수정

멋진 인터페이스를 구현하기 전에

먼저 확장 문제를 해결해 봅시다.

예약 인터페이스

주어진 n의 크기에 따라 직접 확장할 수 있습니다.

void reserve(size_t n) {
	if (n > _capacity) {
		char* tmp = new char[n + 1];
		memcpy(tmp, _str, _size + 1);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

인터페이스 크기 조정

용량을 늘리는 또 다른 방법은 크기 조정이 있지만 문자열은 거의 사용되지 않는 크기 조정,

구현을 보자:

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

푸시백 인터페이스

 문자열의 push_back은 끝에 요소를 삽입하는 것입니다.

이중 확장 메커니즘을 채택합니다(개인 취향에 따라 1.5배 확장도 있음).

void push_back(char ch) {
	if (_size == _capacity) {
		// 2倍扩容
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}

추가 인터페이스

append는 끝에 문자열을 삽입하는 것입니다.

내가 사용하는 확장 메커니즘은 주문형 확장입니다.

void append(const char* str) {
	size_t len = strlen(str);
	if (_size + len > _capacity) {
		// 至少扩容到 _size + len
		reserve(_size + len);
	}
	memcpy(_str + _size, str, len + 1);
	_size += len;
}

물론 실제로는 +=를 사용하는 것을 선호합니다.

연산자+=() 구현

물론 이 함수의 경우 이전에 구현된 push_back 및 추가를 직접 재사용할 수 있습니다.

string& operator+=(char ch) {
	push_back(ch);
	return *this;
}

string& operator+=(const char* str) {
	append(str);
	return *this;
}

이것은 물론 사용하기가 훨씬 쉽습니다.

void test4() {
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	s1 += " ";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += " ";
	s1 += "string";
	cout << s1.c_str() << endl;
}

산출:

 삽입 인터페이스

실제로 STL의 문자열은 많은 중복 오버로드를 구현합니다.

학습으로 핵심 호출 방법만 구현합니다.

삽입 우리는 두 가지 오버로드를 구현합니다.

void insert(size_t pos, size_t n, char ch) {

}

void insert(size_t pos, const char* str) {

}

먼저 문자를 삽입하여 첫 번째 것을 구현해 보겠습니다.

void insert(size_t pos, size_t n, char ch) {
	assert(pos <= _size);
	if (_size + n > _capacity) {
		// 至少扩容到_size + n
		reserve(_size + n);
	}
	// 挪动数据
	size_t end = _size;
	while (end >= pos && end != npos) {
		_str[end + n] = _str[end];
		end--;
	}
	// 填值
	for (size_t i = 0; i < n; i++) _str[pos + i] = ch;

	_size += n;
}

둘째, 문자열을 삽입합니다.

구현 방법은 비슷합니다.

void insert(size_t pos, const char* str) {
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity) {
		// 至少扩容到_size + len
		reserve(_size + len);
	}
	// 挪动数据
	size_t end = _size;
	while (end >= pos && end != npos) {
		_str[end + len] = _str[end];
		end--;
	}
	// 填值
	for (size_t i = 0; i < len; i++) _str[pos + i] = str[i];

	_size += len;
}

테스트해 봅시다:

void test5() {
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	s1.insert(5, 3, 'x');
	s1.insert(0, "string ");
	cout << s1.c_str() << endl;
}

산출:

 인터페이스 지우기

삭제된 문자가 일부 문자를 초과하거나 삭제된 문자 수가 지정되지 않은 경우 모두 삭제됩니다.

void erase(size_t pos, size_t len = npos) {
	assert(pos <= _size);
	if (len == npos || pos + len >= _size) {
		_size = pos;
		_str[pos] = '\0';
	}
	else {
		size_t end = pos + len;
		while (end <= _size) {
			_str[pos++] = _str[end++];
		}
		_size -= len;
	}
}

우리는 그것을 테스트할 수 있습니다:

void test5() {
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	s1.insert(5, 3, 'x');
	s1.insert(0, "string ");
	cout << s1.c_str() << endl;

	s1.erase(10, 3);
	cout << s1.c_str() << endl;

	s1.erase(2, 100);
	cout << s1.c_str() << endl;
}

산출:

인터페이스 찾기

단일 문자인 경우 직접 찾으십시오.

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

문자열의 경우 strstr을 사용하여 격렬하게 일치시킬 수 있습니다.

size_t find(const char* str, size_t pos = 0) {
	const char* ptr = strstr(_str + pos, str);
	if (ptr) return ptr - _str;
	else return npos;
}

substr 인터페이스

문자열 가로채기 작업:

string substr(size_t pos = 0, size_t len = npos) {
	assert(pos <= _size);
	size_t n = len + pos;
	if (len == npos || pos + len > _size) {
		n = _size;
	}
	string tmp;
	tmp.reserve(n);
	for (size_t i = pos; i < n; i++) {
		tmp += _str[i];
	}
	return tmp;
}

테스트해 봅시다:

void test6() {
	xl::string s1("hello string");
	cout << s1.c_str() << endl;
	
	size_t pos = s1.find('s');
	cout << s1.substr(pos, 3).c_str() << endl;

	pos = s1.find('s');
	cout << s1.substr(pos, 100).c_str() << endl;
}

산출:

명확한 인터페이스

그건 그렇고, 그것을 깨달으십시오.

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

스트림 삽입 및 스트림 추출 

연산자의 순서가 필요하기 때문에 클래스 외부에서 구현해야 합니다.

먼저 스트림 삽입을 살펴보겠습니다.

ostream& operator<<(ostream& out, const string& s) {
	for (auto e : s) cout << e;
	return out;
}

스트림 추출을 다시 살펴보겠습니다.

istream& operator>>(istream& in, string& s) {
	s.clear();
	char ch = in.get();
	while (ch != ' ' && ch != '\n') {
		s += ch;
		ch = in.get();
	}
	return in;
}

시험시간~

void test7() {
	string s1;
	cin >> s1;
	cout << s1 << endl;
}

산출:

5. 비교를 위한 연산자 오버로드 함수

여전히 동일한 작업을 수행하고 두 가지를 먼저 구현한 다음 모두 재사용합니다.

연산자<

라이브러리 함수 memcmp를 사용하여 다음을 달성합니다.

bool operator<(const string& s) {
	int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
	return ret == 0 ? _size < s._size : ret < 0;
}

연산자==

bool operator==(const string& s) {
	return memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}

 기타 멀티플렉싱

bool operator<=(const string& s) {
	return *this < s || *this == s;
}

bool operator>(const string& s) {
	return !(*this <= s);
}

bool operator>=(const string& s) {
	return !(*this < s);
}

bool operator!=(const string& s) {
	return !(*this == s);
}

6. 소스 코드 공유

#pragma once

#include <iostream>
#include <string>

#include <assert.h>
#include <string.h>

using namespace std;

namespace xl {
	class string {
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		const static size_t npos = -1; // 可以这样用,但不建议,违背了C++的语法准则(建议声明和定义分离)

	public:
		string(const char* str = "")
			: _size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			memcpy(_str, str, _size + 1);
		}

		 传统写法
		//string(const string& s) {
		//	_str = new char[s._capacity + 1];
		//	memcpy(_str, s._str, s._size + 1);
		//	_size = s._size;
		//	_capacity = s._capacity; 
		//}

		// 现代写法
		string(const string& s) 
			: _str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}

		 传统写法
		//string& operator=(const string& s) {
		//	if (this != &s) {
		//		char* tmp = new char[s._capacity + 1];
		//		memcpy(tmp, s._str, s._size + 1);
		//		delete[] _str;
		//		_str = tmp;
		//	}
		//	return *this;
		//}

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

		// 现代写法
		string& operator=(string tmp) {
			swap(tmp);
			return *this;
		}

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

	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin() {
			return _str;
		}

		iterator end() {
			return _str + _size;
		}

		const_iterator begin() const {
			return _str;
		}

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

	public:
		void reserve(size_t n) {
			if (n > _capacity) {
				char* tmp = new char[n + 1];
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

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

		void push_back(char ch) {
			if (_size == _capacity) {
				// 2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}
		
		void append(const char* str) {
			size_t len = strlen(str);
			if (_size + len > _capacity) {
				// 至少扩容到 _size + len
				reserve(_size + len);
			}
			memcpy(_str + _size, str, len + 1);
			_size += len; 
		}

		string& operator+=(char ch) {
			push_back(ch);
			return *this;
		}

		string& operator+=(const char* str) {
			append(str);
			return *this;
		}

		void insert(size_t pos, size_t n, char ch) {
			assert(pos <= _size);
			if (_size + n > _capacity) {
				// 至少扩容到_size + n
				reserve(_size + n);
			}
			// 挪动数据
			size_t end = _size;
			while (end >= pos && end != npos) {
				_str[end + n] = _str[end];
				end--; 
			}
			// 填值
			for (size_t i = 0; i < n; i++) _str[pos + i] = ch;

			_size += n;
		}

		void insert(size_t pos, const char* str) {
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity) {
				// 至少扩容到_size + len
				reserve(_size + len);
			}
			// 挪动数据
			size_t end = _size;
			while (end >= pos && end != npos) {
				_str[end + len] = _str[end];
				end--;
			}
			// 填值
			for (size_t i = 0; i < len; i++) _str[pos + i] = str[i];

			_size += len;
		}

		void erase(size_t pos, size_t len = npos) {
			assert(pos <= _size);
			if (len == npos || pos + len >= _size) {
				_size = pos;
				_str[pos] = '\0';
			}	
			else {
				size_t end = pos + len;
				while (end <= _size) {
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

		size_t find(char ch, size_t pos = 0) {
			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 char* ptr = strstr(_str + pos, str);
			if (ptr) return ptr - _str;
			else return npos;
		}

		string substr(size_t pos = 0, size_t len = npos) {
			assert(pos <= _size);
			size_t n = len + pos;
			if (len == npos || pos + len > _size) {
				n = _size;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < n; i++) {
				tmp += _str[i];
			}
			return tmp;
		}

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

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

		bool operator<(const string& s) {
			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;
		}

		bool operator==(const string& s) {
			return _size == s._size && memcmp(_str, s._str, _size) == 0;
		}

		bool operator<=(const string& s) {
			return *this < s || *this == s;
		}

		bool operator>(const string& s) {
			return !(*this <= s);
		}

		bool operator>=(const string& s) {
			return !(*this < s);
		}

		bool operator!=(const string& s) {
			return !(*this == s);
		}

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

		size_t size() const {
			return _size;
		}

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

	ostream& operator<<(ostream& out, const string& s) {
		for (auto e : s) cout << e;
		return out;
	}

	istream& operator>>(istream& in, string& s) {
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n') {
			s += ch;
			ch = in.get();
		}
		return in;
	}
}

마지막에 작성하십시오.

이상이 이 글의 내용입니다. 읽어주셔서 감사합니다.

무언가를 얻었다고 생각되면 블로거에게 좋아요를 줄 수 있습니다 .

기사 내용에 누락이나 실수가 있으면 블로거에게 비공개 메시지를 보내거나 댓글 영역에 지적하십시오 ~

추천

출처blog.csdn.net/Locky136/article/details/131640133