目录
2.5 operator<<、operator>>、getline
2.7 push_back、append、+=、reserve
1、面试时写的string
在面试时,限于时间,不可能要求具备std::string的功能,但至少要求能正确管理资源,即完成构造,析构,拷贝构造,赋值运算符重载,这时候类的成员变量只需要一个_str
1.1 传统写法的String类
注意,只要是接收字符常量的指针变量都要加const
但此时拷贝构造会报错
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
1.2 现代写法的String类
现代写法的String针对拷贝构造和赋值运算符重载进行了改进,让深拷贝不用自己做,让别人做,再交换
tmp是临时对象会自动释放是因为会自动调用析构函数
2、真正的string类
实现一个能够进行增删查改的string
2.1 size、capacity
在上面的string中,成员变量中并没有siae、capacity,这样子没办法完成扩容,是不完美的
现在,我们需要在成员变量中增加_size,_capacity,_size是字符数组中有效字符的个数,_capacity是这个字符数组最多可以存放多少个有效字符
2.2 构造函数、拷贝构造、析构函数、赋值运算符重载
因为这里增加了_size和_capacity,所以相较于上面有一些变化
在拷贝构造和赋值运算符重载中,swap只会将字符串交换,但是_size和_capacity并没有改变,所以还需要修改_size和_capacity
2.3 operator[]
2.4 c_str
插个题外话
2.5 operator<<、operator>>、getline
这三个需要写在类外,不需要让其称为友元,因为s[i]、s.size()等调用了成员函数,不是直接访问成员变量
2.6 迭代器
end返回的是\0
在string中,迭代器是指针,但是不是所有迭代器都是指针,也正因为返回的指针,所以迭代器是可读可写的
此时会发现只要支持了迭代器,就能支持范围for,因为范围for最终被替换成迭代器,此过程是编译器进行的,所以迭代器中的begin和end必须是这样写,若变成Begin等,编译器会报错
2.7 push_back、append、+=、reserve
此时会发现,在push_back和append的扩容操作中,有很多重复的操作,所以可以将其封装成函数,且这个函数就reserve
2.8 resize
2.9 insert、erase
2.10 find
2.11 relational operators
class String
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
String(const char* str = "")//最好还是合二为一用全缺省
{//这里一定要加const,因为像"hello"是字符串常量,不能权限放大
if (nullptr == str)
{
assert(false);
return;
}
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
~String()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
String(const String& s)
: _str(nullptr)
//这里一定要给空指针,否则为随机值,下面交换后要析构strTmp时会报错
{
String strTmp(s._str);//构造函数
_size = s._size;
_capacity = s._capacity;
swap(_str, strTmp._str);
}
String& operator=(String s)//直接利用传参时来拷贝构造
{
swap(_str, s._str);
_size = s._size;
_capacity = s._capacity;
return *this;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i) const//还需要给一个const版本
{
assert(i < _size);
return _str[i];
}
const char* c_str() const
{
return _str;
//拿到指向字符串的指针就可以直接cout输出
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* newstr = new char[n + 1];
strcpy(newstr, _str);
delete[] _str;
_str = newstr;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
//使用三目操作符是为了防止空字符串调用
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
//插入后,一定要在插入之后的一个位置加一个\0
//也可以用insert
//insert(_size,ch);
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newcapacity = _size + len;
reserve(newcapacity);
}
strcpy(_str + _size, str);
_size += len;
//insert(_size,str);
}
String& operator+=(char ch)
{
this->push_back(ch);
return *this;
}
String& operator+=(const char* str)
{
this->append(str);
return *this;
}
void rasize(size_t n, char ch='\0')
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
String& insert(size_t pos, char ch)
{
assert(pos <= _size);//=即尾插
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
reserve(newcapacity);
}
//insert(1,'a')是在下标为1的位置插入一个a,所以从\0开始挪动
int end = _size;
while (end >= (int)pos)
//pos用size_t是因为下标不可能有负数
//要对pos强转是因为当end变成-1时能够出循环
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
String& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, str, len);
//不能使用strcpy,因为这样会把\0也拷贝过去
//可以使用strncpy,因为可以控制长度
//当然也可以自己写循环,将str的数据放到pos往后len个空间中
_size += len;
return *this;
}
String& erase(int pos, size_t len = npos)
{
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
int i = pos + len;
while (i <= _size)
{
_str[i - len] = _str[i];
i++;
}
_size -= len;
}
return *this;
}
size_t find(char ch, size_t pos = 0)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
char* p = strstr(_str, str);
//string有\0是为了兼容C,所以可以使用C的函数
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
//p-_str就是下标
}
}
bool operator<(const String& s) const
{
int ret = strcmp(_str,s._str);
return ret < 0;
}
bool operator==(const String& s) const
{
int ret = strcmp(_str, s._str);
return ret == 0;
}
bool operator<=(const String& s) const
{
return *this < s || *this == s;
}
bool operator>(const String& s) const
{
return !(*this <= s);
}
bool operator>=(const String& s) const
{
return !(*this < s);
}
bool operator!=(const String& s) const
{
return !(*this == s);
}
private:
char* _str;
int _size;//已经有多少个有效字符
int _capacity;//最多能存多少个有效字符,\0不是有效字符
static size_t npos;
};
size_t String::npos = -1;
ostream& operator<<(ostream& out, const String& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, String& s)
{
while (1)//遇到空格或换行就结束
{
char ch;
//in >> ch;
//这样子是错的,会死循环,因为cin是编译器实现的
//只要遇到空格或换行就自动忽略,去读下一个
//若没有下一个则ch为空,不会加入if而跳出循环
ch = in.get();//in.get是无论什么都读
if (ch == ' ' || ch == '\n')
{
break;
}
else
{
s += ch;
}
}
return in;
}
istream& getline(istream& in, String& s)
{
while (1)
{
char ch;
ch = in.get();
if (ch == '\n')
{
break;
}
else
{
s += ch;
}
}
return in;
}