C++11新特性——基于范围的for循环

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()函数
  • beginend返回的迭代器重载了!=,==运算符
  • 迭代器实现了++,--前缀自增自减运算符
  • 迭代器实现了解引用运算符*

比如下面一段代码就是错误的,他无法通过编译

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循环时,需要注意几个点

  • --,++和*运算符一定是返回自身的引用
  • 在容器中一定要注意实现beginend来返回这个迭代器
  • 这个前缀自增和后缀自增运算符的重载方式是不一样的
	MyIterator<T>& operator++() {		// 前缀自增
		t++;
		return *this;
	}

	MyIterator<T>& operator++(int dumy) {	// 后缀自增
		t++;
		return *this;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值