目录
一、 string 的深浅拷贝
0x00 构造函数与析构函数的实现
0x01 拷贝构造
0x02 赋值
0x03 整体代码
二、 string的实现
0x01 引入
0x02 c_str
0x03 默认构造函数
三、size()与operator[]的实现
0x01 size()的实现
0x02 operator[]的实现
0x03 遍历实现
四、迭代器
0x01 迭代器的实现
0x02 再分析范围for
一、 string 的深浅拷贝
0x00 构造函数与析构函数的实现
#pragma once
#include<iostream>
using namespace std;
namespace yh
{
class string
{
public:
//进行构造函数,即进行初始化
string(const char* str)//str不能改变,所以加const
:_str(new char[strlen(str)+1])//为了能使_str之后能够修改,所以先分配空间
{
strcpy(_str,str);//再进行拷贝
}
//析构函数,即清理资源
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;//string应该是一个指针
};
void test_string1()
{
string s1("hello world");
}
}
注意点: strlen:计算的是有效字符的个数,+1表示的是'\0'的空间
0x01 拷贝构造
问题引入:
默认的拷贝构造对于内置类型会进行浅拷贝,对于自定义类型会调用它的拷贝构造
假设是s2(s1),s1对s2的初始化; 如果成员变量是内置类型char* ,那么s2会和s1所指向的空间地址是相同的,但是在析构的时候,后创建的先析构,也就是先析构s2,而s1也是指向这块空间的,再析构不就会出现err了嘛
问题解决:
那么此时,就该到深拷贝出场了
//拷贝构造 string(const string& str1) :_str(new char[strlen(str1._str)+1]) { strcpy(_str,str1._str); }
从此图中可以看出,s1与s2所指向的空间是不同的,所以也就解决了上述的问题
0x02 赋值
问题分析:
①如果s3空间比s1大或者相等,那没问题,但是如果比它小,还要考虑其他情况所以不如释放空间,重新创建和s1一样大的空间,再进行赋值
②如果自己给自己赋值,如s3 = s3,而开始s3这块空间已经被释放了,之后又去开一块这样的空间,然后又去访问被释放掉的空间中的值,这样难免会出错,所以判断一下,防止自己给自己赋值
//赋值重载 s3 = s1;
string& operator=(const string& str2)
{
if (this != &str2)//防止自己赋值给自己
{
delete[] _str; //释放s3空间
_str = new char[strlen(str2._str) + 1];//重新创建空间
strcpy(_str, str2._str);//拷贝
}
return *this;
}
但是上面的代码又会有一下小问题,虽然delete不会出现什么问题,但是new(开空间)是可能没有这么大的空间的,所以可能需要抛异常,所以我们可以对以上的代码进行一下调整,如下:
if (this != &str2)//防止自己赋值给自己
{
char* temp = new char[strlen(str2._str) + 1];
strcpy(temp, str2._str);
delete[] _str;
_str = temp;
}
0x03 整体代码
#pragma once
#include<iostream>
using namespace std;
namespace yh
{
class string
{
//进行构造函数,即进行初始化
public:
string(const char* str)//str不能改变,所以加const
:_str(new char[strlen(str)+1])//为了能使_str之后能够修改,所以先分配空间
{
strcpy(_str,str);//再进行拷贝
}
//析构函数,即清理资源
~string()
{
delete[] _str;
_str = nullptr;
}
//拷贝构造
string(const string& str1)
:_str(new char[strlen(str1._str)+1])
{
strcpy(_str,str1._str);
}
//赋值重载 s3 = s1;
string& operator=(const string& str2)
{
//if (this != &str2)//防止自己赋值给自己
//{
// delete[] _str; //释放s3空间
// _str = new char[strlen(str2._str) + 1];//重新创建空间
// strcpy(_str, str2._str);//拷贝
//}
if (this != &str2)//防止自己赋值给自己
{
char* temp = new char[strlen(str2._str) + 1];
strcpy(temp, str2._str);
delete[] _str;
_str = temp;
}
return *this;
}
private:
char* _str;//string应该是一个指针
};
void test_string1()
{
string s1("hello world");
string s2(s1);
string s3("hello");
s3 = s1;
}
}
二、 string的实现
0x01 引入
以上是未考虑增删查改的情况,所以现在我们需要增加一下成员变量,进行增删查改的学习
char* _str; int _size;//有效字符的个数 int _capacity;//有效字符的个数,不算'\0'
优化之后代码:
class string { //进行构造函数,即进行初始化 public: 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; } //拷贝构造 string(const string& str1) :_size(str1._size) ,_capacity(str1._capacity) { _str = new char[_capacity + 1]; strcpy(_str,str1._str); } //赋值重载 s3 = s1; string& operator=(const string& str2) { if (this != &str2)//防止自己赋值给自己 { char* temp = new char[str2._capacity + 1]; strcpy(temp, str2._str); delete[] _str; _str = temp; _size = str2._size; _capacity = str2._capacity;//容量和大小记得更新 } return *this; } private: char* _str; int _size; int _capacity; };
0x02 c_str
c_str():返回当前字符串的首字符地址,会去寻找'\0',可读不可写
c_str的实现:
const char* c_str() const { return _str; }
0x03 默认构造函数
问题引入:
①如果创建一个string s3;那么系统回去调用它的默认构造函数,所以我们还需要补充一个默认构造函数② 因为c_str()是去找'\0'的,找不到'\0'可能会出现错误
不带参默认构造函数:
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 s4;也可以去使用这个全缺省的构造函数,空字符串中默认有'\0',有效字符个数和容量还是为0,拷贝的时候将'\0'拷贝过去
此时用全缺省和不带参的构造函数的效果是一样的
三、size()与operator[]的实现
0x01 size()的实现
size_t size()const//因为这个this指针不改变,所以加const
{
return _size;
}
0x02 operator[]的实现
char& operator[](size_t pos)
{
return _str[pos];//返回每个位置的字符
}
同时,operator[]也可以充当写的功能:
s3[0] = 'w'; for (size_t i = 0;i < s3.size();i++) { cout << s3[i] << " " ; } cout << endl;
问题引入:
此时普通对象是可以调用的,但是const对象呢?所以此时,我们在重载一个const版本,可读不可写
const char& operator[](size_t pos)const { return _str[pos];//返回每个位置的字符 }
问题引入:
如何调用这个const版本的operator[]呢?
const char& operator[](size_t pos)const { return _str[pos]; } void f(const string& s3) { cout << s3[0] << endl; } int main() { string s3("hello"); f(s3); }
此时,s3传参可以当做是权限的缩小,变为了可读不可写,也就会调用带const版本的operator[]
0x03 遍历实现
size_t size()const
{
return _size;
}
char& operator[](size_t pos)
{
assert(pos < _size);//进行检验是否合理
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
int main()
{
string s3("hello");
for (size_t i = 0;i < s3.size();i++)
{
cout << s3[i] << " " ;
}
cout << endl;
}
四、迭代器
迭代器:像指针一样的东西,但是可能并非指针,但是模拟了指针的部分功能
0x01 迭代器的实现
class string
{
typedef char* iterator;
iterator begin()
{
return _str;//首元素地址
}
iterator end()
{
return _str + _size;//最后一个元素的下一个位置
}
}
int main()
{
string s1("hello world");
string::iterator it = s1.begin();//内嵌类型,表示string的迭代器
string::iterator temp = it;
while (it != s1.end())//迭代器写
{
*it += 1;
it++;
}
it = temp;
while (it != s1.end())//迭代器读
{
cout << *it << " ";
it++;
}
}
其次,就是const迭代器,只能读,不能写
typedef const char* iterator;
const iterator begin()const
{
return _str;//首元素地址
}
const iterator end()const
{
return _str + _size;//最后一个元素的下一个位置
}
0x02 再分析范围for
for (auto e:s1)
{
cout << e << " ";
}
范围for在编译的时候被替换为了迭代器,只要把迭代器实现好,那么范围for自然而然就可以运用了