在前面我们学习string的功能,今天我们来模拟一下string类的实现。
string(const char *str)//(1)
:_size(strlen(str)),//(2)
_capacity(_size)//(3)
{
_str = new char[_capacity + 1];//(4)
strcpy(_str, str);//(5)
}
( 1 ) (1) (1)形参传递的字符串属于常量区,不能修改,const
不能忘了
( 2 ) (2) (2)计算字符串的长度
( 3 ) (3) (3)初始化字符串容量_capacity
( 4 ) (4) (4)为字符指针开辟空间,这里需要多开一个字节,因为传进来的可能是个空串,空串含有一个'/0'
(strlen
计算到\0
即停止,不含\0
)。
------------------------------------------------------------我是分割线-----------------------------------------------------------
string()
:_size(0),//(1)
_capacity(0),//(2)
_str(new char[1])//(3)
{
_str[0] = '\0';
}
( 1 ) ( 2 ) (1)(2) (1)(2)因为传进来的是一个空串,所以_size
和_capacity
都设置为0
( 3 ) (3) (3)空串有个/0
,为其开一个字节空间。
为了简略,上面的两种构造函数可以合成一个:
string(const char*str="")//传进来的可能是空串 这里包含了无参和有参
:_str(nullptr),
_size(0),
_capacity(0)
{
int newcapacity = strlen(str);//(1)
_str = new char[newcapacity + 1];//(2)
strcpy(_str, str);//(3)
_size = newcapacity;//(4)
_capacity = newcapacity;//(5)
}
( 1 ) (1) (1)计算形参字符串的长度
( 2 ) (2) (2)申请空间
( 3 ) (3) (3)拷贝字符串的数据
( 4 ) (4) (4)更新_size
( 5 ) (5) (5)更新_capacity
------------------------------------------------------------我是分割线-----------------------------------------------------------
在模拟拷贝构造函数之前,我们首先学习一下深拷贝和浅拷贝。
浅拷贝:计算机默认提供的拷贝构造函数和赋值重载函数实现的都是浅拷贝,浅拷贝完成的字节序的拷贝。举例说明:浅拷贝就是抄作业,连名字也一起照抄的一种行为。
这种不修改的名字的抄作业行为有时候就容易被老师发现,然后被逮住。
在计算机中也是如此:
浅拷贝带来的问题就是总结就是:堆区申请的内存被重复释放
浅拷贝的问题需要通过人为深拷贝进行解决。
解决方法如下:
在了解了深浅拷贝以后,我们继续学习拷贝构造函数的实现:
拷贝构造函数的传统写法:自己开空间,完成深拷贝
string(const string &s)//拷贝构造
:_size(s._size)//(1)
_capacity(s._capacity),//(2)
_str(nullptr)
{
//reserve(_capacity); !!!!拷贝构造这里不能使用reserve ,因为strcpy会解引用空指针
_str = new char[_capacity+1];//(3)
strcpy(_str, s._str);//(4)
}
注意:这里的拷贝构造不能用reserve
开空间,指针_str
是一个空指针,而strcpy
需要传入空指针,系统会报错,所以需要通过new
申请空间。
( 1 ) ( 2 ) (1)(2) (1)(2)拷贝_size
和_capacity
。
( 3 ) (3) (3)进行深拷贝,在堆区重新申请一块空间,字符串末尾有个'\0'
所以需要多开一个空间。
( 4 ) (4) (4)将数据拷贝到新空间。
拷贝构造函数的现代写法:复用string的有参构造函数
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string &s)
:_size(0),
_capacity(0),
_str(nullptr)
{
string tmp(s._str);//复用有参构造函数
swap(tmp);
}
------------------------------------------------------------我是分割线-----------------------------------------------------------
赋值重载的传统写法:老老实实进行开空间,深拷贝
string& operator=(string &s) {
if (this!=&s)//(1)
{
char *temp = new char[s._capacity + 1];//(2)
strcpy(temp, s._str);//(3)
delete []_str;//(4)
_size = s._size;//(5)
_capacity = s._capacity;//(6)
_str = temp;//(7)
}
return *this;
}
( 1 ) (1) (1)赋值重载的时候,可能会有这样一种情况s1 = s1
,对于这种自己给自己赋值的情况,直接进行返回。
( 2 ) (2) (2) 在进行深拷贝的时候,先进行申请空间,因为申请空间可能会失败。
( 3 ) (3) (3)将数据拷贝到临时对象中。
( 4 ) (4) (4)赋值重载对象可能先前就指向了一块空间,需要将这块空间进行释放。
( 5 ) (5) (5)拷贝临时对象的_size
和_capacity
( 6 ) (6) (6)将_str
指针指向临时对象中申请的空间
------------------------------------------------------------我是分割线-----------------------------------------------------------
赋值重载的现代写法:复用拷贝构造函数
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(string s)//(1)
{
if (this != &s)
{
swap(s);//(2)
}
return *this;
}
( 1 ) (1) (1):赋值重载运算符的现代写法精髓正是在这,因为形参是实参的拷贝,所以这里会先调用拷贝构造函数完成深拷贝。
( 2 ) (2) (2):防止s1=s1
这种自己给自己赋值的情况,直接将拷贝构造得到的对象和该对象交换。
~string()
{
delete[]_str;
_str = nullptr;
_size = _capacity = 0;
}
析构函数的实现很简单,释放空间就好。
------------------------------------------------------------我是分割线-----------------------------------------------------------
string::npos是一个静态成员常量,表示size_t的最大值(Maximum value for size_t)。在使用中通常将其定义为-1。
const char* c_str() const
{
return _str;//(1)
}
( 1 ) (1) (1)返回字符指针。
typedef char* iterator;//(1)
typedef const char* const_iterator;//(2)
iterator begin() {//(3)
return _str;
}
iterator end()//(4)
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
( 1 ) ( 2 ) (1)(2) (1)(2)迭代器又分为普通迭代器
和const迭代器
,这里对两个返回值进行typedef
( 3 ) (3) (3)返回字符的第一个位置
( 4 ) (4) (4)返回最后一个字符的下一个位置
------------------------------------------------------------我是分割线-----------------------------------------------------------
void reserve(int newcapacity)
{
char *tmp = new char[newcapacity + 1];//(1)
strcpy(tmp, _str);//(2)
delete []_str;//(3)
_str = tmp;//(4)
_capacity = newcapacity;//(5)
}
( 1 ) (1) (1)申请新空间。
( 2 ) (2) (2)将旧空间的数据拷贝到新空间。
( 3 ) (3) (3)将旧空间的数据释放。
( 4 ) (4) (4)让指针指向新空间。
( 5 ) (5) (5)更新_capacity
。
( 1 ) (1) (1)push_back()
void push_back(char ch)
{
if (_size == _capacity)//(1)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);//(2)
}
_str[_size++] = ch;//(3)
_str[_size] = '\0';//(4)
}
( 1 ) (1) (1)说明空间用完了,需要扩容
( 1 ) (1) (1)如果原空间为0
,就扩容为4
,反之扩大2倍
( 1 ) (1) (1)尾插字符
( 1 ) (1) (1)别忘了'\0'
( 1 ) (1) (1)append()
void append(const char *str)
{
int len = strlen(str);//(1)
int newcapacity = len + _size;//(2)
if (newcapacity >= _capacity)//(3)
{
reserve(newcapacity + 1);
}
memcpy(_str + _size, str, len);//(4)
_size = newcapacity;//(5)
_capacity = newcapacity ;//(6)
_str[_size] = '\0';//(7)
}
( 1 ) (1) (1)计算要插入的字符串的长度。
( 2 ) (2) (2)计算当前所需要的空间容量。
( 3 ) (3) (3)当前容量如果不够,就进行扩容。
( 4 ) (4) (4)将字符串插入。
( 5 ) ( 6 ) (5)(6) (5)(6)更新_size
和_capacity
。
( 7 ) (7) (7)别忘了末尾还有个'\0'
------------------------------------------------------------我是分割线-----------------------------------------------------------
( 1 ) (1) (1)普通版本
char& operator[](int pos)
{
assert(pos < _size);
return _str[pos];
}
( 1 ) (1) (1)const版本
const char& operator[](int pos)const
{
assert(pos < _size);
return _str[pos];
}
ps::这里返回的时候别忘了+引用
,[]
需要支持读写操作
两个版本的实现基本相同,const
版本返回值是const
的,只能读但是不能写。
------------------------------------------------------------我是分割线-----------------------------------------------------------
( 1 ) (1) (1)insert插入字符
void insert(int pos,char ch)
{
assert(pos <= _size);
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2);//(1)
int l = pos;
int r = _size;
while (l < r)//(2)
{
_str[r] = _str[r - 1];
r--;
}
_str[pos] = ch;//(3)
_str[++_size] = '\0';//(4)
}
( 1 ) (1) (1)如果容量不够,就进行扩容。
( 2 ) (2) (2)插入字符需要挪动数据,从最后一个位置开始挪动。
( 3 ) (3) (3)将要插入的位置赋值。
( 4 ) (4) (4)更新_size
,并且末尾添上\0
。
( 2 ) (2) (2)insert插入字符串
。
string& insert(int pos, const char *str)//插入字符串
{
//判断容量够不够
int len = strlen(str);//(1)
if (len + _size > _capacity)//(2)
reserve(len + _size);
int l = pos;
int r = _size + len;
while (l+len-1 < r)//(3)
{
_str[r] = _str[r - len];
r--;
}
strncpy(_str + pos, str, len);//(4)
_size += len;//(5)
return *this;
}
( 1 ) (1) (1)计算要插入的字符串的长度。
( 2 ) (2) (2)判断是否需要扩容。
( 3 ) (3) (3)挪动数据。
( 4 ) (4) (4)将要插入的字符串插入。
( 5 ) (5) (5)更新_size
。
------------------------------------------------------------我是分割线-----------------------------------------------------------
string& erase(int pos = 0, int len = npos)
{
assert(pos < _size);
if (len == npos || pos+len >= _size)//(1)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);//(2)
_size -= len;
}
return *this;
}
( 1 ) (1) (1):判断是否需要删除pos
位置开始的所有字符。
( 2 ) (2) (2),将数据拷贝到被删除的位置。这里我们进行画图理解。
( 1 ) (1) (1)+=字符串
string& operator+=(const char *str)
{
append(str);//(1)
return *this;
}
( 1 ) (1) (1)复用append函数。
( 1 ) (1) (1)+=字符
string& operator+=(char ch)
{
push_back(ch);//(1)
return *this;
}
( 1 ) (1) (1)复用push_back函数。
------------------------------------------------------------我是分割线-----------------------------------------------------------
( 1 ) (1) (1)查找字符第一次出现的位置
int find(char ch,int pos=0)
{
for (int i = pos; i < _size; i++)//(1)
{
if (_str[i] == ch)
return i;
}
return -1;
}
( 1 ) (1) (1)对字符串遍历一遍,如果找到了就返回该字符对应位置
( 2 ) (2) (2)没找到就返回-1
。
( 2 ) (2) (2)查找字符串第一次出现的位置
int find(const char* s, int pos = 0)
{
const char* ptr = strstr(_str + pos, s);//(1)
//出现的位置,没找到返回空指针。
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;//(2)
}
}
( 1 ) (1) (1)使用strstr库函数进行查找字串,如果没找到,返回-1
( 2 ) (2) (2)这里涉及指针-指针,我们进行画图理解:
cout
ostream& operator<<(ostream&cout, string &s)
{
//cout << s.c_str() << endl;//不能这样写 因为cout判断结束是遇到'\0',但是如果字符串中插入'\0',
//就会导致后面的字符打印不出来
for (int i = 0; i < s._size; i++)
cout << s[i];
return cout;
}
cin
istream& operator>>(istream& in, string& s)
{
s.clear();//(1)
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();//(2)
}
return in;
}
( 1 ) (1) (1)在输入字符串之前必须先clear一次,字符串在先前可能有字符存在
( 2 ) (2) (2)cin.get(),每次读取一个字符。
( 1 ) (1) (1):使用strcpy函数时要注意实参是不是一个可以使用的地址,并且不能为空。
( 2 ) (2) (2):临时对象的生命周期到函数结束,接着会被析构掉。
( 3 ) (3) (3):指针-指针得到的是两个指针的间隔长度。
如:
( 4 ) (4) (4):reserve每次都会多开一个空间,用来存储‘\0’
( 5 ) (5) (5):字符串属于常量区,形参接收的时候需要加const
( 6 ) (6) (6):重载[]运算符的时候需要+引用,因为需要[]运算符需要支持读写操作
( 7 ) (7) (7):insert,find,erase这里的对于库函数的用法很灵活,需要多练习。
#pragma once
#include
#include
using namespace std;
#include
namespace m_string {
class string {
public:
typedef char * iterator;
typedef const char* const_iterator;//const 迭代器就是字符串的内容只能读不能写
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;//因为end指向所以数据的下一个位置,所以直接+size就好了
}
const_iterator begin()const//const迭代器const对象进行调用,后面的const修饰的是this指针,也就是类的成员不能修改
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
string(const char*str="")//传进来的可能是空串 这里包含了无参和有参
:_str(nullptr),
_size(0),
_capacity(0)
{
int newcapacity = strlen(str);
_str = new char[newcapacity + 1];
strcpy(_str, str);
_size = newcapacity;
_capacity = newcapacity;
}
char* c_str()
{
return _str;
}
int capacity()
{
return _capacity;
}
int size()
{
return _size;
}
void reserve(int newcapacity)
{
char *tmp = new char[newcapacity + 1];
strcpy(tmp, _str);
delete []_str;
_str = tmp;
_capacity = newcapacity;
}
void push_back(char ch)//尾插字符
{
//需要判断是否需要扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
~string()
{
delete []_str;
_size = _capacity = 0;
}
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//传统写法!!!!!!!
//string(const string &s)//拷贝构造
// :_size(s._size),
// _capacity(s._capacity),
// _str(nullptr)
//{
// //reserve(_capacity); !!!!拷贝构造这里不能使用reserve ,因为strcpy会解引用空指针
// _str = new char[_capacity+1];
// strcpy(_str, s._str);
//}
//现代写法
string(const string &s)
:_size(0),
_capacity(0),
_str(nullptr)
{
string tmp(s._str);//复用有参构造函数
swap(tmp);
}
//-----------------------------------------------------------------
//operator=传统写法
//string& operator=(string &s) {
// if (this != &s)//(1)
// {
// char *temp = new char[s._capacity + 1];//(2)
// strcpy(temp, s._str);//(3)
// delete[]_str;//(4)
// _size = s._size;//(5)
// _capacity = s._capacity;//(6)
// _str = temp;//(7)
// }
// return *this;
//}
string& operator=(string s)
{
if (this != &s)
{
swap(s);
}
return *this;
}
void resize(int newcapacity,char ch='\0')
{
//开的空间大于原空间
if (newcapacity >= _capacity)
{
reserve(newcapacity);
memset(_str + _size, ch, newcapacity - _size);
_capacity = newcapacity;
}
else if (newcapacity < _capacity)
{
_str[newcapacity] = '\0';
_size = newcapacity;
}
}
char operator[](int x)
{
assert(x < _size);
return _str[x];
}
const char& operator[](int x)const
{
assert(x < _size);
return _str[x];
}
void insert(int pos,char ch)
{
assert(pos <= _size);
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2);
int l = pos;
int r = _size;
while (l < r)
{
_str[r] = _str[r - 1];
r--;
}
_str[pos] = ch;
_str[++_size] = '\0';
}
string& insert(int pos, const char *str)//插入字符串
{
//判断容量够不够
int len = strlen(str);
if (len + _size > _capacity)
reserve(len + _size);
int l = pos;
int r = _size + len;
while (l+len-1 < r)
{
_str[r] = _str[r - len];
r--;
}
cout << _str << endl;
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
int find(char ch,int pos=0)
{
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
return -1;
}
int find(const char* s, int pos = 0)
{
const char* ptr = strstr(_str + pos, s);//找到了就返回子串在主串中第一次
//出现的位置,没找到返回空指针。
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string &operator+=(char ch)
{
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2);
push_back(ch);
return *this;
}
char *_str;
int _size;
int _capacity;
static const size_t npos = -1;
void append(const char *str)
{
int len = strlen(str);
int newcapacity = len + _size;
if (newcapacity >= _capacity)
{
reserve(newcapacity + 1);
}
memcpy(_str + _size, str, len);
_size = newcapacity;
_str[_size] = '\0';
_capacity = newcapacity ;
}
string& erase(int pos = 0, int len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
cout << (_str + pos) << endl;
cout << (_str + pos + len) << endl;
_size -= len;
}
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
};
ostream& operator<<(ostream&cout, string &s)
{
//cout << s.c_str() << endl;//不能这样写 因为cout判断结束是遇到'\0',但是如果字符串中插入'\0',
//就会导致后面的字符打印不出来
for (int i = 0; i < s._size; i++)
cout << s[i];
return cout;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
void test1()//测试有参和无参 ok!
{
//测试无参
/*string s1;
cout << s1.c_str() << endl;
cout << s1.capacity() << endl;
cout << s1.size() << endl;*/
//测试有参
/* string s2("hello");
cout << s2.c_str() << endl;
cout << s2.capacity() << endl;
cout << s2.size() << endl;*/
//测试有参空串
/*string s3("");
cout << s3.c_str() << endl;
cout << s3.capacity() << endl;
cout << s3.size() << endl;*/
}
void test2()//测试迭代器
{
//测试普通迭代器+无参
/*string s1;
string::iterator it = s1.begin();
while (it!=s1.end())
{
cout << *it<<" ";
it++;
}*/
//测试普通迭代器+有参
/*string s2("helloddddddddddddd");
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}*/
//测试const迭代器+
/*const string s2("helloc");
string::const_iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}*/
}
void test3()//测试push_Back 和reserve
{
//测试能否正常扩容和插入
/*string s1;
s1.push_back(1);
s1.push_back(2);
s1.push_back(3);
s1.push_back(4);
cout << s1.size() << endl;
cout << s1.capacity() << endl;*/
//前4个容量为4,插入第五个以后容量变为8
/* s1.push_back(5);
cout << s1.size() << endl;
cout << s1.capacity() << endl;*/
//测试能否搭配迭代器进行使用
/*string s2("h");
s2.push_back('e');
s2.push_back('l');
s2.push_back('l');
s2.push_back('o');
for (auto ch : s2)
cout << ch;*/
}
void test4()//测试拷贝构造
{
string s1("hello");
string s2(s1);
for (auto ch : s2)
cout << ch;
}
void test5()//测试赋值重置运算符
{
string s1("hello");
string s2("hellooooo");
s2 = s1;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
}
void test6()//测试resize
{
//string s1("aaaaa");//原空间大于要申请的空间
//s1.resize(1);
//for (auto ch : s1)
// cout << ch;
string s2("aaaa");//原空间小于要申请的空间
s2.resize(10);
for (auto ch : s2)
cout << ch;
cout << s2.capacity() << endl;
}
void test7()//测试[]
{
string s1("hello");
cout << s1[0] << endl;
cout << s1[1] << endl;
cout << s1[2] << endl;
cout << s1[3] << endl;
cout << s1[4] << endl;
cout << s1[5] << endl;//越界,断言程序崩溃
}
void test8()//+=
{
string s1;
s1 += 'a';
cout << s1.c_str() << endl;
}
void test9()//append
{
string s1("aaa");
s1.append("h");
cout << s1.c_str() << endl;
}
void test10()//cout 和cin
{
string s1("hello dasdasd");
cin >> s1;
cout << s1 << endl;
}
void test11()//insert
{
string s1("hello");
s1.insert(1, "adc");
cout << s1.c_str() << endl;
}
void test12()//find
{
//查找单个字符
string s1("hello");
cout << s1.find("llo");
//查找字符串
}
void test13()//erase
{
string s1("hello");
s1.erase(2,2);
cout << s1.c_str() << endl;
}
void test14()//测试现代写法的拷贝构造
{
string s1("hello");
string s2(s1);
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
}
void test15()//测试operator现代写法
{
string s1("dasd");
string s2=s1;
cout << s2 << endl;
cout << s2.capacity() << endl;
cout << s1.size() << endl;
}
}