2.string类常用接口的实现:
size()和capacity()
size_t size()const { return _size; } size_t capacity()const { return _capacity; }
clear函数
对于 clear() 而言就是去清除当前对象的数据,我们直接在_str[0]
这个位置放上一个\0即可,并且再去修改一下它的_size = 0即可
- 不过这个接口来说我们不要去加【const成员】,因为修改了其成员变量
_size
void clear() { _str[0] = '\0'; _size = 0; }
c_str函数
返回一个指向数组的指针,该数组包含一个以空字符结尾的字符序列(即C-string),表示string对象的当前值。
这个数组包含的字符序列与string对象的值相同,另外还包含一个以空字符(‘\0’)结尾的字符串。
- 🔥c_str返回的是一个const char*的数组指针,只读不写
const char* c_str()const { return _str; }
❓调试到这个地方就直接崩了,不应该直接打印null吗?
如果我们换成std中的string,不会报错,说明我们初始化存在问题
namespace st { class string { public: string() :_str(nullptr) , _size(0) , _capacity(0) {} string(const char* str) :_str(str) , _size(strlen(str)) , _capacity(strlen(str)) {} const char* c_str() { return _str; } private: const char* _str; size_t _size; size_t _capacity; }; void test_string1() { string s1; string s2("hello world"); std::cout << s1.c_str() << std::endl; std::cout << s2.c_str() << std::endl; } } int main() { st::test_string1(); return 0; }
2.1全缺省构造函数
我们还要考虑不带参数的构造函数,如下:
void test_string1() { string s1("hello world"); // 带参 string s2; // 不带参 }
string(const char* str = "") :_size(strlen(str)) { _capacity = _size == 0 ? 3 : _size; _str = new char[_capacity + 1]; strcpy(_str, str); }
🔥这里值得注意的是缺省值,我们给了一个“”
🔑详细解析:
str是一个char*类型,正常情况下,我们会给缺省值为nullptr
string(const char* str = nullptr)
这里运行后会崩!!!
strlen是不会去检查空的,它是一直找到 \0为止的
也就相当于直接对这个字符串进行解引用了,这里的字符串又是空,所以会引发空指针问题。
所以我们这里给的是一个空的字符串 " ",常量字符串默认就带有 \0,这样就不会出问题:
string(const char* str = "")
❓为什么我们用new char[1]而不是直接用new char,都是一个啊为什么啊?
🔥为了跟有参构造那里匹配析构函数,这样就方便释放
string() :_str(new char[1]) , _size(0) , _capacity(0) { _str[0] = '\0'; } string(const char* str) :_size(strlen(str)) { _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); }
❓这里可以优化吗?
string(const char*str=nullptr) string(const char* str = '\0')
🔑详细解析:
这两个都不可以,不可以解引用空指针
string(const char* str = "\0")
这样是可以的,给常量字符串,但是没必要这样,可以下面这样
string(const char* str = "")
如果我们不写拷贝构造函数,默认生成了一个拷贝构造函数,会报错!
void test_string2() { string s1; string s2("hello world"); string s3(s2); std::cout << s1.c_str() << std::endl; std::cout << s2.c_str() << std::endl; std::cout << s3.c_str() << std::endl; }
这里发生浅拷贝,同一块空间会被释放两次
string(const string& str) :_size(str._size) ,_capacity(str._capacity) { _str = new char[str._capacity+ 1]; strcpy(_str, str._str); }
2.2拷贝构造函数
2.3operator[]的实现
❓[]重定向,这里有什么问题呢?
char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } //成员变量 private: const char* _str; size_t _size; size_t _capacity;
普通对象可以调用,但是 const 对象呢?所以我们还要考虑一下 const 对象。
我们可能会修改pos位置的字符,也可能加字符,这里会报错,因为str为const char*类型
const char& operator[](size_t pos)const { assert(pos < _size); return _str[pos]; } char& operator[](size_t pos)//构成函数重载 { assert(pos < _size); return _str[pos]; }
2.4operator=的实现及其必要性
赋值的话,不写拷贝构造的话也是值拷贝(浅拷贝)
s1 = s3;下图拷贝构造分为三种:
第一种:s1的空间和s3的空间一样大
第二种:s1的空间比s3的空间大
第三种:s1的空间比s3的空间小
显然:这里第三种情况内存不够,要先释放防止内存泄漏,第二种是内存浪费,干脆全部都重新开空间就好了
string& operator=(const string& str) { if (&str == this) return *this;//防止自己给自己赋值 char*tmp = new char[str._capacity + 1];//防止开辟失败 strcpy(tmp, str._str); delete[] this->_str; _str = tmp; _size = str._size; _capacity = str._capacity; return *this; }
2.5Print函数
这里权限放大了
const char& operator[](size_t pos)const { assert(pos < _size); return _str[pos]; } size_t size()const { return _size; }
const函数,修饰this指针,但是这样另外一个地方又报错了
构成函数重载就可以解决问题了,各调用各的,这里调用第二个就可以了,this没有const修饰,并且返回类型没有const,就可以进行++等修改操作了
const char& operator[](size_t pos)const { assert(pos < _size); return _str[pos]; } char& operator[](size_t pos)//构成函数重载 { assert(pos < _size); return _str[pos]; }