C++ range-based for

200원짜리개발자·2023년 7월 3일
0

C++

목록 보기
24/39
post-thumbnail

강의 보고 공부한 것을 정리하는 목적으로 작성된 글이므로 틀린 점이 있을 수 있음에 양해 부탁드립니다. (피드백 환영입니다)

range-based for

일단 왜 필요한지에 대해 알아보겠다.

왜 필요한가?

일단 대표적으로 vector를 이용해 순회를 하고 출력하는 코드가

vector<int> v{1, 2, 3, 4, 5};

for(int i = 0; i < v.size(); i++)
{
	int data = v[i];
	cout << data << endl;
}

for(auto it = v.begin(); it != v.end(); it++)
{
	int data = *it;
	cout << data << endl;
}

이런식으로 있다고 했을 때

위와 같은 코드처럼 모든 것을 스캔하면 추가/삭제를 하지 않는다면 굳이 저렇게 힘들게 적을 필요가 없다.

그래서 세 번째 문법이 등장하는데 그것이 range-based for이다.

for(int data : v)
{
	cout << data << endl;
}

이런식으로 사용하는데 정말로 간단하게 사용할 수 있다.

C#의 foreach를 생각하면 된다.

보면 가독성 측면으로도 굉장히 알아보기도 쉽고 코드를 작성할 때도 편리하다.
하지만 속도는 진짜 조금 느리다고 한다.
그럼에도 1,2번 코드보다 훨씬 좋은 코드라고 볼 수 있다.

🍀사용하면 좋다!

조심해야 할 부분

우리가 데이터를 바꾸고 싶은 때가 있을 수 있다.

vector<int> v{1, 2, 3, 4, 5};

for(int i = 0; i < v.size(); i++)
{
	v[i] = 100;
	cout << data << endl;
}

for(auto it = v.begin(); it != v.end(); it++)
{
	*it = 100;
	cout << data << endl;
}

이런식으로 할 수 있었다.

그럼 range-based for로는 어떤식으로 사용 할 수 있을까?

for(int& data : v)
{
	data = 100;
	cout << data << endl;
}

바로 reference(&)붙여서 작업을 해주면 된다.

수정하는게 안되는 것이 아니라 추가/삭제가 안되는 것이기 때문에 하다가 바꾸고 싶은 것이 있다면 &를 붙여서 작업하면 된다.

하지만 &를 붙이는 경우는 int,float처럼 데이터가 작은 경우가 아니라

struct itemInfo
{
	// 엄청 큰 데이터들
};

int main()
{
	vector<itemInfo> v;
	for (auto& data : v)
	{

	}
}

이러한 것과 같이 엄청 큰 데이터들을 가지고 있을 때복사가 크게 일어나기 때문에 &를 붙여줘야 한다.

그래서 보통 위처럼 사용한다.

그러면 여기서 range-based for가 얼마나 느리고, 어떤식으로 동작하는지 궁금할 수 있다. 그래서 알아볼 것이다.

어떤식으로 구현되었을까?

우리는 커스텀 클래스를 사용하여 구현을 해볼 것이다.

예를 들어보면

class Inventory
{
public:
	
    
    int _items[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
};

int main()
{
	Inventory inventory;
}

이런식으로 있을 때 inventory의 _items를 순회하고 싶다고 하면

class Inventory
{
public:
	
    
    int _items[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
};

int main()
{
	Inventory inventory;
    
    for(auto item : inventory)
    {
    	
    }
}

이런식으로 짜줄텐데 이런식으로 하면 오류가 뜰 것이다.

즉, 두 번째 방식으로 코드가 만들어진다고 볼 수 있다.

for(auto it = v.begin(); it != v.end(); it++)
{
	*it = 100;
	cout << data << endl;
}

이 방식

그래서 저번에 우리가 iterator를 구현해본 것처럼
v.begin(), v.end(), it++, !=, *it이러한 것들을 클래스에 구현을 해준다면 정상적으로 작동할 것이라는 것을 알 수 있다.

그럼 다시 iterator를 만들어보자!

class Iterator
{
public:
	Iterator() : _data(nullptr) { }
    Iterator(int* data) : _data(data) { }
    
    bool operator==(const Iterator& other)
    {
    	return _data == other._data;
    }
    
    bool operator!=(const Iterator& other)
    {
    	return _data != other._data;
    }
    
    void operator++()
    {
    	_data++;
    }
    
    int& operator*()
    {
    	return *_data;
    }
public:
	int* _data;
};

class Inventory
{
public:
	using iterator = Iterator;
    
    iterator begin() { return iteraotr(&_items[0]); }
    iterator end() { return iterator(&_items[10]); }
    
    int _items[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
};

이렇게 만들어진다면

뿅! 이런식으로 오류가 사라진 것을 볼 수 있다.

그럼이제 총 코드를

#include <iostream>
#include <vector>
using namespace std;

class Iterator
{
public:
    Iterator() : _data(nullptr) {}
    Iterator(int* data) : _data(data) {}

    bool operator==(const Iterator& other)
    {
        return _data == other._data;
    }

    bool operator!=(const Iterator& other)
    {
        return _data != other._data;
    }

    void operator++()
    {
        _data++;
    }

    int operator*()
    {
        return *_data;
    }
public:
    int* _data;
};

class Inventory
{
public:
    using iterator = Iterator;

    iterator begin() { return iterator(&_items[0]); }
    iterator end() { return iterator(&_items[10]); }

    int _items[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
};

int main()
{
    Inventory inventory;

    for (auto item : inventory)
    {
        cout << item << endl;
    }
}

이런식으로 짜준다면


이런식으로 잘 출력이 되는 걸 알 수 있다!

그래서 range-based for라는 것은 우리 눈에는 :로 보이지만 내부적으로는

for(auto it = v.begin(); it != v.end(); it++)
{
	*it = 100;
	cout << data << endl;
}

이 방식과 같다는 사실을 알 수 있다!

뭐 사실 우리가 iterator를 직접만드는 일은 발생하지 않을 것이고
어지간해서는 container를 사용하여서 코드를 짤 것이기 때문에 편리하게 사용할 수 있게 될 것이다.

그래서 앞으로 어떤 container를 사용하여서 순회를 해서 무언가를 해야한다? 라고 하면 range-based for를 떠올리면 된다.
특이한 경우면 &도 붙여주고
그래서 autorange-based for는 배워야지만 엄청나게 코딩할 때 편해진다.

물론 실시간으로 추가/삭제를 하면 안된다.
데이터를 수정하는 것은 상관이 없지만 이 데이터를 안에서 빼거나 넣거나 한다면 문제가 될 것이다.

추가/삭제가 필요하면 원래방식으로 돌아오거나 나중에 배우는 알고리즘을 사용해서 할 수도 있다고 한다.

뭐가 되었든 range-based for에서는 데이터를 추가/삭제할 생각을 하면 안된다.

메모리 오염이 생길 수 있다.
근데 생각보다 사람들이 이러한 실수를 많이 한다고 한다.
왜냐하면 거기서 어떤 함수를 들어가서 지우는 작업을 해도 메모리 오염이나 다른 오류들이 일어날 수 있기 때문이다.

마무리

그래서 오늘은 range-based for를 배웠는데
range-based for는 정말 편리하게 사용할 수 있는 문법이기 때문에 꼭 알고 있느 것이 좋고
조심해야 할 점은 전체 스캔을 할 때는 정말 간단하게 필요한 작업만 골라서 해야한다.

다음에는 map을 배우기 위해 우선순위 큐와 양대산맥을 이루는 탐색 트리(이진 트리)를 배워볼 것이다.

profile
고3, 프론트엔드

0개의 댓글