C++ 自定义String类
想要实现高级的String类还挺复杂的,C++和C#不一样,许多模块都需要自己来解决内存问题,并且如何提高内存空间的利用效率,调用函数是否触发拷贝构造函数,触发拷贝构造函数时又该如何避免重复的内存释放。
还剩下几个重载没实现,剩下明天再写,关于该类的测试和说明还需要慢慢补充。
类的声明
使用char来实现一个string类。
首先是定义一个String类,然后定义内部的成员变量和成员函数。
内容:
1.
私有成员 | 说明 |
---|---|
_str | 字符串 |
_size | 字符串长度 |
bool _SetString | 封装设置_str和_size |
_log | 用于测试的临时输出信息,由于要节省时间,暂时使用string储存,类实现完毕后删除所有log相关代码 |
公有成员 | 说明 |
---|---|
String | 构造函数包括默认构造函数,重载字符数组,重载拷贝构造函数,以及实现move函数 |
~String | 析构函数,销毁对象时对内存进行释放 |
String& operator = | 重载=操作符,并实现move函数,难点在于写成了自身的递归 |
(暂未实现)String operator + | 重载+操作符,由于返回的是临时变量,前面应当加上const,并且函数头不需要使用引用 |
(暂未实现)String operator += | 重载+=操作符,应该与重载=操作符类似 |
char& operator[] | 重载中括号,要实现c=s[0]和s[0]=c |
(暂未实现)istream& operator >> | 重载输入流,需要研究一下有没有高效写法,以及会不会出现 \r\n 的问题 |
(暂未实现)ostream& operator << | 重载输出流 |
String* GetAddress | 返回自身地址 |
String GetCopy() | 返回克隆 |
String& GetOriginal() | 返回本身 |
char* c_str() | 返回字符串本身的字符数组地址,要加上const防止被修改内存 |
int length() | 返回字符串长度 |
class String {
private:
char* _str = NULL;
size_t _size = 0;
string _log;
bool _SetString(const char* str);
public:
String();
String(const char *str);
String(const String& other);
String(String&& other) noexcept; // TODO: noexcept
~String();
// 重载 = 操作符
String& operator =(const char* str);
String& operator =(const String& other);
String& operator =(String&& other) noexcept; // TODO: noexcept
// 重载+操作符, 返回的是临时对象,不需要使用引用
const String operator +(const char* str);
const String operator +(const char ch);
const String operator +(const String& other);
// 重载+=操作符
String operator +=(const char* str);
String operator +=(const char ch);
String operator +=(const String& other);
// 重载中括号
//char operator[](size_t index);
char& operator[](size_t index); // 可以实现str[0]='1'
// 重载==操作符
bool operator ==(const char* str);
bool operator ==(const String& other);
// 重载输入输出流
friend istream& operator >> (istream& cin, String& str);
friend ostream& operator << (ostream& cin, String& str);
//返回自身地址
String* GetAddress() { return this; }
// 返回克隆
String GetCopy() { return *this; }
// 返回本身
String& GetOriginal() { return *this; }
// 返回字符串本身的字符数组地址,加上const防止被修改
const char* c_str() { return _str; }
// 返回字符串长度
int length() { return _size; }
};
_SetString
bool String::_SetString(const char* str)
{
try {
if (str == NULL) {
_size = 0;
_str = NULL;
}
else {
if (_str != NULL) {
delete[] _str;
_str = NULL;
}
_size = strlen(str);
_str = new char[_size + 1];
strcpy(_str, str);
_str[_size] = '\0';
}
}
catch(...){
return false;
}
return true;
}
构造函数和析构函数
String::String() {
// 在声明处已经初始化了变量,该析构函数只是为了输出信息
_log = "String()";
cout << "String()" << endl;
}
String::String(const char* str)
{
_SetString(str);
_log = "String(const char* str)";
cout << "String(const char* str)" << endl;
}
String::String(const String& other)
{
_SetString(other._str);
_log = "String(const String& s)";
cout << "String(const String& s)" << endl;
}
// String的move实现
String::String(String&& other) noexcept
{
_size = other._size;
_str = other._str;
other._size = 0;
// 这样在析构other的时候,不会回收other._str指向的内存空间
other._str = NULL;
}
String::~String()
{
cout << "~String" << _log << endl;
if (_str != NULL) {
delete _str;
_str = NULL;
}
}
待测试项
String s1();
String s2("123");
String s3(s2);
String s4 = s2; // s4未被实例化,此处等价于String s4(s2)
String s5;
s5 = s2; // 触发=重载
如果没重载=,会报错“尝试引用已删除的函数”,因此必须重载
触发“尝试引用已删除的函数”的条件:
- 含有非静态的const成员变量
- 含有非静态的reference成员变量
- 含有不能被拷贝的成员变量
- 含有不能被拷贝的基类
- 含有用户定义的移动构造函数或者移动赋值函数
看到其他博客对move的解释(暂时没看懂先用着):
std::move可以将一个左值引用强制转为右值引用,只是转移状态没有转移内存,等同于 static_cast<T&&>
而函数参数T&&是一个指向模板类型参数的右值引用,通过引用折叠,此参数可以与任何类型的实参匹配
重载=操作符
String& String::operator=(const char* str)
{
cout << "operator=(const char* str)" << endl;
_SetString(str);
return *this;
}
String& String::operator=(const String& other)
{
cout << "operator=(const String& other) self" << endl;
// 检查自赋点
if (this != &other) {
// 释放内存,分配新空间
_SetString(other._str);
}
return *this;
}
String& String::operator=(String&& other) noexcept
{
cout << "operator=(String&& other)" << endl;
// 检查自赋点
if (this != &other) {
_size = other._size;
_str = other._str;
other._size = 0;
other._str = NULL;
}
return *this;
}
下面是错误写法,会进入无限递归的死循环,我很自然的以为*this=String(…)可以实现我想要的拷贝,但是会不断触发第三个拷贝而进入死循环
String& String::operator=(const char* str)
{
cout << "operator=(const char* str)" << endl;
*this = String(str);
return *this;
}
String& String::operator=(const String& other)
{
cout << "operator=(const String& other)" << endl;
*this = String(other);
return *this;
}
String& String::operator=(String&& other) noexcept
{
cout << "operator=(String&& other)" << endl;
*this = String(other);
return *this;
}
待测试
String s("123");
String s2("456");
String *s3;
s3 = &s2; // 指针引用
String s4;
s4 = CreateString("111"); // 触发第三个=重载
cout << endl<< s[0] <<endl;
cout << endl<< s2[0] <<endl;
s[0]='9';
cout << endl << s[0] << endl;
cout << endl << s2[0] << endl;
cout << endl << (*s3)[0] << endl;
重载+操作符
重载+=操作符
重载中括号
char& String::operator[](size_t index)
{
return _str[index];
}
重载==操作符
重载输入输出流
返回地址/克隆/自身