C++中为我们提供了字符串类型(string),使得我们在处理字符串时非常方便。在此将实现我们常用的方法。
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include <assert.h>
using namespace std;
inline void SafeRelease(char* s){
if (s != nullptr)
{
delete[]s;
s = nullptr;
}
}
class MyString{
public:
typedef char* Iterator;
public:
MyString::MyString(const char* str = "")
{
if (nullptr == str){
assert(false);
return;
}
_size = myStrlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
myStrcpy(_str, str);
}
MyString::MyString(const MyString& str)
:_str(new char[str._capacity + 1])
, _size(str._size)
, _capacity(str._capacity)
{
myStrcpy(_str, str._str);
}
MyString& operator=(const MyString& str);
MyString& operator+(const MyString& str);
MyString::~MyString()
{
if (nullptr != _str){
delete[] _str;
_str = nullptr;
}
}
Iterator Begin();
Iterator End();
void Push_back(char c);
void Pop_back();
void Append(size_t n, char c);
void Append(const char* str);
MyString& operator+=(char c);
MyString& operator+=(const char* str);
void Clear();
void Swap(MyString& str);
const char* C_str()const;
size_t Size()const;
size_t Capacity()const;
bool Empty()const;
void Resize(size_t newSize, char c = char());
void Reserve(size_t newCapacity);
char& operator[](size_t index);
const char& operator[](size_t index)const;
bool operator<(const MyString& s);
bool operator<=(const MyString& s);
bool operator>(const MyString& s);
bool operator>=(const MyString& s);
bool operator==(const MyString& s);
bool operator!=(const MyString& s);
size_t Find(const char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t Find(const char* s, size_t pos = 0) const;
// 截取string从pos位置开始的n个字符
MyString SubStr(size_t pos, size_t n);
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
MyString& Insert(size_t pos, char c);
MyString& Insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
MyString& Erase(size_t pos, size_t len);
private:
friend ostream& operator<<(ostream& o, MyString& str);
friend istream& operator >> (istream& is, MyString& ss);
friend int IndexKMP(const char* S, const char* T);
friend void GetNext(string T, int* next);
private:
int myStrlen(const char* str);
char* myStrcpy(char* dest, const char* src);
private:
char* _str;
size_t _capacity;
size_t _size;
};
为方便查看代码已传至github。相关实现代码:https://github.com/Timecur/STL.git
在这里主要就介绍一些实现string类时遇到的问题。
- 浅拷贝问题
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以 当继续对资源进项操作时,就会发生非法访问。
解决方法:深拷贝。
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
- delete释放问题
inline void SafeRelease(char* s){
if (s != nullptr)
{
delete[]s;
s = nullptr;
}
}
MyString& MyString::Insert(size_t pos, char c){
assert(pos <= _size);
char* str = new char[_size+1]; // size一定要足够大
myStrcpy(str, _str);
if (_size + 1 > _capacity)
Reserve(_size + 1);
_str[pos] = c;
_str[pos+1] = '\0';
strcat(_str, str+pos);
SafeRelease(str);
return *this;
}
最初为str申请了size大小的空间,但是在strcopy时,由于还要写入 '\0' 所以拷贝后的str实际上是size+1的大小,因此破坏了原来的指针,所以在delete释放时会出错。
参考博客:
https://blog.youkuaiyun.com/fjz13/article/details/2535126
http://www.cppblog.com/kerlw/archive/2007/04/10/21604.aspx
- KMP算法
在实现find方法中的字符串查找时,使用了KMP算法。
KMP算法:是在每次模式串与目标串失配地方,找寻模式串最合适位置继续匹配。
例如:
可以看出在下标为2的位置失配, 从图可以看出T[1] = T[2] = 'b',所以T[2]处失配,T[1]也一定失配,所以最好方法就是从T[0]处去匹配(T[0]为当前合适位置)。
当T[0]与S[2]匹配发现依然失配,而T[0]为模式串首位元素,则只能从S串下一元素进行匹配。
以此方式进行直至匹配成功或匹配完目标串的所有字符。
size_t MyString::Find(const char* s, size_t pos) const{
assert(pos >= 0);
return IndexKMP(_str + pos, s);
}
int IndexKMP(const char* S,const char* T)
{
int i = 0;
int j = 0;
int s_len = strlen(S);
int t_len = strlen(T);
int *next = new int[t_len + 1];
GetNext(T, next);
while (i < s_len && j < t_len){
if (-1 == j || S[i] == T[j]){
++i;
++j;
}
else{
j = next[j];
}
}
delete[] next;
if (j == t_len){
return i - t_len;
}
else{
return 0;
}
}
在了解KMP算法后,我们还需要一个next数组来辅助我们。next数组作用是记录当前元素失配后,下一合适元素的下标。
构建方法:
- 初始化0号下标位置存 -1。
- 前缀:i , 后缀:j
举例来理解前缀后缀:
字符串:a b b a
前缀 后缀 共有元素 a 空 空 0 ab a b 0 abb a,ab bb,b 0 abba a,ab,abb
bba,ba,a 1
- i == j 时 next[j] = next[i]+1,前后缀向下匹配;
- i != j 时 则在前缀从头再与后缀比较,直到 i == -1(next[j] = -1) 或 字符串匹配(如上步)。
void GetNext(string T, int* next)
{
int i = 0, j = -1;
next[0] = -1;
// 前缀是固定的,后继是相对的
while (i < T.size()){
if (-1 == j || T[i] == T[j]){
if (T[++i] != T[++j]){
next[i] = next[j];
}
else{
next[i] = j;
}
}
else{
j = next[j];
}
}
}
参考KMP算法博客: http://www.cnblogs.com/SYCstudio/p/7194315.html
https://zhuanlan.zhihu.com/p/24274982
前缀后缀详解博客:https://blog.youkuaiyun.com/maotianwang/article/details/34466483