티스토리 뷰
선수지식 키워드
배열
for문 하나만 알아도 충분!
C++의 반복문은 for문, while문, do~while문이 있지만, 우리는 for문 하나만 알아도 충분하다! while문이나 do~while문으로 할 수 있는 것들은 for문으로도 충분히 구현할 수 있기 때문!
그럼 우선 for문에 대해 알아보자!
먼저 for문을 사용하는 간단한 예시를 살펴보자. 아래는 for문을 활용하여 0부터 9까지의 정수를 출력하고 있다.
#include <iostream>
int main() {
for (int i = 0; i < 10; ++i) {
std::cout << i << ' '; // output: 0 1 2 3 4 5 6 7 8 9
}
std::cout << std::endl; // 줄바꿈
return 0;
}
대충 감이 오는가? for문이 어떻게 동작하는지 이제 설명해보겠다. for문의 구조는 다음과 같다.
for ((초기식); (조건식); (증감식)) {
// 매 반복마다 수행할 코드
}
for문이 시작되면 가장 먼저 수행하는 것이 바로 초기식이다. 이 초기식은 for문 시작할 때 최초 1회 수행되고 이후로는 수행되지 않는다. 앞선 예시에서는 이 초기식에서 정수형 변수 i를 선언하고 이를 0으로 초기화했다.
초기식을 읽었으면 그 다음은 조건식을 읽을 차례! 이 조건식은 true나 false를 반환하는 식이어야 한다. 이 조건식이 true를 반환하면 for문 안에 있는 코드를 한 차례 쭉 수행하게 되고, false면 즉시 for문을 끝내고 나오게 된다. 위 예시에서는 조건식이 i < 10이었고, i는 지금 0으로 초기화되어 있으므로 이 조건식은 true가 되어 아래 i를 출력하는 코드를 수행하게 된다.
코드를 한 차례 수행하고 나면 그 다음으로 보는 것이 바로 증감식이다. 증감식이란 말 그대로 값을 증가시키거나 감소시키는 식을 말한다. 위 예시에서는 ++i를 통해 i의 값을 1만큼 올려주고 있다.
증감식의 처리가 끝나면 이제 다시 조건식을 본다. 증감식에서 i의 값은 이제 1이 되었고, 1 < 10은 참이므로 for문은 또 아래 코드를 수행한 후 증감식을 수행한다. 그리고 이를 반복한다. 반복하다보면 i의 값이 9가 되는 시점이 올 것이다. 9 < 10은 참이므로 9를 출력할 것이고, 증감식으로 인해 i의 값은 이제 10이 된다. 그리고 조건식을 보면 10 < 10은 거짓이므로 for문을 종료하고 나오게 된다.
정리하면 for문의 처리 순서는 최초 1회 초기식을 수행하고, 이후로는 조건식 -> 코드 -> 증감식을 반복하게 된다(조건식이 false가 될 때 까지).
아래는 조건식과 증감식을 변화시켜 사용한 다양한 for문의 예제이다.
// 10이하의 자연수 중 짝수만 출력
for (int i = 2; i <= 10; i += 2) {
std::cout << i << ' '; // output: 2 4 6 8 10
}
// 20 이하 자연수 중 3의 배수를 내림차순으로 출력
for (int i = 18; i > 0; i -= 3) {
std::cout << i << ' '; // output: 18 15 12 9 6 3
}
// 100 이하의 2의 거듭제곱 출력
for (int i = 2; i <= 100; i *= 2) {
std::cout << i << ' '; // output: 2 4 8 16 32 64
}
배열 다루는 데는 역시 for문이 딱이지!
아래의 예제를 먼저 보자. 배열을 차례로 순회할 때 for문을 사용하면 매우 편리하다.
#include <iostream>
int main() {
int arr[] = {11, 13, 17, 19, 23};
int arrLength = 5;
for(size_t i = 0; i < arrLength; ++i) { //for문으로 인덱싱할 때는 int 타입보다는 size_t 타입을 권장!
std::cout << arr[i] << ' '; // output: 11 13 17 19 23
}
std::cout << std::endl; //마지막에 줄바꿈을 위해
return 0;
}
그런데 for문으로 위와 같이 배열을 다룰 때는 아주아주 조심해야 한다. 바로 배열의 범위를 벗어나는 액세스가 발생하지 않도록 해야 한다는 것! 만약 위 예제에서 배열 길이를 착각하여 arrLength가 6이었다면 어떻게 될까? 한 번 숫자만 바꿔서 직접 실행해보시라. 그리고 만약 4라고 했으면 어땠을 까? 이 경우 에러는 생기지 않지만 우리는 배열의 마지막 요소에 대해서는 아무런 처리를 하지 못했으므로 이 것이 프로그램에서 어떤 스노우볼로 굴러갈지는 알 수 없다.
정리하면 for문으로 배열을 다룰 때에는 배열의 크기를 정확히 알고 사용해야 한다는 것! 그러지 않으면 반드시 문제가 생긴다!
배열의 크기를 신경쓰지 않아도 되는 범위 기반 for문!
위의 배열 예제를 범위 기반 for문을 사용하여 수정해보겠다!
#include <iostream>
int main() {
int arr[] = {11, 13, 17, 19, 23};
int arrLength = 6;
for(auto num : arr) {
std::cout << num << ' '; // output: 11 13 17 19 23
}
std::cout << std::endl; //마지막에 줄바꿈을 위해
return 0;
}
범위 기반 for문이 동작하는 원리는 이렇다. arr의 첫 요소부터 마지막 요소까지 하나씩 꺼내와서 num에 저장하고, 아래 코드를 수행한다. 이는 배열에 더 이상 요소가 남아 있지 않을 때까지 수행한다. 즉, 우리가 배열의 길이를 전혀 신경쓰지 않아도 된다는 것! 매우매우 유용하므로 꼭꼭 기억해두고 자주 사용하자!
auto 타입에 대해 간단히 설명하자면, auto 타입은 그 타입을 자동으로 추론해서 정해준다. 추론할 수 있는 근거는 다음과 같다. arr은 int타입이 들어 있는 배열이고 따라서 각 요소는 int 타입이다. 이 요소들을 하나씩 꺼내와서 num에 저장할 것이므로 num 역시 int 타입이 되어야 한다고 추론할 수 있다. 그리고 실제 이런 과정으로 auto num은 int num과 같이 처리 된다.
무한 반복문도 for문으로 만들 수 있다! 하지만 while문이 더 편하다!
내 생각에 우리가 while문을 써야할 때는 무한 반복문이 필요한 경우인 것 같다. 예를 들어 사용자가 2를 입력해줄 때까지 계속해서 입력을 받아야 하는 경우 말이다. 사용자가 언제 2를 입력해줄지 알 수 없기 때문에 우리는 이를 무한 반복문으로 처리해야 한다. 아래의 예제를 보자!
#include <iostream>
int main() {
int num = 0;
for ( ; ; ) { // 초기식, 조건식, 증감식을 모두 비우면 무한반복!
std::cout << "your number? ";
std::cin >> num;
if (num == 2) {
break;
}
}
std::cout << "thank you!" << std::endl;
return 0;
}
/*
output:
your number? 1
your number? 5
your number? 48
your number? 25
your number? 4
your number? 2
thank you!
*/
break에 대해 간단히 설명하자면 반복문을 강제로 종료하고 밖으로 나오는 트리거 정도라고 이해하면 쉽다. for문으로 만든 무한 반복문도 간단해보이지만 무한 반복문은 보통은 while문으로 다음과 같이 작성한다.
#include <iostream>
int main() {
int num = 0;
while (true) {
std::cout << "your number? ";
std::cin >> num;
if (num == 2) {
break;
}
}
std::cout << "thank you!" << std::endl;
return 0;
}
while문의 구조는 아래와 같은데, 조건식이 참인 경우에 반복을 계속 수행한다. 그런데 위 예제에서는 조건식이 아예 true로 못 박혀 있기 때문에 이 반복문은 무한반복하게 되는 것이다.
while (조건식) {
// 반복을 수행할 코드
}
반복을 수행하든 안 하든 일단 한 번은 코드를 꼭 실행하고 싶은데요?
반복문의 조건식의 결과에 따라 어쩌면 반복이 단 한 차례도 일어나지 않을 수 있는데 그럼에도 꼭 한 번은 코드를 실행하고 싶다면 이때 쓸 수 있는 것이 do~while문이다. 아래의 예제를 보자
#include <iostream>
int main() {
int num = 2; // 사용자의 입력이 2였다고 가정
do {
std::cout << "hello!" << std::endl;
} while (num != 2);
std::cout << "thank you!" << std::endl;
return 0;
}
위 예제를 실행시켜보면 "hello!"가 딱 한 차례 출력됨을 알 수 있다. do~while문은 do 위에 중괄호 안의 코드를 우선 한 차례 실행하고 그 다음 while 뒤의 조건식을 판별한다. 그리고 조건식이 참이면 아까의 코드를 반복한다. 위 예제의 경우 num의 값이 2가 아니었다면 "hello!"가 무한으로 출력되는 지옥이 열렸을 것이다. do~while문의 구조는 다음과 같다.
do {
// 반복을 수행할 코드
} while (조건식); //do~while문은 특이하게 끝이 세미콜론이 붙는다
근데 사실 do~while문이 하는 일은 아래처럼 그냥 while문으로도 구현할 수는 있다.
#include <iostream>
int main() {
int num = 2; // 사용자의 입력이 2였다고 가정
while (true) {
std::cout << "hello!" << std::endl;
if (num == 2) break;
}
std::cout << "thank you!" << std::endl;
return 0;
}
그리고 이런 이유로 do~while문은 그닥 반복문으로서는 잘 쓰이지 않는다.
do~while문의 새로운 쓰임
일단 이 내용은 반복문으로서의 이 글의 취지와는 멀고 어려울 수 있기 때문에 그냥 훑어보고 넘어가도 좋다. do~while문은 반복문으로서는 잘 쓰이지 않지만 다른 용도로 쓰이곤 하는데 아래 예제를 보자.
#include <iostream>
#define SAY_HELLO do {\
std::cout << "hello" << std::endl;\
std::cout << "world" << std::endl;\
} while (false)
int main() {
SAY_HELLO; //매크로 함수는 아닌데... 매크로 상수도 아니고...
return 0;
}
/*
output:
hello
world
*/
위 예제를 보면 뭔가 매크로를 하나 만들었는데, 이게 매크로 함수는 아니고... 그런데 어떠한 로직을 수행하고 있다. do~while(false)에서 조건식이 false라서 코드는 딱 한 번 실행되고 반복을 전혀 하지 않는데 왜 굳이 do~while문을 썼을까? 이건 로직을 반복하려는 것이 아닌 단순히 이 코드를 SAY_HELLO자리에 붙여 넣는 것에 의의가 있다고 볼 수 있다. 자세한 설명은 솔직히 나도 할 수는 없다. 그냥 전처리 과정에서 저렇게 어떤 로직을 붙여 넣고 싶을 때 쓸 수 있구나 정도로만 이해하고 넘어가자.