CS50 - 2. C언어

1. C 기초

#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
}

// 출력
// hello, world
//

컴파일러

우리가 직접 작성한 코드는 “소스 코드” 라고 불립니다. 이를 2진수로 작성된 “머신 코드”로 변환해야 컴퓨터가 이해할 수 있습니다. 이런 작업을 컴파일러라는 프로그램이 수행해줍니다.

01

clang hello.c 라는 명령어는 “clang” 이라는 컴파일러로 “hello.c”라는 코드를 컴파일하라는 의미입니다.

clang hello.c

02

a.out 이라는 머신코드가 생성됩니다.

./a.out
hello, world$ # 출력

새로운 코드를 생성한다면 재컴파일이 필요합니다. 재컴파일을 해야 적용이 됩니다.

Clang에는 여러 옵션이 있습니다.

clang -o hello hello.c
# hello 생성

2. 문자열

문자열은 쌍따옴표 사이에 있는 0개 이상의 문자를 의미합니다.

#include <stdio.h>

int main(void)
{
  string answer = get_string("What's your name?\n");
  printf("hello, %s\n", answer);
}

하지만 이 코드는 컴파일하지 못합니다.

clang -o string hello.c
hello.c:5:3: error: use of undeclared identifier 'string'; did you mean 'stdin'?
  string answer = get_string("What's your name?\n");
  ^~~~~~
  stdin
/nix/store/4pqv2mwdn88h7xvsm7a5zplrd8sxzvw0-glibc-2.35-163-dev/include/stdio.h:143:14: note: 'stdin' declared here
extern FILE *stdin;             /* Standard input stream.  */
             ^
hello.c:5:9: error: expected ';' after expression
  string answer = get_string("What's your name?\n");
        ^
        ;
hello.c:5:3: warning: expression result unused [-Wunused-value]
  string answer = get_string("What's your name?\n");
  ^~~~~~
hello.c:5:10: error: use of undeclared identifier 'answer'
  string answer = get_string("What's your name?\n");
         ^
hello.c:5:19: warning: implicit declaration of function 'get_string' is invalid in C99 [-Wimplicit-function-declaration]
  string answer = get_string("What's your name?\n");

문자열은 C에선 존재하지 않습니다.

그래서 강의에선 cs50.h 라는 라이브러리를 제공합니다.

#include <cs.50.h>
#include <stdio.h>

int main(void)
{
  string answer = get_string("What's your name?\n");
  printf("hello, %s\n", answer);
}

다음과 같이 컴파일을 실행합니다.

# shell
clang -o string string.c -lcs50
# 더 쉬운 컴파일 방법
make string

make는 리눅스의 명령어로 맥 + 윈도우에도 있는 명령어입니다.

3. 조건문과 루프

int counter = 0;

int 는 변수가 정수(integer)라는 것을 알려주는 것이고, counter는 변수의 이름, 0은 그 값에 0을 저장(초기화)하는 것입니다.

증가하는 방법

counter = counter + 1;
counter += 1;
counter++;

조건문

if (x < y)
{
	printf("x is less than y\n");
}
else
{
	printf("x is not less than y\n");
}

또다른 조건문

if (x < y)
{
	printf("x is less than y\n");
}
else if (x > y)
{
	printf("x is greater than y\n");
}
else if (x == y)
{
	printf("x is equal y\n");
}

반복문

while (true)
{
	printf("hello, world\n");
}

뭔가를 50번 정도 반복하고 싶다면

int i = 0;
while (i < 50)
{
	printf("hello, world\n");
	i++;
}

for (int i = 0; i < 50; i++)
{
	printf("hello, world\n");
}

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

데이터 타입

  • bool
  • char
  • string
  • int
  • long
  • float
  • double

형식 지정자

여러가지 데이터 타입마다 사용되는 형식 지정자

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

정수와 실수를 받아서 출력하기

정수 출력

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

int main(void)
{
  int age = get_int("what's your age?\n");
  int days = age * 365;
  printf("Your are at least %i days old.\n", days);
}

실수 출력

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

int main(void)
{
    float price = get_float("What's the price?\n");
    printf("Your total is %f\n", price*1.0625);
}
// 소수점 2번째까지 표현하고 싶다면?
printf("Your total is %.2f \n", price*1.0625);

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

사용자 정의 함수

#include <stdio.h>

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

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

나만의 함수 만들기

#include <stdio.h>

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

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

함수를 1개가 아닌 여러개를 만들수록 main 함수는 아래로 내려가기 때문입니다.

중요한 것이 아래에 있는 것보다 바로 나오는 것이 보기 좋습니다.

그런데 cough함수는 아래에 있습니다.

C는 오래되었고 똑똑하지 않기 때문에 아래에 cough라는 함수가 있을 것이라 생각하지 못하는 것이죠. 이 것을 해결하려면 다시 cough함수를 위로 올려야합니다.

물론 이것은 악순환의 반복일 것입니다. 영원히 새로운 함수를 위에 올릴 수 없으니까요. 그래서 다른 방법이 있습니다.

#include <stdio.h>

void cough(void);

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

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

n값을 받아 처리할 수도 있습니다.

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

다음은 get_positive_int 함수로 CS50라이브러리에 없는 함수입니다.

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

int get_positive_int(void);

int main(void)
{
    int i = get_positive_int();
    printf("%i\n", i);
}

int get_positive_int(void)
{
    int n;
    do
    {
        n = get_int("Positive Integer: ");
    }
    while (n < 1);
    return n;
}

int n; 은 컴퓨터에게 n이라고 하는 변수를 달라는 힌트입니다. 그 안에 어떤 값을 저장할지 모르기 때문에 int n; 만 적습니다.

n은 쓰레기 값(Garbage Value)이라고 부르는 값을 가지게 됩니다.

중첩 루프

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

6. 하드웨어의 한계

컴퓨터는 RAM(랜덤 액세스 메모리)이라는 물리적 저장장치를 포함하고 있습니다. 우리가 작성한 프로그램은 구동 중에 RAM에 저장되는데요, RAM은 유한한 크기의 비트만 저장할 수 있기 때문에 때때로 부정확한 결과를 내기도 합니다.

부동 소수점 부정확성

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

int main(void)
{
    // 사용자에게 x 값 받기
    float x = get_float("x: ");

    // 사용자에게 y 값 받기
    float y = get_float("y: ");

    // 나눗셈 후 출력
    printf("x / y = %.50f\n", x / y);
}

// 출력
//x: 1
//y: 10
// x / y = 0.10000000149011611938476562500000000000000000000000

float에서 저장 가능한 비트 수가 유한하기 때문에 다소 부정확한 결과가 발생합니다.

컴퓨터에서는 왜 소수점을 정확하게 계산을 하지않을까?

실수를 표현하는 방식으로 고정 소수점 방식과 부동 소수점 방식이 있습니다.

고정 소수점 방식은 소수점 이상 또는 소수점 이하를 지정하여 처리하는 방식입니다. 소수부의 자리수를 정하여 고정된 자리수의 소수를 표현합니다.

고정 소수점 방식은 제한된 자릿수로 인하여 표현 가능한 실수의 범위와 정밀한 정도가 제한적이라 잘 사용되지 않습니다.

부동 소수점 방식은 상대적으로 훨씬 더 넓은 범위까지 값을 표현할 수 있습니다. 그러나 소수점 단위 값을 명확히 표현하는 것이 아니라 근사값으로 처리하기 때문에 오차가 발생합니다.

부호비트, 지수(8비트), 가수(23비트)가 존재하며 Java에서는 BigDecimal 클래스를 제공하고 있어 소수점을 다루는 연산을 하게 된다면 BigDecimal 클래스를 사용하면 됩니다.

정수 오버플로우

#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개의 비트가 전부이기 때문에 10억을 넘기자 앞으로 넘어갈 1의 자리가 없어진 것입니다.

출처


Written by@Sunny Son
개발자는 오늘도 뚠뚠

GitHubFacebook