for 루프
목차
C에는 while 과 for 두 종류의 루프문이 있습니다. 기계적으로 보면 둘이 다른 것은 아닙니다. 다른 문법으로도 같은 루프를 만들 수 있습니다. 그렇다면 왜 번거롭게 루프문이 두개나 되느냐 – 에 대한 의문이 생길 수 있습니다.
가장 큰 차이라고 하면 for 루프는 루프를 제어하는 주요 동작들이 한 곳에 모여있습니다. 루프에 필요한 동작은 다음과 같습니다.
- 초기화 (인덱스, 초기값)
- 표현식 검사(참/거짓에 따라 루프 지속/종료)
- 값의 갱신(인덱스 등)
for 루프의 문법은 다음과 같습니다.
for(초기화; 표현식 검사; 값의 갱신) { Statement; (문 - 단일문/복합문) }
예제를 보는게 좀 빠릅니다. 아래는 1부터 10까지 숫자를 더하는 예제입니다.
#include <stdio.h> int main() { int sum = 0; for (int i = 0; i < 11; i++) { sum += i; printf("i: %3d, sum: %3d\n", i, sum); } return 0; }
[run cpp...] i: 0, sum: 0 i: 1, sum: 1 i: 2, sum: 3 i: 3, sum: 6 i: 4, sum: 10 i: 5, sum: 15 i: 6, sum: 21 i: 7, sum: 28 i: 8, sum: 36 i: 9, sum: 45 i: 10, sum: 55 [script closed...]
인덱스가 0부터 10까지 가면서 숫자의 합계가 나옵니다. for 문의 ( ) 이 감싼 부분이 헤더입니다. 헤더를 보면 단번에 이 루프가 무엇을 하려는지 예측이 가능합니다. i가 0부터 10까지, 1씩 증가한다. while 문은 한줄에 확인이 안되지요. 특히 카운터를 while문 바깥에 만들어야 하는 것은 카운팅의 어려움을 높입니다. 별거 아니라고 할 수도 있겠지만 프로그래밍은 반복이기 때문에 while 문을 계속 작성하다 보면 카운팅을 빼먹을 날도 있을 겁니다. 반면 for 문은 한줄 헤더에서 다 확인이 됩니다. 한눈에 들어오는 것과 그렇지 않은 것은 차이가 큽니다,
원래데로 라면 초기화에 1라인, 표현식에 1라인, 증감하는 갱신게 1라인 총 세줄의 문장이 들어가야 하는데 이것을 한 줄에 몰아넣었으니까 각각의 의미를 이해하기 전에는 어렵게 보일 수가 있습니다. 위의 for문을 while 문으로 바꿔서 비교해보겠습니다.
#include <stdio.h> int main() { int sum = 0; int count = 0; // 1. for 문의 초기화 while(count < 11) // 2. 표현식 { sum += count; printf("count: %3d, sum: %3d\n", count, sum); count++; // 3. 갱신(값의 증감) } return 0; }
위의 코드는 for문과 같은 결과를 출력합니다. 여기서 보면 for 문에서는 헤더에서 한줄에서 끝나는 것이 while 문에서는 세 개의 라인으로 분리됩니다. 즉 for문 보다 코드가 2줄 더 깁니다. 단순히 긴게 문제가 아니라 count (인덱스)는 while 문 바깥에서 찾아야 하고 표현식은 while 의 헤더에 있고 값의 갱신은 while문 내부의 끝 부분에서 찾아야 합니다.(끝이 아닐 수도 있다)
이 코드를 자신이 쓰더라도 찾아야 뭔지 보이는데 남이 쓴 코드라면 그걸 찾아서 의미를 연결하는데는 시간이 더 걸립니다. 같은 코드라도 for문이냐 while 문이냐에 따라 같은 작업에도 효율 차이가 날 수 있습니다.
반복문에 있어서 대다수의 프로그래머들은 for문을 좋아합니다. 일단 한눈에 의미 파악이 되기 때문에 읽기가 쉽고 for문은 상대적으로 시작과 끝점이 명확합니다. 논리적인 두뇌라는 건 시작과 끝이 있는 것을 좋아하는 경향도 있지요.
그렇다고 while 문이 for문보다 떨어지는 문법이다? 절대 그런 것은 아닙니다. for문이 직선적인 결론이라면 while 은 상황에 따라 달라지는 프로그램에 적합합니다. while문의 헤드에서는 어디서 시작해서 어디까지 가는지는 알 수 없지만 종료 조건은 명확합니다. 그래서 프로그램이 어떤 조건을 달성했을 때 종료한다, 다시 말해 어떤 조건을 달성할 때 까지는 무한루프라도 돌릴 수 있다고 보면 됩니다.
예를 들어서 빵집에서 오늘 만들 빵이 200개입니다. 신제품이라 한시간에 얼마나 만드는지 알 수가 없는데 일단 200개를 만들면 종료하기로 합니다. while(생산된 빵의 수 < 201) 잘못하면 밤새서 빵을 구워야할 지도 모르는 일이지요. 처음에 빵이 몇 개에서 시작한 건지는 모르겠지만 어쨋든 200개가 넘어가면 끝납니다.
또 while문은 자동문 같이 조건 반사적인 로직에 적합합니다. 자동문은 사람이 문앞에 선 것을 센서가 24시간 미세하게 감지하고 있다가 문이 자동으로 열립니다. 아라비안 나이트의 열려라 참깨는 마법같이 보이지만 현대의 자동문은 while 의 원리로 동작합니다. while (IsSomeThere()) 같이 조건식에 bool 형 함수를 자주 사용합니다. 자동문이란게 딱히 종료시점이 없으니까 자동문을 철거하기 전까지 유효합니다. 간단한 원리지만 현대 사회의 많은 시스템이 이 while 문에 의존하고 있습니다.
그래서 for문과 while문 두개의 루프가 있는 것 입니다. 용도를 구분하지 않았다면 굳이 두개를 놔두지 않았을 겁니다. 다른 고급언어(high level language)에도 거의 대부분 for문과 while문이 따로 있는데 언어에 상관없는 논리기 때문입니다. 사용하는 스킬은 여러가지가 있겠으나 크게 나눠본다면 for 문은 시작점과 끝이 명확한 루프에 적당하고, while문은 조건에 따라 회수가 달라지는 루프에 잘 맞습니다. 실습을 많이 해보고 다양한 루프를 만들다 보면 상황에 맞게 for나 while을 선택할 안목이 길러집니다.
for 루프의 다양한 사용법
for 루프는 헤더에 많은 것을 담고 있어서 조작에 따라 다양한 사용법이 있습니다.
다음은 1부터 100까지 홀수를 출력하는 루프입니다. 헤더의 갱신 표현식을 활용할 수 있습니다. 카운터의 값에 1 이외의 다른 값을 더할 수 있습니다. 곱하거나 나누는 등 다른 어떤 식도 가능합니다.
#include <stdio.h> int main(void) { for (int i = 1; i <= 100; i+=2) { if((i-1)%9==0) printf("\n"); printf("(%2d) ", i); } printf("\n"); return 0; }
( 1) ( 3) ( 5) ( 7) ( 9) (11) (13) (15) (17) (19) (21) (23) (25) (27) (29) (31) (33) (35) (37) (39) (41) (43) (45) (47) (49) (51) (53) (55) (57) (59) (61) (63) (65) (67) (69) (71) (73) (75) (77) (79) (81) (83) (85) (87) (89) (91) (93) (95) (97) (99)
아스키 문자를 출력하는데 사용할 수도 있습니다.
#include <stdio.h> int main(void) { for (char ch = 'A'; ch <= 'z'; ch++) { printf("[%3d : %c] ", ch, ch); if((ch-1)%7==0) printf("\n"); } printf("\n"); return 0; }
[ 65 : A] [ 66 : B] [ 67 : C] [ 68 : D] [ 69 : E] [ 70 : F] [ 71 : G] [ 72 : H] [ 73 : I] [ 74 : J] [ 75 : K] [ 76 : L] [ 77 : M] [ 78 : N] [ 79 : O] [ 80 : P] [ 81 : Q] [ 82 : R] [ 83 : S] [ 84 : T] [ 85 : U] [ 86 : V] [ 87 : W] [ 88 : X] [ 89 : Y] [ 90 : Z] [ 91 : [] [ 92 : \] [ 93 : ]] [ 94 : ^] [ 95 : _] [ 96 : `] [ 97 : a] [ 98 : b] [ 99 : c] [100 : d] [101 : e] [102 : f] [103 : g] [104 : h] [105 : i] [106 : j] [107 : k] [108 : l] [109 : m] [110 : n] [111 : o] [112 : p] [113 : q] [114 : r] [115 : s] [116 : t] [117 : u] [118 : v] [119 : w] [120 : x] [121 : y] [122 : z]
유니코드 문자의 출력도 할 수 있습니다.
#include <stdio.h> #include <wchar.h> #include <locale.h> int main(void) { setlocale(LC_ALL,""); wchar_t korUnicode = 0xac00; wchar_t lastKor = korUnicode + 100; for (int i = korUnicode; i < lastKor; i++) { wprintf(L"%C, ",i); if ((i-6)%15 == 0) wprintf(L"\n"); } wprintf(L"\nDone!\n"); return 0; }
가, 각, 갂, 갃, 간, 갅, 갆, 갇, 갈, 갉, 갊, 갋, 갌, 갍, 갎, 갏, 감, 갑, 값, 갓, 갔, 강, 갖, 갗, 갘, 같, 갚, 갛, 개, 객, 갞, 갟, 갠, 갡, 갢, 갣, 갤, 갥, 갦, 갧, 갨, 갩, 갪, 갫, 갬, 갭, 갮, 갯, 갰, 갱, 갲, 갳, 갴, 갵, 갶, 갷, 갸, 갹, 갺, 갻, 갼, 갽, 갾, 갿, 걀, 걁, 걂, 걃, 걄, 걅, 걆, 걇, 걈, 걉, 걊, 걋, 걌, 걍, 걎, 걏, 걐, 걑, 걒, 걓, 걔, 걕, 걖, 걗, 걘, 걙, 걚, 걛, 걜, 걝, 걞, 걟, 걠, 걡, 걢, 걣,
헤더를 비워두면 무한루프를 돕니다. while(1)과 같습니다. 리눅스는 Ctrl+C 윈도우는 Ctrl+Z 키보드 인터럽트로 정지할 수 있습니다.
#include <stdio.h> int main(void) { for ( ; ; ) { printf("무한루프... [Ctrl + C]로 정지\n"); } return 0; }
다음은 1분마다 두배로 늘어나는 미생물을 for루프로 계산합니다. 표현식에 하나 이상의 변수를 사용하여 상당히 복잡한 식도 for 헤더 한줄에 다 들어갑니다. i값만 바꿔주면 얼마든지 늘리고 줄일 수 있습니다. 기하급수적으로 늘어나니까 long long 의 사용도 고려합니다. 단 for의 헤더를 지나치게 복잡하게 구현하면 이득이 크지 않습니다. 프로그램의 상황에 맞춰서 적당한 복잡함이 좋습니다. for문안에 다양한 연산자를 사용할 수 있다는 부분이 포인트입니다.
#include <stdio.h> int main(void) { double seed = 1000; for (int i = 1, j = 2; i <= 10; i++, j*=2) { printf("%2d minutes : %5d\n", i, j); } return 0; }
1 minutes : 2 2 minutes : 4 3 minutes : 8 4 minutes : 16 5 minutes : 32 6 minutes : 64 7 minutes : 128 8 minutes : 256 9 minutes : 512 10 minutes : 1024
요약
프로그래머들은 for 문을 사랑할 수 밖에 없습니다. 사랑이라고 할까 for 문의 가치를 깨닫게 되면 매일 사용하게 됩니다. 딱히 C언어가 아니라도 어떤 언어에도 for 문이 있기 때문에 반복의 가치는 모두가 알고 있습니다.
for 루프는 어셈블리어의 jump를 사용하여 반복 규칙을 만든 문법입니다. 어셈블리어로 for문과 같은 고급언어의 로직을 사용하려면 상당히 머리 아픈 코드를 작성해야 합니다.(어셈블리어는 인간의 논리에 별로 맞지 않다) for 루프는 많은 일을 해주고 또 사람이 생각할 수 있게 해주기 때문에 가치가 높습니다.
조금 철학적으로 보면 대부분의 사람들은 비슷한 일을 반복하면서 인생을 보냅니다. for 루프도 비슷합니다. 하지만 인간 대신 해줍니다. 우리는 반복적인 일상에서 인생이라는 귀중한 시간을 써서 뭔가를 계산하고 행동하며 반복하지만, 저렴한 컴퓨터도 인간보다 더 빠르게 많은 계산과 일을 할 수 있습니다.
흔히 컴퓨터는 0과1밖에 모르는 바보라서 인간이 뭔가 잘 생각해서 프로그램을 짜야 한다고 말합니다. 그것도 맞는 말이긴 한데 반복하는 일에 있어서 컴퓨터는 이 우주의 모든 타의 추종을 불허합니다. 오늘날 대충 PC를 사면 1초에 수억회의 루프연산을 할 수 있습니다.
i7-10세대에서 C언어로 테스트해보면 C언어로 10초동안 약 50억회 이상의 단순 누산이 가능합니다. 실로 엄청난 속도입니다. 사람이 태어나서 평생 동안 1억번 이상의 계산을 할 수 있을까요? 초중교 수학시험을 다합쳐도 그 정도는 아닐 겁니다. 물리적으로 불가능하지요. 하지만 CPU는 10초에 50억회 이상의 계산이 가능합니다. 하드웨어의 발전으로 봤을 때 이 속도는 향후 더 빨라질 것으로 기대하고 있습니다.
이것이 바로 CPU의 잠재능력인데 이 잠재능력을 끌어내는 것은 순전히 프로그래머의 능력에 달려있습니다. 그리고 반복해서 CPU에게 일을 시킬 수 있는 능력은 for문과 while문 둘 중 하나에서 나옵니다.
물론 좀 과장된 부분이 있겠지요. 무한정 반복한다고 뭐가 잘 되진 않습니다. 의미가 있게 반복해야겠지요. 어쨋든 for문을 제대로 사용할 수 있게 되면 세상이 달라 보일 겁니다. C언어의 문법은 후에 다른 최신 언어들에서 다양한 변형으로 나오지만 핵심을 뚫는 포인트가 있습니다.