目录
0.前言
1 .构造函数—析构函数—[]重载实现
2.深浅拷贝问题
2.1 浅拷贝
2.2 深拷贝
2.3写时拷贝
3.拷贝函数——赋值重载传统及现代写法
4.迭代器实现
5.reserve、push_back、append、+=运算符重载
6.insert、erase实现
7.find、关系运算符、流插入流提取等的实现
8.string类面试参考
模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。
class string { public: /* string() :_str(new char[1]) , _size(0) , _capacity(0) { _str[0] = '\0'; }*/ //string(const char* str = "\0")//相当于两个\0 /* string(const char* str = "") :_str(new char[strlen(str)+1]) ,_size(strlen(str)) ,_capacity(strlen(str)) //strlen时间复杂度 O(N) { strcpy(_str, str); }*/ //不推荐下面写法,因为想当于将成员绑定了,不利于维护 /* string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) ,_str(new char[_capacity + 1]) { strcpy(_str, str); }*/ string(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; } char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } //测试调用 const char* c_str() const { return _str; } size_t size()const { return _size; } size_t capacity()const { return _capacity; } private: //vs下,sizeof(),给一个小于16的字符串,默认算出结果是28个字节 // 因为其成员变量中有一个_Buf[16]的数组 // 以空间换时间,避免较小空间的频繁扩容 // < 16 字符串存在_Buf数组中,否则,存在堆空间 // char _Buf[16]; char* _str; size_t _size; size_t _capacity; public: //C++特列,const static特殊处理 //直接可以定义初始化 const static size_t npos = -1; }; }
测试:
void test_string1() { string s1("hello world"); string s2; std::cout << s1.c_str() << std::endl; std::cout << s2.c_str() << std::endl; for (size_t i = 0; i < s1.size(); ++i) { std::cout << s1[i] << " "; } std::cout << std::endl; for (size_t i = 0; i < s1.size(); ++i) { s1[i] ++; std::cout << s1[i] << " "; } std::cout << std::endl; }
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构 造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共 享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为 还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就 你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给 计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该 对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell
C++的std::string的“读时也拷贝”技术! | 酷 壳 - CoolShell
/*string(const string& s)
:_str(new char[s._capacity + 1])
,_size(s._size)
,_capacity(s._capacity)
{
strcpy(_str, s._str);
}*/
//现代写法 - - 拷贝构造
void swap(string& tmp)
{
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
//现代写法 - - 赋值重载
//s顶替tmp做大工人
string& operator=(string s)
{
swap(s);
return *this;
}
//传统写法
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}*/
/*string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
//先开空间,拷贝数据,再去释放原空间
//就算开辟失败,对元数据没有破坏
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
测试用例:
void test_string3()
{
string s1("hello world");
string s2(s1);
std::cout << s1.c_str() << std::endl;
std::cout << s2.c_str() << std::endl;
string s3 = "1111111111111111111";
s1 = s3;
std::cout << s3.c_str() << std::endl;
std::cout << s1.c_str() << std::endl;
s1 = s1;
std::cout << s1.c_str() << std::endl;
}
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _size + _str;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _size + _str;
}
测试用例:
void test_string2()
{
string s1("hello world");
string::iterator it = s1.begin();
while (it != s1.end())
{
std::cout << *it <<" ";
++it;
}
std::cout << std::endl;
for (auto ch : s1)
{
std::cout << ch << " ";
}
std::cout << std::endl;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str+_size,str);
_size += len;
}
void append(const string& s)
{
append(s._str);
}
void append(size_t n, char ch)
{
reserve(_size + n);
for (size_t i = 0; i < n; i++)
{
push_back(ch);
}
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(const string& s)
{
append(s);
return *this;
}
测试用例:
void test_string4()
{
string s1;
char ch = 'l';
s1.push_back('h');
s1.push_back('e');
s1.push_back(ch);
s1.push_back(ch);
s1.push_back('o');
std::cout << s1.c_str() << std::endl;
std::cout << s1.capacity() << std::endl;
s1 += ' ';
s1 += 'w';
s1 += 'o';
s1 += 'r';
s1 += 'l';
s1 += 'd';
std::cout << s1.c_str() << std::endl;
std::cout << s1.capacity() << std::endl;
s1.append(" 算你厉害");
std::cout << s1.c_str() << std::endl;
const char* str = " 六六六";
s1 += (" 你六");
s1 += str;
s1.append(str);
std::cout << s1.c_str() << std::endl;
string s2 = "你好 ";
string s3("世界");
s2.append(s3);
std::cout << s2.c_str() << std::endl;
}
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
//满了就扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
挪动数据——当pos为0就会越界访问
//end为 -1 时,由于是无符号类型,会被转为很大得数
//把end类型改为int,仍然运行错误
//因为pos为无符号,和end比较,发生隐士类型转换
/*size_t end = _size;
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}*/
//不推荐,修改一
/*int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}*/
//推荐,修改二
size_t end = _size+1;
while (end > pos)
{
_str[end] = _str[end-1];
--end;
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* ch)
{
assert(pos <= _size);
size_t len = strlen(ch);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, ch, len);
_size += len;
return *this;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
测试用例:
void test_string5()
{
string s1 = "hello 你好";
std::cout << s1.c_str() << std::endl;
s1.insert(5, '#');
std::cout << s1.c_str() << std::endl;
s1.insert(0, '#');
std::cout << s1.c_str() << std::endl;
s1.insert(0, "hello");
std::cout << s1.c_str() << std::endl;
s1.insert(2, "hello");
std::cout << s1.c_str() << std::endl;
}
void test_string6()
{
string s1 = "hello 你好";
s1.erase(3, 3);
std::cout << s1.c_str() << std::endl;
s1.erase(3);
std::cout << s1.c_str() << std::endl;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* sub, size_t pos = 0) const
{
assert(pos < _size);
assert(sub);
//子串匹配算法:strstr kmp bm
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string substr(size_t pos, size_t len = npos) const
{
assert(pos < _size);
size_t realLen = len;
if (len == npos || len+pos > _size)
{
realLen = _size - pos;
}
string sub;
for (size_t i = 0; i < realLen; ++i)
{
sub += _str[pos+i];
}
return sub;
}
bool operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator>(const string& s) const
{
return strcmp(_str, s._str) > 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);
}
流插入、流提取:
std::istream& operator>>(std::istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const size_t N = 32;
char buff[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff; // 一批一批的进行扩容,buff出作用域就会销毁
// 因此流插入多少个字符,就会扩容多大的空间,不会进行频繁扩容
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
输入字符串很长,不断+=,频繁扩容,效率很低,需要优化
//char ch;
in >> ch;// 使用流插入遇到换行和空格不会自己结束
//ch = in.get();
s.reserve(128);// 一上来就reserve,缺陷空间可能会浪费
//
//while (ch != ' ' && ch != '\n')
//{
// size_t old = s.capacity();
// s += ch;
// /*in >> ch;*/
// /*if (s.capacity() != old)
// {
// std::cout << old << "扩容" << s.capacity() << std::endl;
// }*/
// ch = in.get();
//}
return in;
}
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
C++面试中string类的一种正确写法 | 酷 壳 - CoolShell
(7条消息) STL 的string类怎么啦?_haoel的博客-CSDN博客