- 构造函数
- 有参构造函数
- 无参构造函数
- 简略版本
- 深拷贝和浅拷贝
- 赋值重载
- 析构函数
- npos
- c_str
- 迭代器
- reserve
- push_back() && append()
- []运算符
- insert
- erase
- +=
- find
- cout,cin
- 总结:
- string.h 和测试代码
在前面我们学习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计算到 即停止,不含 )。
------------------------------------------------------------我是分割线-----------------------------------------------------------
无参构造函数 string()
:_size(0),//(1)
_capacity(0),//(2)
_str(new char[1])//(3)
{
_str[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)进行深拷贝,在堆区重新申请一块空间,字符串末尾有个' '所以需要多开一个空间。
(
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;
}
析构函数的实现很简单,释放空间就好。
------------------------------------------------------------我是分割线-----------------------------------------------------------
nposc_strstring::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)返回最后一个字符的下一个位置
------------------------------------------------------------我是分割线-----------------------------------------------------------
reservevoid 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] = ' ';//(4)
}
(
1
)
(1)
(1)说明空间用完了,需要扩容
(
1
)
(1)
(1)如果原空间为0,就扩容为4,反之扩大2倍
(
1
)
(1)
(1)尾插字符
(
1
)
(1)
(1)别忘了' '
( 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] = ' ';//(7)
}
(
1
)
(1)
(1)计算要插入的字符串的长度。
(
2
)
(2)
(2)计算当前所需要的空间容量。
(
3
)
(3)
(3)当前容量如果不够,就进行扩容。
(
4
)
(4)
(4)将字符串插入。
(
5
)
(
6
)
(5)(6)
(5)(6)更新_size和_capacity。
(
7
)
(7)
(7)别忘了末尾还有个' '
------------------------------------------------------------我是分割线-----------------------------------------------------------
[]运算符( 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] = ' ';//(4)
}
(
1
)
(1)
(1)如果容量不够,就进行扩容。
(
2
)
(2)
(2)插入字符需要挪动数据,从最后一个位置开始挪动。
(
3
)
(3)
(3)将要插入的位置赋值。
(
4
)
(4)
(4)更新_size,并且末尾添上 。
( 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。
------------------------------------------------------------我是分割线-----------------------------------------------------------
erasestring& erase(int pos = 0, int len = npos)
{
assert(pos < _size);
if (len == npos || pos+len >= _size)//(1)
{
_str[pos] = ' ';
_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函数。
------------------------------------------------------------我是分割线-----------------------------------------------------------
find( 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判断结束是遇到' ',但是如果字符串中插入' ',
//就会导致后面的字符打印不出来
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每次都会多开一个空间,用来存储‘ ’
(
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] = ' '; } ~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=' ') { //开的空间大于原空间 if (newcapacity >= _capacity) { reserve(newcapacity); memset(_str + _size, ch, newcapacity - _size); _capacity = newcapacity; } else if (newcapacity < _capacity) { _str[newcapacity] = ' '; _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] = ' '; } 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] = ' '; _capacity = newcapacity ; } string& erase(int pos = 0, int len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = ' '; _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] = ' '; _size = 0; } }; ostream& operator<<(ostream&cout, string &s) { //cout << s.c_str() << endl;//不能这样写 因为cout判断结束是遇到' ',但是如果字符串中插入' ', //就会导致后面的字符打印不出来 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! { //测试无参 //测试有参 //测试有参空串 } void test2()//测试迭代器 { //测试普通迭代器+无参 //测试普通迭代器+有参 //测试const迭代器+ } void test3()//测试push_Back 和reserve { //测试能否正常扩容和插入 //前4个容量为4,插入第五个以后容量变为8 //测试能否搭配迭代器进行使用 } 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; } }



