모두를 위한 컴퓨터 과학(CS50 2019) [2. C언어] 강의

David J. Malan (데이비드 J. 말란)의 <모두를 위한 컴퓨터 과학(CS50 2019)> 수강 내용
https://www.boostcourse.org/cs112/joinLectures/41307

2. C언어

1) C 기초

학습목표

C로 “hello, world”를 출력하는 프로그램을 만들 수 있습니다.

핵심키워드

  • studio.h
  • clang
  • 컴파일러

C언어

#include <stdio.h>

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

C : 아주 오래되고 전통적인 순수 텍스트 기반의 언어

#include <studio.h>

  • int main(viod) : 스크래치의 when clicked 블록과 같은 역할
    → '시작한다'의 의미를 가진 역할
  • printf() : 스크래치의 say 블록과 같은 역할
  • 글자나 단어, 문장을 적을 때는 언제나 텍스트에 "" 표기
  • 문장의 끝에는 마침표로 C에서는 세미콜론(;)을 표기
  • \n : 줄바꿈 기능, 키보드에서 ENTER의 기능과 동일

💡 파일저장 : C로 작성한 코드는 확장자 "c"를 붙여서 "파일이름.c"로 저장

컴파일러

  • source code : 우리가 직접 작성한 코드
    • ex) C, Python, Java, C++ etc
  • machine code : 컴퓨터가 실제로 이해하는 '0','1'의 조합
  • compiler : 소스코드로부터 머신코드를 얻기 위해서 번역을 수행하는 알고리즘 혹은 소프트웨어를 지칭
    • clang : 코드를 컴파일하는 프로그램의 이름
      → clang을 사용해서 소스코드를 머신코드로 번역가능

Clang hollo.c

  • 터미널창의 명령어 프롬프트에서 $ 기호 옆에 우리가 원하는 명령어 입력
  • "clang"이라는 컴파일러로 "hello.c"라는 코드를 컴파일하라는 의미
    → a.out이라는 파일 생성

./a.out : 컴퓨터가 현재 디렉토리에 있는 a.out이라는 프로그램을 실행


2) 문자열

학습목표

C로 문자열 형식을 가진 변수를 선언하고 출력하는 프로그램을 만들 수 있습니다.

핵심키워드

  • 형식지정자
  • string
  • make

string
스크래치 : ask 함수 = CS50 Sandbox : get_string 함수
String은 단어나 구절, 문장을 부르는 말(숫자와는 다른 종류의 데이터)

string answer = get_string("what's your name?\n");

💡 할당 연산자
프로그래밍 언어에서 '=' 오른쪽에 있는 것을 왼쪽에 지정한다는 의미로 생각

stting answer = get string("what's your name?\n");
printf("hello, %s\n", answer);

형식지정자
c 언어는 오래된 언어이기 때문에 변수가 저장하는 데이터의 종류를 아주 정확하게 명시해야되기 때문에 화면상에 출력하고자 하는 구절이나 문장을 적는데 그 값이 아직 모른다면 %와 문자열을 의미하는 s를 적어 형식지정자를 사용

  • answer이라는 변수에 들어있는 이름을 출력하려면 %를 사용
  • 문자열을 받기 때문에 string에서의 s를 %뒤에 붙여서 인자를 받는다

make

#include <stdio.h>

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

위 프로그램을 컴파일(소스코드에서 머신코드로 번역) 하려면
$clang string.c

  • 기본설정으로 인해 프로그램 이름이 a.out으로 저장
  • 앞에 -o string을 입력하여 프로그램 이름을 string으로 지정

$clang -o string string.cError

  • 컴퓨터가 string이라는 문자열 형식과 get_string이라는 함수에 대한 코드를 모르기 때문에 에러가 발생

  • string 문자열 형식과 get_string 함수 코드를 아는 cs50.h를 소스코드에 추가하고 명령어 -lcs50으로 연결

    • 소스코드 - #include <cs50.h>
    • 컴파일 - -lcs50
    #include <cs50.h>
     #include <stdio.h>
    
     int main(void)
     {
         string answer = get_string("What's your name?\n");
         printf("hello, %s\n", answer);
     }

    $clang -o string string.c -lcs50

하지만 이 모든 내용이 솔직히 말하면 쓸데없이 복잡하고 어렵기때문에 외우는 대신 개념만 이해하도록 하고 프로그램을 만들어 달라는 명령어를 사용

$make string

  • make라는 명령어는 알아서 어떤 인자를 사용해야 할 지 파일명은 무엇으로 할 지 다른 사람들은 어떤 라이브러리나 코드를 연결했는지 등을 찾아준다

3) 조건문과 루프

학습목표

조건문과 루프를 c로 작성할 수 있습니다.

핵심키워드

  • int
  • if
  • while
  • for

int

int counter = 0;

int : 변수가 정수(integer)라는 것을 알림
counter : 변수의 이름
0 : 값에 0을 저장

💡 구문설탕Syntax Sugar
컴퓨터 프로그래밍에서 구문을 변경하여 사람이 이해 하고 표현하기 쉽게 코딩할 수 있도록 하기 위해 사용

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

if

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

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 to y\n"); 
}

💡 할당연산자와 일치연산자 구분
할당연산자는 등호표시를 하나만 사용하고 일치연산자는 등호표시를 둘을 사용

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

: x가 y보다 작지도 크지도 않으면 남은 유일한 가능성이 x와 y가 같다는 것이기 때문에 더 간결하게 표현이 가능하다

→ 얼마나 효율적으로 메모리나 CPU를 적게 사용해서 코딩을 하는 것이 중요!!

루프
(1) 무한 반복

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

(2) 특정 횟수

  • while문
    int i = 0;
     while (i < 50)
     {
         printf("Hello, world\n");
         i++;
      }
  • for문
    for (int i = 0; i < 50; i++)
     {
         printf("Hello, world\n");
      }

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

학습목표

다양한 데이터 타입과 형식 지정자를 나타내는 방법을 학습합니다.
다양한 연산자를 이용하여 조건문을 표현하는 방법을 학습합니다.

핵심키워드

  • char
  • long
  • float
  • double
  • %
  • &&
  • ||

데이터 타입
아래 목록은 변수의 데이터 타입으로 사용할 수 있는 것들

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

CS50 라이브러리 내의 get 함수
데이터 타입을 입력값으로 받을 수 있는 아래와 같은 함수들을 포함

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

형식 지정자
printf 함수에서는 각 데이터 타입을 위한 형식 지정자를 사용 가능

  • %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("You are at least %i days old.\n", days);
}

더 간단하게 코드 작성

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

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

더 간단하게 코드 작성

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

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

✅ 극단적으로 줄여버린 코드는 디자인적 측면에서나 가독성에서나 좋지 못하여 이전 코드가 나은 선택

실수 데이터 받아서 출력

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

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

💡 소수점 2자리까지만 보려면 형식 지정자 %.2f를 사용한다

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

int main(void)
{
    float price = get_float("What's the price?\n");
    printf("Your total is %.2f", 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에서는 //로 주석이 가능

//주석입니다.
  • 기능적으로는 아무 의미 없지만 일종의 포스트잇
  • 코드를 보는 모든 사람들을 위해 이 코드가 무슨 일을 하는지 설명하는 글
  • 주석으로 잘 설명하는 습관이 중요

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

학습목표

사용자 정의 함수와 중첩 루프를 작성할 수 있습니다.

핵심키워드

  • 사용자 정의 함수
  • 중첩 루프

사용자 정의 함수
'cough'라고 3번 말하는 C 프로그램을 작성하려면?

  1. 기본적인 작성

    #include <stdio.h>
    
     int main(void)
     {
         printf("coungh\n");
         printf("coungh\n");
         printf("coungh\n");
     }
  2. for문으로 작성

    #include <stdio.h>
    
     int main(void>
     {
         for(int i = 0; i < 3; i++)
         {
             print("cough\n");
         }
     }
  3. 우리만의 함수(사용자 정의 함수)

    #include <stdio.h>
     
     void cough(void)
     {
     	ptintf("cough\n");
     }
     
     int main(void)
     {
     	for (int i = 0; i < 3; i++)
       {
       		cough();
       }
     }
    • 위 코드는 함수를 여러 개 만들수록 main 함수가 아래로 내려가기 때문에 문제가 발생
      → void cough(void)를 세미클론과 함께 올려서 C를 속이는 방법으로 해결
    #include <studio.h>
    
     void cough(void);
    
     int main(void)
     {
         for (int i = 0; i < 3; i++)
         {
             cough();
         }
     }
    
     void cough(void)
     {
         printf("cough\n");
     }
    • cough 함수를 좀 더 다재다능하게 만들 수 있다.
      → void cough(int n){...} : 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);
// 1번
int main(void)
{
    int i = get_positive_int(); // 2번, 3번
    printf("%i\n", i);
}
// 4번, 5번
int get_positive_int(void) // 6번, 7번
{
    int n; // 8번
    do // 9번
    {
        n = get_int("Positive Integer: ");
    }
    while (n < 1);
    return n;
}

// 1번 : get_positive_int 함수는 CS라이브러리에 없는 함수
// 2번 : 입력을 받지 못 했기 때문에 괄호 안이 비었음
// 3번 : 아무 양의 정수를 받으면 된다
// 4번 : get_postive_int 함수가 무언 가를 반환하게 하기 위해 int
// 5번 : int get_positive_int(void)에서 함수의 좌측 int는 출력의 종류
// 6번 : int get_positive_int(void)에서 괄호 안의 void는 입력의 종류
// 7번 : 만약 입출력이 없다면 void를 사용
// 8번 : int n;은 어떤 값을 저장할 지 아직 모르기 때문에 임시 표기
→ garbage value을 가지게 된다
// 9번 : do-while 루프
불리언 표현 while(n<1);이 참일 때 수행
→ n이 1보다 작으면 계속해서 질문을 반복

💡 while문과 do-while문의 차이

  • while문은 조건이 참이어야만 수행
  • do-while문은 do에서 무조건 한 번은 먼저 수행

중첩 루프
예시 1. ????블록

  • 1) 보이는 대로 코드

    // Prints a row of 4 question marks
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("????\n");
    }
  • 2) 중첩 루프를 사용한 코드

    // Prints a row of n question marks with a loop
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int n;
        do
        {
            n = get_in("width: ");
        }
        while (n < 1);
        for (int i = 0; i < n; i++)
        {
            printf("?");
        }
        printf("\n");
    }

예시2. 2차원 공간

#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에 저장
    → do{}while()을 사용하면 조건과 상관없이 최소한 한 번은 {}안의 내용을 실행할 수 있기 때문에
  • for 루프를 두 번 중첩해서 돌면서 "#블록"을 출력
    • 외부루프 : 변수 i를 기준으로 n번 반복 : 세로 "#블록" 생성
    • 내부루프 : 변수 j를 기준으로 n번 반복 : 가로 "#블록" 생성
      → 내부루프에서 "#블록"을 출력하고, 내부루프가 끝날 때마다 외부루프에서 줄바꿈을 실행하면서 최종적으로 가로 n개, 세로 n개인 "#블록"이 출력

6) 하드웨어의 한계

학습목표

메모리의 용량이 프로그램의 구동에 미치는 영향을 설명할 수 있습니다.

핵심키워드

  • 메모리
  • 오버플로우

컴퓨터는 RAM(랜덤 엑세스 메모리)이라는 물리적 저장장치를 포함
즉, 우리가 작성한 프로그램은 구동 중에 RAM에 저장
But 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자리까지 출력(%.50f)하고, x에 1, y에 10을 입력하면 다음 결과가 나온다

x: 1
y: 10
x / y = 0.10000000149011611938476562500000000000000000000000

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

정수 오버플러우
1부터 시작하여 2를 계속 곱하여 출력하는 프로그램

#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
...

✅ 정수를 계속 키우는 프로그램에서 10억을 넘기자 앞으로 넘어갈 1의 자리가 없어진 것
why? int에서는 32개의 비트가 다였기 때문, 그 이상의 숫자는 저장불가

실제 오버플로우 사례

  • 1990, Y2K 문제
    연도를 마지막 두 자리수로 저장했던 관습때문에 새해가 오면 '99'에서 '00' 으로 정수 오버플로우가 발생했는데, 새해가 2000년이 아닌 1900년으로 인식된다는 문제

✅ 수백만 달러를 투자해서 프로그래머들에게 더 많은 메모리를 활용해서 해결

  • 비행기 보잉 787 문제
    구동 후 248일이 지나면 모든 전력을 잃는 문제 → 강제로 안전 모드로 진입
    248일 1/100초로 계산하면 대략 232
    보잉을 설계할 때 사용한 변수보다 너무 커졌던 것

✅ 주기적으로 재가동하여 변수를 0으로 리셋

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


Quiz 2

좋은 웹페이지 즐겨찾기