C++ STL标准库解析|简单实现STL容器string代码中我们实现了简单的string容器。
现在首先能够明确的,string就是容器,底层放了一组char类型的字符。
那么我现在想要通过指针来遍历该容器应该怎么做呢?
在标准库中是这样完成遍历的:
int main () {
string str1 = "hello world!";
//容器迭代器类型
string::iterator it = str1.begin();
for (; it != str1.end(); ++i) {
cout << *it << " ";
}
cout << endl;
}
这段使用迭代器遍历str1的代码到底是什么意思呢?
如下图,在str1的底层就是字符串,但是这一串内容我们时看不见的。因为字符串的底层成员变量来说,这些都是私有的,我们根本看不见。
那我们像遍历字符串底层的这些字符的时候,我们应该怎么办呢?迭代器是如何做到的呢?
-
首先容器有begin()方法,它返回的是底层首元素的迭代器标识,所以现在我们的it就指向了底层容器的首元素,对于我们使用者来说只需要知道it指向了这个字符串底层的首元素;
-
在循环的过程中
it!=str1.end()
,说明容器的end()方法返回的最后一个元素后继位置的迭代器标识这样我们也可以遍历到底层的最后一个元素。 -
循环过程中做了++it,说明我们当前迭代器要跳到下一个迭代器来完成遍历,至于它底层到底是数组、链表还是哈希表无论是什么我们都不用管,我们作为使用者只需要对迭代器进行++即可。
-
底层数据结构如何到下一个元素的具体操作都封装在了我们迭代器的++操作中,用户作为使用者是无感的(这就是所谓的透明地访问容器内部元素的值)。
-
最后打印使用迭代器进行解引用。
- 每一种容器都有自己的迭代器,因为每个容器底层的数据结构都是不一样的,所以迭代器被设计成了容器的嵌套类型:
string::iterator
、vector::iterator
等等。- 泛型算法是一种全局的函数,是给所有容器用的,所以泛型算法必须有一套统一方式,能够统一得遍历所有的容器元素,所以其参数接收的都是迭代器!
从上面的论述中我们应该实现以下接口:
- end(),返回首元素迭代器
- begin(),返回尾元素的后继位置
- 实现容器嵌套类型 string::iterator
- 重载!=运算符
- 重载迭代器的++运算符
- 重载解引用
1.提供嵌套类型iterator、接口函数begin() \end()
迭代器本质上就是对指针的一个封装,因为它其实指向的就是一个位置,那么最好的方式肯定是用指针了。
由于我们需要让begin()和end()返回一个迭代器类型,并且分别返回的是特定位置的指针,很容易想到
我们迭代器的构造函数应该就应该接受某个指针,
class iterator {
public:
iterator(char *p = nullptr) : _p(p) { }
private:
char *_p;
};
接下来我们就可以来实现begin()和end()了:
//begin返回的是容器底层首元素的迭代器表示
iterator begin() { return iterator(_pstr); }
//end返回的是容器底层末尾元素后继位置的迭代器表示
iterator end() { return iterator(_pstr + length()); } //这里其实指向了'\0'的位置
从这里我们可以看出:
string::iterator it = str1.begin();
迭代器的等于就是底层指针的等于;it != str1.end()
迭代器的不等于就是底层指针的不等于。
至此我们已经实现了:
end(),返回首元素迭代器begin(),返回尾元素的后继位置实现容器嵌套类型 string::iterator- 重载!=运算符
- 重载迭代器的++运算符
- 重载解引用
2.实现重载!=运算符
bool operator!=(const iterator &it) {
return _p != it._p;
}
也就是说当前迭代器的指针与外面引用的迭代器指针不想等,本质上就是底层指针的不想等
3.实现重载前置++和解引用*
首先要明确的就是,后置++运算符返回的是一个对象;前置++运算符返回的是一个引用。具体可以看这篇文章:++i和i++的效率问题|重载前置++运算符和重载后置++运算符
我们给迭代器尽量使用前置++!!!
void operator++() {
++_p;
}
char& operator*() { return *_p;}
测试代码
int main () {
string str1 = "hello world";
string::iterator it = str1.begin();
for (; it != str1.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
补充
在C++11里面,对迭代器的使用有一个更加简单的方式:
//C++11 foreach的方式来遍历容器内部元素的值
for (char ch : str1) {
cout << ch << " ";
}
cout << endl;
也是能够正常使用的,但是只要我们把begin()和end()函数注释掉,就不能完成正常的调用。
所以所谓的 foreach方式来遍历容器,其底层就是使用迭代器来遍历的。
NOTE:
迭代器的功能:提供统一的方式,来透明得遍历容器。
所谓的统一就是指,无论底层数据结构是什么,我们对迭代器的使用方法都是一致的;
所谓的透明就是指,即我们根本不用知道容器底层用的什么数据结构,不同迭代器的遍历全部封装在我们的前置++运算符上
整体代码(含迭代器)
class String {
public:
String(const char *p = nullptr) {
if (p != nullptr) {
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
} else {
_pstr = new char[1];
*_pstr = '\0';
}
}
~String() {
delete[] _pstr;
_pstr = nullptr;
}
String(const String &str) {
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
}
String& operator=(const String &str) {
if (this == &str)
return *this;
delete[] _pstr;
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
return *this;
}
bool operator>(const String &str) const {
return strcmp(_pstr, str._pstr) > 0;
}
bool operator==(const String &str) const {
return strcmp(_pstr, str._pstr) == 0;
}
bool operator<(const String &str) const {
return strcmp(_pstr, str._pstr) < 0;
}
int length() const { return strlen(_pstr); }
// char ch = str6[6]; str6[6] = '7'
char& operator[](int index) { return _pstr[index]; }
// char ch = str6[6]; 不允许修改! str6[6] = '7'
const char& operator[](int index) const { return _pstr[index]; }
const char* c_str() const { return _pstr; }
//给String字符串类型提供迭代器的实现
class iterator {
public:
iterator(char *p = nullptr) : _p(p) { }
bool operator!=(const iterator &it) {
return _p != it._p;
}
void operator++() {
++_p;
}
char& operator*() { return *_p;}
private:
char *_p;
};
//begin返回的是容器底层首元素的迭代器表示
iterator begin() { return iterator(_pstr); }
//end返回的是容器底层末尾元素后继位置的迭代器表示
iterator end() { return iterator(_pstr + length()); }
private:
char *_pstr;
friend ostream& operator<<(ostream &out, const String &str);
friend String operator+(const String &lhs, const String &rhs);
};
String operator+(const String &lhs, const String &rhs) {
//char *ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
String tmp;
tmp._pstr = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
strcpy(tmp._pstr, lhs._pstr);
strcat(tmp._pstr, rhs._pstr);
//delete[] ptmp;
return tmp;
}
ostream& operator<<(ostream &out, const String &str) {
out << str._pstr;
return out;
}