본문 바로가기

NOTES/CS50

[CS50] Chapter 2. C언어

최초작성일: 2023-01-27

부스트코스 <모두를 위한 컴퓨터 과학 (CS50 2019)> 강의 수강

2. C언어

1. C 기초

  • C로 "hello, world"를 출력하는 프로그램을 만들 수 있다.
  • stdio.h, clang, 컴파일러

C언어

#include <stdio.h> //필요한 것. 없으면 컴퓨터가 printf를 이해하지 못함
//'stdio.h'라는 이름의 파일을 찾아 'printf' 함수에 접근할 수 있게 함

int main(void) //스크래치의 '초록 깃발을 클릭했을 때' 블록. '시작'의 의미
{
    printf("hello, world\n");
    //스크래치 '말해라' 블록
    //개행 없으면 터미널에서 줄바꿈 안된 채로 나타남 
    //텍스트는 큰따옴표 안에. 문장 끝에는 세미콜론(;)
    /*이 사이에 작성되는 것도 주석*/
}
  • C는 아주 오래되고 전통적인 순수 텍스트 기반의 언어
  • 파일 이름은 파일이름.c로 저장

컴파일러

  • 소스코드를 입력으로 받아 머신코드라는 출력을 내보내는 프로그램이 필요
  • 소스코드: 프로그래밍 언어로 작성된 코드
  • 머신코드(기계어): 컴퓨터가 실제로 이해하는 0과 1의 조합
  • 번역을 수행하는 소프트웨어가 컴파일러

터미널 명령어

  • clang: 코드를 컴파일하는 프로그램. 컴파일러.
$ clang hello.c             - clang으로 hello.c를 컴파일하라
                            - a.out(assembler output) 생성
$ ./a.out                   - 현재 디렉토리에 있는 `a.out` 프로그램 실행
$ clang -o hello hello.c    - hello.c를 컴파일한 hello라는 파일 생성
$ ls                        - 현재 폴더 내 파일 목록 보여줌
a.out* hello.c              - *는 실행가능, 즉 머신코드라는 의미
$ rm a.out                          - a.out 지우기
rm: remove regular file 'a.out'?    - 지울 거냐고 물어봄. 'y'나 'yes' 입력하면 지워짐. 다른 것 입력하면 그냥 넘어감.

2. 문자열

  • C로 문자열 형식을 가진 변수를 선언하고 출력하는 프로그램을 만들 수 있다.
  • 형식지정자, string, make

이름을 입력받아 인사하는 프로그램

#include <cs50.h> //string이라는 형식과 get_string이라는 함수에 대한 코드 포함
#include <stdio.h>

int main(void)
{
    //변수 answer. string이라고 데이터의 종류를 명시해줘야 함.
    string answer = get_string("What's your name?\n"); //이때 string은 형식지정자
    printf("hello, %s\n", answer); //'%s'는 형식지정자. string 의미.
    //'%s'자리에 string이 들어가는데 그것은 뒤에 넣어 준 answer라는 변수의 내용
}
  • 프로그래밍 언어에서 =할당연산자: 오른쪽에 있는 것을 왼쪽에 지정한다는 뜻
  • get_string 함수가 사용자의 이름을 반환하면 그 이름을 answer라는 변수에 저장
$ clang -o string string.c -lcs50
string.c 파일을 컴파일하여 string이라는 머신코드로 저장
-l은 'link'의 의미. 컴파일시 cs50 파일을 연결하라
  • 코드에만 cs50 파일을 보라고 하는 것이 아니라 컴파일할 때에도 cs50의 내용을 추가하도록 해야.
  • cs50 또한 C로 작성되어 있을 것이고 기계어로 번역된 것이 어딘가에 있을텐데 그것과 연결되어야 두 코드가 한 프로그램으로 실행될 수 있음.
$ make string
  • 간단하게 컴파일 수행
  • 컴퓨터가 알아서 파일을 찾아 연결시켜 컴파일한다.

3. 조건문과 루프

  • 조건문과 루프를 C로 작성할 수 있다.
  • int, if, while, for

변수 생성, 값 저장

  • counter라는 변수에 숫자를 저장하고 싶음
  • C는 저장하고자 하는 변수의 종류를 알려줘야.
  • int: 변수가 정수(integer)라는 것을 알려줌
int counter = 0; //counter라는 변수에 0이라는 값을 저장(초기화, 할당)

counter = counter + 1;  //counter에 1을 더한 값을 다시 counter에 저장(할당)
counter += counter;
counter++;              // 모두 같은 의미

조건문

if (x < y)      //보통 조건과 같은 것의 끝에는 세미콜론으로 끝내지 않음. 
{
    printf("x is less than y\n"); //함수가 들어간 줄들은 세미콜론으로 끝냄.
}
else if (x > y)
{
    printf("x is greater than y\n");
}
else    // else if (x == y) '=='는 '일치 연산자'
{
    printf("x is equal to y\n");
}

반복문(loop)

while (true)    //무한루프
{
    printf("hello, world\n");
}

int i = o;
while (i < 50)  // i < 50이 참이면 계속 수행하라
{
    printf("hello, world\n");
    i = i + 1;
}
for (int i = 0; i < 50; i++)   // (변수 초기화; 변수 조건; 변수 변화)
{
    printf("hello, world\n");
}

조건문: switch문

  • 조건식의 결과값에 따라 매칭되는 case의 코드를 실행
switch (x)
{
    case 1:                 //만약 x가 1이면
        printf("A\n");      //"A\n" 출력한 후
        break;              //switch문 빠져나옴
    case 2:                 //x가 2이면
        printf("B\n");      //"B\n" 출력 후
        break;              //빠져나옴
    default:                //그 외의 경우
        printf("C\n");      //"C\n" 출력 후 switch문 빠져나옴
}

조건문: 3항 연산자

  • 식 하나를 받아 식이 참이면 :기호 왼편의 값으로, 거짓이면 오른편의 값으로 계산
int y = (x > 3) ? 2 : 1;    //x > 3가 참이면 y는 2, 그렇지 않으면 1

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

  • 다양한 데이터 타입과 형식 지정자를 나타내는 방법을 학습한다.
  • 다양한 연산자를 이용하여 조건문을 표현하는 방법을 학습한다.
  • char, long, float, double, %, &&, ||

데이터 타입

bool: 불리언 표현 참 / 거짓
char: single character. 단 하나의 문자
double: 소수점 뒤에 더 많은 숫자를 가질 수 있는 실수. 부동소수점을 포함한 더 큰 실수
float: 실수. 부동소수점을 갖는 실수
int: 정수. 특정 크기를 가짐. 정해진 수까지만 셀 수 있음. 보통 40억 정도
long: int보다 더 많은 비트 사용. 더 높은 수까지 셀 수 있음
string: 문자열 큰따옴표 안에 들어간 한 개 이상의 문자

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; //이걸 없애고 age * 365 형태로 넣는 것도 가능
    printf("Your are at least %i days old.\n", days);
    // age 변수까지 없애고 get_int 함수를 printf 파라미터에 바로 넣는 것도 가능
}
#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);
    // Your total is 106.250000. 출력

    //자릿수 조절
    printf("Your total is %.2f.\n", price * 1.0625);
    // Your total is 106.25. 출력
}

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

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

int main(void)
{
    //사용자로부터 정수 입력받기
    int n = get_int("n: ");

    //입력받는 정수를 2로 나눈 나머지가 0일 경우
    if (n % 2 == 0)
    {
        //"짝수" 출력
        printf("even\n");
    }
    //그렇지 않으면
    else if (n % 2 == 1) // else
    {
        //"홀수" 출력
        printf("odd\n");
    }
}

기타 연산자 및 주석

%: 나머지
||: 'or(또는)'를 의미
&&: 'and(그리고)'를 의미
//: 이 기호 뒤에 이어서 쓰는 내용은 코드로 기능하지 않음. 코드의 설명을 씀.

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

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

사용자 정의 함수

#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 함수명(void)
  • 기능은 잘 알아볼 수 있지만 함수를 여러 가지 만들면 가장 중요한 main 함수 부분은 아래로 내려가게 됨
  • 그렇다고 cough 함수정의 아래로 내리면 에러 발생
  • C는 반드시 위에서 아래로, 왼쪽에서 오른쪽으로 명령을 내려야.
  • 아래에 있을지 모르는 함수를 찾아주지 않음.
#include <stdio.h>

void cough(void); //세미콜론과 함께 함수 이름 붙여넣기

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

void cough(void)
{
    printf("cough\n");
}
  • 전에 cough 함수를 본 것처럼 C를 속이는 방법
  • 함수 내용을 다 보지는 않았지만 이름은 봤으니까 main 함수에 나올 때까지 계속 코드를 읽는 것
#include <stdio.h>

void cough(int n); //함수 프로토타입

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

void cough(int n) //cough 함수 매개변수화
// 함수 이름 왼쪽은 출력의 종류, 오른쪽은 입력의 종류. 없으면 void
{
    for (int i = 0; i < n; i++)
    {
        printf("cough\n");
    }
}
  • 이러면 main 부분만 보고도 어떤 기능을 수행하는지 알 수 있음
  • 원하는 횟수만큼 cough 출력
  • 파일 아래로 내려가면 세부 정의를 확인할 수 있지만 그걸 모르더라도 cough 함수를 활용할 수 있음

원하는 형태로 입력받기

#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를 반환하는 함수
{
    int n;  //어떤 값을 저장할지 몰라 이것만 적음. //n은 garbage value를 가짐
    do      //do-while loop
    {
        n = get_int("Positive Integer: ");
    }
    while (n < 1);  //양의 정수 입력할 때까지 반복
    return n;
}
  • do-while 루프: 적어도 한 번은 do 부분을 수행한 뒤 반복을 계속할지 결정
  • while 루프는 먼저 조건을 확인

중첩 루프

#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++) //i
    {
        for (int j = 0; j < n; j++)
        {
            printf("#");
        }
        printf("\n");
    }
}

6. 하드웨어의 한계

  • 메모리 용량이 프로그램의 구동에 미치는 영향을 설명할 수 있다.
  • 메모리, 오버플로우

메모리 용량은 유한하기 때문에 때때로 부정확한 결과를 낸다.

부동 소수점 부정확성

#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
*/
  • 정확한 결과는 0.1이지만 float에서 저장 가능한 비트 수가 유한하기 때문에 다소 부정확한 결과를 냄
  • 계산할 수 있는 값들 중 1/10에 가장 가까운 값 저장
  • 유한한 정보를 사용해서는 무한한 숫자들을 정확하게 저장할 수 없다.

정수 오버플로우

#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 타입이 저장할 수 있는 수를 넘은 후에는 에러.
  • 10억을 넘기자 앞으로 넘어갈 1의 자리가 없어짐.

float의 정확성이나 정수의 크기에 한계가 있다.

실생활에서의 오버플로우 문제

  • Y2K
    • 연도를 마지막 두 자리수로 저장했던 관습 때문에 2000년이 되면 '99'에서 '00'으로 정수 오버플로우가 발생하고 새해가 1900으로 인식된다는 문제
  • 보잉787
    • 구동 후 248일이 지나면 강제로 안전 모드로 진입, 모든 전력을 잃는 문제
    • 소프트웨어의 변수가 248일이 지나면 오버플로우 발생
    • 248일을 1/100초로 계산하면 대략 2의 32제곱
    • 주기적으로 재가동해 변수를 다시 0으로 리셋

결론

다루고자 하는 데이터 값의 범위를 유의하며 프로그램을 작성해야 한다.

 

'NOTES > CS50' 카테고리의 다른 글

[CS50] Chapter 6. 자료구조  (0) 2023.03.04
[CS50] Chapter 5. 메모리  (0) 2023.02.25
[CS50] Chapter 4. 알고리즘  (0) 2023.02.13
[CS50] Chapter 3. 배열  (2) 2023.02.04
[CS50] Chapter 1. 컴퓨팅 사고  (0) 2023.01.30