C++11에서는 코드의 간결성과 가독성을 높이기 위해 여러 가지 새로운 기능이 도입되었습니다. 그중 하나가 range-based for 문입니다.
기존의 반복문을 사용할 때는 이터레이터(iterator)나 인덱스를 활용해야 했지만, range-based for 문을 사용하면 더 직관적이고 간결한 방식으로 STL 컨테이너를 순회할 수 있습니다.
예를 들어, C++98 스타일로 std::vector
를 순회하려면 다음과 같이 이터레이터를 사용해야 합니다.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
C++11의 range-based for 문을 사용하면 위 코드를 훨씬 간결하게 작성할 수 있습니다.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
이와 같이 range-based for 문은 반복문을 간략하게 만들고, 코드 가독성을 높이는 데 매우 유용합니다.
이번 기사에서는 range-based for 문의 기본 개념부터 활용법, 최적화 기법까지 자세히 살펴보겠습니다.
range-based for 문의 기본 개념
C++11에서 도입된 range-based for 문은 for
루프의 새로운 형태로, 컨테이너(예: std::vector
, std::array
, std::map
등) 또는 배열의 요소들을 더 간결하게 순회할 수 있도록 합니다.
기존의 for
문은 이터레이터(iterator)나 인덱스를 이용하여 컨테이너의 요소를 하나씩 접근해야 했습니다. 하지만 range-based for 문을 사용하면 이러한 반복 작업을 자동으로 처리할 수 있습니다.
기존 반복문과 range-based for 문 비교
방식 | 코드 예시 | 특징 |
---|---|---|
기존 for 루프 (인덱스 사용) | for (size_t i = 0; i < v.size(); ++i) { std::cout << v[i]; } | 명시적으로 인덱스를 사용해야 함 |
기존 for 루프 (이터레이터 사용) | for (auto it = v.begin(); it != v.end(); ++it) { std::cout << *it; } | 이터레이터를 직접 사용해야 하며, 가독성이 떨어짐 |
range-based for 문 | for (int n : v) { std::cout << n; } | 간결하고 직관적인 문법 |
기본 문법
range-based for 문의 기본 문법은 다음과 같습니다.
for (declaration : container) {
// 컨테이너의 요소를 이용한 연산 수행
}
declaration
: 컨테이너의 각 요소를 받을 변수container
: 순회할 컨테이너 (배열,std::vector
,std::map
등)
예제 코드
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
출력:
10 20 30 40 50
위 코드에서 for (int num : numbers)
부분은 numbers
컨테이너의 요소를 자동으로 하나씩 가져와 num
변수에 저장하고, 이를 출력하는 역할을 합니다.
이처럼 range-based for 문은 반복문을 더 단순하게 만들고, 코드 가독성을 높이는 데 매우 유용한 기능입니다.
기본 사용법과 코드 예제
range-based for 문은 C++의 다양한 컨테이너와 함께 사용할 수 있으며, 반복문의 코드 길이를 줄이고 가독성을 높이는 데 유용합니다. 기본적인 사용법을 코드 예제를 통해 살펴보겠습니다.
1. 배열과 함께 사용
C++의 기본 배열을 순회할 때 range-based for 문을 사용할 수 있습니다.
#include <iostream>
int main() {
int arr[] = {1, 2, 3, 4, 5};
for (int num : arr) {
std::cout << num << " ";
}
return 0;
}
출력:
1 2 3 4 5
이처럼 for (int num : arr)
문을 사용하면 arr
배열의 모든 요소를 자동으로 순회할 수 있습니다.
2. std::vector
와 함께 사용
std::vector
는 동적 크기를 가지는 배열로, range-based for 문을 사용할 수 있는 대표적인 컨테이너입니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> fruits = {"Apple", "Banana", "Cherry"};
for (std::string fruit : fruits) {
std::cout << fruit << " ";
}
return 0;
}
출력:
Apple Banana Cherry
3. std::map
과 함께 사용
std::map
은 키-값 쌍을 저장하는 컨테이너입니다. range-based for 문을 사용하면 쉽게 모든 요소를 순회할 수 있습니다.
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
for (std::pair<std::string, int> entry : scores) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
return 0;
}
출력:
Alice: 90
Bob: 85
Charlie: 95
4. std::set
과 함께 사용
std::set
은 중복을 허용하지 않는 컨테이너이며, range-based for 문으로 손쉽게 순회할 수 있습니다.
#include <iostream>
#include <set>
int main() {
std::set<int> uniqueNumbers = {10, 20, 30, 40, 50};
for (int num : uniqueNumbers) {
std::cout << num << " ";
}
return 0;
}
출력:
10 20 30 40 50
정리
range-based for 문을 사용하면 배열뿐만 아니라 std::vector
, std::map
, std::set
등 다양한 STL 컨테이너를 간결한 문법으로 순회할 수 있습니다. 기존의 반복문보다 더 직관적이며, 특히 이터레이터를 사용할 필요가 없는 점에서 코드의 가독성을 크게 향상시킵니다.
다음으로 auto
키워드를 활용하여 더욱 효율적으로 range-based for 문을 사용할 수 있는 방법을 살펴보겠습니다.
auto 키워드와 함께 사용하는 방법
C++11에서 도입된 auto
키워드는 변수의 타입을 컴파일러가 자동으로 추론하도록 해줍니다. 이는 range-based for 문과 결합할 때 특히 유용합니다.
1. auto
키워드를 활용한 간결한 코드
C++11 이전에는 반복문에서 컨테이너의 요소 타입을 명시적으로 선언해야 했습니다. 하지만 auto
를 사용하면 타입을 자동으로 추론할 수 있어 코드가 더욱 간결해집니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> fruits = {"Apple", "Banana", "Cherry"};
for (auto fruit : fruits) {
std::cout << fruit << " ";
}
return 0;
}
출력:
Apple Banana Cherry
위 코드에서 auto fruit
는 std::string fruit
으로 자동 변환됩니다.
2. std::map
에서 auto
활용
std::map
은 std::pair
형태의 키-값 쌍을 저장합니다. 일반적으로 std::pair<std::string, int>
형태를 명시적으로 선언해야 하지만, auto
를 사용하면 훨씬 간결한 코드가 됩니다.
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
for (auto entry : scores) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
return 0;
}
출력:
Alice: 90
Bob: 85
Charlie: 95
위 코드에서 auto entry
는 std::pair<std::string, int> entry
로 자동 변환됩니다.
3. 참조(&
)와 auto
를 함께 사용
기본적으로 range-based for 문에서 요소가 복사되어 변수에 저장됩니다. 이는 성능 저하를 유발할 수 있는데, 특히 큰 객체를 다룰 때 문제가 됩니다.
이를 방지하려면 참조(&
)를 사용하여 복사를 방지하고 원본 데이터를 직접 참조할 수 있습니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> words = {"Hello", "World", "C++"};
for (auto& word : words) { // 참조 사용
word += "!";
}
for (const auto& word : words) { // const 참조 사용
std::cout << word << " ";
}
return 0;
}
출력:
Hello! World! C++!
auto& word
→ 참조를 사용하여 원본 데이터를 수정 가능const auto& word
→ 데이터 변경을 방지하며 복사를 피함
4. auto
와 const
를 함께 사용
컨테이너의 요소를 수정하지 않는 경우 const
를 함께 사용하면 불필요한 변경을 방지할 수 있습니다.
for (const auto& element : container) {
// 요소를 변경할 필요가 없을 때 사용
}
이를 활용하면 읽기 전용 데이터 순회 시 불필요한 복사 비용을 줄일 수 있습니다.
정리
auto
를 사용하면 range-based for 문의 가독성을 높이고, 코드 길이를 줄일 수 있습니다.auto&
를 사용하면 불필요한 복사를 방지하여 성능을 향상시킬 수 있습니다.const auto&
를 사용하면 읽기 전용 데이터 순회 시 성능을 최적화할 수 있습니다.
다음으로는 참조(&
)를 활용한 성능 최적화 기법에 대해 자세히 살펴보겠습니다.
참조(reference)를 사용한 성능 최적화
C++에서 range-based for 문을 사용할 때, 기본적으로 컨테이너의 요소가 복사(copy) 되어 변수에 저장됩니다. 작은 크기의 기본 자료형(int, char 등)이라면 문제가 되지 않지만, 크기가 큰 객체를 복사할 경우 불필요한 성능 저하가 발생할 수 있습니다. 이를 해결하기 위해 참조(&
)를 사용하면 원본 데이터를 직접 접근하여 성능을 최적화할 수 있습니다.
1. 기본 복사 방식의 문제점
먼저, 기본적인 range-based for 문을 사용하여 std::vector<std::string>
을 순회하는 예제를 살펴보겠습니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> words = {"Hello", "World", "C++"};
for (auto word : words) { // 요소가 복사됨
word += "!";
}
for (auto word : words) { // 원본은 변경되지 않음
std::cout << word << " ";
}
return 0;
}
출력:
Hello World C++
위 코드에서 auto word : words
문장은 words
벡터의 각 요소를 복사하여 word
변수에 저장한 후, 수정 작업을 수행합니다. 하지만 복사된 변수에서 변경이 일어나므로 원본 데이터(words
)에는 아무런 영향을 미치지 않습니다.
2. 참조(&
)를 사용한 최적화
복사를 방지하고 원본 데이터를 직접 수정하려면 참조(&
)를 사용해야 합니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> words = {"Hello", "World", "C++"};
for (auto& word : words) { // 참조 사용 -> 원본 데이터 변경 가능
word += "!";
}
for (const auto& word : words) { // 원본 데이터 확인
std::cout << word << " ";
}
return 0;
}
출력:
Hello! World! C++!
이제 word
변수가 words
의 각 요소를 참조하게 되어, 원본 데이터를 직접 수정할 수 있습니다.
3. const
와 함께 사용하여 안전한 읽기
원본 데이터를 수정할 필요가 없을 때는 const auto&
를 사용하여 불필요한 복사를 방지하고, 동시에 데이터 변경을 막을 수 있습니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> words = {"Hello", "World", "C++"};
for (const auto& word : words) { // 원본을 수정하지 않음
std::cout << word << " ";
}
return 0;
}
이 방식은 읽기 전용 순회에 적합하며, 특히 대형 객체를 반복문에서 다룰 때 성능 최적화에 도움이 됩니다.
4. std::map
에서 참조 사용
std::map
의 키-값 쌍(std::pair
)도 크기가 크다면 복사를 피하는 것이 좋습니다.
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
for (const auto& entry : scores) { // 복사 방지
std::cout << entry.first << ": " << entry.second << std::endl;
}
return 0;
}
이 경우 entry
는 std::pair<std::string, int>
타입의 값이므로, 복사가 발생하면 성능이 저하될 수 있습니다. 따라서 const auto&
를 사용하여 불필요한 복사를 방지합니다.
5. 참조 사용 시 주의할 점
참조(&
)를 사용할 때는 순회 중 컨테이너의 요소를 제거하거나 변경하면 예기치 않은 동작이 발생할 수 있음을 유의해야 합니다.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto& num : numbers) {
if (num % 2 == 0) {
numbers.erase(numbers.begin()); // 잘못된 접근 (런타임 오류 가능)
}
}
return 0;
}
이런 상황에서는 std::remove_if()
와 같은 알고리즘을 활용하는 것이 더 안전한 방법입니다.
정리
방식 | 사용법 | 특징 |
---|---|---|
auto | for (auto element : container) | 요소가 복사됨 (대형 객체 사용 시 비효율적) |
auto& | for (auto& element : container) | 원본 데이터를 직접 수정 가능 |
const auto& | for (const auto& element : container) | 읽기 전용 순회에서 복사 방지 및 성능 최적화 |
- 참조(
&
)를 사용하면 복사 비용을 줄여 성능을 최적화할 수 있다. - 읽기 전용 순회에서는
const auto&
를 사용하여 불필요한 복사를 방지해야 한다. - 참조를 사용할 때는 컨테이너의 크기 변경에 주의해야 한다.
다음으로 const
와 mutable
요소를 처리하는 방법을 살펴보겠습니다.
const
와 mutable
요소 처리하기
C++에서 const
는 객체의 값을 변경하지 않도록 보장하는 키워드입니다. 하지만 때로는 const
객체 내에서 특정 요소를 변경해야 할 경우가 있습니다. 이런 상황에서 mutable
키워드를 사용하여 특정 멤버 변수를 변경할 수 있습니다. 이를 range-based for 문에서 어떻게 활용할 수 있는지 살펴보겠습니다.
1. const
와 mutable
의 개념
const
: 객체가 수정되지 않도록 제한합니다. 예를 들어,const
멤버 함수 내에서는 멤버 변수를 수정할 수 없습니다.mutable
:const
객체 내에서도 변경할 수 있는 멤버 변수에 적용됩니다. 즉,const
로 선언된 객체에서도mutable
키워드를 사용한 멤버 변수는 수정할 수 있습니다.
2. const
객체 내에서 mutable
멤버 변수 사용
mutable
키워드를 사용하면 const
객체 내에서 특정 멤버 변수를 수정할 수 있습니다. 예를 들어, std::map
에서 const
로 지정된 객체를 순회하면서 mutable
멤버 변수를 변경할 수 있습니다.
#include <iostream>
#include <map>
class Counter {
public:
mutable int count = 0; // mutable로 선언된 멤버 변수
void increment() const {
count++; // const 객체 내에서 count 수정 가능
}
};
int main() {
std::map<std::string, Counter> counters;
// Counter 객체를 삽입
counters["A"] = Counter();
counters["B"] = Counter();
// range-based for 문으로 순회하며 count를 증가
for (auto& pair : counters) {
pair.second.increment();
}
// 결과 출력
for (const auto& pair : counters) {
std::cout << pair.first << ": " << pair.second.count << std::endl;
}
return 0;
}
출력:
A: 1
B: 1
이 코드에서는 Counter
클래스의 count
멤버 변수를 mutable
로 선언하여, const
로 지정된 객체 내에서도 count
값을 수정할 수 있습니다. increment()
함수는 const
멤버 함수로 정의되어 있지만, count
는 mutable
로 선언되어 있기 때문에 값을 변경할 수 있습니다.
3. range-based for 문에서 const
와 mutable
사용
std::map
또는 std::vector
와 같은 컨테이너를 const
참조로 순회할 때, mutable
키워드를 사용한 멤버 변수는 변경할 수 있다는 점을 활용할 수 있습니다.
#include <iostream>
#include <vector>
class Item {
public:
mutable int accessCount = 0; // mutable 멤버 변수
void access() const {
accessCount++; // const 함수에서 count 수정 가능
}
};
int main() {
std::vector<Item> items(5); // 5개의 Item 객체를 가진 벡터
// range-based for 문을 사용해 아이템에 접근
for (const auto& item : items) {
item.access(); // const 멤버 함수 내에서 accessCount 수정
}
// 결과 출력
for (const auto& item : items) {
std::cout << "Access Count: " << item.accessCount << std::endl;
}
return 0;
}
출력:
Access Count: 1
Access Count: 1
Access Count: 1
Access Count: 1
Access Count: 1
위 예제에서 access()
함수는 const
멤버 함수로 선언되었지만, accessCount
는 mutable
로 선언되어 있으므로, const
객체 내에서도 이 값을 변경할 수 있습니다.
4. mutable
과 const
를 사용할 때 주의할 점
mutable
을 사용하면const
객체 내에서도 변경할 수 있지만, 이는 의도된 설계에 맞게 신중하게 사용해야 합니다. 객체의 상태를 수정할 때 외부에서 객체가 수정될 수 있음을 명확하게 나타내는 것이 중요합니다.- 주로 캐시, 접근 횟수 추적 등 특정 속성만 변경해야 할 경우에 유용합니다. 예를 들어, 어떤 객체의 접근 횟수를 추적하는 경우,
count
와 같은 멤버 변수를mutable
로 선언하여const
함수에서 수정할 수 있도록 하는 것입니다.
5. const
와 mutable
의 활용 예시
#include <iostream>
#include <vector>
class Cache {
public:
mutable bool isValid = false; // mutable 멤버 변수
void markAsValid() const {
isValid = true; // const 멤버 함수 내에서 isValid 수정
}
void markAsInvalid() const {
isValid = false; // const 멤버 함수 내에서 isValid 수정
}
};
int main() {
std::vector<Cache> cacheList(3); // Cache 객체 3개로 구성된 벡터
// range-based for 문을 사용하여 객체를 순회하며 상태 변경
for (auto& cache : cacheList) {
cache.markAsValid(); // 각 객체를 valid 상태로 변경
}
// 결과 출력
for (const auto& cache : cacheList) {
std::cout << "Cache Validity: " << cache.isValid << std::endl;
}
return 0;
}
출력:
Cache Validity: 1
Cache Validity: 1
Cache Validity: 1
위 예시에서는 Cache
객체의 isValid
멤버 변수를 mutable
로 선언하여, const
멤버 함수인 markAsValid()
에서도 이 값을 변경할 수 있도록 했습니다. 이는 캐시의 유효성을 추적하는 등의 목적에 유용할 수 있습니다.
정리
키워드 | 사용법 | 설명 |
---|---|---|
const | const Type obj; | 객체의 값을 변경할 수 없도록 제한 |
mutable | mutable Type var; | const 객체 내에서도 값을 변경할 수 있도록 허용 |
const
는 객체의 불변성을 보장하고,mutable
은const
객체 내에서도 특정 멤버 변수를 변경할 수 있도록 합니다.- 주로 캐시 처리나 읽기 전용 객체 상태 변경과 같은 특정 상황에서 사용됩니다.
const
와mutable
을 사용할 때는 객체 상태 변경이 의도된 것임을 명확히 해야 합니다.
다음으로는 std::vector
나 std::map
에서 const
참조와 mutable
을 활용한 성능 최적화에 대해 다뤄보겠습니다.
range-based for 문과 구조체 활용
C++에서 구조체(Struct)는 데이터를 그룹화하여 다룰 수 있는 유용한 기능을 제공합니다. range-based for 문을 사용하면 구조체를 포함한 컨테이너를 더 쉽게 순회할 수 있습니다. 특히, std::vector
나 std::map
등의 컨테이너와 구조체를 함께 사용할 경우 더욱 직관적인 코드 작성이 가능합니다.
1. 구조체를 포함한 std::vector
순회
구조체를 저장하는 std::vector
를 range-based for 문으로 순회할 수 있습니다.
#include <iostream>
#include <vector>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 28}};
for (const auto& person : people) { // const 참조 사용 (복사 방지)
std::cout << person.name << " is " << person.age << " years old." << std::endl;
}
return 0;
}
출력:
Alice is 25 years old.
Bob is 30 years old.
Charlie is 28 years old.
📌 코드 설명
std::vector<Person>
을 사용하여people
목록을 생성했습니다.- range-based for 문을 사용하여 벡터의 각 요소를 순회했습니다.
const auto& person
을 사용하여 복사를 방지하고 원본 데이터를 직접 참조했습니다.
2. 구조체와 std::map
활용
std::map
을 사용할 때 구조체를 값(value)으로 저장할 수 있으며, range-based for 문을 사용하면 쉽게 순회할 수 있습니다.
#include <iostream>
#include <map>
struct Product {
std::string name;
double price;
};
int main() {
std::map<int, Product> productCatalog = {
{101, {"Laptop", 1200.50}},
{102, {"Smartphone", 799.99}},
{103, {"Tablet", 450.75}}
};
for (const auto& item : productCatalog) {
std::cout << "Product ID: " << item.first
<< ", Name: " << item.second.name
<< ", Price: $" << item.second.price << std::endl;
}
return 0;
}
출력:
Product ID: 101, Name: Laptop, Price: $1200.5
Product ID: 102, Name: Smartphone, Price: $799.99
Product ID: 103, Name: Tablet, Price: $450.75
📌 코드 설명
std::map<int, Product>
을 사용하여 제품 ID와 제품 정보를 매핑했습니다.- range-based for 문을 사용하여
std::map
을 쉽게 순회할 수 있었습니다. const auto&
를 사용하여 복사 비용을 줄였습니다.
3. 구조체 멤버 변수를 수정하는 경우
만약 range-based for 문을 통해 구조체의 멤버 변수를 수정해야 한다면 참조(&
)를 사용해야 합니다.
#include <iostream>
#include <vector>
struct Employee {
std::string name;
int salary;
};
int main() {
std::vector<Employee> employees = {{"Alice", 5000}, {"Bob", 5500}, {"Charlie", 6000}};
// 직원의 급여를 10% 인상
for (auto& emp : employees) { // 참조 사용
emp.salary *= 1.1;
}
// 결과 출력
for (const auto& emp : employees) {
std::cout << emp.name << "'s new salary: $" << emp.salary << std::endl;
}
return 0;
}
출력:
Alice's new salary: $5500
Bob's new salary: $6050
Charlie's new salary: $6600
📌 코드 설명
auto&
를 사용하여 구조체의 원본 데이터를 직접 수정했습니다.emp.salary *= 1.1;
을 통해 모든 직원의 급여를 10% 증가시켰습니다.- 두 번째
for
문에서는const auto&
를 사용하여 데이터를 읽기 전용으로 접근했습니다.
4. std::set
과 구조체 활용
std::set
에서 구조체를 정렬된 형태로 저장하고 range-based for 문을 활용하여 순회할 수 있습니다. std::set
을 사용할 경우, 비교 연산자(operator<
)를 정의해야 합니다.
#include <iostream>
#include <set>
struct Student {
std::string name;
int grade;
// 비교 연산자 정의 (std::set에서 정렬을 위해 필요)
bool operator<(const Student& other) const {
return grade > other.grade; // 높은 점수순 정렬
}
};
int main() {
std::set<Student> students = {{"Alice", 85}, {"Bob", 90}, {"Charlie", 80}};
for (const auto& student : students) {
std::cout << student.name << ": " << student.grade << std::endl;
}
return 0;
}
출력:
Bob: 90
Alice: 85
Charlie: 80
📌 코드 설명
std::set<Student>
를 사용하여 학생 목록을 생성했습니다.operator<
를 정의하여grade
값이 높은 순서로 정렬되도록 했습니다.- range-based for 문을 사용하여
std::set
을 순회했습니다.
5. mutable
멤버 변수와 구조체 활용
C++에서는 mutable
키워드를 사용하여 const
객체 내에서도 특정 멤버 변수를 수정할 수 있습니다.
#include <iostream>
#include <vector>
struct Logger {
std::string message;
mutable int accessCount = 0; // mutable 사용
void printMessage() const {
accessCount++; // const 함수에서도 변경 가능
std::cout << message << " (Accessed " << accessCount << " times)" << std::endl;
}
};
int main() {
std::vector<Logger> logs = {{"Error: File not found"}, {"Warning: Low memory"}};
for (const auto& log : logs) {
log.printMessage();
}
return 0;
}
출력:
Error: File not found (Accessed 1 times)
Warning: Low memory (Accessed 1 times)
📌 코드 설명
mutable
키워드를 사용하여accessCount
를const
함수에서도 변경할 수 있도록 했습니다.const auto& log
를 사용하여 데이터를 복사하지 않으면서printMessage()
를 호출했습니다.
정리
활용 예제 | 주요 특징 |
---|---|
std::vector + 구조체 | 데이터 목록을 쉽게 저장하고, range-based for 문으로 순회 가능 |
std::map + 구조체 | 키-값 형태의 데이터를 구조체와 함께 저장 |
참조(& ) 활용 | 원본 데이터 직접 수정 가능 |
std::set + 구조체 | operator< 를 정의하여 정렬 기준을 설정 |
mutable 과 const | const 객체 내에서도 특정 멤버 변수 수정 가능 |
range-based for 문과 구조체를 결합하면 코드의 가독성을 높이고, 반복문을 더욱 직관적으로 사용할 수 있습니다.
다음으로, range-based for 문을 표준 라이브러리(STL) 컨테이너에서 어떻게 활용할 수 있는지 살펴보겠습니다.
range-based for 문과 표준 라이브러리(STL)
C++의 표준 라이브러리(STL)에는 다양한 컨테이너(std::array
, std::vector
, std::map
, std::set
, std::unordered_map
등)가 포함되어 있으며, range-based for 문을 사용하면 이러한 컨테이너를 더욱 직관적으로 순회할 수 있습니다.
1. std::array
와 range-based for 문
std::array
는 C++11에서 도입된 정적 배열로, 컴파일 타임에 크기가 결정됩니다. range-based for 문을 사용하면 더 간결한 코드로 순회할 수 있습니다.
#include <iostream>
#include <array>
int main() {
std::array<int, 5> numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
출력:
1 2 3 4 5
📌 특징
std::array
는 C++의 일반 배열과 비슷하지만,std::vector
와 유사한 인터페이스를 제공합니다.- range-based for 문을 사용하여 보다 쉽게 순회할 수 있습니다.
2. std::vector
와 range-based for 문
std::vector
는 동적 배열로, 가장 많이 사용되는 컨테이너 중 하나입니다. range-based for 문을 사용하면 기존의 for
루프보다 훨씬 간결하게 데이터를 순회할 수 있습니다.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> fruits = {"Apple", "Banana", "Cherry"};
for (const auto& fruit : fruits) { // 복사 방지
std::cout << fruit << " ";
}
return 0;
}
출력:
Apple Banana Cherry
📌 특징
auto&
를 사용하면 복사 비용을 줄이고 원본 데이터를 직접 참조할 수 있습니다.- 읽기 전용 순회라면
const auto&
를 사용하여 데이터 변경을 방지하는 것이 좋습니다.
3. std::map
과 range-based for 문
std::map
은 키-값 쌍을 저장하는 컨테이너로, range-based for 문을 활용하면 쉽게 데이터를 조회할 수 있습니다.
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
for (const auto& [name, score] : scores) { // C++17 구조 분해 사용
std::cout << name << ": " << score << std::endl;
}
return 0;
}
출력:
Alice: 90
Bob: 85
Charlie: 95
📌 특징
- C++17부터는 구조 분해(destructuring) 문법을 사용하여
[key, value]
형태로 데이터를 쉽게 접근할 수 있습니다. - C++11을 사용할 경우
const auto& pair : scores
형태로 반복문을 작성한 후,pair.first
와pair.second
로 접근해야 합니다.
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
4. std::set
과 range-based for 문
std::set
은 중복을 허용하지 않는 정렬된 컨테이너입니다. range-based for 문을 사용하면 간단하게 순회할 수 있습니다.
#include <iostream>
#include <set>
int main() {
std::set<int> uniqueNumbers = {10, 20, 30, 40, 50};
for (int num : uniqueNumbers) {
std::cout << num << " ";
}
return 0;
}
출력:
10 20 30 40 50
📌 특징
std::set
은 자동으로 정렬된 상태를 유지하며, range-based for 문을 사용하면 쉽게 모든 요소를 순회할 수 있습니다.
5. std::unordered_map
과 range-based for 문
std::unordered_map
은 std::map
과 유사하지만, 내부적으로 해시 테이블을 사용하여 빠른 조회 속도를 제공합니다.
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}};
for (const auto& [name, age] : ages) {
std::cout << name << " is " << age << " years old." << std::endl;
}
return 0;
}
출력: (출력 순서는 해시 테이블의 내부 상태에 따라 달라질 수 있음)
Charlie is 35 years old.
Bob is 25 years old.
Alice is 30 years old.
📌 특징
- 정렬되지 않은 컨테이너이므로 순회할 때 원래 입력한 순서와 다를 수 있습니다.
std::map
보다 데이터 조회가 빠른 장점이 있지만, 정렬이 필요하다면std::map
을 사용하는 것이 좋습니다.
6. std::deque
와 range-based for 문
std::deque
(Double-Ended Queue)는 양쪽 끝에서 삽입과 삭제가 빠른 컨테이너로, range-based for 문을 사용할 수 있습니다.
#include <iostream>
#include <deque>
int main() {
std::deque<int> numbers = {5, 10, 15, 20, 25};
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
출력:
5 10 15 20 25
📌 특징
std::vector
와 유사하지만, 양쪽에서 삽입 및 삭제가 빠르다는 차이점이 있습니다.
7. std::list
와 range-based for 문
std::list
는 이중 연결 리스트(Doubly Linked List) 구조로 되어 있으며, range-based for 문으로 쉽게 순회할 수 있습니다.
#include <iostream>
#include <list>
int main() {
std::list<std::string> names = {"Alice", "Bob", "Charlie"};
for (const auto& name : names) {
std::cout << name << " ";
}
return 0;
}
출력:
Alice Bob Charlie
📌 특징
std::list
는 임의 접근(random access)이 불가능하지만, 삽입/삭제가 빠른 특성을 가집니다.
정리
컨테이너 | range-based for 문 사용 가능 여부 | 특징 |
---|---|---|
std::array | ✅ | 정적 배열, 크기 변경 불가 |
std::vector | ✅ | 동적 크기 조정 가능, 빠른 조회 |
std::map | ✅ | 정렬된 키-값 저장 |
std::set | ✅ | 중복 불허, 자동 정렬 |
std::unordered_map | ✅ | 빠른 조회, 순서 보장 X |
std::deque | ✅ | 양쪽 끝 삽입/삭제 빠름 |
std::list | ✅ | 이중 연결 리스트, 삽입/삭제 최적화 |
range-based for 문을 사용하면 STL 컨테이너를 더 직관적이고 효율적으로 순회할 수 있습니다.
다음으로, 기존 이터레이터 기반 반복문과 비교하여 장단점을 분석해 보겠습니다.
8. 기존 이터레이터 기반 반복문과 비교
C++에서 범위 기반 for
문은 기존 이터레이터 기반 반복문에 비해 여러 장점을 제공합니다. 이 장점들을 비교하여 각각의 사용 사례에 대해 좀 더 이해를 돕겠습니다.
기존 이터레이터 기반 반복문
전통적인 for
문을 사용하면, begin()
과 end()
이터레이터를 사용해 STL 컨테이너를 순회합니다. 아래 예시는 std::vector
를 순회하는 전통적인 방식입니다.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
출력:
1 2 3 4 5
📌 특징
- 이터레이터를 명시적으로 사용하므로 반복문이 길어지고 코드가 복잡해질 수 있습니다.
- 값을 수정하거나
it->first
,it->second
와 같은 멤버 접근 시 코드가 길어지거나 오류를 발생시킬 수 있습니다.
범위 기반 for
문
범위 기반 for
문을 사용하면 동일한 작업을 간결하고 직관적으로 처리할 수 있습니다. auto
키워드를 사용하면 타입을 자동으로 추론하여 더 간결하게 코드를 작성할 수 있습니다.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto num : numbers) {
std::cout << num << " ";
}
return 0;
}
출력:
1 2 3 4 5
📌 특징
- 더 직관적이고 간결한 코드로,
begin()
과end()
를 명시적으로 호출할 필요가 없습니다. auto
를 사용하여 타입을 명시적으로 지정할 필요가 없으며, 특히 복잡한 컨테이너에서는 유용합니다.- 범위 기반
for
문은 값 기반으로 순회하므로, 값의 복사 비용이 발생할 수 있습니다. 하지만 이는const auto&
를 사용해 참조로 순회함으로써 방지할 수 있습니다.
성능 측면 비교
반복문 종류 | 성능 | 장점 |
---|---|---|
기존 이터레이터 | 적당히 빠르지만, 반복문이 복잡함 | 반복문을 세밀하게 제어 가능 |
범위 기반 for | 성능상 큰 차이 없음 | 코드 간결성, 가독성 우수 |
범위 기반 for
문은 성능에 거의 차이가 없습니다. 다만 코드가 더 간결하고 가독성이 좋아 유지보수에 유리합니다. 이터레이터 기반 반복문은 더 복잡한 조건이나 반복 제어가 필요한 경우에 사용될 수 있습니다.
9. 다양한 컨테이너와 범위 기반 for
문 활용
범위 기반 for
문은 모든 STL 컨테이너에서 잘 동작합니다. 다양한 컨테이너에서의 활용 예시는 다음과 같습니다.
std::unordered_map
순회 예시
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
return 0;
}
출력:
Charlie: 95
Bob: 85
Alice: 90
std::set
순회 예시
#include <iostream>
#include <set>
int main() {
std::set<int> numbers = {10, 20, 30, 40, 50};
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
출력:
10 20 30 40 50
std::map
순회 예시
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}};
for (const auto& [name, age] : ages) {
std::cout << name << " is " << age << " years old." << std::endl;
}
return 0;
}
출력:
Alice is 30 years old.
Bob is 25 years old.
Charlie is 35 years old.
범위 기반 for
문을 사용하면 컨테이너 타입에 관계없이 일관되게 순회할 수 있으며, 코드가 훨씬 간결해집니다.
결론
범위 기반 for
문은 C++11에서 도입된 강력하고 직관적인 반복문 방식입니다. 특히 STL 컨테이너와 함께 사용하면 코드의 가독성을 크게 향상시키며, 값을 복사할 필요가 없거나 참조를 사용할 경우 성능도 손쉽게 최적화할 수 있습니다. 이를 통해 코드를 더 간결하고 이해하기 쉽게 작성할 수 있습니다.
10. 요약
본 기사에서는 C++11에서 도입된 range-based for 문을 활용하여 다양한 STL 컨테이너를 순회하는 방법을 다루었습니다. range-based for 문은 기존의 이터레이터 기반 반복문에 비해 코드의 가독성을 높이고, 반복문을 간결하게 작성할 수 있는 큰 장점이 있습니다.
std::array
,std::vector
,std::map
,std::set
,std::unordered_map
등 다양한 STL 컨테이너에서 범위 기반for
문을 사용할 수 있습니다.- 성능상 큰 차이가 없으며,
auto
와const auto&
를 사용하여 불필요한 복사 비용을 줄일 수 있습니다. - 기존 이터레이터 기반 반복문은 더 세밀한 제어가 가능하지만, 범위 기반
for
문은 더 직관적이고 코드가 간결해집니다.
range-based for 문은 C++ 프로그래밍에서 더 효율적이고 깔끔한 코드를 작성하는 데 유용한 도구로 자리잡고 있습니다.