C언어 제어문 (Control Statement)

C언어 제어문에 대한 내용을 자세히 알아보겠습니다.

 

C언어가 아니라도 대부분의 프로그래밍 언어에는 제어문이 있습니다.

 

도대체 제어문은 어떤 일에 필요한 것 일까요?

 

바로 흐름을 제어하는 것 입니다. (flow of control)

 

컴퓨터 코드의 흐름은 기본적으로 순차적으로 진행됩니다.

 

위에서 아래로 내려오죠.

 

아래의 코드는 first_line 부터 시작해서 마지막 printf 까지 순차적으로 실행합니다.

int main()
{

    int first_line;
    int second_line;

    int forthline = printf("this is third line\n");

    while (forthline)
    {
        int fifth_line = 1;
        if (fifth_line)
            break;
    }
    printf("this is the last one before return\n");

    return 0;
}
this is third line
this is the last one before return

main 함수 바깥에 전처리기, 전역변수나, 함수원형 등이 있을 수 있습니다.

 

그러나 C언어 프로그램을 실행시키면 언제나 main 함수의 첫번째 줄에서 코드가 실행됩니다.

 

아래에서 위로 가는게 아니라 위에서 아래로 내려온다는 방향이 있습니다.

 

최초에 그렇게 정한 이유는 사람이 읽도록 하기 위해서겠죠.

 

책을 읽는 사람이 읽었을 때 편하도록 위에서 아래로 순서를 정한 것 입니다.

 

제어문의 역할

위에서 아래로 내려오는 것은 좋은데 그렇게 하면 1차원적인 코드밖에 만들 수 없습니다.

 

대표적인 예가 이거죠.

    printf("line 1\n");
    printf("line 2\n");
    printf("line 3\n");
    printf("line 4\n");
    printf("line 5\n");

위 코드는 숫자의 증가를 출력하기 위해 다섯줄을 사용합니다. 10개는 10줄 100개는 100줄이 됩니다.

 

반복문으로 제어를 하면 같은 다섯줄로 무한에 가까운 실행을 할 수 있습니다.

    int count = 0;
    while (count < 100)
    {
       printf("line %d\n",count);
       count++;
    }

 

위 코드는 다섯줄로 같지만 100개의 문장을 출력합니다.

 

반복문이 하는 일은 단순합니다.

 

조건식을 검사하고 반환값이 1이면 코드블록을 실행하고 0이면 블록을 빠져나갑니다.

 

즉 위에서 내려가면 끝이 아니라 위에서 아래로 내려가다가

 

다시 올라오는 것을 반복하는 것 입니다.

 

이 포스팅은 중급이니까 어떻게 올라오는지도 설명하겠습니다.

 

C언어의 라인마다 명령어 포인터가 달려 있습니다.

한줄의 코드가 실행되면 그 다음 포인터로 바뀝니다.

예를 들어 위에서 명령어 포인터가 while 의 조건식 라인이 7입니다.

} 끝나는 중괄호가 11이니까 11에서 끝나면

프로그램 포인터는 12로 가지 않고 다시 7로 점프합니다.

7의 검사결과가 1이면 다음줄인 8로가고 다시 11에 가서 7로 점프합니다.

조건식 결과가 0이 되면 12번 라인으로 보냅니다.

 

어수선해 보이지만 결국 while 조건식이 0이 될 때까지 위아래를 왔다갔다 한다는 말입니다.

 

이렇게 만드는 장점이 있죠.

 

  1. 코드를 많이 쓸 필요가 없습니다.


    - 반복하면서 코드를 재사용하고 있는 것입니다. 메모리와 저장공간도 절약합니다.

  2. 가독성이 좋습니다.


    - 코드는 짧을 수록 가독성이 좋습니다.

    어떤 코드는 너무 짧아서 이해를 못할 것 같은 코드도 있는데 그게 좋은 겁니다.

    컴퓨터공학이 발달할수록 코드는 더 점점 짧아지고 있습니다. 마지막에는 의미만 남겠죠.

  3. 고차원적인 프로그래밍이 가능합니다.


    - 위에서 아래까지 달리는 방식은 1차원적이라 프로그램의 기능에 한계가 있습니다.

    - 반복과정에서 제어의 변형을 함으로써 다채로운 기능을 만들 수 있습니다.

 

컴퓨터로 이뤄진 대부분의 세상이 제어문 없이는 돌아가지 않습니다.

 

24시간 시스템은 기본적으로 while 문이 대기하고 있는 것과 같습니다.

 

인터넷의 서버는 그냥 루프가 아닌 무한루프를 하고 있기 때문에 모두가 잠든 사이에도 돌아갑니다.

 

 

for 루프

for 루프는 루프를 제어하는 세가지 요소를 한 곳에 모아놓습니다.

 

1. 초기화

 

2. 조건식 검사

 

3. 카운터 증감

 

예제를 보며 알아보겠습니다.

int main()
{
    for (int i = 0; i < 5; i++)
    {
        printf("%d, ", i);
    }

    return 0;
}
0, 1, 2, 3, 4,

 

for (초기화; 조건식 검사; 카운터 증감)

{

    코드블록

}

 

의 형태로 되어있습니다. 루프를 도는 것은 while 과 다를바가 없습니다.

 

기능상 차이가 없는데 왜 for 문을 쓸까요?

 

for 문이 while 문보다 인간에게 직관적입니다.

 

특히 정해진 횟수만큼 반복하는 과정을 잘 보여주고 있습니다.

 

while 이 false 조건에 언제 도달할지 정확히 알기 어려운 측면이 있는데

 

for 문은 식을 쓰는 순간 얼마나 반복할지 정해집니다.

 

프로그래머는 본능적으로 불확실성을 싫어하기 때문에 for 문을 좋아합니다.

 

 

for 문의 다양한 사용법

 

for 문의 사용방법을 좀 더 예시해보겠습니다.

 

for 제어문으로 CPU에게 명령한다고 생각하면서 실행해보세요.

 

 

1. 0부터 9까지 올라가면서 열번을 반복해라.

    for (int i = 0; i < 10; i++)
    {
        printf("%d, ", i);
    }
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

2. 10부터 1까지 내려오면서 열번을 반복해라.

    for (int i = 10; i > 0; i--)
    {
        printf("%d, ", i);
    }
10, 9, 8, 7, 6, 5, 4, 3, 2, 1,

1번과 2번의 루프한 횟수는 열번으로 같습니다. 한편 수의 방향이 다르고 출력한 수의 집합이 다릅니다.

 

3. 10부터 2의 간격으로 내려오라.

    for (int i = 10; i > 0; i -= 2)
    {
        printf("%d, ", i);
    }
10, 8, 6, 4, 2,

카운터의 간격을 조정하는 것도 가능합니다.

 

4. 2층부터 9층까지 올라가면서 한층에 1부터 9까지 요소를 넣어라. 

    for (int i = 2; i < 10; i += 1)
    {
        for (int j = 1; j < 10; j += 1)
        {
            printf("[%d],[%d] | ", i, j);
        }
        printf("\n");
    }
[2],[1] | [2],[2] | [2],[3] | [2],[4] | [2],[5] | [2],[6] | [2],[7] | [2],[8] | [2],[9] |
[3],[1] | [3],[2] | [3],[3] | [3],[4] | [3],[5] | [3],[6] | [3],[7] | [3],[8] | [3],[9] |
[4],[1] | [4],[2] | [4],[3] | [4],[4] | [4],[5] | [4],[6] | [4],[7] | [4],[8] | [4],[9] |
[5],[1] | [5],[2] | [5],[3] | [5],[4] | [5],[5] | [5],[6] | [5],[7] | [5],[8] | [5],[9] |
[6],[1] | [6],[2] | [6],[3] | [6],[4] | [6],[5] | [6],[6] | [6],[7] | [6],[8] | [6],[9] |
[7],[1] | [7],[2] | [7],[3] | [7],[4] | [7],[5] | [7],[6] | [7],[7] | [7],[8] | [7],[9] |
[8],[1] | [8],[2] | [8],[3] | [8],[4] | [8],[5] | [8],[6] | [8],[7] | [8],[8] | [8],[9] |
[9],[1] | [9],[2] | [9],[3] | [9],[4] | [9],[5] | [9],[6] | [9],[7] | [9],[8] | [9],[9] |

중첩시킨(Nested) for문으로 결과창을 보면 2차원 좌표나 행렬처럼 보입니다.

 

구구단이 보입니다.

 

구구단은 2차원 좌표와 같죠.

 

C언어 교재나 알고리즘 사이트에서 흔히 나오는 내용인데요.

 

여기다 i * j 만 더하면 구구단이 출력되는 것 입니다.

 

구구단의 단을 아파트의 한 층으로 생각합니다. 한 층에 9개 호수가 있습니다.

 

사는 사람은 층과 호수의 곱입니다. 5층 7호는 5 * 7 = 35 입니다.

 

 

5. 1층부터 7층까지 올라가면서 한층에 층의 수만큼 요소를 넣어라.

    for (int i = 1; i <= 7; i += 1)
    {
        for (int j = 1; j <= i; j += 1)
        {
            printf("[%d],[%d] | ", i, j);
        }
        printf("\n");
    }
[1],[1] |
[2],[1] | [2],[2] |
[3],[1] | [3],[2] | [3],[3] |
[4],[1] | [4],[2] | [4],[3] | [4],[4] |
[5],[1] | [5],[2] | [5],[3] | [5],[4] | [5],[5] |
[6],[1] | [6],[2] | [6],[3] | [6],[4] | [6],[5] | [6],[6] |
[7],[1] | [7],[2] | [7],[3] | [7],[4] | [7],[5] | [7],[6] | [7],[7] |

1층부터 7층에 올라가는데 우리눈에는 거꾸로 보입니다.

 

콘솔의 텍스트 출력은 위에서 아래로 내려오기 때문입니다. 소스코드를 위에서 아래로 읽는 원리와 같습니다.

 

사실 컴퓨터에게는 위와 아래는 없습니다. 먼저 들어왔냐 늦게 들어왔냐 순서가 있을 뿐이죠.

 

늦게 들어온 것들을 높이라고 생각하는 우리의 사고를 반영한 문장일 뿐입니다.

 

위의 코드는 층의 높이만큼 올라가는 직각삼각형이 만들어졌죠?

 

이 직각삼각형의 공간을 비워두고 그대로 밀어보겠습니다.

 

    for (int i = 1; i <= 7; i += 1)
    {
        for (int j = 1; j <= i; j += 1)
        {
            printf("            ");
        }        
        for (int j = 1; j <= i; j += 1)
        {
            printf("[%d],[%d] | ", i, j);
        }
        printf("\n");
    }
            [1],[1] |
                        [2],[1] | [2],[2] |
                                    [3],[1] | [3],[2] | [3],[3] |
                                                [4],[1] | [4],[2] | [4],[3] | [4],[4] |
                                                            [5],[1] | [5],[2] | [5],[3] | [5],[4] | [5],[5] |
                                                                        [6],[1] | [6],[2] | [6],[3] | [6],[4] | [6],[5] | [6],[6] |
                                                                                    [7],[1] | [7],[2] | [7],[3] | [7],[4] | [7],[5] | [7],[6] | [7],[7] |

 

 

뒤쪽부분이 지저분하니 아래로 뒤집어 보겠습니다.

 

    for (int i = 1; i <= 7; i += 1)
    {
        for (int j = 1; j <= i; j += 1)
        {
            printf("            ");
        }        
        for (int j = 7; j >= i; j -= 1)
        {
            printf("[%d],[%d] | ", i, j);
        }
        printf("\n");
    }
            [1],[7] | [1],[6] | [1],[5] | [1],[4] | [1],[3] | [1],[2] | [1],[1] |
                        [2],[7] | [2],[6] | [2],[5] | [2],[4] | [2],[3] | [2],[2] |
                                    [3],[7] | [3],[6] | [3],[5] | [3],[4] | [3],[3] |
                                                [4],[7] | [4],[6] | [4],[5] | [4],[4] |
                                                            [5],[7] | [5],[6] | [5],[5] |
                                                                        [6],[7] | [6],[6] |
                                                                                    [7],[7] |

 

for 루프로 도형그리기

*이것을 활용하여 2차형 도형을 그릴 수 있습니다.

 

for 루프가 실행되는 위치가 그래픽으로 표시되는데,

 

단지 그래픽 이미지라고만 생각하는 것에 그치기보다는

 

그래픽이 출력되는 위치의 제어라고 보면 됩니다.

 

아파트의 층과 호수처럼 각기 높이와 순서가 있습니다.

 

for 문을 단순 반복자로 사용하는 것도 유용하지만

 

제어의 측면에서 바라보면 다양한 사고로 알고리즘을 구현할 수 있습니다.

 

많은 유용한 알고리즘이 반복문으로 가능하게 됩니다.

 

재귀(recursive) 용법에서 배우는 재귀도 결국 루프 (돈다) 개념과 관련된 것 입니다.

 

컴퓨터가 아직 인간만큼의 사고력은 없지만

 

이 반복하는 분야에 있어서는 인간보다 뛰어납니다.

 

for 루프로 도형그리기

int main()
{
    printf("\n\n");

    int level = 7;

    for (int i = 1; i <= level; i += 1)
    {
        printf("  ");
        for (int j = 1; j <= i; j += 1)
        {
            printf("_");
        }        
        for (int j = level; j >= i; j -= 1)
        {
            printf("|", i);
        }
        for (int j = level; j >= i; j -= 1)
        {
            printf("|", i);
        }
        for (int j = 1; j <= i; j += 1)
        {
            printf("_");
        }
        printf("\n");
    }    

    printf("");

    for (int i = 1; i <= level; i += 1)
    {
        printf("  ");
        for (int j = level; j >= i; j -= 1)
        {
            printf("|", i);
        }
        for (int j = 1; j <= i; j += 1)
        {
            printf("_");
        }      
        for (int j = 1; j <= i; j += 1)
        {
            printf("_");
        }
        for (int j = level; j >= i; j -= 1)
        {
            printf("|", i);
        }
        printf("\n");
    }
    return 0;
}

 

for 무한루프

for 문도 무한루프가 가능합니다. while(1) 과 같습니다.

 

무한루프는 while 의 가독성이 좋습니다. 굳이 for 를 사용할 거 까지는 없지만,

 

어쨋든 C는 사용자가 마음대로 할 수 있는 권한을 주는 겁니다.

 

프로그래머가 자신의 권한이 침해받았다고 느낄 수 없습니다.

 

    for (; ; )
    {
        Sleep(200);
        printf("infinite for loop\n");
    }

for 루프 break

for 루프도 while 루프처럼 break 문으로 빠져나올 수 있습니다.

 

break 문을 for 문에 직접 사용하면 for 문의 장점인 직관성이 떨어지는 부분이 있습니다.

 

for 문은 for ( ) 이 한문장으로 파악이 끝나는 코드가 좋기 때문에 break 가 꼭 필요한지 한번 더 생각하도록 합니다.

 

while 과 for 의 차이점에 대하여 지난 포스트 while 루프에서도 이야기 했습니다만,

 

대화로 바꾸면

 

- 사장: 오늘 주문이 많습니다. 다들 힘내서 한 사람당 업무 끝날때까지 100개 포장하세요.

- 직원: 주문이 900개인데요. 직원 7명이 100개씩하면 700개라 200개가 남습니다.

- 사장: 200개는 내가 하겠습니다.

- 직원: 추가 주문이 들어오면요?

- 사장: 그러면 내가 끝날때까지 할 거에요.

오늘 각각의 직원은 100개, 사장은 200개 + 추가주문을 끝내야 한다.

for 루프는 직원의 100개와 사장 200개로 정해진 숫자다.

추가주문은 아직 확정되지 않았다. 이런 경우 for 문으로 일을 할 수 없다.

while 에 무한루프를 걸어놓고 추가주문이 extraOrder = 0 될때 break 를 하면 일이 끝난다.

 

* 예제 for 문 break;

#include <stdio.h>
#include <windows.h>

int main()
{

    long forTime = 100;

    for (; ; )
    {
        Sleep(forTime);
        printf("infinite for loop\n");
        
        forTime += 100;
        if (forTime > 1000)
            break;
    }

    return 0;
}
infinite for loop
infinite for loop
infinite for loop
infinite for loop
infinite for loop
infinite for loop
infinite for loop
infinite for loop
infinite for loop
infinite for loop

 

멀티 초기화

for 문에서 콤마연산자 , 를 사용하여 멀티 초기화를 할 수 있습니다.

 

이것을 사용하면 유연성이 더 확장되는 부분이 있으니 적절히 사용합니다.

 

    for (int a = 1, b = 5; a <= 5; a++, b--)
    {
        printf("a: %d, b: %d\n", a, b);
    }
a: 1, b: 5
a: 2, b: 4
a: 3, b: 3
a: 4, b: 2
a: 5, b: 1

 

제논의 역설 - for 루프

제논의 역설은 아킬레우스와 거북이가 경주를 할 때 거북이를 100m 앞에 세워두고 아킬레우스가 100m 갈 때 거북이가 10m, 10m에 1m 이런 식으로 10분의 1씩 이동하면 발이빠른 아킬레우스가 영원히 거북이를 따라잡을 수 없다는 내용으로 수학역사에서 아직 무한의 개념이 아직 없을 때 만들어진 내용입니다.

 

for 문으로 한번 구현해 보겠습니다.

 

*문제:

 

과녁까지의 거리가 D 입니다.

 

화살을 쏘면 과녁까지 절반인 D/2 가 날라갑니다. 다시 D/4, D8로 줄어들면서 날아갑니다. 이렇게 거리의 반반씩 날아가면 화살은 과녁에 도달할 수 없습니다.

 

이말을 믿고 반복해보겠습니다.

 

#include <stdio.h>

#define Line printf("------------------------\n")

int main()
{
    Line;

    double time;
    double power_of_two;
    int time_count = 1;

    int limit = 20;

    for (time = 0, power_of_two = 1, time_count = 1;
        time_count <= limit;
        power_of_two *= 2.0, time_count++)
    {
        time += 1.0/power_of_two;
        printf("시간 = %10.20f (횟수 %d)\n", time, time_count);
        //printf("power of two : %e \n", power_of_two);
    }

    return 0;
}
------------------------
시간 = 1.00000000000000000000 (횟수 1)
시간 = 1.50000000000000000000 (횟수 2)
시간 = 1.75000000000000000000 (횟수 3)
시간 = 1.87500000000000000000 (횟수 4)
시간 = 1.93750000000000000000 (횟수 5)
시간 = 1.96875000000000000000 (횟수 6)
시간 = 1.98437500000000000000 (횟수 7)
시간 = 1.99218750000000000000 (횟수 8)
시간 = 1.99609375000000000000 (횟수 9)
시간 = 1.99804687500000000000 (횟수 10)
시간 = 1.99902343750000000000 (횟수 11)
시간 = 1.99951171875000000000 (횟수 12)
시간 = 1.99975585937500000000 (횟수 13)
시간 = 1.99987792968750000000 (횟수 14)
시간 = 1.99993896484375000000 (횟수 15)
시간 = 1.99996948242187500000 (횟수 16)
시간 = 1.99998474121093750000 (횟수 17)
시간 = 1.99999237060546875000 (횟수 18)
시간 = 1.99999618530273437500 (횟수 19)
시간 = 1.99999809265136718750 (횟수 20)

 

1초에서 시작한 시간을 쪼개서 이동시키면 영원히 2초로 가지 못합니다. 가까워질 뿐이죠.

 

그러나 실상 C언어의 소수점 표현은 한계가 있기 때문에 50회 정도에 올림 해버립니다.

 

이것은 무한등비급수의 계산으로

time = 1 + 1/2 + 1/4 + 1/8 + ... [공비가 1/2]

time/2 = 1/2 + 1/4 + 1/8 + 1/16 + ...

time - time/2 = 1 + 1/2 - 1/2 + 1/4 - 1/4 + 1/8 - 1/8 + ...

time/2 = 1

time = 2

1초에서 2초로 못가는게 아니라

결국 2에 수렴한다는 것을 알 수 있습니다.

즉 2초가 되면 화살은 과녁에 박힙니다.

 

 

제논의 역설 - 나무위키 (namu.wiki)

 

제논의 역설 - 나무위키

올림피우스가 달리기를 할 때, 결승점에 도달하기 전에 1/2 지점에 도달해야 한다. 이후 중간점과 결승점의 1/2이 되는 지점에 도달한다. 이후 또 다시 중간점과 결승점의 중간에 해당하는 지점과

namu.wiki

C언어 배열과 for 루프

배열(array)과 for 루프는 궁합이 잘 맞아서 함께 사용할 일이 많습니다.

 

베열은 자료타입이 나열되는 자료구조입니다.

 

메모리상에 붙어있고 인덱스가 있기 때문에 속도가 잘 나옵니다.

 

배열에 대한 포스팅은 아래 링크에 자세히 설명했으니 참고 하시길 바랍니다.

 

여기서는 바로 배열과 for 루프를 알아보겠습니다.

 

C언어 자료구조 - 배열의 구조 (tistory.com)

 

C언어 자료구조 - 배열의 구조

C의 대표적 자료 구조인 배열에 대하여 알아본다. 배열은 대부분의 언어에 기본으로 장착되어 있는 자료형태이다. 컴퓨터는 모든 데이터를 0과1의 이진 데이터의 형태로 저장한다. 이진법과 C의

digiconfactory.tistory.com

C언어 5 - 1 | 배열 (tistory.com)

 

C언어 5 - 1 | 배열

배열은 같은 타입의 데이터를 묶어서 관리할 수 있게 해준다. 배열의 자료구조에 대한 상세한 사항은 아래 포스트를 참고한다. 자료구조 1 배열의 구조 C의 대표적 자료 구조인 배열에 대하여 알

digiconfactory.tistory.com

배열 for 루프 예제

int main()
{

    int myEvenSet[5];

    for (int i = 1; i <= 5; i++)
    {
        myEvenSet[i-1] = i*2;
    }

    for (int i = 0; i < 5; i++)
    {
        printf("array[%d] : %2d\n",i, myEvenSet[i]);
    }

    return 0;
}
array[0] :  2
array[1] :  4
array[2] :  6
array[3] :  8
array[4] : 10

배열의 인덱스는 0부터 시작합니다. 1이 아니라 0이기 때문에 혼동스러울 때가 있습니다만,

 

인덱스를 맞추는 일은 중요한 일입니다. 약간 퍼즐같이 느껴지죠.

 

파이썬 소프트웨어에 관한 책을 쓰는 알 스웨이가트는 대부분의 프로그래밍은 어려운 수학문제를 푸는게 아니라 퍼즐같은 일이라고 합니다. 보이지 않는 숫자의 블록을 옮기는 일들이 많습니다.

 

그 숫자의 블록들이 메모리에서 이동하고 있는데 C언어의 배열은 기본이 되는 자료구조입니다.

 

for 문의 인덱스는 배열의 인덱스와 함께 사용하기에 좋은데 for 루프의 카운터를 증감하면서 배열의 모든 블록에 순차적으로 접근할 수 있기 때문입니다.

 

    int size = sizeof(myEvenSet);
    printf("array size : %d\n", size);
    printf("array length : %d\n", size / sizeof(int));
array size : 20
array length : 5

위의 코드에 추가하면 배열의 사이즈가 20 길이는 5가 나옵니다. 사이즈는 바이트의 크기입니다. 정수형이 4바이트 이므로 4 * 5는 20바이트입니다.

 

sizeof 연산자로 측정한 배열의 크기는 바이트단위를 말합니다.

 

자료형 마다 다른데 char 형은 1바이트니까 사이즈가 20이면 문자가 20개 있는 것입니다.

 

이것도 다른 언어에서는 잘 없는 개념인데 C언어에서는 바이트 단위로 연산을 할 수 있습니다.

 

C언어에서는 메모리를 다루기 때문에 바이트 단위로 조작하는 기술이 필요합니다.

 

 

Control flow - Wikipedia

공유하기

facebook twitter kakaoTalk kakaostory naver band