Unit 22. 불 자료형 사용하기

불(boolean)자료형은 논리 자료형이라고 하며 참과 거짓을 나타낸다. 

C에서는 0을 거짓으로, 0이 아닌 숫자를 참으로 사용하지만 stdbool.h 헤더를 사용하여 true를 참으로, false를 거짓으로 나타낼 수 있다.

파이썬에서는 불 자료형을 True와 False로 바로 사용할 수 있었지만 C에서는 별도의 헤더를 지정해 줘야 한다.

 

22.1 stdbool.h 헤더 사용하기

#include <stdio.h>
#include <stdbool.h>    
 
int main()
{
    bool b1 = true;
 
    if (b1 == true)        
        printf("참\n");   
    else
        printf("거짓\n");
 
    return 0;
}

stdbool.h 헤더는 bool, true, false가 정의되어 있다. bool 자료형으로 true 또는 false값을 할당할 수 있다.

 

22.2 불 자료형 크기 알아보기

int 와 bool 자료형의 크기는 다음과 같다.

#include <stdio.h>
#include <stdbool.h>    
 
int main()
{
    printf("int의 크기: %d\n", sizeof(int));      
    printf("bool의 크기: %d\n", sizeof(bool));    
 
    return 0;
}

자료형 int의 크기는 4바이트이고, bool의 크기는 1바이트임을 확인할 수 있다.

 

22.3 불 자료형과 논리연산자 사용하기

#include <stdio.h>
#include <stdbool.h>    
 
int main()
{
    printf("%d\n", true && true);     
    printf("%d\n", true && false);     
    printf("%d\n", false && false);   
 
    printf("%d\n", true || true);     
    printf("%d\n", true || false);     
    printf("%d\n", false || false);    

    return 0;
}

bool 자료형의 true, false도 논리연산자로 사용할 수 있다. stdbool.h헤더파일에는 불 자료형의 서식지정자가 없기 때문에 결과를 출력할 때는 정수로 출력해야 한다.

 

22.4 true, false를 문자열로 출력하기

#include <stdio.h>
#include <stdbool.h>    

int main()
{
    bool b1 = true;
    bool b2 = false;
 
    printf(b1 ? "true" : "false");   
    printf("\n");
    printf(b2 ? "true" : "false");   
    printf("\n");
 
    printf("%s\n", b1 ? "true" : "false");   
    printf("%s\n", b2 ? "true" : "false");    

    return 0;
}

true, false를 출력하려면 다음과 같이 삼항연산자를 이용할 수 있다. 삼항연산자는 값의 참/거짓도 판단할 수 있어서 불 값이 들어있는 변수를 그대로 사용하면 된다. 서식지정자 없이 그대로 출력해도 되고, %s 서식지정자를 이용해도 된다.

 

22.5 if 조건문에서 불 자료형 사용하기

#include <stdio.h>
#include <stdbool.h>    // bool, true, false가 정의된 헤더 파일
 
int main()
{
    if (true)           
        printf("참\n");    
    else
        printf("거짓\n");

    if (false)         
        printf("참\n");
    else
        printf("거짓\n");  

    return 0;
}

조건식에 true를 넣으면 if의 코드가 실행되고, false를 넣으면 else의 코드를 실행한다.

위 코드에서는 값이 고정되어 있기 때문에 결과는 항상 똑같다.

 

22.6 퀴즈

정답은 d이다.

정답은 1이다.

 

22.7 연습문제 : 불 자료형 사용하기

정답은 #include <stdbool.h>, b1, b2 이다.

 

22.8 심사문제 : 불 자료형 사용하기

정답은 다음 코드와 같다.

bool b1 = false;
bool b2 = true;

 

Unit 23. 비트 연산자 사용하기

비트 연산자는 바이트 단위보다 더 작은 비트 단위로 연산하는 연산자이다. 비트는 2진수를 저장하는 단위로, 0과 1을 나타낸다.

비트 연산자의 종류는 다음과 같다.

비트 연산자는 비트로 옵션을 설정할 때 주로 사용하며 저장공간을 아낄 수 있다는 장점이 있다. 이런 방식을 플래그(flag)라고 부른다. 비트 연산은 모든 연산을 2진수로 처리한다.

 

23.1 비트 AND, OR, XOR 연산자 사용하기

비트 AND 연산은 &, OR연산은 |, XOR연산은 ^연산자를 사용한다.

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 1;    // 0000 0001
    unsigned char num2 = 3;    // 0000 0011
 
    printf("%d\n", num1 & num2);    // 0000 0001
    printf("%d\n", num1 | num2);    // 0000 0011
    printf("%d\n", num1 ^ num2);    // 0000 0010
    return 0;
}

10진수를 2진수로 사용할 때는 계산기의 프로그래머 모드를 사용하면 확인할 수 있다.

10진수 3을 2진수로 표현하면 0011 인것을 확인할 수 있다.

다음은 &, |, ^ 연산자로 각 비트를 연산했을 때의 결과(진리표) 이다.

비트 연산은 두 값을 비트 단위로 나열한 뒤 각 자리를 비트 연산자로 연산한다. 각 자리의 연산은 독립적이며 다른 자리에 영향을 주지 않는다. 그리고 비트 단위로 연산한 각 자리수를 모으면 최종 결과가 된다.

&연산자는 두 비트가 모두 1일때 1이다. 하나라도 0이면 0이 된다. 따라서 0000 0001 (10진수 1)과 0000 0011 (10진수 3)을 &연산하면 0000 0001 (10진수 1)이 된다.

|연산자는 두 비트중 하나라도 1이면 1이다. 두 비트가 모두 0이면 0이다. 따라서 0000 0001 (10진수 1)과 0000 0011 (10진수 3)을 | 연산하면 0000 0011 (10진수 3)이 된다.

^연산자는 두 비트가 다를때 1이다. 두 비트가 모두 1과1 또는 0과 0이면 0이다. 따라서 0000 0001 (10진수 1)과 0000 0011 (10진수 3)을 ^ 연산하면 0000 0010(10진수 2)이 된다.

 

23.2 비트 NOT 연산자 사용하기

비트 NOT 연산자는 ~이다.

#include <stdio.h>

int main()
{
    unsigned char num1 = 162;    // 162: 1010 0010
    unsigned char num2;

    num2 = ~num1;

    printf("%u\n", num2);  

    return 0;
}

~ 연산자는 비트 NOT연산자로 0은 1로, 1은 0으로 비트를 뒤집는다. 또는 비트 반전 이라고도 한다.

1010 1010의비트를 뒤집으면 0101 0101이 되고 이는 10진수로 93이다.

 

23.3 시프트 연산자 사용하기

C에서는 비트의 논리 연산 뿐 아니라 >>, << 연산자로 각 자리를 이동시킬 수도 있다.

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 3;     //  3: 0000 0011
    unsigned char num2 = 24;    // 24: 0001 1000
 
    printf("%u\n", num1 << 3);  // 24: 0001 1000: num1의 비트 값을 왼쪽으로 3번 이동
    printf("%u\n", num2 >> 2);  //  6: 0000 0110: num2의 비트 값을 오른쪽으로 2번 이동
 
    return 0;
}

시프트 연산은 변수 << 이동할 비트수 또는 변수 >> 이동할 비트수 형식으로 사용한다. 비트를 이동시키고 남는 공간은 0으로 채운다.

0000 0011 (3)의 경우 num1 << 3 으로 왼쪽으로 3번 이동하여 0001 1000 (24) 가 되었다.

num2 >> 2는 0001 1000 (24)가 오른쪽으로 2번 이동하여 0000 0110 (6)이 되었다. 

한번씩 이동할 때 마다 2의 거듭제곱으로 곱하거나 나누면 된다. 3 << 3 은 3 x 2^3 = 24 이고, 24 >> 2 는 24 / 2^2 = 6이다.

 

23.4 비트 연산후 할당하기

비트연산자도 다음과 같이 할당연산자와 함께 사용할 수 있다.

#include <stdio.h> 

int main()
{
    unsigned char num1 = 4;    // 4: 0000 0100
    unsigned char num2 = 4;    // 4: 0000 0100
    unsigned char num3 = 4;    // 4: 0000 0100
    unsigned char num4 = 4;    // 4: 0000 0100
    unsigned char num5 = 4;    // 4: 0000 0100
 
    num1 &= 5;     // 5(0000 0101) AND 연산 후 할당
    num2 |= 2;     // 2(0000 0010) OR 연산 후 할당
    num3 ^= 3;     // 3(0000 0011) XOR 연산 후 할당
    num4 <<= 2;    // 비트를 왼쪽으로 2번 이동한 후 할당
    num5 >>= 2;    // 비트를 오른쪽으로 2번 이동한 후 할당
 
    printf("%u\n", num1);    //  4: 0000 0100
    printf("%u\n", num2);    //  6: 0000 0110
    printf("%u\n", num3);    //  7: 0000 0111
    printf("%u\n", num4);    // 16: 0001 0000
    printf("%u\n", num5);    //  1: 0000 0001
 
    return 0;
}

연산자에 해당하는 연산을 수행한 후 다시 자기 자신 변수에게 할당하였다. 

위 연산 과정을 풀어보면 다음과 같다.

num1 = num1 & 5;     // 5(0000 0101) AND 연산 후 할당
num2 = num2 | 2;     // 2(0000 0010) OR 연산 후 할당
num3 = num3 ^ 3;     // 3(0000 0011) XOR 연산 후 할당
num4 = num4 << 2;    // 비트를 왼쪽으로 2번 이동한 후 할당
num5 = num5 >> 2;    // 비트를 오른쪽으로 2번 이동한 후 할당

할당 연산자는 반복되는 변수 부분을 생략하기 위해 사용한다.

 

23.5 퀴즈

정답은 a 이다.

정답은 e 이다.

정답은 c이다.

정답은 d 이다.

정답은 b이다.

 

23.6 연습문제 : 비트 논리 연산자 사용하기

정답은 |, ^, &, ~ 이다. 파이썬 터미널과 10진수를 2진수로 바꾸어 계산했다.

 

23.7 연습문제 : 시프트 연산자 사용하기

오른쪽으로 4번 시프트 하면 2가 된다. 4가 되려면 2에서 왼쪽으로 1번 시프트 해야 하므로 정답은 1이다.

 

23.8 심사문제 : 비트 논리 연산자 사용하기

정답은 다음 코드와 같다.

#include <stdio.h>

int main()
{
    unsigned int num1, num2;
    scanf("%u %u", &num1, &num2);
    
    printf("%u\n", num1 ^ num2);
    printf("%u\n", num1 | num2);
    printf("%u\n", num1 & num2);
    printf("%u\n", ~num1);
    
    return 0;
}

 

23.9 심사문제 : 시프트 연산자 사용하기

정답은 다음 코드와 같다.

#include <stdio.h> 

int main()
{
    unsigned long long num1;
    scanf("%llu", &num1);
    
    num1 = num1 << 20 >> 4;
    printf("%llu\n", num1);
    
    return 0;
}

 

Unit 24. 비트 연산자 응용하기

24.1 시프트 연산과 2의 거듭제곱 알아보기

시프트 연산자는 2의 거듭제곱을 빠르게 구할 때 유용하다.

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 1;    
 
    printf("%u\n", num1 << 1);  
    printf("%u\n", num1 << 2);    
    printf("%u\n", num1 << 3);    
    printf("%u\n", num1 << 4);    
    printf("%u\n", num1 << 5);   
    printf("%u\n", num1 << 6);    
    printf("%u\n", num1 << 7);    

    return 0;
}

0000 0001 을 왼쪽으로 한번씩 이동하면 2의 거듭제곱으로 수가 늘어난다. 비트의 각 자리수는 2의 거듭제곱을 뜻하므로 비트의 이동 횟수는 지수라고 할 수 있다.

 

24.2 시프트 연산으로 자릿수를 넘어서는 경우 알아보기

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 240;    // 240: 1111 0000
    unsigned char num2 = 15;     //  15: 0000 1111
 
    unsigned char num3, num4;
 
    num3 = num1 << 2;    
    num4 = num2 >> 2;   
 
    printf("%u\n", num3);   
    printf("%u\n", num4);   
 
    return 0;
}

240을 왼쪽으로 두번 이동하면 앞의 11이 없어지고 1100 0000 이 되서 192가 된다. 15를 오른쪽으로 두번 이동하면 뒤에 11이 없어져서 0000 0011 이 되어 3이 된다.

비트연산에서 첫째자리나 마지막 자리를 넘어서는 비트는 그대로 사라진다.

비트에서 첫 번째 비트를 최상위 비트(Most Significant Bit, MSB), 마지막 비트를 최하위 비트(Least Siginificant Bit, LSB)라고 부른다.

 

24.3 부호있는 자료형의 비트 연산 알아보기

부호 있는 자료형을 비트 연산 할 때는 부호 비트를 주의해야 한다.

부호 없는 자료형과 부호 있는 자료형에 비트연산 하면 다음과 같다.

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 131;    //  131: 1000 0011
    char num2 = -125;            // -125: 1000 0011
 
    unsigned char num3;
    char num4;
 
    num3 = num1 >> 5;    
    num4 = num2 >> 5;    
 
    printf("%u\n", num3);    
    printf("%d\n", num4);    
                            
    return 0;
}

부호있는 자료형의 첫번째 비트는 부호비트라고 하는데, 이 비트가 1이면 음수, 0이면 양수다.

부호 있는 자료형에 저장된 -125는 1000 0011로 첫번째 비트가 1이라 음수이며 10진수로는 -125 이다. 이 비트들을 오른쪽으로 5번 이동시키면 모자라는 공간은 모두 부호비트의 값으로 채워지기 때문에 1111 1100 (-4) 이 된다. 부호없는 자료형은 비트를 오른쪽으로 이동하면 모자란 공간은 모두 0으로 채워진다.

부호 있는 자료형에서 부호비트가 0인 양수에 >> 연산을 하면 모자라는 공간은 부호비트인 0으로 채운다.

    char num1 = 67;    // 67: 0100 0011
    char num2;
 
    num2 = num1 >> 5;

따라서 위 코드를 실행하면 num2 에는 모자라는 비트는 부호비트인 0으로 채워져서 0000 0010 (2) 이 된다.

부호 있는 자료형에서 << 연산자 (왼쪽으로 시프트)를 사용하면 다음과 같다.

#include <stdio.h>
 
int main()
{
    char num1 = 113;    //  113: 0111 0001
    char num2 = -15;    //  -15: 1111 0001
    char num3, num4, num5, num6;
 
    num3 = num1 << 2;   
    num4 = num2 << 2;    
    num5 = num1 << 4;    
    num6 = num2 << 4;   
 
    printf("%d\n", num3);    
    printf("%d\n", num4);   
 
    printf("%d\n", num5);    
    printf("%d\n", num6);   
 
    return 0;
}

부호있는 자료형에서 양수를 왼쪽으로 두번 이동시키면 부호비트 0을 1이 덮어써서 음수가 될 수 도 있다. 반대로 음수의 비트를 왼쪽으로 이동시켜도 부호비트 1을 0이 덮어쓰게 되면 양수가 될 수도 있다. 부호있는 자료형에 시프트 연산을 할 때는 부호비트에 위치한 숫자에 따라 음수, 양수가 결정되므로 의도치 않은 결과가 나올 수 있으니 주의해야 한다.

 

24.4 비트 연산자로 플래그 처리하기

플래그는 깃발에서 유래한 용어이다. 깃발을 위로 올리면 on, 아래로 내리면 off가 되는 것 처럼 비트가 1이면 on, 비트가 0이면 off이다.

다음과 같이 8비트 크기의 자료형은 비트가 8개 들어가므로 8가지 상태를 정할 수 있다. 다음은 두번째 비트와 8번째 비트가 켜진 상태이다.

0100 0001

int와 같은 4바이트 크기의 자료형은 32비트 이기때문에 32개의 상태를 저장할 수 있다.

플래그는 적은 공간에 정보를 저장해야 하며 빠른 속도가 필요할 때 사용한다. CPU는 내부 저장공간이 매우 작기 때문에 각종 상태를 비트로 저장한다.

비트는 다음과 같이 |=로 킨다.

#include <stdio.h>
 
int main()
{
    unsigned char flag = 0;
 
    flag |= 1;    // 0000 0001 마스크와 비트 OR로 여덟 번째 비트를 켬
    flag |= 2;    // 0000 0010 마스크와 비트 OR로 일곱 번째 비트를 켬
    flag |= 4;    // 0000 0100 마스크와 비트 OR로 여섯 번째 비트를 켬
 
    printf("%u\n", flag);    // 7: 0000 0111
 
    if (flag & 1)    
        printf("0000 0001은 켜져 있음\n");
    else
        printf("0000 0001은 꺼져 있음\n");
 
    if (flag & 2)    
        printf("0000 0010은 켜져 있음\n");
    else
        printf("0000 0010은 꺼져 있음\n");
 
    if (flag & 4)    
        printf("0000 0100은 켜져 있음\n");
    else
        printf("0000 0100은 꺼져 있음\n");
 
    return 0;
}

플래그로 사용할 변수에 |= 연산자와 숫자로 특정 비트를 킨다. 비트를 조작하거나 검사할때 사용하는 숫자를 마스크(mask)라고 부른다. 여기서는 1,2,4가 마스크이다.

플래그 비트를 켜는것은 OR연산의 특징을 이용하여 해당 비트가 꺼져있으면(0이면) 비트를 키고(1로 만들고) 켜져있으면 유지한다. 특정 비트가 꺼져있는지 확인하려면 &연산자를 사용하면 된다. 플래그에 저장된 값 0000 0111과 1(0000 0001)을 & 연산하면 0000 0001이 된다. 연산 결과가 마스크 값이 나오면 비트가 켜져있고, if 조건을 만족시키며 연산 결과가 0이 나오면 비트가 꺼져있음을 의미하고 if 조건을 만족시키지 못한다.

다음은 플래그의 비트를 끄는 방법이다.

#include <stdio.h>
 
int main()
{
    unsigned char flag = 7;    // 7: 0000 0111
 
    flag &= ~2;    // 1111 1101 마스크 값 2의 비트를 뒤집은 뒤 비트 AND로 일곱 번째 비트를 끔
 
    printf("%u\n", flag);    // 5: 0000 0101
 
    if (flag & 1)    
        printf("0000 0001은 켜져 있음\n");
    else
        printf("0000 0001은 꺼져 있음\n");
 
    if (flag & 2)   
        printf("0000 0010은 켜져 있음\n");
    else
        printf("0000 0010은 꺼져 있음\n");
 
    if (flag & 4)    
        printf("0000 0100은 켜져 있음\n");
    else
        printf("0000 0100은 꺼져 있음\n");
 
    return 0;
}

마스크 값을 ~로 뒤집은 뒤 &= 연산자를 사용하여 특정 비트를 끈다. 2의 비트를 뒤집으면 1111 1101 이 되어 AND연산 하면 2번째 비트만 0이된다. AND연산을 하면 원하는 비트가 켜져있든, 꺼져있든 항상 끄게 된다. 

다음은 비트가 켜져있으면 끄고, 꺼져있으면 키는 방법이다. 다른 말로는 토글(toggle)이라고도 한다. ^= 연산자를 이용한다.

#include <stdio.h>
 
int main()
{
    unsigned char flag = 7;    // 7: 0000 0111
 
    flag ^= 2;    // 0000 0010 마스크와 비트 XOR로 일곱 번째 비트를 토글
    flag ^= 8;    // 0000 1000 마스크와 비트 XOR로 다섯 번째 비트를 토글
 
    printf("%u\n", flag);    // 13: 0000 1101
 
    if (flag & 1)    
        printf("0000 0001은 켜져 있음\n");
    else
        printf("0000 0001은 꺼져 있음\n");
 
    if (flag & 2)   
        printf("0000 0010은 켜져 있음\n");
    else
        printf("0000 0010은 꺼져 있음\n");
 
    if (flag & 4)   
        printf("0000 0100은 켜져 있음\n");
    else
        printf("0000 0100은 꺼져 있음\n");
 
    if (flag & 8)    
        printf("0000 1000은 켜져 있음\n");
    else
        printf("0000 1000은 꺼져 있음\n");
 
    return 0;
}

XOR 연산은 두 비트가 다르면 1, 같으면 0이 되기 때문에 마스크의 플래그의 비트가 1이면 마스크의 비트 1과 같아서 0이되고, 플래그의 비트가 0이면 마스크의 비트 1과 달라서 1이 된다.

 

24.5 퀴즈

정답은 C 이다.

정답은 a 이다.

정답은 d이다.

정답은 b이다.

정답은 b이다. 끄는것은 c 보기처럼 해야 한다.

 

24.6 연습문제 : 시프트 연산과 플래그 활용하기

1 << 7은 1000 0000(128) 이다. 이걸 4로 만드려면 첫번째 비트를 끄고, 여섯번째 비트를 켜야 한다. 근데 위 코드에서 마스크가 시프트로 되어있으므로 1 << 2로 여섯번째 비트를 키고, ~(1 << 7) 연산으로 첫번째 비트를 끈다.

flag2는 0000 0000으로 만들어야 하는데 1 << 3은 0000 1000(8)이므로 1 << 3을 마스크로 하면 플래그와 마스크가 같기 때문에 모든 비트가 0이된다. 따라서 정답은 1 << 2, ~(1 << 7), 1 << 3 이다.

 

24.7 심사문제 : 시프트 연산과 플래그 활용하기

정답은 다음 코드와 같다. 

flag |= num1 << 3;
flag &= ~(num2 >> 2);
flag ^= 1 << 7;

 

 

Unit 25. 연산자 우선순위 알아보기

C언어도 수학처럼 곱셈/나눗셈이 덧셈/뺄셈보다 우선순위가 높다. 또한 다양한 다른 연산자들의 우선순위는 다음과 같다.

실무에서는 연산자의 계산 순서를 ( ) 괄호로 명확히 나타내는 것을 선호한다.

 

25.1 괄호 사용하기

#include <stdio.h>
 
int main()
{
    int num1, num2;

    num1 = 35 + 1 * 2;
    num2 = (35 + 1) * 2;

    printf("%d\n", num1);
    printf("%d\n", num2);

    return 0;
}

위 결과와 같이 연산의 우선순위는 곱셈이 덧셈보다 높지만, 괄호를 사용하면 우선순위가 낮아도 먼저 계산할 수 있다. 괄호를 사용한 식의 계산 순서는 다음과 같다.

  1. 괄호를 사용한 연산자
  2. 우선순위가 높은 연산자
  3. 결합방향에 따라 순서대로 계산(+, *는 왼쪽에서 오른쪽)

다음과 같이 가독성을 높여 주기 위해 우선순위가 높은 연산자라도 괄호를 묶어주거나, 복잡한 식의 경우 괄호를 여러번 겹쳐서 사용하는 경우도 많다.

num1 = 35 + (1 * 2);
num1 = ((35 + 1) * 2);

 

25.2 연산자의 결합 방향 알아보기

보통 연산자는 -> 방향으로 계산하지만 <- 방향인 것도 있다.

#include <stdio.h>

int main()
{
    int num1 = 1;
    int num2;

    num2 = ++num1;   
    printf("%d\n", num2);   

    return 0;
}

++ 연산자는 ++ 연산자 뒤의 변수를 먼저 계산(확인)하고, 1을 증가시키므로 <- 방향이다. 

또한 할당 연산자도 왼쪽의 값을 오른쪽에 저장하는 것이므로 <- 방향 이다.

++, --, = 뿐 아니라 +(양의 부호), -(음의부호), !, ~ 등 변수나 숫자 앞에 붙는 연산자도 <- 방향이다.

그러나 변수 뒤에 붙는 ++, -- 은 -> 방향이다.

 

25.3 결합 방향이 다른 연산자와 괄호 사용하기

#include <stdio.h>

int main()
{
    int num1;
    int num2 = 3;

    num1 = 10 + 2 / (5 - 3) * ++num2;    

    printf("%d\n", num1);     
    return 0;
}

위 식에서 연산 순서는 다음과 같다. 

5-3 을 먼저 계산하고 ++num2를 계산하면 10 + 2 / 2 * 4 가 된다. 이후 /, * 먼저 계산하는데, 결합 방향이 -> 이므로 순서대로 계산하면 10 + 4가 되서 14가 된다.

 

25.4 논리, 비교, 시프트 연산자에 괄호 사용하기

다음과 같이 논리연산자도 우선 순위가 있고, 괄호를 사용할 수 있다.

#include <stdio.h>
#include <stdbool.h>

int main()
{
    bool b1;

    b1 = (false || false) && !false || false;  
    
    printf("%d\n", b1);    
    return 0;
}

논리연산자의 우선순위는 높은 순서로 !, &&, ||이다. 따라서 괄호 먼저 계산하면 false && !false || false 가 되고 이후 !를 계산하면 false && true || false 가 되고, &&를 계산하면 false || false가 되서 최종 결과는 false로 0이 출력된다.

다음과 같이 비교연산자에도 우선순위가 있다.

#include <stdio.h>

int main()
{
    int num1;

    num1 = 5 == 5 < 10; 

    printf("%d\n", num1);   
    return 0;
}

비교 연산자는 == 보다 <가 우선순위가 높다. 따라서 5 < 10이 참이므로 1이 나오고 5 == 1은 거짓이므로 num1에는 0이 저장된다.

#include <stdio.h>

int main()
{
    int num1 = 1;
    int num2 = 2;
    int num3;

    num3 = num1 << 2 + num2 << 1;    

    printf("%d\n", num3);    
    return 0;
}

산술 연산자가 시프트 연산자보다 우선 순위가 높기 때문에 2 + num2 가 먼저 계산 된 후 num1 << 4 << 1을 계산하여 32가 나온다.

num3 = (num1 << 2) + (num2 << 1);

위 코드와 같이 시프트 연산자를 괄호로 묶으면 먼저 계산되서 num3에는 8이 저장된다.

 

25.5 퀴즈 

정답은 c이다.

정답은 b이다.

정답은 true이다.

정답은 a이다.

 

25.6 연습문제 : 괄호 사용하기

주어진 순서대로 괄호로 묶으면 정답은 2 * ((1 << num1) + (2 >> num2)) 이다.

 

25.7 심사문제 : 괄호 사용하기

정답은 다음 코드와 같다.

((num1 + num2) * 10) - num3

 

Unit 26. switch 분기문으로 다양한 조건 처리하기

switch 분기문은 조건이 많아도 손쉽게 처리할 수 있다. switch문의 형태는 다음과 같다.

switch 분기문은 항상 case와 함께 사용한다. 변수의 값이 case에 지정한 값과 일치하면 해당 코드를 실행하고, case에 일치하는 것이 하나도 없으면 default의 코드를 실행한다. switch 분기문은 형식이 균일하며 처리해야 할 조건이 많을 때 사용한다.

파이썬에서는 switch 분기문이 없다.

 

26.1 사용자가 입력한 값에 따라 문자열 출력하기

#define _CRT_SECURE_NO_WARNINGS    
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);   

    switch (num1) 
    {
    case 1:         
        printf("1입니다.\n");
        break;
    case 2:    
        printf("2입니다.\n");
        break;
    default:      
        printf("default\n");
        break;
    }

    return 0;
}

1을 입력하면 case1 에 해당하는 코드가 실행되고, case에 없는 숫자를 입력하면 default에 해당하는 코드가 실행된다. 

switch의 괄호에는 값을 판단할 변수를 지정한다. case 다음에는 반드시 값(리터럴)이 와야 하며 변수나 조건식은 올 수 없다. case에 값을 지정하면 :(클론)을 붙이고, 다음줄 부터 실행할 코드를 입력한다. 코드의 마지막에는 break를 입력해야 한다. default에는 아무 case에도 해당하지 않을 때 실행할 코드를 입력한다.

조건식이 바뀌지 않고, 값만 바뀔 때는 switch문이 적합하며, 값과 조건식 모두 바뀔 때는 else if가 적합하다.

 

26.2 case에서 break를 사용하지 않을 때의 동작 알아보기

#define _CRT_SECURE_NO_WARNINGS    
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);   

    switch (num1) 
    {
    case 1:         
        printf("1입니다.\n");
    case 2:    
        printf("2입니다.\n");
    default:      
        printf("default\n");
    }

    return 0;
}

위 코드와 같이 break가 없으면 1을 입력했을 때 case 1:에 해당하는 코드만 실행하는 것이 아니라 case 2:, default: 에 해당하는 코드 모두 실행되었다. case를 작성할 때는 break로 중단해야 해당 case만 실행된다. break로 중단하지 않으면 그 다음에 있는 case, default의 코드가 모두 실행된다.

 

26.3 case에서 break 생략 응용하기

case에서 break를 생략하는 것은 버그같지만 실제로도 의도적으로 많이 사용하는 방식이다.

#define _CRT_SECURE_NO_WARNINGS    
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);   

    switch (num1)
    {
    case 1:   
    case 2:   
        printf("1 또는 2입니다.\n");
        break;
    case 3:    
    case 4:   
        printf("3 또는 4입니다.\n");
        break;
    default:
        printf("default\n");
    }

    return 0;
}

위 코드와 같이 case 1: 과  case 2:를 연달아 지정하면 num1의 값이 1일때와 2일 때 모두 코드가 실행된다. num1의 값이 3또는 4일때도 마찬가지다. case에서 break를 생략하면 여러가지 값으로 같은 코드를 실행할 수 있다.

이 코드는 다음과 같은 if 조건문을 표현할 수 있다.

if (num1 == 1 || num1 == 2)
    printf("1 또는 2입니다.\n");
else if (num1 == 3 || num1 == 4)
    printf("3 또는 4입니다.\n");
else
    printf("default\n");

 if조건문은 일일히 조건식을 나열해줘야 하므로 처리해야할 숫자가 많아지면 번거로워지기 때문에 이런 경우에는 case에서 break를 생략하는 것이 유리하다.

 

26.4 case  안에서 변수 선언하기

#define _CRT_SECURE_NO_WARNINGS   
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);    

    switch (num1)  
    {
    case 1:
        int num2 = num1;    
        printf("%d입니다.\n", num2);
        break;
    case 2:
        printf("2입니다.\n");
        break;
    default:
        printf("default\n");
        break;
    }

    return 0;
}

위와 같은 코드는 컴파일러에 따라 에러를 발생할 수 도 있고, 발생하지 않을 수도 있다. case부분을 중괄호로 묶지 않았기 때문이다.

case안에서 변수를 선언하려면 다음과 같이 해당 case를 중괄호로 묶어야 한다.

#define _CRT_SECURE_NO_WARNINGS   
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);    

    switch (num1)  
    {
    case 1:
    {
        int num2 = num1;    
        printf("%d입니다.\n", num2);
        break;
    }
    case 2:
        printf("2입니다.\n");
        break;
    default:
        printf("default\n");
        break;
    }

    return 0;
}

변수를 선언한 case를 중괄호로 묶어주면 컴파일 에러가 발생하지 않고, 변수를 선언할 수 있다. 중괄호 안에 사용한 변수는 case 1:안에서만 사용할 수 없고 case 2:나 switch바깥에서는 사용할 수 없다.

 

26.5 switch에서 판별할 수 있는 자료형 알아보기

switch에서 판별할 변수는 정수 자료형만 사용할 수 있고, 실수 자료형은 사용할 수 없다. 문자 자료형은 정수 자료형이므로 switch에서 사용할 수 있다.

#define _CRT_SECURE_NO_WARNINGS  
#include <stdio.h>

int main()
{
    char c1;

    scanf("%c", &c1);   

    switch (c1)  
    {
    case 'a':    
        printf("a입니다.\n");
        break;
    case 'b':   
        printf("b입니다.\n");
        break;
    default:   
        printf("default\n");
        break;
    }

    return 0;
}

작은 따옴표를 이용하여 문자 자료형을 사용할 수 있다. 그러나 case "hello": 처럼 문자열은 사용할 수 없다.

 

26.6 퀴즈

정답은 e이다. case 뒤에는 클론이 붙어야 한다.

정답은 e이다.

정답은 a,e이다.

 

26.7 연습문제 : switch 분기문 사용하기

비트 시프트 연산을 사용하므로 정답은 1,2,3 이다.

 

26.8 심사문제 : 음료수 자판기 만들기

정답은 다음 코드와 같다.

case 'f':
    printf("환타\n");
    break;
case 'c':
    printf("콜라\n");
    break;
case 'p':
    printf("포카리스웨트\n");
    break;
default:
    printf("판매하지 않는 메뉴\n");
    break;

 

Unit 27. for 반복문으로 Hello, world! 100번 출력하기

대부분의 프로그래밍 언어는 반복되는 작업을 간단하게 처리하기 위해 반복문이라는 기능을 제공한다. 반복문은 반복 횟수, 반복 및 정지 조건을 자유 조재로 제어할 수 있다. 

C의 for 반복문은 다음과 같이 괄호 안에 초기식, 조건식, 변화식을 지정하며 이 부분을 루프 선언문 이라고 부른다. 그리고 중괄호 안에 반복할 코드를 작성하는데 이 부분을 루프 본체라 부른다.

for (초기식; 조건식; 변화식)
{
    반복할 코드
}

초기식 부터 시작하여 조건식을 판별하여 조건식이 참이면 반복할 코드를 실행하고, 변화식을 수행한다. 다시 조건식을 검사하여 참이면 코드를 계속 반복하고, 거짓이면 반복문을 끝낸 뒤 다음 코드를 실행한다. 

조건식 -> 루프본체 -> 변화식 -> 조건식으로 순환하는 것을 루프라 한다.

파이썬에서는 for문에 in 키워드를 사용하여 지정한 숫자만큼 반복하거나, range 함수를 이용하여 지정한 범위만큼 반복한다. 초기식, 조건식, 변화식이 따로 정해져 있지 않다.

 

27.1 for 반복문 사용하기

다음과 같이 for 반복문으로 Hello, world!를 100번 출력할 수 있다.

#include <stdio.h>

int main()
{
    for (int i = 0; i < 100; i++)    
    {
        printf("Hello, world!\n");
    }

    return 0;
}

초기식은 int i = 0;으로 반복에 사용할 변수를 선언하고 0으로 초기화 했다. 조건식은 i < 100; 으로 i가 100보다 작을 때 까지만 반복하겠다는 의미이다. 변화식은 i++로 반복 할 때 마다 i를 1씩 증가시키겠다는 의미이다.

반복문이 처음 시작하면 i에 0이 들어가고, i 가 100보다 작은지 검사하여 100보다 작으면 중괄호 안의 코드를 실행하고 i를 1 증가시킨다. i가 100이 되면 조건식이 거짓이 되므로 반복문을 끝낸다.

초기식에서 변수를 선언하는 것은 C99방식이다. 이전의 방식은 초기식에서 선언할 수 없고 밖에서 초기식에 사용할 변수를 선언해야 한다. C99방식에서 초기식에 선언한 변수는 for문 안에서만 사용할 수 있고, 옛날 방식은 for문 밖에서 선언했기 때문에 for문 안과 밖에서 모두 사용할 수 있다.

 

27.2 초기값의 변화 알아보기

#include <stdio.h>

int main()
{
    int i;
    for (i = 0; i < 10; i++)   
    {
        printf("Hello, world!\n");
    }

    printf("%d\n", i);    
    return 0;
}

i가 0부터 1씩 증가하여 10이 되면 조건이 거짓이 되어 반복문이 종료되기 때문에 반복문이 끝난 뒤 변수 i의 값을 보면 10이 나온다.

 

27.3 초깃값을 1부터 시작하기

프로그래밍에서 반복문의 초기값은 보통 0부터 시작하지만 원하는 반복 횟수를 채울 수 있다면 어떤 초기값을 사용해도 상관 없다.

#include <stdio.h>

int main()
{
    for (int i = 1; i <= 100; i++)   
    {
        printf("Hello, world! %d\n", i); 
    }

    return 0;
}

위 코드에서 초기식이 1부터 시작하지만 i <= 100이므로 100보다 작거나 같을때까지 반복하여 반복 횟수는 100번이다. i가 101이 되면 조건식이 거짓이 되어 반복문을 끝낸다.

 

27.4 초깃값을 감소시키기

다음과 같이 초깃값을 크게 주고, 변수를 감소시키면서 반복할 수 도 있다.

#include <stdio.h>

int main()
{
    for (int i = 100; i > 0; i--)     
    {
        printf("Hello, world! %d\n", i); 
    }

    return 0;
}

초깃값은 100이고, 변화식에서 i--로 i를 1씩 감소시켰다. 조건식이 0이므로 i가 0이 되면 반복문이 끝나서 반복 횟수는 총 100번이다.

 

27.5 for 반복문과 세미클론

for 반복문도 if문과 마찬가지로 루프 선언문 뒤에 세미클론을 붙이면 안된다. 세미클론을 붙이면 for문과 아래 코드는 관계 없는 코드가 되어 반복을 하지 않는다.

 

27.6 for 반복문에서 중괄호 생략하기

다음과 같이 반복할 코드가 한 줄이면 중괄호를 생략할 수 있다.

#include <stdio.h>

int main()
{
    for (int i = 0; i < 100; i++)
        printf("Hello, world!\n");    

    return 0;
}

for 문에서 반복할 코드가 두 줄 이상일 때 중괄호를 생략하면 첫번째 줄만 반복하게 되므로 반복할 코드가 두 줄 이상이면 반드시 중괄호를 사용해야 한다.

 

27.7 입력한 횟수대로 반복하기

#define _CRT_SECURE_NO_WARNINGS  
#include <stdio.h>

int main()
{
    int count;
    scanf("%d", &count);    

    for (int i = 0; i < count; i++)   
    {
        printf("Hello, world! %d\n", i);
    }

    return 0;
}

입력값을 받아서 count에 저장하고, i가 count보다 작을때까지 반복했다. 3을 입력했으므로 Hello world!는 3번 출력된다.

#define _CRT_SECURE_NO_WARNINGS  
#include <stdio.h>

int main()
{
    int count;
    scanf("%d", &count);    

    for (int i = count; i  > 0; i--)   
    {
        printf("Hello, world! %d\n", i);
    }

    return 0;
}

위 코드와 같이 입력값을 초기값으로 사용할 수도 있다.

또한 다음과 같이 i를 따로 선언하지 않고 입력받은 값을 그대로 사용할 수 도 있다.

#define _CRT_SECURE_NO_WARNINGS  
#include <stdio.h>

int main()
{
    int count;
    scanf("%d", &count);    

    for (; count > 0; count--)   
    {
        printf("Hello, world! %d\n", count);
    }

    return 0;
}

for 반복문에 사용할 변수와 초기값이 준비되어 있으면 초깃값 부분은 생략할 수 있다. 그리고 두 조건식과 변화식을 입력받은 변수를 기준으로 만들면 된다.

 

27.8 for 반복문에서 변수 두 개 사용하기

#include <stdio.h>

int main()
{
    for (int i = 0, j = 0; i < 10; i++, j += 2)   
    {
        printf("i: %d, j: %d\n", i, j);
    }

    return 0;
}

위 코드는 for 초기식에서 변수를 두 개 선언하고, 변화식에서 i를 1씩 증가하고, j는 2씩 증가시켜 10번 반복 하도록 하였다. 변화식에서는 ++, -- 뿐 아니라 +=, -=, *=, /=등의 연산자도 사용할 수 있다.

 

27.9 for 반복문으로 무한 루프 만들기

#include <stdio.h>

int main()
{
    for (;;)    
    {
        printf("Hello, world!\n");
    }

    return 0;
}

for문에서 초기식, 조건식, 변화식을 모두 생략하면 무한루프 이다. 무한루프는 반복문이 종료되지 않기 때문에 ctrl+c를 눌러 강제로 종료시켜야 한다.

 

27.10 퀴즈

정답은 c,d 이다.

정답은 c, e이다.

정답은 b이다. for문 뒤에는 세미클론이 오면 안된다.

정답은 for(;;)이다.

 

27.11 연습문제 : for 반복문에서 변수 두 개 사용하기

정답은 int i = 2, j = 5; i <= 32; i *= 2, j-- 이다.

 

27.12 심사문제 : 알파벳 순서로 출력하기

정답은 다음 코드와 같다.

#include <stdio.h>

int main()
{
    char c1;
    scanf("%c", &c1);
    
    for(int i = c1; i <= 122; i++)
    {
        printf("%c", i);
    }
    printf("\n");
    return 0;
}

z는 아스키코드로 122이므로 i가 122가 될때까지 1씩 더하며 반복하며 i 값을 문자로 출력하였다.

 

Unit 28. while 반복문으로 Hello, world! 100번 출력하기

while 반복문은 괄호 안에 조건식만 들어가고, 초기식은 반복문 바깥에 있다. 중괄호 안에는 반복할 코드와 변화식이 들어간다.

초기식
while (조건식)
{
    반복할 코드
    변화식
}

초기식부터 시작하여 조건식을 판별한다. 조건식이 참이면 반복할 코드와 변화식을 수행한다. 그리고 다시 조건식을 판별하여 참이면 코드를 계속 반복하고, 거짓이면 반복문을 끝낸 뒤 다음 코드를 실행한다.

파이썬에서 while 반복문은 조건식 뒤에 클론(:)을 붙이고, 반복할 코드와 변화식은 들여쓰기로 구분한다.

 

28.1 while 반복문 사용하기

#include <stdio.h>

int main()
{
    int i = 0;
    while (i < 100)    
    {
        printf("Hello, world!\n");
        i++;          
    }

    return 0;
}

반복문애 사용할 변수 i를 선언하고, 0으로 초기화 한다. while에는 조건식만 지정하고, 변화식은 중괄호 안에 지정한다. 중괄호 안에 변화식이 없다면 반복문이 끝나지 않는 무한루프 현상이 발생한다.

위 코드는 조건식이 i < 100 이기 때문에 i가 100이 되면 반복문이 종료되고, 변화식으로 i++를 사용했기 때문에 반복할 때 마다 i의 값이 1 씩 증가하여 0부터 99까지 100번 반복하게 된다.

 

28.2 초깃값을 1부터 시작하기

#include <stdio.h>

int main()
{
    int i = 1;
    while (i <= 100)    
    {
        printf("Hello, world!\n");
        i++;          
    }

    return 0;
}

위 코드는 i를 1부터 시작했기 때문에 조건식을 i <= 100으로 지정하여 i가 101이 되면 반복문이 종료된다.

 

28.3 초깃값을 감소시키기

다음과 같이 초깃값을 크게 주고, 변수를 감소시키면서 반복문을 진행할 수 도 있다.

#include <stdio.h>

int main()
{
    int i = 100;
    while (i > 0)    
    {
        printf("Hello, world! %d\n", i);  
        i--;                             
    }

    return 0;
}

i가 100이고, 변화식에서 i--를 지정하여 반복을 할 때마다 i의 값을 1씩 감소시킨다. i > 0 이 조건식이므로 100부터 1까지 100번 반복하고, i가 0이되면 반복문이 종료된다.

 

28.4 while 반복문과 세미클론

while문도 끝에 세미클론을 붙이면 안된다. while 끝에 세미클론을 붙이면 while문은 중괄호 안의 반복할 코드와 변화식과는 무관한 코드가 되기 때문에 중괄호 안에 있는 변화식도 실행할 수 없어서 무한루프 현상이 발생한다. 그러나 중괄호 안의 코드는 아직 실행 전이기에 출력되는값은 아무것도 없다.

 

28.5 입력한 횟수대로 반복하기

다음 코드와 같이 입력한 값을 반복문에 사용할 수 있다.

#define _CRT_SECURE_NO_WARNINGS   
#include <stdio.h>

int main()
{
    int count;
    scanf("%d", &count);   

    int i = 0;
    while (i < count)                     
    {
        printf("Hello, world! %d\n", i);  
        i++;                              
    }

    return 0;
}

scanf 함수로 값을 입력 받아서 count변수에 저장하여 count에 들어간 값만큼 반복한다. 위에서는 3을 입력했기 때문에 3번 반복한다.

다음은 초깃값을 입력받은 것이다.

#define _CRT_SECURE_NO_WARNINGS   
#include <stdio.h>

int main()
{
    int count;
    scanf("%d", &count);   

    while (count > 0)                     
    {
        printf("Hello, world! %d\n", count);  
        count--;                              
    }

    return 0;
}

위 코드에서는 i를 선언하지 않고 count를 바로 사용하여 변화식은 count--로 반복할 때 마다 count의 값을 1씩 감소시킨다. count가 0이 되면 반복문이 끝난다.

 

28.6 반복 횟수가 정해지지 않은 경우

while 반복문은 반복 횟수가 정해지지 않았을 때 논리 조건에 따라 반복 여부를 결정할 때 주로 사용된다. 

다음은 while반복문 안에서 무작위로 정수를 생성한 뒤 3이 나오면 반복을 끝낸다.

#include <stdio.h>
#include <stdlib.h>   
#include <time.h>   

int main()
{
    srand(time(NULL)); 
    int i = 0;
    while (i != 3)        
    {
        i = rand() % 10;  
        printf("%d\n", i);
    }

    return 0;
}

stdlib.h는 srand, rand 함수를 사용할 수 있는 헤더파일이고, time.h는 time함수를 사용할 수 있는 헤더파일이다. 무작위로 정수를 생성하려면 srand, rand, time 함수가 필요하다. 

srand함수는 난수를 발생시킬 초기값 시드(seed)를 설정한다. 보통은 현재 시간값을 사용한다. rand함수는 난수를 발생시킨다. time 함수는 정수 형태로 된 현재 시간값을 반환한다. 

다음 코드로 현재 시간값을 시드로 설정한다.

srand(time(NULL));

while 반복문에서 i != 3으로 i가 3이 아니면 계속 반복한다. rand() % 10은 rand의 반환값이 크기 때문에 10으로 나눠서 나머지로 1 ~ 9까지만 사용한다. 1 ~ 9까지의 숫자가 랜덤으로 출력되다가 3이 출력되면 반복문이 종료된다. while 반복문은 반복 횟수가 정해져 있지 않을 때 유용하다.

 

28.7 while 반복문으로 무한루프 만들기

#include <stdio.h>

int main()
{
    while (1)    
    {
        printf("Hello, world!\n");
    }

    return 0;
}

while 조건식에 1을 지정하면 무한루프가 만들어 진다. 조건식 자체가 없으므로 변화식도 필요 없다. 무한루프를 종료시키려면 ctrl + c를 누르면 된다. 

다음과 같이 1 대신 불 값인 true를 넣기도 한다.

#include <stdio.h>
#include <stdbool.h>    

int main()
{
    while (true)   
    {
        printf("Hello, world!\n");
    }

    return 0;
}

1 대신 true를 사용하는 것은 코드의 의도를 좀 더 명확하게 하기 위함이다.

 

28.8 while 반복문에서 중괄호 생략하기

#include <stdio.h>

int main()
{
    while (1)
        printf("Hello, world!\n");   
    return 0;
}

while 반복문에서 수행할 코드가 한줄이라면 중괄호를 생략할 수 있지만, while문은 보통 본체에 변화식이 함께 들어가므로 중괄호를 생략할 일은 많지 않다.

 

28.9 퀴즈

정답은 a, d, e이다.

정답은 b이다. i < 10 이여야 한다.

정답은 while(1)이다.

 

28.10 연습문제 : while 반복문 사용하기

i 변수는 8비트 이므로 i는 1에서 1칸씩 계속 왼쪽으로 시프트하다보면 1000 0000은 128 이고,  0000 0000이 되므로 i는 0일때 반복문을 종료시키면 된다. 답은 i != 0 이다.

 

28.11 연습문제 : 교통카드 잔액 충전하기

 정답은 다음 코드와 같다.

#include <stdio.h>

int main()
{
    int money;
    scanf("%d", &money);
    
    while(money >= 1200)
    {
        money -= 1200;
        printf("%d\n", money);
    }
    return 0;
}

 

+ Recent posts