C++之string
string的模拟实现
insert插入字符
代码如下:
C++
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size ;
while (end >= pos)
{
_str[end+1] = _str[end ];
--end;
}
_str[pos] = ch;
_size++;
}
这里面在头插的情况下,编译器会报错
因为size_t是无符号,头插会因为C语言数据存储的原因,数据超过了最大程度
调试的时候可以尝试条件断点
就是写入一段无关的程序,但是打断点的时候,必须要内容
对上面的代码优化得到了:
C++
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size ;
while (end >=(int) pos)//类型转换,否则数据要进行整型提升
{
_str[end+1] = _str[end ];
--end;
}
_str[pos] = ch;
_size++;
}
但是pos==0也存在问题,所以不能出现等号
进一步优化得到:
C++
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//这里举个例子,相当于放在hello world\0的'\0'后面
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
insert插入字符串
代码如下:
C++
void string::insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
//如果2倍还不够,就有多少扩容多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
size_t end = _size + len;
while (end > pos + len - 1)//这里很关键
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
拿hello world来举例子:
pos+len-1减去1是为了把h,也就是开头的字符顶掉
erase消除字符串
代码:
C++
void string::erase(size_t pos, size_t len)
{
assert(pos < len);
if (len >= _size - pos)
{
//用0赋值过去
_str[pos] = '\0';
_size = pos;
}
else
{
//从后往前挪
size_t end = pos + len;
while (end<=_size)
{
_str[end - len] = _str[end];
++end;
}
_size -= len;
}
}
find查找字符
代码:
C++
size_t string::find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
//返回对应的下标,找到的情况下
if (ch == _str[i])
return i;
}
//找不到就返回无
return npos;
}
这里对npos再进行进一步说明:
C++
public:
//static const size_t npos=-1;//声明和定义分离
//const修饰的静态成员变量可以给缺省值,对于整型的特例
//不建议这样做,还是建议正常的声明和定义分离
static const size_t npos;
定义的时候进行初始化,const修饰的对象只能修改一次值,就是初始化的时候才能修改
C++
const size_t string::npos= -1;//不能定义两次npos,一个文件里面
//const修饰的对象,定义只有一次才能初始化
find查找字符串
代码:
C++
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
substr提取字符串
代码如下:
string.h
C++
string substr(size_t pos, size_t len = npos);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
string.cpp
C++
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//大于后面串的长度,则直接取完
if (len > (_size - pos))
{
len = _size - pos;
}
Tzuyu::string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
和上面find查找字符串综合起来的应用如:
提取网址的部分数据:
C++
void test_string01()
{
Tzuyu::string s3("hello world");
cout << s3.find(' ') << endl;
cout << s3.find("wo") << endl;
Tzuyu::string s4 = "https://legacy.cplusplus.com/reference/cstring/strstr/?kw=strstr";
size_t pos1 = s4.find(':');
size_t pos2 = s4.find('/', pos1 + 3);
if (pos1 != string::npos && pos2 != string::npos)
{
Tzuyu::string domain = s4.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << domain.c_str() << endl;
Tzuyu::string uri = s4.substr(pos2 + 1);
cout << uri.c_str() << endl;
}
}
浅拷贝和深拷贝
具体来说,String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。
下面是一个赋值拷贝
代码如下:
C++
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;//出了作用域,自己返回自己
}
这也可以自己给自己赋值,有点像汽车窗子的防夹手功能
比较大小
代码如下:
string.h
C++
//比较大小这是相对于私有的来说的,用类域里面,不是私有的可以考虑函数重载比较大小
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
string.cpp
C++
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
test.cpp
C++
cout << (s1 == s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 == "hello world") << endl;
cout << ("hello world" == s1) << endl;
流插入和流提取
流插入
string.h
C++
ostream& operator<<(ostream& os, const string& str);
string.cpp
C++
ostream& operator<<(ostream& os, const string& str)
{
//os<<'"';
//os << "xx\"xx";//这不可以是因为无法判断结束的位置
for (size_t i = 0; i < str.size(); i++)
{
//os << str[i];
os << str[i];
}
//os << '"';
return os;
}
流提取
string.h
C++
istream& operator>>(istream& is, string& str);
void clear()
{
_str[0] = '\0';
_size = 0;
}
string.cpp
C++
istream& operator>>(istream& is, string& str)
{
str.clear();
char ch;
//is >> ch;//遇到空格会停止输入,进入缓冲区
ch = is.get();//get会解决is>>的问题
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
完整代码实现
string.h
C++
#pragma once
#include <iostream>
#include <assert.h>
#include <string>
using namespace std;
namespace Tzuyu
{
class string
{
public:
string(const char* str = " ");
~string();
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);//不建议过多使用,因为是全局变量
string& operator+=(const char* str);
public:
//static const size_t npos=-1;//声明和定义分离
//const修饰的静态成员变量可以给缺省值,对于整型的特例
//不建议这样做,还是建议正常的声明和定义分离
static const size_t npos;
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
using iterator = char*;
using const_iterator = const char*;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
size_t size() const
{
return _size;
}
const char* c_str() const
{
return _str;
}
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos);
string substr(size_t pos, size_t len = npos);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
public :
/*char* _str;
size_t _size;
size_t _capacity;*/
void clear()
{
_str[0] = '\0';
_size = 0;
}
};
//比较大小这是相对于私有的来说的,用类域里面,不是私有的可以考虑函数重载比较大小
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
ostream& operator<<(ostream& os, const string& str);
istream& operator>>(istream& is, string& str);
}
string.cpp
C++
#include "string.h"
namespace Tzuyu
{
const size_t string::npos= -1;//不能定义两次npos,一个文件里面
//const修饰的对象,定义只有一次才能初始化
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
string::~string()
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
void string::push_back(char ch)
{
//if (_size == _capacity)
//{
// reserve(_capacity == 0 ? 4 : _capacity * 2);
//}
//_str[_size] = ch;
//_size++;
insert(_size, ch);
}
void string::append(const char* str)
{
//size_t len = strlen(str);
//if (_size + len > _capacity)
//{
// size_t newCapacity = 2 * _capacity;
//扩2倍不够,则需多少扩多少
// if (newCapacity < _size + len)
// newCapacity = _size + len;
// reserve(newCapacity);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
//代码的复用
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
string&string:: operator+=(char ch)
{
push_back(ch);
return*this;
}
string& string:: operator+=(const char* str)
{
append(str);
return*this;
}
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//这里举个例子,相当于放在hello world\0的'\0'后面
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
size_t end = _size + len;
while (end > pos + len - 1)//这里很关键
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < len);
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//从后往前挪
size_t end = pos + len;
while (end<=_size)
{
_str[end - len] = _str[end];
++end;
}
_size -= len;
}
}
size_t string::find(char ch, size_t pos) //声明的时候可以给缺省值,实现的时候不能给
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
return i;
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//大于后面串的长度,则直接取完
if (len > (_size - pos))
{
len = _size - pos;
}
Tzuyu::string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
//s1(s2)
string::string(const string& s) //尽量用命名空间抱起来,否则string会被认为是库里面的
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//s1=s2=s3
//s1=s1
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
ostream& operator<<(ostream& os, const string& str)
{
//os<<'"';
//os << "xx\"xx";
for (size_t i = 0; i < str.size(); i++)
{
//os << str[i];
os << str[i];
}
//os << '"';
return os;
}
istream& operator>>(istream& is, string& str)
{
str.clear();
char ch;
//is >> ch;
ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
}
test.cpp
C++
#include "string.h"
void test_string01()
{
Tzuyu::string s3("hello world");
cout << s3.find(' ') << endl;
//cout << s3.find("wo") << endl; 没有实现find(const char*)
Tzuyu::string s4 = "https://legacy.cplusplus.com/reference/cstring/strstr/?kw=strstr";
size_t pos1 = s4.find(':');
size_t pos2 = s4.find('/', pos1 + 3);
if (pos1 != string::npos && pos2 != string::npos)
{
Tzuyu::string domain = s4.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << domain.c_str() << endl;
Tzuyu::string uri = s4.substr(pos2 + 1);
cout << uri.c_str() << endl;
}
}
void test_string06()
{
Tzuyu::string s1("hello world");
Tzuyu::string s2(s1);//拷贝构造
Tzuyu::string s3 = s1;//这里也是拷贝构造,不能单独看=就认为是赋值,赋值的前提是
//两个对象都创立了
Tzuyu::string s4 = "hello world";
//隐式类型转换,构造+拷贝构造->直接构造
//operator<<(cout, s1);
cout << s1 << endl;
cin >> s1;
cout << s1 << endl;
std::string ss1("hello world");
cin >> ss1;
cout << ss1 << endl;
}
int main()
{
Tzuyu::string s2;
cout << s2.c_str() << endl;
Tzuyu::string s1("hello world");
cout << s1.c_str() << endl;
s1[0] = 'x';
cout << s1.c_str() << endl;
Tzuyu::string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
(*it1)--;
++it1;
}
cout << endl;
it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
for (auto& ch : s1)
{
ch++;
}
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
const string s3("xxxxxxxx");
for(auto& ch : s3)
{
//ch++;//不可以这样进行操作,因为auto自动推导的时候发现的是const修饰的,不能修改
cout << s3 << " ";
}
cout << endl;
return 0;
}