分布解析 :
string的构造,析构
namespace zephyr
{
class string
{
public:
/*string()
:str(new char[1]{'\0'})
,size(0)
,capacity(0)
{}*/
//string(const char* s=nullptr) 错误示范
//string(const char* s="\0") 错误示范
string(const char* s="")
:_size(strlen(s))
//:str(new char[strlen(s) + 1] )
//,size(strlen(s)) //多次调用了strlen,牺牲了效率
//,capacity(strlen(s))
{
if (s == nullptr) //构造类对象时,如果传递nullptr,可认为程序非法
{
assert(false);
return;
}
_capacity=_size;
_str = new char[_size + 1];
//strcpy(_str, s); //遇到\0会终止
memcpy(_str, s, _size + 1);
}
template<class InputIterator>
string(InputIterator first, InputIterator last) //使用迭代区间,防止中途遇到\0而终止
{
while (first != last)
{
push_back(*first);
first++;
}
}
const char* c_str()const
{
return str;
}
~string()
{
delete[]str;
str = nullptr;
size = 0;
capacity = 0;
}
private:
char* str;
//计算时都不包含结尾的'\0'
size_t size;
size_t capacity;
};
}
int main()
{
zephyr::string s;
cout << s.c_str() << endl;
}
拷贝构造 :
//传统写法:
/*string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size+1);
_size = s._size;
_capacity = s._capacity;
}*/
//现代写法:
string(const string& s)
{
//错误写法:string tmp(s._str); //中间有\0会导致提前终止
string tmp(s.begin(), s.end());//reserve中一定要判断原字符串是否为空
swap(tmp);
}
构造函数和拷贝构造函数的区别:
增操作(push_back、append、insert、operator+=)
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity==0?4:2 * _capacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(max(_size + len, 2 * _capacity));
}
memcpy(_str + _size, str, len+1);//相当于加完字符串后,把最后的'\0'也加上了
_size += len;
//_str[_size] = '\0'; 前面是memcpy(_str + _size, str, len)时才要加这个'\0'
}
注意:记得判断是否后面要手动加\0
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size;//指向'\0' 即使写成int下面比较的时候会被 提升
while (end >= pos)//size_t 类型 无负数故而会导致头插有问题
{
_str[end + 1] = _str[end];
if (end == 0)break; //直接使用memmove便不用考虑该问题
end--;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(max(_size + len, 2 * _capacity));
}
memmove(_str + pos + len, _str + pos, _size + 1 - pos);
_size += len;
for (int i = 0; i < len; i++)
_str[pos + i] = str[i];
}
注意:insert 头插时判断是否会因为比较大小而出现问题(size_t类型无负数,会导致0下一个仍为正数而出现死循环等)eg:if (end == 0)break; 就是解决方法之一
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
直接使用push_back和append来实现
扩容操作:(reserve)
void reserve(size_t n)
//只做扩容处理
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
//检查是否旧空间为空
if(_str)
{
//错误示范:strcpy拷贝数据的话遇到'\0'就停止了,但字符串中可能中间包含'\0'
memcpy(tmp, _str, _size + 1);//_size个字符 外加 最后的'\0'
delete[]_str;
}
_str = tmp;
_capacity = n;
}
}
注意:拷贝时使用memcpy
一定要检查旧的空间是否为空,防止程序崩溃
迭代器的实现:
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
删操作(erase)
void erase(size_t pos, size_t len = npos)//从pos开始往后去删
{
assert(pos < _size);
if (len == npos || pos + len > _size)//pos后全删
{
_str[pos] = '\0';
_size = pos;
}
else//删部分
{
memmove(_str + pos, _str + pos + len, _size + 1 - (pos + len));//将末尾的\0也一起移
}
}
注意分类讨论
查找操作(find):
size_t find(char ch, size_t pos=0)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == pos)return i;
}
}
size_t find(const char* sub, size_t pos=0)
{
const char* p = strstr(_str, sub);//返回_str中第一次出现sub的位置的指针
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;//求下标
}
}
获取子串(substr):
string substr(size_t pos, size_t len=npos)
{
assert(pos < _size);
string ret;
//判断是否需要更新len
if (pos + len > _size || len == npos)//或者直接写成len>_size-pos
{
len = _size - pos;
}
for (size_t i = 0; i < len; i++)
{
ret += _str[pos + i];
}
return ret;
}
比较大小 :
最初比较时使用的是strcmp()--按字典序比较
注意:但是其遇到\0就会停止
memcmp()需要第三个参数(比较多少个字节),也并不能一步到位
bool operator<(const string& s)const
{
//错误案例:return strcmp(_str,s._str)<0; //有坑 遇到\0就停止了
//memcmp 需要第三个参数size
//此比较方法为:先比长度,再进行字典序比较
if (_size < s._size)return true;
else if (_size == s._size)
{
for (int i = 0; i < _size; i++)
{
if (_str[i] < s._str[i])return true;
else if (_str[i] > s._str[i])return false;
}
}
else return false;
}
bool operator<=(const string& s)const
{
return (*this < s || *this == s);
}
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
{
if (_size == s._size)
{
for (int i = 0; i < _size; i++)
{
if (_str[i] != s._str[i])return false;
}
return true;
}
return false;
}
bool operator!=(const string& s)const
{
return !(*this == s);
}
此处写得比较大小与库(字典序)的无关
operator=
string& operator=(const string& s)
{
if(this!=&s)//this和s的地址进行比较
{
//1.
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size + 1);//s的_size
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
//另一种写法:2.
//string tmp(s);
//swap();交换后tmp的空间会自行析构带走
}
return *this;
}
//更为简洁的写法:3.(但无法判断s1=s1这种自己给自己赋值)不过这本就是非正常场景
string& operator=(string tmp)//传形参,调用拷贝构造
{
swap(tmp);
return *this;
}
int main()
{
zephyr::string s1("hello world");
zephyr::string s2 = s1.substr(6);//初始化时调用的是拷贝构造函数
cout << s2.c_str() << endl;
zephyr::string s3(s2);//不写拷贝构造函数就是浅拷贝
cout << s3.c_str() << endl;
s2 = s1;//没写operator=的话浅拷贝,指向同一块地址,释放两次而程序崩溃
cout << s2.c_str() << endl;
}
上述我认为2.最简介合适
注意:
1、上述代码s2 = s1;(两个已经存在的变量)调用的是operator=,如若为自行定义(默认浅拷贝)导致后续指向同一块空间,最后被释放两次而导致程序崩溃。
2、一定要做地址检查防止(s1=s1)时造的成错误和消耗。
流插入和流提取:(不需要写为友元函数)
std::ostream& operator<<(std:: ostream& os, const zephyr::string& s) //记得写zephyr::域
{
for (auto x : s)
{
os << x;
}
return os;
}
std::istream& operator>>(istream& is, zephyr::string& s)
{
s.clear();//清空之前的数据
char tmp[256];//解决输入长字符串、短字符串的折中方法,减少reserev开空间次数造成的消耗
size_t i = 0;
char ch;
//错误示范:is >> ch; //cin输入 空格换行 被认为是 多项值之间的分割 故而不会读入
ch=is.get();
while (ch != ' ' && ch != '\n')
{
//s += ch;//不会停止
tmp[i++] = ch;
if (i == 255)
{
tmp[255] = '\0';
s += tmp;
i = 0;//重置为零
}
//is >> ch;//不会读入空格换行 故而不会停止
ch = is.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return is;//返回输入流是为了支持连续的输入
}
注意:
1.获取字符时不能写 is >> ch; (原因:cin输入 空格换行 被认为是 多项值之间的分割 故而不会读入空格和换行,故而不会停止),故而采取get函数
2.为解决输入长字符串时reserve次数过多而造成的空间消耗,可以先reserve一个较大的空间,但这样如若遇到的是短字符串会造成空间浪费(reserve后的容量不可能缩小)。
故而采取在栈区上开一个tmp数组(根据实际自行安排大小)先进行存储,达到上限\输入结束后 再进行s+=来减少reserve次数
c_str:(与cou<<还是有区别的)
const char* c_str()const
{
return _str;
}
如图所示:c_str遇到'\0'便会停止
交换函数swap():效率陷阱
1.模拟实现算法库的swap模板:(消耗过大)
template <class T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
int main()
{
zephyr::string s1("hello world");
zephyr::string s2("zephyr");
swap(s1, s2);
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
return 0;
}
算法库的交换会多次深拷贝,如上面写的swap模板,多次开空间释放空间和拷贝
2.直接交换指针指向位置,即个数和容量(有效减少消耗)
void swap(string& s)//这是在类中定义的
{
//算法库的交换会多次深拷贝,如上面写的swap模板,多次开空间释放空间和拷贝
//直接交换指针指向位置,即个数和容量
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
void swap(string& s1,string& s2)//类外定义
{
s1.swap(s2);
}
int main()
{
zephyr::string s1("hello world");
zephyr::string s2("zephyr");
s1.swap(s2);//类外定义后swap(s1,s2);的实质便是前面的
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
return 0;
}
这也是为何许多类中有单独自己的swap的原因
下述为完整代码 :
template <class T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
namespace zephyr
{
// 错误案例:const size_t string:: npos = -1; 此时string还未声明//类里面声明,类外面定义
class string
{
public:
static const size_t npos;//特殊处理(因为是const静态,且得是整型)
//static const double x = 1.1; 错误案例 const后给值仅针对整型,其他类型会报错
//static size_t x = 1; 错误案例 报错是因为给缺省值用于初始化列表,但静态成员变量并不走初始化列表
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()
:str(new char[1]{'\0'})
,size(0)
,capacity(0)
{}*/
//string(const char* s=nullptr) 错误示范
//string(const char* s="\0") 错误示范
string(const char* s="")
:_size(strlen(s))
//:str(new char[strlen(s) + 1] )
//,size(strlen(s)) //多次调用了strlen,牺牲了效率
//,capacity(strlen(s))
{
if (s == nullptr) //构造类对象时,如果传递nullptr,可认为程序非法
{
assert(false);
return;
}
_capacity=_size;
_str = new char[_size + 1];
//strcpy(_str, s);
memcpy(_str, s, _size + 1);
}
template<class InputIterator>
string(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
//传统写法:
/*string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size+1);
_size = s._size;
_capacity = s._capacity;
}*/
//现代写法:
string(const string& s)
{
//错误写法:string tmp(s._str); //中间有\0会导致提前终止
string tmp(s.begin(), s.end());
swap(tmp);
}
const char* c_str()const
{
return _str;
}
~string()
{
if(_str)
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
}
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
{
assert(i < _size);
return _str[i];
}
void reserve(size_t n)
//只做扩容处理
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
//检查是否旧空间为空
if(_str)
{
//错误示范:strcpy拷贝数据的话遇到'\0'就停止了,但字符串中可能中间包含'\0'
memcpy(tmp, _str, _size + 1);//_size个字符 外加 最后的'\0'
delete[]_str;
}
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity==0?4:2 * _capacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(std::max(_size + len, 2 * _capacity));
}
memcpy(_str + _size, str, len+1);//相当于加完字符串后,把最后的'\0'也加上了
_size += len;
//_str[_size] = '\0'; 前面是memcpy(_str + _size, str, len)时才要加这个'\0'
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator=(const string& s)
{
if(this!=&s)//this和s的地址进行比较
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size + 1);//s的_size
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
//另一种写法:
//string tmp(s);
//swap();交换后tmp的空间会自行析构带走
}
return *this;
}
//更为简洁的写法:
string& operator=(string tmp)//传形参,调用拷贝构造
{
swap(tmp);
return *this;
}
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size;//指向'\0' 即使写成int下面比较的时候会被 提升
while (end >= pos)//size_t 类型 无负数故而会导致头插有问题
{
_str[end + 1] = _str[end];
if (end == 0)break; //直接使用memmove便不用考虑该问题
end--;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(std::max(_size + len, 2 * _capacity));
}
memmove(_str + pos + len, _str + pos, _size + 1 - pos);
_size += len;
for (int i = 0; i < len; i++)
_str[pos + i] = str[i];
}
void erase(size_t pos, size_t len = npos)//从pos开始往后去删
{
assert(pos < _size);
if (len == npos || pos + len > _size)//pos后全删
{
_str[pos] = '\0';
_size = pos;
}
else//删部分
{
memmove(_str + pos, _str + pos + len, _size + 1 - (pos + len));//将末尾的\0也一起移
}
}
size_t find(char ch, size_t pos=0)const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == pos)return i;
}
}
size_t find(const char* sub, size_t pos=0)const
{
const char* p = strstr(_str, sub);//返回_str中第一次出现sub的位置的指针
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;//求下标
}
}
string substr(size_t pos, size_t len=npos)const
{
assert(pos < _size);
string ret;
//判断是否需要更新len
if (pos + len > _size || len == npos)//或者直接写成len>_size-pos
{
len = _size - pos;
}
for (size_t i = 0; i < len; i++)
{
ret += _str[pos + i];
}
return ret;
}
bool operator<(const string& s)const
{
//错误案例:return strcmp(_str,s._str)<0; //有坑 遇到\0就停止了
//memcmp 需要第三个参数size
//此比较方法为:先比长度,再进行字典序比较
if (_size < s._size)return true;
else if (_size == s._size)
{
for (int i = 0; i < _size; i++)
{
if (_str[i] < s._str[i])return true;
else if (_str[i] > s._str[i])return false;
}
}
else return false;
}
bool operator<=(const string& s)const
{
return (*this < s || *this == s);
}
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
{
if (_size == s._size)
{
for (int i = 0; i < _size; i++)
{
if (_str[i] != s._str[i])return false;
}
return true;
}
return false;
}
bool operator!=(const string& s)const
{
return !(*this == s);
}
//friend ostream& operator>>(ostream& os, const string& str);已经给了适配接口无需使用友元访问
void clear()
{
_str[0] = '\0';
_size = 0;
}
void swap(string& s)
{
//算法库的交换会多次深拷贝,如上面写的swap模板,多次开空间释放空间和拷贝
//直接交换指针指向位置,即个数和容量
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
private:
//char _buf[16];
char* _str=nullptr;
//计算时都不包含结尾的'\0'
size_t _size=0;
size_t _capacity=0;
};
const size_t string::npos = -1;//类里面声明,类外面定义
}
std::ostream& operator<<(std:: ostream& os, const zephyr::string& s) //记得写zephyr::域
{
for (auto x : s)
{
os << x;
}
return os;
}
std::istream& operator>>(std::istream& is, zephyr::string& s)
{
s.clear();//清空之前的数据
char tmp[256];//解决输入长字符串、短字符串的折中方法,减少reserev开空间次数造成的消耗
size_t i = 0;
char ch;
//错误示范:is >> ch; //cin输入 空格换行 被认为是 多项值之间的分割 故而不会读入
ch=is.get();
while (ch != ' ' && ch != '\n')
{
//s += ch;//不会停止
tmp[i++] = ch;
if (i == 255)
{
tmp[255] = '\0';
s += tmp;
i = 0;//重置为零
}
//is >> ch;//不会读入空格换行 故而不会停止
ch = is.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return is;//返回输入流是为了支持连续的输入
}
//int main()
//{
// /*zephyr::string s;
// cout << s.c_str() << endl;
// zephyr::string s2("hello world");
// cout << s2.c_str() << endl;*/
//
// /*for (int i = 0; i < s2.size(); i++)
// {
//
// cout << s2[i] << " ";
// }*/
//
// /*zephyr::string s3;
// zephyr::string s4;
//
// for (int i = 1; i <= 7; i++)
// {
// s3.push_back('x');
// s4.append("ab");
// }
// cout << s3.c_str() << endl;
// cout << s4.c_str() << endl;
//
// for (int i = 1; i <= 10; i++)
// {
// s3+=('x');
// s4+=("ab");
// }
// cout << s3.c_str() << endl;
// cout << s4.c_str() << endl;*/
//
// /*zephyr::string s5("hello world");
// s5.erase(6);
// cout << s5.c_str() << endl;
//
// zephyr::string s6("hello world");
// s6.erase(6,3);
// cout << s6.c_str() << endl;
//
// zephyr::string s7("hello world");
// s7.insert(0, "my ");
// cout << s7.c_str() << endl;*/
//
// return 0;
//}
//int main()
//{
// zephyr::string s1("hello world");
// zephyr::string s2 = s1.substr(6);
// cout << s2.c_str() << endl;
// zephyr::string s3(s2);//不写拷贝构造函数就是浅拷贝
// cout << s3.c_str() << endl;
// s2 = s1;//没写operator=的话浅拷贝,指向同一块地址,释放两次而程序崩溃
// cout << s2.c_str() << endl;
//}
//int main()
//{
// zephyr::string s1("hello");
// zephyr::string s2("world");
// cout << (s1 >= s2) << endl;
// return 0;
//}
//int main()
//{
// zephyr::string s("hello world");
// //cout << s << endl;
//
// s += '\0';
// s += "xxxxxxxxx";
// cout << s.c_str() << endl;
// cout << s << endl;
//
//}
//int main()
//{
// zephyr::string s1,s2;
// cin >> s1 >> s2;
//
// cout << s1 << endl;
// cout << s1.size() << endl;
// cout << s1.capacity() << endl;
//
// cout << s2 << endl;
// cout << s2.size() << endl;
// cout << s2.capacity() << endl;
//}
int main()
{
zephyr::string s1("hello world");
zephyr::string s2("zephyr");
s1.swap(s2);
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
return 0;
}
其余问题:
1.为何要typedef const char* const_iterator;而不直接使用const iterator呢?有何区别?
也就是说const iterator相当于char* const是指针常量,相当于const修饰的是iterator(char*)类型别名本身,而不是其指向的char内容。
例如:const iterator str;即str不能变,*str可变。
而const_str 才是真正的常量指针,指向内容不可变。
区分:指针常量(指针是常量) 常量指针(指向常量的指针)
2.计算string对象的大小,为何64位平台下不是24字节?
可以看到除了_ptr外还有一个存储小范围字符串的_buf(如下图所示),这是一种编译器的优化用于提高效率
3.扩展:g++早期使用的写时拷贝:(先浅拷贝+引用计数+适当时深拷贝)
浅拷贝的问题:
1.析构多次程序崩溃(利用引用计数来解决)
2.一个修改影响另一个(修改时深拷贝)
写时拷贝:设计优势:拷贝后不一定修改对象