目录
- 基础知识
- string构造函数和析构函数的坑
- 构造函数
- 析构函数
- 迭代器、范围for
- 运算符重载
- operator []
- const
- 增删查改
- push_back
- reserve
- append
- +=
- insert
- erase
- swap
- find
- substr
- 拷贝构造
- =
- 流插入和流提取
- <<流插入
- >>流提取
- clear
- 深浅拷贝
- 传统写法
- 现代写法
- 赋值
- 传统写法
- 现代写法
- string的声明和定义分离
基础知识
capacity:指可以存储有效字符的个数,不包含/0
size:指现有的有效字符个数,不包含/0
string构造函数和析构函数的坑
构造函数
1、重复使用strlen()造成效率降低
上述构造函数中使用的strlen(s)
的时间复杂度为(O(n),没算一次都要从头数到尾)
为了解决上述问题,提出了下面的解决方法
上述解决方法是不对的,这样声明和初始化列表的顺序必须一致
private:
size_t _size;
size_t _capacity;
char* _str;
上述解决方法的不足:如果动了声明的顺序或初始化列表的顺序就会报错
我们可以不用初始化列表,直接在里面构造就好了
2、无参的构造函数
我们写了上述的无参构造函数,但这样写是存在问题的。因为在cout的时候要对c_str()返回的char*进行解引用,而无参时返回的是空指针,这也就造成了空指针解引用问题
但是平时写构造函数的时候也没有两个分开写的时候,所以要把两个合起来
上述还有个问题s的内容没有copy给_str
string(const char* s = "")
{
_size = strlen(s);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, s);
}
析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
迭代器、范围for
typedef char* iteractor;
iteractor begin()
{
return _str;
}
iteractor end()
{
return _str + _size;
}
范围for的底层实际上就是迭代器,他只会傻瓜式的替换成迭代器
如果把我们string里面的begin换掉Begin,那么范围for就会无法替换,产生报错。
运算符重载
operator []
const
const char* a
这表示指针指向的内容不能被修改
char* const a
这里表示指针不能被修改
namespace zyh
{
class string
{
public:
string(const char* s = "")
{
_size = strlen(s);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, s);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
typedef const char* const_iterator; //指向的内容不能修改,指针本身可以修改
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
const char* c_str() const //不需要修改,因此只有一个const类型就可以,const对象可以调用,非const对象也可以调用
{
return _str;
}
char& operator[](size_t pos)//const对象调用时不能修改,非const对象调用时要能修改,因此需要两种类型
{
assert(pos <= _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos <= _size);
return _str[pos];
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
增删查改
push_back
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
};
reserve
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
append
void append(char* s)
{
size_t len = strlen(s);//没有\0
if ((_size + len) > _capacity)
{
reserve(_size + len + 1);
}
strcpy(_str + _size, s);
_size += len;
}
+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(char* s)
{
append(s);
return *this;
}
insert
插入字符串时:扩容写错
strcpy
会把\0
也拷贝进去
如果不想把\0
也拷进去,就用strncpy
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
//size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(_size+len);
}
size_t end = _size+1;
while (end > pos)
{
_str[end + len-1] = _str[end-1];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = s[i];
}
return *this;
}
上述代码在拷贝上可以简化,其次在返回值上不太对
void insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, s, len);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace zyh;
void test1()
{
string s1("hello world");
s1.insert(5, "zyh");
std::cout << s1.c_str() << std::endl;
}
int main()
{
test1();
return 0;
}
erase
1、缺省值忘写
npos不属于某个对象,它是属于整个类,因此要写成静态的
可以认为是编译器的特殊处理,当为const的时候既算是也算是定义
在成员变量那里定义,而且这个用法只支持整型
const static size_t npos = -1;
普通静态成员变量,在类里面声明,在类外面定义,不能给缺省值,因为缺省值是初始化列表初始化时用的
2、使用strcpy、strncpy
3、不判断len!=npos
当len为npos时是不能加的,因为一加就溢出了
void erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
size_t end = _size;
if (pos + len >= _size || len == npos)
{
end = _size;
}
else
{
end = pos + len;
}
strcpy(_str + pos, _str + end);
}
swap
1、类里面swap的写法
void swap(string& y)
{
std::swap(_str, y._str);
}
2、三种swap的意义
成员函数里面的swap
非成员函数的swap
公共的swap
第二种
swap
底层实现上还是s1.swap(s2)
find
size_t find(const char* str, size_t pos = 0)
{
assert(pos <= _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
return npos;
else
return ptr - _str;
}
使用了
strstr
substr
从某个位置取len个字符
1、考虑空间大小
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
size_t end = _size;
if (pos + len < _size && len != npos)
{
end = pos + len;
}
string tmp;
tmp.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
tmp += _str[i];
}
return tmp;
}
上述代码存在问题
2、传值返回 - 浅拷贝问题
当没写拷贝构造的时候,默认生成的是浅拷贝
拷贝构造
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
=
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
流插入和流提取
流插入和流提取不用非要设计为友元,因为不需要访问私有成员,用范围for、迭代器什么的就可以
<<流插入
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
>>流提取
1、对于空格或换行不能提取
cin不能提取空格和换行
为了解决上面的问题可以用cin.get()
这个get是不管是什么都提取
std::istream& operator>>(std::istream& in, string& s)
{
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
2、多次对同一个string进行cin,没有覆盖,而是在后面增加
解决方法:
写一个clear,每次cin的时候先clear一下
std::istream& operator>>(std::istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
3、如果一次性输入内容太多,会一直扩容
std::istream& operator>>(std::istream& in, string& s)
{
s.clear();
char buff[128];
char ch = in.get();
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
clear
为什么要置\0
,如果不置\0
会出现下面问题
cout是按
_size
一个字符一个字符的走
c_str
则是看\0
void clear()
{
_size = 0;
_str[0] = '\0';
}
深浅拷贝
传统写法
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
现代写法
问题:如果初始化时指向是空那没有问题,如果是随机值就会出问题
解决方法:
初始化列表处理一下或给缺省值
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
赋值
传统写法和现代写法没什么区别,只是更简洁一点
传统写法
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
现代写法
没必要检查是否是自己赋值给自己了,因为已经早就拷贝构造了,在传参的时候
string& operator=(string s)
{
swap(s);
return *this;
}
string的声明和定义分离
1、不认识官方库的内容
(1)没包头文件
(2)命名空间的问题
2、缺省参数
只能在声明给,不能声明定义同时给
3、重定义问题
所以的重定义都是由于在多个cpp里面定义导致的