⊙ 범위 기반 for 문의 문법을 안다.
⊙ 범위 기반 for 문을 효율적으로 사용하는 문법을 안다.
⊙ 범위 기반 for 문을 사용할 수 없는 경우를 이해한다.
코드를 보다보면 분명 내가 아는 for문인데 다른 모양을 하고 있는 for문들을 본 적이 있다. 초기화, 조건, 증감의 내용이 아니라 변수 하나를 선언해놓고 거기에 배열이나 벡터 등의 이름만 붙어 있는 것들이 그것이다. 이를 향상된 for문이라 부르는 것을 들어본 적이 있다.
범위 기반 for문(ranged-based for statement)
for문은 배열을 반복할 때 편리하고 유리한 방법을 제공하지만, 조금 복잡하고 실수로인해 오류가 발생하기 쉽다. 인덱스를 벗어난다던가, 배열을 끝까지 다 돌지 못한다던가 하는 오류들 말이다. C++ 11에서는 범위 기반 for문(ranged-based for statement)이라는 새로운 유형의 루프를 도입하여 안전하게 배열 등의 요소를 반복하는 방법을 제공한다.
범위 기반 for문의 문법은 아래와 같다.
for(element_declaration : array)
statement;
반복문을 돌면서 각 array의 요소를 element_declaration에 선언된 변수에 대입한다. 반복문이 한번 돌면 다음 array 요소, 다시 돌면 다음 array 요소로 넘어가면 변수에 대입되는 것이다. element_declaration에 선언된 변수의 데이터 타입과 array 요소의 데이터 타입은 같아야 한다.
또한 문법에 array라고 적혀있지만 사실 vector와 list, set, map까지 모두 사용이 가능하다. 범위 기반 for 문은 유연하게 사용이 가능하다.
사용 예제(Array)
아래와 같은 반복문을 원래 사용하고 있었다고 생각해보자.
#include <iostream>
int main()
{
int fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(int i=0; i<12; i++)
{
std::cout<<fibonacci[i]<<" ";
}
return 0;
]
아주아주 익숙한 이 for문을 범위 기반 for문을 이용하면 좀 더 쉽게 표현할 수 있다.
#include <iostream>
int main()
{
int fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(int number : fibonacci)
{
std::cout<<number<<" ";
}
return 0;
]
for문 안을 보면 상당히 뭔가 간단해졌음을 알 수 있다. 배열 요소와 같은 데이터 타입으로 int number를 선언해주었다. 그리고 ":" 기호 옆에 배열의 이름을 적어주었다. 이렇게 하면 반복문이 돌아갈 때마다 fibonacci에 있는 배열의 요소가 처음부터 순서대로 number에 복사되어 대입되는 것이다. 즉 number을 바꾼다고 하여도 fibonacci 배열의 원본에는 아무런 영향이 없다. number를 이용하여 반복문 안에서 원하는 작업을 수행할 수 있다. 위 코드에서는 배열의 요소를 차례대로 출력할 것이다.
사용 예제(Vector, Map, List, Set)
범위 기반 for문은 아주 다행히도 STL의 자료형들에도 열려있다.
#include <vector>
#include <iostream>
int main()
{
std::vector<int> fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(int number : fibonacci)
{
std::cout<<number<<" ";
}
return 0;
]
예제 코드를 보면 특별히 바뀐 부분이 있는가? 배열이었던 fibonacci가 벡터로 선언되어 있다는 것을 제외하고는 바뀐 것이 거의 없음을 알 수 있을 것이다. 이처럼 우리가 자주 사용하는 Vector도 범위 기반 for문을 이용하여 반복문을 돌릴 수 있다.
범위 기반 for문의 업그레이드
위의 예제와 같이 for문을 사용하면 되는데 무슨 업그레이드가 더 필요할까?
for문 안에 선언되는 변수의 데이터 타입에 제한을 두지 않고 알아서 배열 요소의 데이터 타입에 맞춰 준다거나, 배열 요소를 복사하는데 드는 비용을 줄일 수 있는 방법이 있다면 업그레이드 하는게 더 낫지 않을까?
auto 키워드 추가
for문 안에 선언되는 변수의 데이터 타입은 배열 요소의 데이터 타입과 맞아야 한다. auto 키워드를 사용하면 자동으로 배열 요소의 데이터 타입과 같은 타입으로 인식된다.
#include <iostream>
int main()
{
int fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(auto number : fibonacci)
{
std::cout<<number<<" ";
}
return 0;
]
참조 변수 선언
위에서 계속 보던 예제들은 배열의 요소가 선언된 변수에 대입될 때 복사되어 대입이 되는 것이다. 문제는 배열 요소를 복사하는 것은 비용이 많이 들 수 있다는 것이다. 함수에서 복사 비용이 많이 들 것 같은 매개변수를 어떻게 받아왔는지 알고 있는가? for문 안에 선언되는 변수를 참조 변수로 선언하는 것이다.
#include <iostream>
int main()
{
int fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(auto& number : fibonacci)
{
std::cout<<number<<" ";
}
return 0;
]
number은 배열 요소에 대한 참조이므로 값이 복사되는 것은 아니다. 주소를 공유한다. 하지만 함수에서 참조에 의한 전달을 할 때 문제가 되었던 것과 마찬가지로 이 상태에서 number의 값을 변경하면 원본인 fibonacci 배열의 요소도 변경된다.
참조 변수의 변형 방지
함수에서의 참조에 의한 전달 문제도 해결 방법이 있었다. 값을 변경하면 원본의 값도 변경된다는 문제를 해결하기 위해 const를 붙여서 값 변경을 방지하였었다. 여기서도 마찬가지다. const를 붙여 참조 변수의 변형을 방지한다.
#include <iostream>
int main()
{
int fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(const auto& number : fibonacci)
{
std::cout<<number<<" ";
}
return 0;
]
이렇게 하면 복사 비용이 발생하지 않으면서 원본 값 변경에 대한 대비도 할 수 있다.
범위 기반 for 문을 사용할 수 없는 경우
범위 기반 for 문은 포인터로 변환된 배열에서는 사용할 수 없다.
만약 사용하면 컴파일 에러가 발생한다.
포인터로 변환된 배열을 그렇게 자주 사용하는가 생각해봤더니 이런 경우들도 모두 배열을 포인터로 변환해버린 경우들이다.
함수에 배열을 전달한 경우
함수에 배열을 전달하면 배열이 배열의 맨 앞을 가리키는 포인터로 변환하게 된다. 크기와 데이터가 고정된 객체인 배열이 함수에 전달되면 포인터로 변환되게 되는 것이다. 포인터로 변환되면 배열이 잃어버리는 것이 있다. 바로 배열의 크기이다.
범위 기반 for문에서는 이 배열의 크기가 아주 중요한데 이 정보가 없는 이유로 포인터로 변환된 배열은 범위 기반 for문을 사용할 수 없다.
#include <iostream>
int sumArray(int array[]) // 포인터로 변환된 배열
{
int sum=0;
for(const auto& number : array) // 컴파일 에러 발생
sum+=number;
return sum;
}
int main()
{
int fibonacci[]={0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
for(const auto& number : fibonacci)
std::cout<<sumArray(fibonacci); // 함수에 포인터로 전달
return 0;
]
동적 배열인 경우
동적 배열은 대놓고 포인터 만들어진 배열이기 때문에 만찬가지로 범위 기반 for문을 사용할 수 없다. 마찬가지로 배열의 크기를 모르기 때문이다.
#include <iostream>
int main()
{
int* fibonacci = new int[12]{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89}; // 동적 배열
for(const auto& number : fibonacci) // 컴파일 에러 발생
{
std::cout<<number<<" ";
}
return 0;
]
배운 내용 정리
- C++ 11에서는 범위 기반 for문(ranged-based for statement)이라는 새로운 유형의 루프를 도입하여 안전하게 배열 등의 요소를 반복하는 방법을 제공한다.
- vector와 list, set, map까지 모두 사용이 가능하다.
- auto, 상수 참조 변수 등을 이용해 성능을 높일 수 있다.
- 동적 배열, 포인터로 변환된 배열의 경우 크기 정보가 없어 범위 기반 for문을 사용할 수 없다.
'C++ > 게임 개발자를 위한 C++ 문법' 카테고리의 다른 글
템플릿(Template) (0) | 2025.01.07 |
---|---|
디자인 패턴(Design Pattern) : 생성(Creational) 패턴 (0) | 2025.01.07 |
스마트 포인터(Smart Pointer) (0) | 2025.01.03 |
선형 탐색과 이진 탐색 (0) | 2025.01.02 |
얕은 복사(Shallow Copy) VS 깊은 복사(Deep Copy) (0) | 2024.12.30 |