목차
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;
}
}
마지막에 작성하십시오.
이상이 이 글의 내용입니다. 읽어주셔서 감사합니다.
무언가를 얻었다고 생각되면 블로거에게 좋아요를 줄 수 있습니다 .
기사 내용에 누락이나 실수가 있으면 블로거에게 비공개 메시지를 보내거나 댓글 영역에 지적하십시오 ~