모두를 위한 컴퓨터 과학 CS50 데이비드 J. 말란 (David J. Malan)

2. C언어

4) 자료형, 형식 지정자, 연산자

 

변수의 자료형(데이터 타입)들은 어떤 것들이 있을까

 

  • bool : boolean 불리언 표현 ex) True, False, 1, 0, yes, no
  • char : 문자 하나
  • string : 문자열
  • int : 특정 크기 또는 특정 비트까지의 정수 (대략 40억 개)
  • long : 더 큰 크기의 정수
  • float : 부동소수점을 갖는 실수 ex) 3.14
  • double : 부동소수점을 포함한 더 큰 실수

 

이 데이터 타입들 마다 printf 함수에서 사용할 형식 지정자들도 있다

 

  • % c : char
  • % f : float, double
  • % i : int
  • % li : long
  • % s : string

% f는 f 앞에. 원하는 자릿수를 넣어 소수점을 원하는 자리까지 나오게 할 수 있다

 

ex) 물건의 가격을 물어보고 세금을 포함한 값을 계산해 출력하는 코드이다.

부가세가 6.25%, 가격이 100이라고 했을 때 코드는 다음과 같다

 

# include <cs50.h>
# include <stdio.h>

int main(void)
{
    float price = get_float("What's the price?\n");
	printf("Your total is %.2f \n", price*1.0625);
}

 

get_float는 cs50의 함수이다

가격이 얼마인지 물어보고 가격을 받아 price에 저장한다

총액이 실수이므로 float를 사용해 % f를 사용한다

% f로만 계산하게 된다면 값은 105.250000까지 나타내기 때문에

%. 2f로 소수점 2번째 자리까지만 나오게 할 수 있다

 

./float
what's the price?
100
Your total is 106.25

 

이외에도 아래와 같이 다양한 수학 연산자, 논리 연산자, 주석 등이 기호로 정의되어 있다

 

  • +, - : 더하기, 빼기
  • *, / : 곱하기, 나누기
  • % : 나머지
  • && : 그리고
  • || : 또는
  • // : 주석

 

주석은 코드가 어떤 일을 하는지 설명한다

처음 보는 사람도 이해하기 쉽게 잘 설명해 두는 습관이 중요하다

 

5) 사용자 정의 함수, 중첩 루프

 

c언어로 나만의 함수를 만들고 싶다면?

 

사용자 정의 함수를 만드는 법

 

#include <stdio.h>

void cough(void)
{
    printf("cough\n")
}

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        cough();
    }
}

 

void를 입력하고 원하는 함수명을 적고 (void)를 적어준다

그리고 실행될 코드를 작성한다

 

다만 이렇게 했을 때 문제는 함수를 여러 개 만들 경우

main 함수가 밑으로 내려가기 때문에 가독성이 떨어진다

 

그렇다고 main 함수를 무턱대고 위로 올리면 main 함수를 실행할 때

만들어둔 함수는 인식하지 못하기 때문에 오류가 발생하므로 이렇게 하는 것이 좋다

 

#include <stdio.h>

void cough(void);

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        cough();
    }
}

void cough(void)
{
    printf("cough\n");
}

 

void cough(void); 만 위로 올리면

C에게 내가 만든 함수를 인식시켜 속일(?) 수 있다

 

함수를 원하는 횟수만큼 출력하는 법

 

#include <stdio.h>

void cough(int n);

int main(void)
{
    cough(3);
}

void cough(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("cough\n");
    }
}

 

int n 은 함수에서 int 정수 형식을 갖는 입력값을 받아 변수명 n에 저장한다는 의미이다

 

중첩루프

 

가로가 n개, 세로가 n 개인 "#"을 출력하려면?

 

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int n;

    do
    {
        n = get_int("Size: ");
    }
    while (n < 1);

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            printf("#");
        }
        printf("\n");
    }
}

 

int n으로 정수값을 받는 변수 n을 정의하고,

do {... } while()을 이용해 while()의 조건이 만족할 때까지

get_int cs50 라이브러리 함수로 입력값을 받아 n에 저장한다

 

do while을 사용하면 조건과 상관없이 최소 한 번은 { } 안의 내용을 실행할 수 있다

 

그리고 for 루프를 두 번 중첩하며 # 을 출력하는 데,

첫 번째 루프는 변수 i를 기준으로 n번 반복하고,

그 안의 내부 루프에서 변수 j를 기준으로 n번 반복한다

 

내부 루프에서 #을 출력하고, 내부 루프가 끝날 때마다 줄 바꿈이 실행되어,

최종적으로 가로, 세로가 n 개인 #이 출력된다

 

이렇게 사용자 정의 함수를 사용했을 때의 장점은?

 

  • 모듈화
    반복적으로 작성하지 않아 작업시간을 단축할 수 있어 재사용성을 높인다
    함수 내부의 코드 구조를 이해 못 해도 사용이 가능하다
  • 구조화, 단순화
    main 함수의 길이를 줄여 간결한 코드로 복잡성을 낮추고 가독성을 높인다
    협업(코드리뷰)에 수월해지고 유지보수(수정)하기도 좋다

모두를 위한 컴퓨터 과학 CS50 데이비드 J. 말란 (David J. Malan)

C언어로 1 나누기 10을 했을 때, 소수점 값은?

#include <cs50.h>
#include <stdio.h>

int main (void)
{
  float x = get_float("x: ");
  float y = get_float("y: ");

  printf("x / y =%f\n", x / y);
}

 

C 언어의 float를 활용해 x를 y로 나눠보자

 

 

x: 1
y: 10
x / y =0.100000

 

우리가 알고 있듯 값은 0.1이다

뒤에 있는 0000... 들은 필요가 없으므로 깔끔하게 보이게 하려면

 

#include <cs50.h>
#include <stdio.h>

int main (void)
{
  float x = get_float("x: ");
  float y = get_float("y: ");

  printf("x / y =%.1f\n", x / y);
}

 

f 앞에. 1을 입력해 주면 된다

 

x: 1
y: 10
x / y =0.1

 

그럼 이렇게 깔끔하게 출력된다

 

그런데 말입니다, 소수점을 50자리까지 보이게 하면 어떨까요?

 

#include <cs50.h>
#include <stdio.h>

int main (void)
{
  float x = get_float("x: ");
  float y = get_float("y: ");

  printf("x / y =%.50f\n", x / y);
}

 

. 50으로 코드를 수정했다

결과는?!

 

x: 1
y: 10
x / y =0.10000000149011611938476562500000000000000000000000

 

으잉? 50자리까지는 출력되는데 중간 숫자가 이상하다

왜 그럴까?

 

float에서 저장가능한 비트의 수가 32비트로 유한하기 때문이다

( double 은 64비트를 사용한다.

고로, double을 사용하면 좀 더 정확하게 계산할 수 있다 )

 

컴퓨터의 저장 공간(메모리)에는 한계가 있고 특정 지점 뒤에는 한계에 부딪혀

결국 저장할 수 있는 값들 중 1 / 10에 가까운 값을 저장하게 된다

 

정수 오버플로우

비슷한 오류로, 1부터 시작해 2를 계속해서 곱하는 코드를 실행했을 때

 

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    for (int i = 1; ; i *= 2)
    {
        printf("%i\n", i);
        sleep(1);
    }
}
...
1073741824
overflow.c:6:25: runtime error: signed integer overflow: 1073741824 * 2 cannot be represented in type 'int'
-2147483648
0
0
...

 

int 타입이 저장할 수 있는 32비트의 크기를 넘은 이후에는 더 이상 넘어갈 1의 자리가 없기 때문에

그 이상의 숫자를 저장하지 못하고 에러와 함께 0이 출력된다

 

실생활에서 발견되었던 오버플로우 문제는 Y2K, 보잉 787 사례가 있다

1999년에 큰 이슈가 되었던 Y2K 문제는 연도를 마지막 두 자리수로 저장했던 관습 때문에 새해가 오면 ‘99’에서 ‘00’으로 정수 오버플로우가 발생하고, 새해가 2000년이 아닌 1900년으로 인식된다는 문제였습니다.
그리고 세계는 수백만 달러를 투자해서 프로그래머들에게 더 많은 메모리를 활용해서 이를 해결하도록 하였습니다.
이는 통찰력 부족으로 발생한 아주 현실적이고 값비싼 문제였습니다.
또한 다른 사례로 비행기 보잉 787에서 구동 후 248일이 지나면 모든 전력을 잃는 문제가 있었습니다.
왜냐하면 강제로 안전 모드로 진입하였기 때문입니다.
이는 소프트웨어의 변수가 248일이 지난 뒤에 오버플로우가되어 발생하였기 때문이었습니다.
248일을 1/100초로 계산하면 대략 2의 32제곱이 나옵니다.
보잉을 설계할때 사용한 변수보다 너무 커졌던 것입니다.
이를 해결하기 위해 주기적으로 재가동을 하여 변수를 다시 0으로 리셋했습니다. 

 

따라서 다루고자 하는 데이터 값의 범위를 유의하며 프로그램을 작성하는 것이 중요하다

+ Recent posts