Table of contents
2. Implementation of iterators
3. String copy construction and assignment (deep copy)
4. Add, delete, check and modify string
Stream Insertion and Stream Extraction
5. Operator Overloaded Functions for Comparison
1. Framework construction
First of all, we need to build a framework,
Then fill it with content.
The framework mainly includes the following points:
1. Basic default member functions
2. Required member variables
3. Commonly used simple interfaces (let the code run first)
Look at the code:
#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;
}
};
}
Implemented functions include:
1. Constructor and destructor
2. Basic [ ] access
3. c_str available for conversion
4. And the size related to the capacity
We can run a traversal first:
#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;
}
output:
2. Implementation of iterators
Iterators may or may not be pointers,
But in string, iterators are pointers.
We implement the iterator into the class, because the iterator in the standard library exists in the class,
We can access it directly through the class field.
Look at the code:
public:
typedef char* iterator;
iterator begin() {
return _str;
}
iterator end() {
return _str + _size;
}
This way we can run directly:
#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;
}
output:
But ah, this only supports iterators of ordinary objects,
There are also const objects, so we have to implement another one:
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;
}
We can observe,
Using const iterators is indeed forbidden:
However, using ordinary iterators can modify the pointed value:
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;
}
output:
3. String copy construction and assignment (deep copy)
copy construction
Need to open a new space:
string(const string& s) {
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s.size() + 1);
_size = s._size;
_capacity = s._capacity;
}
assignment construct
We directly adopt the strategy of deleting old space, opening up new space, and copying data:
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;
}
The above-mentioned well-regulated method, we call it the traditional writing method,
Then there are traditional ways of writing, and of course modern ways of writing, let’s look at this way of writing:
void swap(string& tmp) {
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
// 现代写法
string& operator=(string tmp) {
swap(tmp);
return *this;
}
We have implemented a swap in a string class, and the tmp formed by copy construction will help us work,
Then we can use the swap to prostitute the content of tmp.
I personally think that this method is essentially the reuse of copy construction.
In fact, copy construction can also be written in the modern way:
string(const string& s)
: _str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
Found no, the essence of the modern writing method of copy construction is also a reuse,
What he reuses is the constructor we implemented.
4. Add, delete, check and modify string
Before implementing those fancy interfaces,
Let's solve the problem of expansion first:
reserve interface
According to the size of the given n, it can be expanded directly:
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;
}
}
resize interface
There is another way to expand capacity is resize, but string is rarely used resize,
Let's see the implementation:
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 interface
The push_back of string is to insert an element at the end.
The mechanism of double expansion is adopted (this depends on personal preference, there is also 1.5 times expansion)
void push_back(char ch) {
if (_size == _capacity) {
// 2倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
append interface
append is to insert a string at the end,
The expansion mechanism I use is on-demand expansion.
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;
}
Of course, we actually prefer to use +=:
operator+=() implementation
Of course, for this function, we can directly reuse the push_back and append implemented earlier:
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
This is of course much easier to use:
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;
}
output:
insert interface
In fact, STL's string implements many redundant overloads,
As a learning, we only implement the core calling method.
Insert we implement two overloads:
void insert(size_t pos, size_t n, char ch) {
}
void insert(size_t pos, const char* str) {
}
Let’s implement the first one first, inserting a character:
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;
}
Second, insert a string:
The implementation methods are similar:
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;
}
Let's test it out:
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;
}
output:
erase interface
If the deleted characters exceed some characters, or if the number of deleted characters is not specified, all will be deleted:
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;
}
}
We can test it out:
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;
}
output:
find interface
If it is a single character, just find it directly:
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;
}
For strings, we can use strstr to violently match:
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 interface
The operation of intercepting the string:
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;
}
Let's test it out:
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;
}
output:
clear interface
By the way, realize it:
void clear() {
_str[0] = '\0';
_size = 0;
}
Stream Insertion and Stream Extraction
We need to implement this outside the class, because the order of operators requires,
Let's look at stream insertion first:
ostream& operator<<(ostream& out, const string& s) {
for (auto e : s) cout << e;
return out;
}
Let's look at stream extraction again:
istream& operator>>(istream& in, string& s) {
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n') {
s += ch;
ch = in.get();
}
return in;
}
Test time~
void test7() {
string s1;
cin >> s1;
cout << s1 << endl;
}
output:
5. Operator Overloaded Functions for Comparison
We still do the same operation, implement two first, and reuse them all:
operator<
We use the library function memcmp to achieve:
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;
}
operator==
bool operator==(const string& s) {
return memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}
Other multiplexing
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. Source code sharing
#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;
}
}
Write at the end:
The above is the content of this article, thank you for reading.
If you feel that you have gained something, you can give the blogger a like .
If there are omissions or mistakes in the content of the article, please private message the blogger or point it out in the comment area~