[CS50] C언어

업데이트:




C 기초

  • C는 오래되고 전통적인 순수 텍스트 기반의 언어다.
#include <stdio.h>
int main(void){
    printf("hello, world");
}
  • 위의 코드에서 int main(void)는 시작한다는 의미를 갖는다.
  • printf("hello, world")는 글자나 단어, 문장을 적을 때는 텍스트에 " " 쌍따옴표로 감싸야한다.
  • C에서는 한 줄마다 세미콜론(;)을 붙여야 한다.
  • \n은 줄바꿈의 기능을 한다. (키보드 엔터와 동일)
  • #include <stdio.h>는 ‘stdio.h’라는 이름의 파일을 찾아서 ‘printf’ 함수에 접근할 수 있도록 해준다.
  • C로 작성한 코드는 파일이름.c로 저장해야한다.
  • 위에서 작성한 코드는 소스 코드라고 불리운다.




컴파일러

  • 소스 코드는 2진수로 작성된 머신 코드로 변환해야 컴퓨터가 이해할 수 있다. 이런 작업을 컴파일러가 수행한다. 컴파일러1
  • 터미널창의 명령어 프롬프트에서 $ 기호 옆에 명령어를 입력한다.
  • clang hello.c 명령어는 clang이라는 컴파일러로 hello.c라는 코드를 컴파일하라는 의미다.
  • 결과로 a.out 파일이 생성된다.
  • ./a.out 명령어는 컴퓨터가 현재 디렉토리에 a.out이라는 프로그램을 실행하게 해준다.




문자열

string answer = get_string("what's your name?\n");
  • 스크래치 ask 함수와 가장 비슷한 함수는 get_string 함수이다.
  • String은 단어나 구절, 문장을 부르는 말이다.
  • 사용자 이름을 받아서 저장할 변수를 스크래치와 같이 answer라고 정했다.
  • 변수의 이름은 마음대로 정할 수 있다.
  • C는 데이터의 종류를 아주 정확하게 명시해줘야한다. 여기서는 문자열(string)이라는 걸 알려줘야 한다. 이때 string을 형식지정자라고 한다.
  • =는 프로그래밍 언어에서는 오른쪽에 있는 것을 왼쪽에 지정(대입)한다. 이를 할당 연산자라고 한다.
  • get_string 함수가 사용자의 이름을 반환하면 그 이름을 answer라는 변수에 저장한다.
  • 위의 코드로 인해 컴퓨터 메모리 어딘가에 사용자 이름이 저장된다.

string answer = get_string("what's your name?\n");
printf("hello, %s\n", answer);
  • answer에 들어있는 이름을 출력하기 위해서는 %와 인자를 받는지 적어줘야한다.
  • 문자열 string을 받기 때문에 s를 % 뒤에 붙여 %s로 인자를 받아준다.
  • 무슨 파일이 있다는데 못찾아서 댓글 참고해서 적었다.
#include <stdio.h>
#include <cs50.h>

int main(void){
    string answer = get_string("what's your name?\n");
    printf("hello, %s\n", answer);
}
  • string.c 파일 생성 후 위의 코드를 작성하고 터미널에 다음과 같이 입력한다.
    $ clang -o string string.c -lcs50
    
  • -o string은 string.c를 string.out이라는 머신코드로 저장하도록 하는 명령어이다. 즉 기본은 a.out인데 파일명을 따로 지정해주는 명령어다.
  • -lcs는 link라는 의미를 지닌 -l 이라는 인자에 추가로 포함한 cs50파일을 합친 것이다. 이를 통해 컴파일 시 cs50 파일을 연결하도록 알려줄 수 있다.
  • 위의 복잡한 과정 대신에 make 명령어를 통해 간단히 컴파일을 수행할 수도 있다.
    $ make string
    




조건문과 루프

int counter = 0;


  • 위의 코드는 counter 변수에 숫자를 저장한다. 여기서 int는 변수가 정수(Integer)라는 것을 알려주고, counter는 변수 이름, 0은 counter 변수에 0을 저장(초기화)한다.
  • counter 변수에 1씩 값을 증가시키고 싶다면?
    counter = counter + 1;
    혹은
    counter++;
    
  • 조건문1
    위의 스크래치 조건문을 C로 작성하면?
    if(x < y){
    printf("x is less than y\n");
    }
    
  • else를 사용하면 처음 조건이 아닌 경우에 어떤 것을 하라라고 적어줄 수 있다.
    if(x < y){
    printf("x is less than y\n");
    } else {
    printf("x is not less than y\n");
    }
    
  • else if를 통해서 조건을 더 추가할 수도 있다.
    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 to y\n");
    }
    
  • ==는 일치 연산자이다. 좌항 우항에 있는 값이 같으면 true, 다르면 false를 반환한다.
  • 위의 코드를 더 효율적으로 바꾸면 아래와 같다.
    if(x < y){
    printf("x is less than y\n");
    } else if( x > y) {
    printf("x is greater than y\n");
    } else {
    printf("x is equal to y\n");
    }
    



루프

  • C에서 while이나 for를 통해서 루프를 구현할 수 있다.
while (true) {
  printf("hello, world\n");
}
  • while의 경우 while 괄호 안에 조건을 넣고 중괄호({}) 안에 수행할 작업을 포함시킨다.
  • C에서 루프를 구현하고 싶다면 성립 조건을 정해줘야 한다.
  • 위의 코드에서는 true라는 항상 참이 되는 조건을 통해 while 루프가 영원히 수행된다.
  • 특정 횟수만큼 출력하고 싶으면?
    int i = 0;
    while (i "< 50){
    printf("hello, world\n");
    i = i + 1;
    }
    
    • i는 0으로 설정 -> i는 50보다 작은가? -> 작다 -> hello world를 출력한다 -> i를 1증가시킨다 -> i가 50보다 작은가? -> (반복) -> i가 50보다 작은가? -> 작지 않다 -> 종료


  • for를 사용하면 () 안에 각각 (변수 초기화; 변수 조건; 변수 증가)에 해당하는 코드를 넣어 간단하게 반복문을 표현할 수 있다.
    for(int i = 0; i < 50; i = i + 1){
    printf("hello, world\n");
    }
    
  • 가장 먼저 정수 값을 가지는 i라는 변수를 0으로 초기화, i가 50인지 매번 검사하고, 이를 만족하면 {} 안에 내용을 수행한 후 i를 1씩 증가시킨다.




데이터타입

  • bool: 불리언 표현, (예) True, False, 1, 0, yes, no
  • char: 문자 하나 (예) ‘a’, ‘Z’, ‘?’
  • string: 문자열
  • int: 특정 크기 또는 특정 비트까지의 정수 (예) 5, 28, -3, 0
  • long: 더 큰 크기의 정수
  • float: 부동소수점을 갖는 실수 (예) 3.14, 0.0, -28.56
  • double: 부동소수점을 포함한 더 큰 실수


CS50 라이브러리 내의 get 함수

  • get_char
  • get_double
  • get_float
  • get_int
  • get_long
  • get_string




형식 지정자

  • %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);
}
  • 그리고 printf 함수에 이번에는 문자가 아닌 정수이기 때문에 %i로 days의 인자를 받아 출력한다.


  • 좀 더 간단하게 작성하기
# include <cs50.h>
# include <stdio.h>

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


  • 더 더 간단하게 작성하기
# include <cs50.h>
# include <stdio.h>

int main(void)
{
    printf("Your are at least %i days old.\n", get_int("what's your age?\n") * 365);
}
  • 많이 짧아지긴 했지만 너무 길어서 가독성이 떨어진다.


실수(float) 사용

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

실수사용


  • 소수점이 너무 길어 보기 안 좋으므로 두 번째 자리까지만 나오도록 수정한다.
    printf("Your total is %.2f \n", price*1.0625);
    




짝수인지 홀수인지 알려주는 코드 짜기

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

int main(void)
{
    int n = get_int("n: ");

    if (n % 2 == 0)
    {
        printf("even\n");
    }
    else
    {
        printf("odd\n")
    }
}




주석

  • C에서는 //로 주석을 달 수 있다.
    // 주석
    
  • 주석은 내가 짠 코드를 처음보는 사람들에게 설명할 때 사용한다.




사용자 정의 함수

“cough”라고 세 번 말하기 위해선 아래처럼 작성할 수 있다.

#include <stdio.h>

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

단순히 printf를 세 번 반복하면 되지만, 동일한 작업을 반복하는 것이기 때문에 사용자 정의 함수를 이용하면 아래 코드와 같이 더 단순화 할 수 있다.

#include <stdio.h>

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();
    }
}

void를 입력하고 원하는 함수명(cough)을 적은 뒤 괄호 안에 void를 적는다.
int main(void)의 안에 cough(함수명)를 사용하면 cough가 출력된다.


메인 함수와 cough함수의 위치를 바꾸면 에러가 발생한다. C는 오래되었고 똑똑하지 않기 때문에 메인 아래에 cough 함수가 있을 거라 생각을 못한다. 이럴 땐 아래 방법을 사용하면 된다.

#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)를 세미콜론과 함께 위로 올리면 된다.

cough 함수를 좀 더 수정해 원하는 횟수만큼 cough를 출력할 수 있도록 해보자.

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



좀 더 쉬운 예제

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

get_positive_int 함수는 CS50 라이브러리에 없는 함수이다.
아래 int get_positive_int(void)를 보면 괄호 안에 아무것도 넣을 필요가 없다. 아무 양의 정수나 받으면 된다. 하지만 이 함수가 뭔가를 반환하게 하고 싶으면 int get_positive_int(void), void가 아니고 int가 된다. 함수 왼쪽에 있는 단어는 출력의 종류를 의미한다. int get_positive_int(void)에서 괄호 안 부분은 입력의 종류를 뜻한다.

int n;은 컴퓨터에게 n이라고 하는 변수를 달라는 일종의 힌트이다.
그 안에 어떤 값을 저장할지 아직 모르기 때문에 그냥 int n;만 적는다.
이러면 n에는 쓰레기 값(Garbage Value)을 가지게 된다.

do-while은 while(n<1); 이 참일때 다음을 수행하라는 뜻이다.
만약 n이 1보다 작다면 계쏙해서 질문을 반복하는 것이다.
while을 단독으로 사용하면 while의 조건이 참이어야만 수행한다.
하지만 do-while은 do에서 무조건 한 번은 먼저 수행하게 해준다.




중첩루프

#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 함수로 사용자가 입력 값을 받아 n에 저장한다.

그리고 for 루프를 두 번 중첩해서 돌면서 ‘#’ 을 출력한다. 첫 번째 루프에서는 변수 i를 기준으로 n번 반복하고, 그 안의 내부 루프에서는 변수 j를 기준으로 n번 반복한다. 내부 루프에서는 ‘#’을 출력하고, 내부 루프가 끝날 때마다 줄바꿈을 수행한다. 따라서 최종적으로는 가로가 n개, 세로가 n개인 ‘#’이 출력되게 한다.




하드웨어의 한계

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


부동 소수점 부정확성

  • 실수 x, y를 인자로 받아 x 나누기 y를 하는 프로그램이 있다.
#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);
}

나눈 결과를 소수점 50자리까지 출력하기로 하고, x에 1을, y에 10을 입력하면 아래와 같은 결과가 나온다.

x: 1
y: 10
x / y = 0.10000000149011611938476562500000000000000000000000

정확한 결과는 0.1이 되어야 하지만, float에서 저장 가능한 비트 수가 유한하기 때문에 다소 부정확한 결과를 내게 된다.



정수 오버플로우

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

int main(void)
{
    for (int i = 1; ; i *= 2)
    {
        printf("%i\n", i);
        sleep(1);
    }
}

변수 i를 int로 저장하기 때문에 2를 계속 곱하다가 int 타입이 저장할 수 있는 수를 넘은 이후에는 에러와 함께 0이 출력된다.

...
1073741824
overflow.c:6:25: runtime error: signed integer overflow: 1073741824 * 2 cannot be represented in type 'int'
-2147483648
0
0
...


int에서는 32개의 비트가 다였기 때문에 그 이상의 숫자는 저장할 수가 없다.

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



퀴즈

태그:

카테고리:

업데이트:

댓글남기기