拓展作用域限定符:
::作用域限定符
因为自定义的swap和std的swap同名,在一个类里优先调用类的swap但是我们想要调用std的就要用::
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string的底层是字符数组,因为要和标准库的string区分开来所以就定义一个命名空间,这里的string底层用字符数组所以用一个_size和_capacity来控制这个数组
1.string模拟实现
1.构造函数
#include <iostream>
using namespace std;
#include <string>
namespace zzy
{
class string
{
public:
string()//无参构造
:_str(nullptr),
_size(0),
_capacity(0)
{
}
string(const char*str="")//如果这里写const就会报错因为权限放大
:_size(strlen(str)),//如果成员——str这里改成const会影响到operator[]的读写
//strlen计算有效的字符个数
{
_capacity = _size;
_str(new char[_capacity+1]),
strcpy(_str,str);
}
private:
char* _str;
size_t _size;
size_t _capacity;
const static npos =-1;
};
}
这里我们发现当我们定义两个构造函数(无参和有参的)当有参数的我们给予缺省值时和无参构造一样所以可以去除无参构造进行优化:
namespace zzy
{
class string
{
public:
string(const char*str="")//如果这里写const就会报错因为权限放大
:_size(strlen(str)),//如果成员——str这里改成const会影响到operator[]的读写
//strlen计算有效的字符个数
{
_capacity = _size;
_str(new char[_capacity+1]),
strcpy(_str,str);
}
private:
char* _str;
size_t _size;
size_t _capacity;
const static npos =-1;
};
}
2、析构函数
~string()
{
if (_str != nullptr)
{
delete[] _str;
_size = 0;
_capacity = 0;
}
}
3、operator[]
char& operator[](size_t pos)
{
assert(pos <= _size);//要包含assert.h头文件,断言函数当不符合时会warn
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos <= _size);
return _str[pos];
}
为什么要重载const?因为有时候当你的对象为const时就要调用const的operator的重载,如当对象调用函数时void print(const string&s)s为const对象只可以读不可以写
4、c_str
const char* c_str()const
{
return _str;
}
5、关于深浅拷贝问题(拷贝构造)
拷贝构造函数如果不写编译器会自动生成,我们在自定义的类里边没有再定义自己的拷贝函数,程序为我们执行的是默认的拷贝构造函数,进行的是浅拷贝
浅拷贝称为值拷贝会一个一个字节将值赋予给新的对象(内置类型如:int ,float,char,指针类 等等但是自定义类型会调用自己的构造函数)
深拷贝:会开辟两块独立的大小一样的空间
这里为什么要注意呢?
是因为当对象中的成员变量有指针类型时,拷贝出来的两个不同对象的成员指针会保存同一份地址,当程序结束时系统会自动调用析构函数释放空间,后定义的先析构,把同一份空间释放了,但是还有一个对象的指针成员还保存这块空间的地址,当它再次调用析构的时候会导致一块空间释放两次所以会报错。注意:只有指针类型的才会这样,如果是int那些内置类型的就是不同空间的它只是初始化而已,但是这个指针类型会保存的是地址
拷贝构造
string(const string& s)
:_str(new char[s._capacity+1]),
_size(s._size),
_capacity(s._capacity)
{
strcpy(_str,s._str);
}
6、赋值operator=
string& operator=(const string&s)
{
if (this != &s)//排除自己给自己赋值的情况
{
char* tmp = new char[s._capacity+1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
这里考虑到对象和赋值的对象的大小可能不相同所以就开辟两个大小相同的独立空间
7、iterator迭代器
可以理解迭代器是类似指针的东西,因为list就不是指针
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;
}
8、输入
这里的增容不是一个一个增容的而是类似于两倍增加的
我们要清楚当我们给string添加元素时,如果容量不够大就要扩容的所以要判断
所以先写reserve resize
要了解
reserve
就是扩容但是不初始化,如果n小于对象的capacity就不会改变原来的
void reserve(size_t n )
{
if (n > _capacity)
{
char* tmp = new char[n+1];//存放 /0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
resize
简单来说resize(n)扩大数组的n个容量并且用0默认补充,如果是resize(n,’a’)就会扩大n个数组并且用a来补充,如果n小于capacity不会改变capacity只会改变_size,如果n>_capacity就会把多的部分初始化
void resize(size_t n)
void resize(size_t n)
{
if (n < _size)
{
_size = n;//【0,size-1】是实际的空间
_str[_size] = '/0';//最后一个存放‘/0’
}
else
{
if (n > _capacity)//当大于容量时扩大容量并初始化
{
reserve(n);//在内部调用相当于(*this).reserve
//将多扩容的部分初始化
for (size_t i = _size; i < n; i++)//[_size,n-1]还剩下一个空间是用来存放/0
{
_str[i] = '\0';
}
_size = n;
_str[_size] = '\0';//实际上开了n+1最后一个是用来存放\0
}
}
}
void resize(size_t n,char ch)
void resize(size_t n,char ch )
{
if (n < _size)
{
_size = n;//【0,size-1】是实际的空间
_str[_size] = '/0';//最后一个存放‘/0’
}
else
{
if (n > _capacity)//当大于容量时扩大容量并初始化
{
reserve(n);//在内部调用相当于(*this).reserve
//将多扩容的部分初始化
for (size_t i = _size; i < n; i++)//[_size,n-1]还剩下一个空间是用来存放/0
{
_str[i] = 'ch';
}
_size = n;
_str[_size] = '\0';//实际上开了n+1最后一个是用来存放\0
}
}
}
优化:
发现除了最后都一样,如果写两个就会显得代码冗余所以将缺省值补上
void resize(size_t n,char ch='\0')
{
if (n < _size)
{
_size = n;//【0,size-1】是实际的空间
_str[_size] = '/0';//最后一个存放‘/0’
}
else
{
if (n > _capacity)//当大于容量时扩大容量并初始化
{
reserve(n);//在内部调用相当于(*this).reserve
//将多扩容的部分初始化
for (size_t i = _size; i < n; i++)//[_size,n-1]还剩下一个空间是用来存放/0
{
_str[i] = 'ch';
}
_size = n;
_str[_size] = '\0';//实际上开了n+1最后一个是用来存放\0
}
}
}
insert
我们是实现自己想要的功能就行,上面是使用了解常用就行
step1:我们在实现给对象增加元素的时候都要检查是否有足够的空间
step2:有了空间才可以插入元素
这里要从‘\0’开始,这里最好写出范围【left,right】防止溢出
string& insert(size_t& pos,char& ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2*_capacity);//我们只需要扩容
}
//这里是数组所以当要在pos位置插入元素时应该从后面开始腾出位置
for (size_t i = _size; i <= pos; i--)//[pos,_size]
{
_str[i + 1] = _str[i];
}
_str[pos] = ch;
_size++;
return *this;
}
也可以
string& insert(size_t& pos,char& ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2*_capacity);//我们只需要扩容
}
//这里是数组所以当要在pos位置插入元素时应该从后面开始腾出位置
size_t end = _size + 1;
for (size_t i = _size; i <= pos; i--)//[pos,_size]
{
_str[i + 1] = _str[i];
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(int pos,const char*str)
重点:
(end >= (int)pos)//这里为什么要强转呢?因为end为int,pos是size_t,int会往范围大的转化,当pos为0时end=0时在继续执行减减导致为-1,但是,-1在size_t中为整形的最大值/
string& insert(size_t pos, const char* str)
{
size_t len =strlen(str);
if (_size + len > _capacity)//这里因为str是一个字符串
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)//这里为什么要强转呢?因为end为int,pos是size_t,int会往范围大的转化,当pos为0时end=0时在继续执行减减导致为-1,但是,-1在size_t中为整形的最大值//[pos,end]
{
_str[end + len] = _str[end];//要在pos这个位置腾出len个位置
end--;
}
for (size_t i = pos; i <= pos + len; i++)
{
_str[i] = str[i];
}
_size = _size + len;
return *this;
}
push_back(两种方法)增强代码复用
void push_back(char ch)
{
//if (_size == _capacity)
//{
// reserve(_capacity == 0 ? 4 : _capacity*2);
//reserve(_capacity*2);
//}
//_str[_size] = ch;
//++_size;
//_str[_size] = '\0';
insert(_size, ch);
}
append
void append(const char* str)
{
/*size_t len = _size + strlen(str);
if (len > _capacity)
{
reserve(len);
}
strcpy(_str + _size, str);
_size = len;*/
insert(_size, str);
}
erase
string& erase(size_t pos = 0, size_t len = npos)
{
if (len==npos||pos+len>=_size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;//定位到要往前推的开始元素
while (begin <= _size)//[begin,_size]这里是=要把‘/0’赋值过去
{ //要操作的那段字符串要以_size作为边界防止越界
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
return *this;
}
find(char ,size_t pos)
size_t find(char ch,size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
输入输出流
- 流插入重载必须实现为友元函数么?
- 不对,使用友元函数是为了在类外面调用类的私有的成员变量,若不需要调用则不用友元函数
ostream& operator<<(ostream& out, const string& s)
{
for (int i = 0; i < s.size(); i++)
{
out << s[i]<<" ";
}
return out;
}
istream& operator>>(istream& in, string& s)//这里不可以const因为要写
{
while (1)
{
char ch;
in >> ch;
if (ch == ' ' || ch == '\n')
{
break;
}
else
{
s.push_back(ch);
}
}
return in;
}
这里的输入流要注意c++库里面只是重载了内置类型
而且上面的istream& operator>>(istream& in, string& s)的in>>ch
这个是不对的因为当用户输入回车或空格的时候,in >>ch 认为结束了,然后陷入死循环中
istream& operator>>(istream& in, string& s)//这里不可以const因为要写
{
while (1)
{
char ch;
ch=in.get();
if (ch == ' ' || ch == '\n')
{
break;
}
else
{
s.push_back(ch);
}
}
return in;
}
传统的拷贝构造和现代拷贝构造写法
先定义一个Swap函数
void Swap(string& s)
{
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
传统的拷贝构造
要自己开空间
string(const string& s)
:_str(new char[s._size + 1]),
_size (s._size),
_capacity (s._size)
{
strcpy(_str, s._str);
}
现代的写法
让用别人的空间完成自己的事
这里要看清楚这里是默认构造函数而不是拷贝构造
string(const string& s)
:_str(nullptr)
{
string tmp(s._str);//初始化调用的是默认构造函数
swap(_str, tmp._str);
_size = tmp._size;
_capacity = tmp._capacity;
}
同理operator=也可以
// cout<<"传统写法"<<endl;
string& operator=(const string& s)
{
if (this != &s)
{
char*tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
//cout<<"现代写法"<<endl;
string& operator=(string s)
{
Swap(s);
return *this;
}
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <iostream>
using namespace std;
#include <string>
namespace zzy
{
class string
{
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;
}
string(const char*str="")//如果这里写const就会报错因为权限放大
:_size(strlen(str))//如果成员——str这里改成const会影响到operator[]的读写
//strlen计算有效的字符个数
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
~string()
{
if (_str != nullptr)
{
delete[] _str;
_size = 0;
_capacity = 0;
}
}
char& operator[](int pos)
{
assert(pos <= _size);//要包含assert.h头文件,断言函数当不符合时会warn
return _str[pos];
}
const char& operator[](int pos)const
{
assert(pos <= _size);
return _str[pos];
}
const char* c_str()const
{
return _str;
}
string(const string& s)
:_str(new char[s._capacity+1]),
_size(s._size),
_capacity(s._capacity)
{
strcpy(_str,s._str);
}
string& operator=(const string&s)
{
if (this != &s)//排除自己给自己赋值的情况
{
char* tmp = new char[s._capacity+1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
void reserve(size_t n )
{
if (n > _capacity)
{
char* tmp = new char[n+1];//存放 /0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n,char ch='\0')
{
if (n < _size)
{
_size = n;//【0,size-1】是实际的空间
_str[_size] = ch;//最后一个存放‘/0’
}
else
{
if (n > _capacity)//当大于容量时扩大容量并初始化
{
reserve(n);//在内部调用相当于(*this).reserve
//将多扩容的部分初始化
for (size_t i = _size; i < n; i++)//[_size,n-1]还剩下一个空间是用来存放/0
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';//实际上开了n+1最后一个是用来存放\0
}
}
}
//输入
string& insert(size_t& pos,char& ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2*_capacity);//我们只需要扩容
}
//这里是数组所以当要在pos位置插入元素时应该从后面开始腾出位置
size_t end = _size + 1;
for (size_t i = _size; i <= pos; i--)//[pos,_size]
{
_str[i + 1] = _str[i];
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
size_t len =strlen(str);
if (_size + len > _capacity)//这里因为str是一个字符串
{
reserve(_size + len);
}
size_t end = _size;
while (end >= pos)//[pos,end]
{
_str[end + len] = _str[end];//要在pos这个位置腾出len个位置
end--;
}
for (size_t i = pos; i <= pos + len; i++)
{
_str[i] = str[i];
}
_size = _size + len;
return *this;
}
void push_back(char ch)
{
//if (_size == _capacity)
//{
// reserve(_capacity == 0 ? 4 : _capacity*2);
//reserve(_capacity*2);
//}
//_str[_size] = ch;
//++_size;
//_str[_size] = '\0';
insert(_size, ch);
}
void append(const char* str)
{
/*size_t len = _size + strlen(str);
if (len > _capacity)
{
reserve(len);
}
strcpy(_str + _size, str);
_size = len;*/
insert(_size, str);
}
size_t size()const
{
return strlen(_str);
}
string& erase(size_t pos = 0, size_t len = npos)
{
if (len==npos||pos+len>=_size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;//定位到要往前推的开始元素
while (begin <= _size)//[begin,_size]这里是=要把‘/0’赋值过去
{ //要操作的那段字符串要以_size作为边界防止越界
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
}
size_t find(char ch,size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
friend ostream& operator<<(ostream& out, const string& s);
friend istream& operator>>(istream& in, string& s);
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos=-1;//可以定义成全局变量
};
//istream& operator>>(istream& in, string& s)
//{
// while (1)
// {
// char ch;
// in >> ch;
// if (ch == ' ' || ch == '\n')
// {
// break;
// }
// else
// {
// s += ch;
// }
// }
//}
ostream& operator<<(ostream& out, const string& s)
{
for (int i = 0; i < s.size(); i++)
{
out << s[i]<<" ";
}
return out;
}
istream& operator>>(istream& in, string& s)//这里不可以const因为要写
{
while (1)
{
char ch;
ch=in.get();
if (ch == ' ' || ch == '\n')
{
break;
}
else
{
s.push_back(ch);
}
}
return in;
}
void test_string()
{
string a("hello");
string s(a);
cout << a ;
}
}
int main()
{
zzy::test_string();
}