C++11基于范围的for循环
介绍
对于用for循环去遍历一个容器或者数组,C98的做法是
#include<iostream>
using namespace std;
int main(int argc, char *argv[]){
int array[] = {1, 2, 3, 4, 5}
int i = 0;
for(;i<5;i++){
cout<<"ArrayValue: "<<array[i]<<endl;
}
cout<<endl;
return 0;
}
这种for循环是一种老式的写法,在C++11中,新的算法库algorithm
提供了一种for_each的函数来简化代码,实现遍历
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
void printfInt(const int& e) {
cout << "ArrayValue: " << e << endl;
}
int main() {
vector<int> array = { 1, 2, 3, 4, 5 };
for_each(array.begin(), array.end(), printfInt);
return 0;
}
for_each相比于之前的遍历写法,用一行代码就实现了数组打印的遍历, 是一种简化代码的方法,除此之外,C++11还提供一种基于范围的for循环,经常结合auto自动类型推断来处理,如下
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> array = { 1, 2, 3, 4, 5 };
// 使用基于范围的for循环
for (int a : array)
cout << "ArrayValue: " << a << "\n";
cout << "\n"<<endl;
// 使用auto自动推断元素类型的基于范围的for循环
for (auto a : array)
cout << "AutoArrayValue: " << a << "\n";
cout << endl;
return 0;
}
上面的for循环,不需要我们自己去对数组下标进行自增,他自动会自增,可以防止出现在循环遍历某些容器的出现忘记写i++
的现象,既减少代码的出错率,而且提高了代码的可读性,相信auto a:array
还是很好理解的,当我们在遍历循环的时候,这样的写法还并不是最优的,可以看一个例子
#include <iostream>
#include <vector>
using namespace std;
class Value {
public:
// 构造函数
Value(int i) : i(i){
cout << "Call The Constructor\n";
}
// 拷贝构造函数
Value(const Value& v) : i(v.i) {
cout << "Call The Copy Constructor\n";
}
// 获取值
int get() const { return i; }
private:
int i;
};
int main() {
vector<Value> array = { 1, 2, 3 };
cout << "\n\n";
// 使用auto自动推断元素类型的基于范围的for循环
for (auto a : array)
cout << "AutoArrayValue: " << a.get() << "\n";
cout << endl;
return 0;
}
输出结果:
Call The Constructor
Call The Constructor
Call The Constructor
Call The Copy Constructor
Call The Copy Constructor
Call The Copy Constructor
Call The Copy Constructor
AutoArrayValue: 1
Call The Copy Constructor
AutoArrayValue: 2
Call The Copy Constructor
AutoArrayValue: 3
会发现,在遍历每一个元素的时候,它又再次调用了Value的拷贝构造函数来构造新的一个Value
,那么此时,有两个缺点就暴露出来了
- 如果要对具体的
Value
里面的值进行修改,这样的遍历无法达到目的,因为a与对应数组位置中的元素是两个对象,对于a的操作并不会影响数组中的元素 - 再遍历的过程中会增加额外的性能开销
一种更高效的遍历方式是采用引用的方式遍历,如果需要修改Value的值,那么就使用auto& value
, 如果不用修改,可以使用const auto& value
, 如果在上面代码的auto遍历,使用引用,其输出结果为
Call The Constructor
Call The Constructor
Call The Constructor
Call The Copy Constructor
Call The Copy Constructor
Call The Copy Constructor
AutoArrayValue: 1
AutoArrayValue: 2
AutoArrayValue: 3
这种基于范围的for循环是基于迭代器
的思想来的,标准库里面的很多容器都是支持这样的循环的,比如map, vector, list, queue, string...
, 使用这种循环的前提是
- 容器具有迭代器的功能,实现了
begin()
和end()
函数 begin
和end
返回的迭代器重载了!=
,==
运算符- 迭代器实现了
++,--
前缀自增自减运算符 - 迭代器实现了解引用运算符
*
比如下面一段代码就是错误的,他无法通过编译
int a[5] = {1, 2, 3, 4, 5};
for(auto v: a){ // 错误,a不满足上述条件
cout<<v<<" ";
}
cout<<endl;
除了C++标准库提供的一些容器之外,我们自己也可以实现这样的容器,只要满足上述条件就可以了,下面我实现了一个可以迭代的数组模板容器,此数组类除了具备C语言数组的功能之外,还能检测访问越界,在访问到超过自身范围的索引时会报错。还可以使用基于范围的for循环进行遍历,
#include <iostream>
#include <string>
#include <initializer_list>
using namespace std;
// 自身的迭代器
template<class T>
class MyIterator {
public:
MyIterator(T* t) : t(t) {}
/*=----核心方法----=*/
MyIterator<T>& operator++(int dumy) { // 后缀自增
t++;
return *this;
}
MyIterator<T>& operator--(int dumy) {
t--;
return *this;
}
MyIterator<T>& operator++() { // 前缀自增
t++;
return *this;
}
MyIterator<T>& operator--() {
t--;
return *this;
}
bool operator==(const MyIterator<T>& it) {
return it.t == t;
}
bool operator!=(const MyIterator<T>& it) {
return it.t != t;
}
T& operator*() {
return *t;
}
/*=----核心方法----=*/
private:
T* t;
};
template<class T>
class MyArray {
public:
typedef MyIterator<T> iterator;
MyArray(int n) :size(n), array(new T[size]) {}
// 采用初始化列表进行初始化,可以隐式的初始化
MyArray(const std::initializer_list<T>& init) : size(init.size()), array(new T[size]) {
T* v = array;
for (auto& value : init) {
*v = value;
v++;
}
}
// 下标索引
T& operator[](unsigned int t) {
if (t >= size)
throw std::exception("Index out of range");
return *(array + t);
}
int length() const { return size; }
iterator begin() { return MyIterator<T>(array); }
iterator end() { return MyIterator<T>(array + size); }
private:
int size;
int* array;
};
int main() {
MyArray<int> array = { 1, 2, 3 };
// 第一种形式
cout << "Style1: \n";
for (auto e : array) {
cout << e << " ";
}
// 第二种形式
cout << "\nStyle2: \n";
using it = MyArray<int>::iterator;
it begin = array.begin();
it end = array.end();
while (begin != end) {
cout << *begin << " ";
begin++;
}
cout<<endl;
}
在实现我们自己的迭代器来满足C++11基于范围的for循环时,需要注意几个点
--,++和*
运算符一定是返回自身的引用- 在容器中一定要注意实现
begin
和end
来返回这个迭代器 - 这个前缀自增和后缀自增运算符的重载方式是不一样的
MyIterator<T>& operator++() { // 前缀自增
t++;
return *this;
}
MyIterator<T>& operator++(int dumy) { // 后缀自增
t++;
return *this;
}