스터디 그룹/ProjectH4C

ProjectH4C 2개월 1주차 과제 (UNIT16 ~ UNIT33)

C언어 복습

c언어 복습을 다시 진행해보자.

얼른 마치고 대학교 시간표나 짜보자 ㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ하 .......

📖UNIT16

자료형의 확장에 대해 알아보자.

 

#include <stdio.h>

int main(){
    int num1 = 11;
    float num2 = 4.4f;

    printf("%f\n", num1 + num2);
    printf("%f\n", num1 - num2);
    printf("%f\n", num1 * num2);
    printf("%f\n", num1 / num2);
    return 0;
}

이런 코드를 작성해보자.

이런 결과를 통해 더욱 많은 걸 표현할 수 있는 자료형을 채택한다는 것을 알 수 있다. 깨알 오차도 보인다.

이런걸 자료형의 확장이라고 한다.

📖UNIT17

c언어의 조건문에 대해 알아보자.

#include <stdio.h>

int main(){
    int a = 10;

    if(a==10){
        printf("%d", a);
    }

    return 0;
}

이렇게 코드를 작성하고 실행하게 된다면 10을 출력하게 된다. 이런 걸 c언어의 조건문이라고 한다.

 

조건문 안의 코드가 한줄이면 중괄호를 생략해도 된다.

 

📖UNIT18

이번엔 else에 대하여 알아보자.

#include <stdio.h>

int main(){
    int a = 10;

    if(a==10){
        printf("%d", a);
    }
    else{
        printf("a is not 10");
    }
    return 0;
}

이렇게 한다면 아까 unit 17의 코드에서 a가 10이 아닌 경우까지 지정해줄 수 있다.

 

물론 else도 else안의 코드가 하나라면, 중괄호를 굳이 작성할 필요가 없다.

 

조건문 안에 조건문을 지정하는 것도 가능하다.

 

📖UNIT19

조건문의 마지막 else if도 알아보자.

 

#include <stdio.h>

int main(){
    int a = 10;

    if(a==10){
        printf("%d", a);
    }
    else if(a==20){
        printf("the a is 20");
    }
    else{
        printf("a is not 10 and 20");
    }
    return 0;
}

요렇게 코드를 작성해주면 된다.

그럼 첫 번째 if문에서 false가 나왔을 때 그 다음 else if문으로 행해진다.

이런 원리대로 진행된다.

 

📖UNIT20

이번엔 비교연산자와 삼항연산자에 대해 알아보자.

비교연산자는 알기에 삼항연산자를 복습하자. ( 비교연산에서 true면 1, false면 0이다. )

 

int num1 = 100;
int num2;

num2 = num1 ? 100 : 200;

이렇게 된다면 당연히 num2에는 100이 들어가진다.

그 이유로는 num1은 먼저 0이 아니기에 True이다. 결국 num1이 true일 때 100을 설정해놓았으므로 100이 들어가지는 것이다.

 

이 그림을 참고하자.

결국 num2 = num1!=0 ? 100 : 200; 으로 바꿔 쓸 수도 있다.

 

📖UNIT21

이번엔 논리 연산자에 대해 알아보자. 논리 연산자는 우선 간단하게 보면 3가지가 있다.

  • && (and) : 둘 다 True여야 True
  • || (or) : 하나라도 True라면 True
  • ! (not) : 논리 연산 결과 뒤집기

그래서 if 조건문에 이 논리연산자를 사용해보도록 하자.

 

if(num1 && num2)

이 조건은 num1과 num2가 모두 true 일 때 수행되는 조건문이다.

 

if(num1 || num2)

이건 num1과 num2 둘 중 하나라도 true면 실행된다.

 

여기서 num이라고 사용된 표현은 js에선 expr이라고 하는 것 같다.

 

📖UNIT22

이번엔 bool 자료형을 사용하여 보자.

bool은 다른 데이터들과 마찬가지인 그냥 데이터이다. 대신 true와 false를 지칭하는 데이터이다.

 

bool을 사용하려면 stdbool.h 헤더파일을 참조해야 한다.

 

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

int main(){
    bool b1 = true;
    if (b1 == true){
    	printf("참");
    }
    else{
    	printf("거짓");
    }
}

이런식으로 사용해줄 수 있다. (아 return 0; 빼먹었다.)

 

불 자료형은 1바이트를 차지한다.

 

위에서 숫자로 논리연산을 진행했다면 이제는 정말 true와 false를 이용하여 논리연산을 진행해줄 수 있다.

true && false

false && true 이런식으로 논리연산을 진행해줄 수 있다.

 

파이썬, js랑은 다르게 true와 false를 사용하려면 헤더파일을 참조해줘야 한다.

 

📖UNIT23

비트연산자에 대해 알아보자.

 

그러기 전에 먼저 비트(bit)를 알아야 한다. 비트란, 2진수를 저장하는 단위로 0과 1을 나타낸다.

컴퓨터에서 사용할 수 있는 가장 작은 최소단위이다.

이 비트를 가지고 연산을 한다는 말이다.

 

이런 비트 연산 중 대표적으로 and, or, xor 연산이 존재한다.

먼저 1과 3을 가지고 연산을 해보자.

 

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 1;    // 0000 0001
    unsigned char num2 = 3;    // 0000 0011
 
    printf("%d\n", num1 & num2);    // 0000 0001: 01과 11을 비트 AND하면 01이 됨
    printf("%d\n", num1 | num2);    // 0000 0011: 01과 11을 비트 OR하면 11이 됨
    printf("%d\n", num1 ^ num2);    // 0000 0010: 01과 11을 비트 XOR하면 10이 됨
 
    return 0;
}

코딩 도장에 있는 코드이다. 비트 연산은 비트로 연산을 진행해야 하기에 2진수로 바꿔줘야 한다.

 

1과 3을 and연산 한다면, 0000 0001 과 0000 0011을 연산하는 것이다. 둘 다 1이어야 1이 되므로

0000 0001이 된다.

 

그렇기에 첫 번째는 1이 출력되는 것이다.

 

1과 3을 or 연산 한다면 0000 0001과 0000 0011을 연산하는 것이다. 둘 중 하나라도 1이면 1이므로

0000 0011이 된다.

 

그렇기에 두 번째 줄에는 3이 출력된다.

 

xor연산은 하나만 1이어야 1이 출력된다.

 

0000 0001과 0000 0011을 xor 한다면 0000 0010이 된다.

 

그렇기에 마지막 줄은 2가 출력된다.

 

Not 연산도 존재한다. not 연산은 1을 0으로, 0을 1로 변환한다.

 

또, shift 연산도 존재한다.

 

#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;
}

이 코드에서 num1 << 3은 num1의 비트를 왼쪽으로 3번 이동하라는 소리이다.

num1은 0000 0011이기에, 왼쪽으로 3칸 이동시킨 다면 0001 1000이 될 것이다.

결국 24가 된다. shift연산과 비트연산들은 계산기에도 존재하므로 계산기를 적극 활용하자.

 

다시 본론으로 돌아와서 24를 쉬프트 연산 해보자.

24는 0001 1000인데, 오른쪽으로 두칸 옮긴다면, 0000 0110이 될 것이다. 그럼 6이 된다.

 

조금 생소한 느낌이 있긴 하지만 꽤 재미있다 !

 

근데 어느정도 규칙이 발견되었다. << n 을 한다면 2의 n 제곱을 그 수에 곱해주는 것이다.

반대로 >> n 을 한다면 2의 n 제곱을 그 수에 나눠주는 것이다.

 

📖UNIT24

만약에 시프트 연산을 하다가 자리가 초과된다면 어떻게 될까? 

 

그냥 사라진다. (끝)

 

최상위 비트와 최하위 비트에 대해 좀 더 알아보도록 하자.

 

비트에서 첫 번째 비트를 최상위 비트라고 한다. (가장 왼쪽에 있는 비트 ? )

Most Significat Bit(MSB)라고 부른다.

 

비트에서 가장 마지막 비트를 최하위 비트라고 한다. (가장 오른쪽에 있는 비트?)

Less Siginificant Bit(LSB)라고 부른다.

 

그럼 만약에 비트연산을 진행할 때 부호가 있다면 어떻게 될까?

#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;    // num1의 비트 값을 오른쪽으로 5번 이동
    num4 = num2 >> 5;    // num2의 비트 값을 오른쪽으로 5번 이동
 
    printf("%u\n", num3);    //  4: 0000 0100: 맨 뒤의 11은 사라지고 0000 0100이 됨
    printf("%d\n", num4);    // -4: 1111 1100: 모자라는 공간은 부호 비트의 값인 1로  
                             // 채워지므로 1111 1100이 됨
 
    return 0;
}

-125를 비트로 표현한다면 1000 0011이다. 쉬프트를 진행하더라도 가장 최상위 비트는 부호를 표현하기에 변하지 않는다.

그래서 1000 0011에서 한 번 쉬프팅이 된다면 1100 0001이 되는 것이다.

1000 0011

1100 0001

1110 0000

1111 0000

1111 1000

이런 순서대로 shift가 진행된다.

만약 음수가 나오지 않는 변수라면 꼭 unsigned를 지정하도록 하자 .........

그 이유는 아래와 같다.

이런 의도치 않은 결과가 나올 수 있기 때문이다.

 

이번엔 플래그에 관해 알아보자.

플래그는 말 그대로 깃발이다. 주로 cpu에 사용된다. 각 자리의 깃발이 켜졌는지 꺼졌는지를 통해 상태를 파악할 수 있다.

 

기본적인 사용법은 플래그 |= 마스크 이다. or 연산을 통해 플래그를 키는 것이다.

끄는 법은 플래그 &= ~마스크 이다. ~마스크를 해주는 순간 해당 자리와 0과 and 연산을 진행하는 것이기에 0이 된다.

 

좀 더 예시를 봐보자.

 

#include <stdio.h>

int main(){
    unsigned char flag = 0;
    flag |= 4; //4번째 flag on
    
    if(flag & 4){ //flag 4가 켜져있는가.
        printf("flag 4 is opened");
        flag &= ~4 //flag 4 off
    }
    
    return 0;
}

이렇게 플래그를 끄고 킬 수 있으며,켜져있는지 확인도 할 수 있다.

 

그럼 비트가 꺼져있다면 바로 키고, 켜져있다면 끌 수 없을까. 

물론 위에 내가 작성한 코드도 일부분이기는 하지만, 좀 더 확실한 방법이 있다.

 

#include <stdio.h>

int main(){
    unsigned flag = 0;
    flag ^= 4;
    flag ^= 4;
    flag ^= 4;
    flag ^= 4;
    flag ^= 4;
    return 0;
}

이렇게 flag ^= 4 를 해준다면 바로 3번째 플래그의 값을 반전시킬 수 있다.

 

📖UNIT26

이번엔 switch 문에 대하여 복습해보자.

switch문은 무조건 case와 함께 사용되어야 한다. switch 내부의 변수가 case와 값이 같을 때의 코드를 실행하게 된다.

 

이 case에서 break를 설정하지 않는다면 어떻게 될까.

 

#define _CRT_SECURE_NO_WARNINGS    // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);    // 값을 입력받음

    // switch의 case에서 break 삭제
    switch (num1)
    {
    case 1:    // 1일 때는 아래 case 2, default가 모두 실행됨
        printf("1입니다.\n");
    case 2:    // 2일 때는 아래 default까지 실행됨
        printf("2입니다.\n");
    default:
        printf("default\n");
    }

    return 0;
}

 

break를 설정하지 않는다면 위와같이 쭈우우우우우욱 미끄러지면서 그 밑의 코드들이 실행된다.

이걸 영어로 fall through라고 한다.

 

신기하다. case문에서 걸릴거라고 생각했는데 그렇지 않고 쭉 실행이 되다니..............

 

case 내부에서 변수를 선언하고 싶다면, 그 case에 중괄호를 쳐주자.

 

switch에서는 float와 double로 case를 만들지는 못한다. 참고하도록 하자.

📖UNIT27

"Hello, World!\n"를 100번 출력해보자.

 

#include <stdio.h>

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

이렇게 해주면 된다.

 

반복문 안에서 i 뿐만 아니라 j 도 포함하여 변수 두개를 한번에 사용할 수도 있다.

 

무한 루프를 돌고 싶다면 for( ; ; )를 하자.

 

📖UNIT28

이번엔 while로 "Hello, World!\n"를 100번 출력해보자.

#include <stdio.h>

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

요런식으로 코드를 작성해주면 된당.

 

while에서 조심 할 것은 while문 내에서 무조건 i 라는 변수를 변화시켜주는 식이 필요하다는 것이다.

그게 없으면 바로 무한 루프에 빠지겠지만...

 

while문은 주로 반복횟수를 정하지 않을 때 많이 쓴다. 즉, 특정한 조건을 만족 시켰을 때 까지 반복을 시킬 때 사용한다.

(ex. 난수 생성해서 생성된 난수와 임의의 값이 같을때 break 등)

📖UNIT29

while은 while 자체로도 많이 사용되지만, do - while로 더 많이 사용되는 것 같다.

do - while문을 좀 더 알아보자.

 

do{
    코드
}while(조건)

이런 형태로 사용된다.

 

#include <stdio.h>

int main()
{
    int i = 0;

    do     
    {
        printf("Hello, world! %d\n", i);    
        i++;                                
    } while (i < 100);    

    return 0;
}

이러면 당연히 Hello, World! 1,2,3,4,5,.......99까지 출력 될 것이다.

 

중요한것은 do - while은 처음 한 번은 무조건 실행이 된다는 것이다. (조건 판별을 do 문이 끝났을 때 하기 때문)

do - while문도 while처럼 반복횟수가 정해지지 않았을 때 주로 사용된다. 하지만 중요한점은 do - while은 적어도 한 번 무조건 실행된다는 것이다.

 

📖UNIT30

이번엔 break, continue를 알아보자.

 

break는 말 그대로 반복문을 탈출시킨다.

#include <stdio.h>

int main(){
    while(1){
        char a;
        a = scanf("%c", &a);
        if(a == 'k'){
            break;
        }
    }
    return 0;
}

간단하게 설명하자면 위의 코드로 설명할 수 있다.

 

프로그래밍을 할 때 반복이 필요하지만 언제까지 반복을 해야하는지 모르는 경우가 있다.

그럴 때에는 우선 기한이 없으니 반복을 무한번 실행하되, 특정 조건을 만족 시킬 때 탈출을 시켜주면 된다.

 

while, do - while 뿐만 아니라 for에서도 똑같이 사용 가능하다.

 

그러면 continue는 무엇일까.

 

만약 2의 배수는 출력하지 않는 프로그램을 짠다고 생각해보자.

int i = 0;
while(1){
    if(i%2 == 0){
        continue;
    }
    printf("%d ", i);
}

그런 프로그램은 위와같이 작성 가능하다.

 

즉, continue가 실행된다면, 해당 반복문의 count(?)를 다음으로 넘어가는 그 부분으로 건너 띄어준다.

그러니 위의 코드에서도 printf()함수는 생략하고 바로 다음 카운트로 넘어가졌다.

 

📖UNIT31

이제 계단식으로 별을 출력해보자.

#include <stdio.h>

int main(){
    for(int i = 0; i < 5; i++){
        for(int j= 0; j<i+1; j++){
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

이와 같다. 다음으로 별을 대각선으로 출력해보자.

#include <stdio.h>

int main(){
    for(int i=0; i<5; i++){
        for(int j=0; j<i+1; j++){
            if(j == i){
                printf("*");
                break;
            }
            else printf(" ");
        }
        printf("\n");
    }
    return 0;
}

사실 굳이 break를 안걸어줘도 되지만 시간복잡도를 조금이라도 단축시키기 위해...

생각해보니 둘 다 n^2이라 크게 안줄여질 것 같다.

 

📖UNIT32

goto에 대해 알아보자.

열혈c를 통해 c언어를 공부할 때 goto는 절대적으로 말리는 듯한 어투였다. 코딩도장에서도 마찬가지이다.

 

그래도 우선 사용법 정도는 아는게 좋다고 생각한다. (리눅스 커널도 어느정도 goto문을 쓴다고 한다.)

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
    int num1;

    scanf("%d", &num1);

    if (num1 == 1)         // num1이 1이면
        goto ONE;          // 레이블 ONE으로 즉시 이동
    else if (num1 == 2)    // num1이 2이면
        goto TWO;          // 레이블 TWO로 즉시 이동
    else                   // 1도 아니고 2도 아니면
        goto EXIT;         // 레이블 EXIT로 즉시 이동

ONE:    // 레이블 ONE
    printf("1입니다.\n");
    goto EXIT; // 레이블 EXIT로 즉시 이동

TWO:    // 레이블 TWO
    printf("2입니다.\n");
    goto EXIT; // 레이블 EXIT로 즉시 이동

EXIT:    // 레이블 EXIT
    return 0;
}

먼저 GOTO문의 기본적인 사용법을 알아보자.

 

먼저 레이블을 정의해줘야 한다. 그래서 레이블 ONE, TWO, EXIT 를 정의해준 것이다.

 

레이블로 구간을 나누어주게 되면, goto문으로 해당 레이블로 보내는 것이 가능하다.

저 코드는 간단하기에 goto문이 바로 이해가기는 하지만, 매우 복잡하고 꼬여있다. 저런걸 스파게티 코드라고 하는구나...

 

하지만 goto를 에러 해결에 사용하면 좋을 것 같다. 만약 여러 상황에서 에러가 발생했을 때 처리를 똑같은 방법으로 진행한다면,

그 처리코드를 레이블로 지정해도 괜찮다고 생각된다.

 

#include <stdio.h>

int main()
{
    int gender;      // 성별: 남자 1, 여자 2
    int age;         // 나이
    int isOwner;     // 주택 소유 여부: 자가 1, 전월세 0

    scanf("%d %d %d", &gender, &age, &isOwner);

    printf("안녕하세요.\n");
    printf("문을 연다.\n");

    if (gender == 2)
        goto EXIT;    // 에러가 발생했으므로 EXIT로 이동

    if (age < 30)
        goto EXIT;    // 에러가 발생했으므로 EXIT로 이동

    if (isOwner == 0)
        goto EXIT;    // 에러가 발생했으므로 EXIT로 이동
 
EXIT:
    printf("안녕히계세요.\n");    // 에러 처리 코드를
    printf("문을 닫는다.\n");     // 한 번만 사용함

    return 0;    // 프로그램 종료
}

이 코드처럼...

 

📖UNIT33

마지막이다. FizzBuzz 문제를 풀어보자.

 

  • 1에서 100까지 출력
  • 3의 배수는 Fizz 출력
  • 5의 배수는 Buzz 출력
  • 3과 5의 공배수는 FizzBuzz 출력

이 규칙을 먼저 따라야 한다.

 

#include <stdio.h>

int main(){
    for(int num=1; num<=100; num++){
        if(num % 15 == 0) printf("FizzBuzz ");
        else if(num % 3 == 0) printf("Fizz ");
        else if(num % 5 == 0) printf("Buzz ");
        else printf("%d ", num);

        if(num==10) printf("\n");
    }
    return 0;
}

매우 성공적이다.