【C++】—掌握STL string类:string的模拟实现
文章目录
- 💞1.浅拷贝
- 💞2.深拷贝
- 💞3. string类的模拟实现
- 💞3.1 string的构造函数
- 💞3.2 string的析构函数
- 💞3.3 string的拷贝构造
- 💞3.4 string的size
- 💞3.5 string的operator[]
- 💞3.6 string的迭代器
- 💞3.7 reserve
- 💞3.8 push_back
- 💞3.9 append
- 💞3.10 operator+=
- 💞3.11 insert
- 💞3.12 erase
- 💞3.13 find
- 💞3.14 operator=
- 💞3.15 比较(全局)
- 💞3.16 流插入 (全局)
- 💞3.16 流提取 (全局)
- 💞3.17getline (全局)
💞1.浅拷贝
什么是浅拷贝
浅拷贝也称之为位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就导致多个对象公用同一份资源,当一个对象销毁时就会导致该资源释放掉,而此时的其他对象不知道该资源已经被释放掉,所以继续对资源进行操作时,就会导致访问违规。
可以使用深拷贝解决浅拷贝的问题即:每个对象都有一份独立的资源,不需要和其他对象共享。
浅拷贝的问题
当对象指向包含动态分配内存的指针时,浅拷贝可能会导致潜在的问题。由于像个对象公用同一块内存空间,因此任何一个对象对该内存的修改都会影响到另一个对象。此外当两个对象中的一个被销毁并释放它所占用的内存时,另一个对象将拥有一个悬垂指针(dangling pointer)
,即指向已经被释放的内存的指针。这可能导致未定义行为,包括程序崩溃。
浅拷贝的实现
在c++
中,如果没有显式定义拷贝构造函数或者赋值重载函数,编译器将自动生成默认的拷贝构造函数和赋值运算符重载函数,它们执行的是浅拷贝。这意味着,对于包含指向动态分配内存的指针的类,如果不显式的实现 深拷贝,那么使用编译器默认生成的拷贝构造函数和运算符重载将导致浅拷贝。
示例代码:
#include<iostream>
#include<cstring>
using namespace std;
class MyClass
{
public:
//构造函数
MyClass(const char* str = "")
{
data = new char[strlen(str) + 1];
strcpy(data,str);
}
//没有显式定义拷贝构造函数和赋值运算符重载,因此使用默认的浅拷贝
//析构函数
~MyClass()
{
delete[] data;
}
//打印数据
void print() const
{
cout << data << endl;
}
private:
char* data;
};
int main()
{
MyClass s1("hello,world!");
MyClass s2 = s1;//使用默认的拷贝构造函数进行浅拷贝
s1.print();//输出:hello,world!
s2.print();//输出:hello,world!
//修改s2的数据
//delete[] s2.data;//这将导致未定义行为,因为s1和s2共享同一块内存
//s2.data = new char[6];
//strcpy(s2.data, "world");
//尝试打印s1的数据(可能导致程序崩溃)
s1.print();//未定义行为,因为s1的data指针现在指向的资源已经被释放
return 0;
}
💞2.深拷贝
什么是深拷贝
深拷贝(deep copy)
是一种对象复制的操作,它不仅复制对象本身的数据成员,还递归的复制对象内部所有指向动态分配内存的指针所引用的对象。这意味这,通过深拷贝创建的对象与原始对象是完全独立的,修改新对象的任何成员都不影响原始对象,反之亦然。
深拷贝的必要性
在c++
中,当对象包含指向动态分配内存的指针时,仅仅复制这些指针的值(即地址)时不够的。这是因为两个对象公用同一块内存,从而导致潜在的内存管理问题,如重复释放内存或者内存泄漏。为了避免这些问题,我们就需要深拷贝,以确保每个对象都有自己独立的内存空间。
深拷贝的实现
在c++
中,实现深拷贝通常涉及以下步骤:
• 定义拷贝构造函数: 拷贝构造函数是一个特殊的构造函数,它接受一个同类型对象的引用作为参数。通过实现拷贝构造函数,我们可以定义对象如何被复制。在拷贝构造函数中,我们需要为新对象分配内存,并复制原始对象的数据成员,包括那些指向动态分配内存的指针所引用的对象。
• 实现赋值运算符重载: 与拷贝构造函数类似,复制运算符重载(operator=)
也用于处理对象的复制。在赋值运算符重载中,我们需要确保在赋值之前释放新对象当前占用的内存,然后为新对象分配内存并复制原始对象的成员数据。
• 递归复制: 对于对象内部包含的任何指向动态分配内存的指针,我们需要递归地调用这些对象的拷贝构造函数或者运算符重载函数,以确保它们也被深拷贝。
示例代码:
#include<iostream>
#include<cstring>
using namespace std;
class MyClass
{
public:
//构造函数
MyClass(const char* str = "")
{
data = new char[strlen(str) + 1];
strcpy(data, str);
}
//拷贝构造函数(实现深拷贝)
MyClass(const MyClass& other)
{
data = new char[strlen(other.data) + 1];
strcpy(data,other.data);
}
//复制运算符重载(实现深拷贝)
MyClass& operator=(const MyClass& other)
{
if (this == &other)
{
return *this; //处理自我赋值
}
//释放当前对象的内存
delete[] data;
//分配新内存并复制数据
data = new char[strlen(other.data) + 1];
strcpy(data,other.data);
return *this;
}
//析构函数
~MyClass()
{
delete[] data;
}
//打印数据
void print() const
{
cout << data << endl;
}
private:
char* data;
};
int main()
{
MyClass s1("hello,world!");
MyClass s2 = s1; //使用拷贝构造函数
MyClass s3;
s3 = s1;//使用赋值运算符重载进行深拷贝
s1.print();
s2.print();
s3.print();
return 0;
}
💞3. string类的模拟实现
💞3.1 string的构造函数
string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
💞3.2 string的析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
💞3.3 string的拷贝构造
string(const string& s)
{
_str = new cahr[s._capacity + 1];
strcpy(_str,s._str);
_size = s._size;
_capacity = s._capacity;
}
💞3.4 string的size
size_t size() const
{
return _size;
}
💞3.5 string的operator[]
//普通版本
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//const版本
const char& operator[](size_t i) const ·
{
assert(i < _size);
return _str[i];
}
💞3.6 string的迭代器
//普通迭代器
using iterator = char*;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//const迭代器
using const_iterator = const char*;
const_iterator begin() const //为什么不是const iterator .因为不是说iterator不能修改,而是说指向的内容不能被修改
{
return _str;
}
const_iterator end() const
{
return _str + _size;//最后一个位置的下一个位置
}
💞3.7 reserve
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//+1是给/0开的
strcpy(tmp,_str);
}
delete[] _str;
_str = tmp;
_capacity = n;
}
💞3.8 push_back
//普通版本
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
}
//复用insert版本
void push_back(char ch)
{
insert(_size,ch);
}
💞3.9 append
//普通版本
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
//扩2倍不够,则需要多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
strcpy(_str + _size, str)
_size += len;
}
复用insert版本
void append(const char* str)
{
insert(_size,char);
}
💞3.10 operator+=
string& operator+= (char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
💞3.11 insert
//插入一个字符
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
//插入一段字符串
//第一代(强转风格)
void insert(size_t pos, char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * capacity;
//扩二倍不够则需要多少扩多少
if (newCpacity < _size + len)
newCpacity = size + len;
reserve(newCapacity);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
//第二代
void insert(size_t pos, char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * capacity;
//扩二倍不够则需要多少扩多少
if (newCpacity < _size + len)
newCpacity = size + len;
reserve(newCapacity);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
💞3.12 erase
//先在类里面定义一个静态成员变量
//static const size_t npos = -1; 特殊处理,只有const整形成员可以在类里面给缺省值
void earse(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//从后往前挪
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
++end;
}
_size -= len;
}
}
💞3.13 find
size_t find(char ch,size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
return i;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos,str);
if (ptr == nullptr)
{
return nops;
}
else
{
return ptr - _str;
💞3.14 operator=
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s.capacity + 1];//+1预留给\0
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
💞3.15 比较(全局)
bool operator==(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str) == 0;
}
bool operator!=(const string& lhs, const string& rhs)
{
return !(lhs == rhs)
}
bool operator>(const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator<(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(),rhs.c_str) < 0;
}
bool operator>=(const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<=(const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
💞3.16 流插入 (全局)
ostream& operator<<(ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
💞3.16 流提取 (全局)
void clear()
{
_str[0] = '\0';
_size = 0;
}
istream& operator>>(istream& is, string& str)
{
str.clear();
int i = 0;
char buff[256];//栈上开空间比堆上开空间的效率高,函数结束buff也就销毁了
char ch;
ch = is.get();
while (ch != ' ' && ch != '\n')
{
//遇到数据先放到buff里面
buff[i++] = ch;
if (i == 255)
{
buff[255] = '\0';
str += buff;
i = 0;
}
ch = is.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}
💞3.17getline (全局)
istream& getline(istream& is, string& str,char delim = '\n')
{
str.clear();
int i = 0;
char buff[256];//栈上开空间比堆上开空间的效率高,函数结束buff也就销毁了
char ch;
ch = is.get();
while (ch != delim)
{
//遇到数据先放到buff里面
buff[i++] = ch;
if (i == 255)
{
buff[255] = '\0';
str += buff;
i = 0;
}
ch = is.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}