2. C

jaegeunsong97·2022년 11월 14일
0

[Harvard] CS 50

목록 보기
2/7
post-thumbnail

참고: https://sandbox.cs50.io

1) C 기초

키워드 >> stdio.h / clang / compiler.

c언어

#include <stdio.h>
int main(void)
{
    printf("hello, world\n");
}

C는 아주 오래되고 전통적인 순수 텍스트 기반의 언어입니다.
여러분들 중에서는 C를 이미 공부하신 분들도 있을 수 있고,
처음 접하하는 분들은 이런 이상한 영어들이 적혀있는 것을 보면 두려움이 생기실 수도 있습니다.
하지만 앞으로 있을 강의를 차근차근 듣다보면 위의 모든 코드는 물론이고, 그 이상을 이해하실 수 있을 것입니다.
우선 검은색 바탕에 있는 이상한 글씨들은 아래의 스크래치 블록과 결과적으로 정확히 같습니다.

하나하나 설명하자면 int main(void) 는 스크래치의 “초록색 깃발을 클릭했을 때” 블록과 같은 역할을 합니다.
즉 '시작한다'의 의미를 가지고 있다고 보면 됩니다.
앞으로 우리가 작성할 코드 모두는 이 int main(void) { }의 중괄호 사이에 작성하게 될 것 입니다.

C에서는 스크래치에서의 say라는 함수는 없습니다. 대신에 printf라는 함수가 있습니다.
printf(“hello, world\n”) 은 스크래치의 “‘hello, world’라고 말하기” 블록과 같은 역할을 합니다.
글자나 단어, 문장을 적을 때는 언제나 텍스트에 " " 쌍따옴표로 감싸야 합니다.
그리고 우리가 일상에서 문장의 끝에 마침표(.)를 붙이는 것 처럼 C에서는 세미콜론(;)을 붙여야 합니다.
(아래 사진파일에는 나와있지 않지만 \n은 줄바꿈의 기능을 합니다. 키보드에서 ENTER의 기능과 동일합니다.)
위의 여러가지 기능들은 C에서 중요하게 사용되는 기본적인 내용이므로 꼭! 기억해주세요.

#include <stdio.h>는 “stdio.h”라는 이름의 파일을 찾아서 “printf” 함수에 접근할 수 있도록 해줍니다.
이게 무슨 말일까? 라고 생각이 드실 거라고 생각이 듭니다.
하지만 이것은 지금 크게 중요하지 않습니다.
오늘 제일 중요한 것은 중간에 있는 printf("hello, world")입니다.
우리가 Word로 문서를 저장했을때 "문서.docx"와 같이 .docx가 붙는 것 처럼,
C로 작성한 코드는 “파일이름.c”로 저장해야 합니다. (확장자 “.c”는 C로 작성된 코드라는 의미입니다.)
마이크로소프트의 Word 처럼 자동적으로 붙여주지 않기 때문에 C의 경우에는 직접 .c를 붙여줘야 합니다.

컴파일러

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

터미널창의 명령어 프롬프트에서 “$” 기호 옆에우리가 원하는 명령어를 입력하면 됩니다.
clang hello.c 라는 명령어는 “clang” 이라는 컴파일러로 “hello.c”라는 코드를 컴파일하라는 의미입니다.
그 결과 a.out 이라는 파일이 생성됩니다.
./a. out 이라는 명령어를 실행하면 컴퓨터가 현재 디렉토리에 있는 a.out이라는 프로그램을 실행하게 해줍니다.
(./a. out에서 제일 앞에 있는 .은 지금 있는 현재 폴더를 나타냅니다.)
참고) 왜 저는 줄바꿈 할때 (백슬래시)가 ₩(원화)로 보이는 것이죠?
-> 해당 문제는 '한글 윈도우' 운영체제에서만 생기는 현상입니다.
한글 윈도우에서는 \를 ₩로 표시를 해주기 때문입니다. 따라서 ₩로 표시가 되어도 문제 없습니다.

2) 문자열

키워드 >> 형식지정자 / string / make.

저번 강의에서는 간단하게 Hello World를 출력해보았습니다.
이번 시간에는 좀 더 다이나믹한 것을 해보도록 하죠
스크래치 강의에서 사용자의 이름을 입력으로 받고
그리고 그 사람의 이름을 불러서 인사를 했습니다.
스크래치 블록은 아래와 같았습니다.

그럼 이것을 C로 해보면 어떻게 될까요?
CS50 Sandbox에서는 스크래치의 ask함수와 가장 비슷한 것은 get_string 함수입니다.
String은 단어나 구절, 문장을 부르는 말입니다. (숫자와는 다른 종류의 데이터 입니다.)

사용자의 이름을 받아서 저장할 변수를 스크래치와 같이 answer이라고 정해보겠습니다.
이때 변수는 xyz, name 등과 같이 여러분 마음대로 정하셔도 무관합니다.
하지만 여기서 유의해할 점은 C는 오래된 언어이기 때문에 변수가 저장하는 데이터의 종류를 아주 정확하게 명시해줘야 합니다.
그래서 우리는 저장하고자 하는 값의 종류가 문자열(string)이라는 것을 알려줘야 합니다. 이때 string을 형식지정자라고 합니다.
왜냐하면 너무나 당연하게 이름은 숫자가 아닌 문자이기 때문에 컴퓨터에게 "answer에 들어갈 것들은 문자야!"라고 말해주는 것이죠
string 이외에도 여러가지 종류가 있지만 그 것은 다음에 알아보도록 하겠습니다.
우리가 일반적으로 사용하는 =은 같다 입니다.
하지만 프로그래밍 언어에서는 오른쪽에서 왼쪽으로 가는 화살표와 비슷하다고 생각해주시면 좋습니다.
쉽게 말하자면 오른쪽에 있는 것을 왼쪽에 지정한다는 것이죠. 이를 할당 연산자라고 합니다.
get_string 함수가 사용자의 이름을 반환하면 그 이름을 anwser이라는 변수에 저장하는 것입니다.
오른쪽에서 왼쪽으로 말이죠
이제 컴퓨터의 메모리 어딘가에 사용자의 이름이 저장되어 있는 것입니다.

그럼 이 것을 printf 함수로 출력을 해보도록 하겠습니다.
이때 유의할 점은 printf("hello, answer");이 아니라는 점입니다.
이 코드를 실행한다면 answer이 출력이 되어 hello, answer이 그대로 결과로 나옵니다.
우리는 answer이라는 변수에 들어있는 이름을 출력을 해야하기 때문에 %를 사용해 줍니다.
이 때도 어떤 종류의 인자를 받는지 말해줘야 합니다.
우리는 이름이라는 문자열을 받기때문에 string에서의 s를 %뒤에 붙여서 인자를 받아줍니다.
그래서 최종적으로는 printf("hello, %s\n", answer);이 되는 것입니다.
가장 위에 포함된 cs50.h 파일 안에 string이라는 문자열 형식과 get_string 이라는 함수에 대한 코드가 포함되어 있습니다. 이 파일을 포함해야만 전체 코드를 컴파일 하고 실행할 수 있습니다.
터미널창에 아래 명령어를 입력하여 컴파일을 할 수 있습니다.

$ clang -o string string.c -lcs50

여기서 -o string 은 string.c 를 string.out 이라는 머신코드로 저장하도록 하는 명령어입니다.
-lcs50은 “link”라는 의미를 지닌 -l 이라는 인자에 우리가 추가로 포함한 “cs50” 파일을 합친 것입니다. 이를 통해 컴파일시 cs50 파일을 연결하도록 알려줄 수 있습니다.
다소 복잡한 이런 과정 대신에, 아래 make 명령어를 통해 간단하게 컴파일을 수행할 수도 있습니다.

$make string

이와 같이 작성한 코드를 컴파일 하고 실행하면, 사용자에게 입력값을 받고 문장 내에 포함하여 출력하는 프로그램이 됩니다.

3) 조건문과 루프

키워드 >> int / if / while / for.

스크래치에서 counter 라는 변수를 생성하고 0을 저장하기 위해서는 아래와 같은 블록을 사용하였었습니다.

저번 강의에서 말씀드렸던 C는 오래된 언어라 저장하고자 하는 변수의 종류를 꼭 알려줘야 한다는 것을 기억하시나요?
우리는 counter라는 변수에 숫자를 저장하고 싶습니다.
여기서 int 는 변수가 정수(integer)라는 것을 알려주는 것이고, counter는 변수의 이름, 0은 그 값에 0을 저장(초기화)하는 것입니다.
그리고 코드의 마지막은 세미콜론(;)을 붙여주는 것도 잊으시면 안됩니다!
저번 강의에서도 말씀드렸듯이 여기서 등호(=)는 같다는 의미가 아닌, 오른쪽에 있는 값을 왼쪽에 할당한다는 의미인 것도 기억해주세요.
스크래치에서 변수의 값을 1씩 증가시키는 것을 해보았습니다. 그럼 C에서는 어떻게 하면 될까요?

즉, counter에 1을 더한 값을 다시 counter에 저장(할당)한다는 의미가 됩니다.
"=은 오른쪽에서 왼쪽이다"를 기억해 주세요!
이를 더 간단하게 아래 두 가지 방식으로 수행할 수도 있습니다.


이런 다양한 방법을 통해 더욱 보기 좋고 간결하게 활용할 수 있도록 해줍니다.
마찬가지로 스크래치의 조건문 블록을 C코드로 나타내볼 수 있습니다.

if ( ) 의 괄호 안에는 검사하고자 하는 조건이 들어가고, { } 안에는 조건을 만족할 때 수행하고자 하는 작업이 들어갑니다.
여기에서는 조건이 True면 "x is less than y"를 출력을 하라는 것입니다.
else를 이용해 처음 조건이 아닌 경우에는 어떤 것을 하라라고 적어줄 수 있습니다.

이 경우에는 첫 번째 x < y 조건이 False, 즉 x가 y보다 작지 않을 경우에는 "x is not less than y"를 출력하라는 것입니다.
else if 를 통해서 아래와 같이 조건을 추가할 수도 있습니다.

여기서 한 가지 이상한 점이 있습니다.
바로 == 입니다.
이전 강의에서 =는 할당 연산자라고 말씀드렸습니다.
이미 등호 표시 하나를 할당 연산자로 정해버린 것이죠
그럼 이제 같다는 것을 어떻게 표현하지에 대한 난관에 봉착한 것입니다.
오래전 사람들이 합의하길 =을 2개 사용하여 같다를 표현하자라고 정한 것입니다.
이를 일치 연산자라고 합니다.
여기서 한 가지 또 알아가야 할 것이 있습니다.
if(x < y), else if (x > y), else if (x == y) 이렇게 3개의 조건문을 사용했습니다.
하지만 여기서 한 가지 굳이 물어볼 필요가 없는 것이 있습니다.
바로 else if (x == y) 입니다.
만약 x가 y보다 작지도 크지도 않다면 우리에게 남은 유일한 가능성은 x와 y가 같다는 것 입니다.
따라서 위의 코드를 수정하면 아래와 같습니다.

이렇게 좀 더 간결하게 만들 수 있습니다.
이렇듯 얼마나 효율적으로 코딩을 하는지, 혹은 얼마나 적은 메모리나 CPU를 사용해서 수행하는지는 정말 중요합니다.
추가로 if, else, else if 뒤에는 세미콜론(;)이 붙지 않은 것을 볼 수 있습니다.
보통 조건과 같은 것들의 끝에는 세미콜론을 붙이지 않습니다.

루프

마지막으로 스크래치에서의 루프는 “forever” 또는 “repeat 50”과 같은 블록을 통해서 수행했었습니다.
무언가를 계속 반복하는 것이였죠
C에서도 while 이나 for 을 통해서 루프를 구현할 수 있습니다.

먼저 while 의 경우 아래 코드와 같이 while ( )의 괄호 안에 조건을 넣고 { } 안에 수행할 작업을 포함시키면 됩니다.
즉, C에서 루프를 구현하고 싶다면 성립 조건을 정해줘야 합니다.
답이 네, 참, 혹은 1로 나올 수 있는 질문을 던져줘야 하는 것이죠
답이 참으로 나오게 하는 방법은 여러가지가 있을 수 있습니다.
5=5, 1<2 등등 하지만 가장 간단한 방법은 그냥 true를 적는 것입니다.
아래 코드에서는 true라는 항상 참이 되는 조건을 통해 while 루프가 영원히 수행되도록 합니다.
따라서 위의 코드는 계속해서 "hello, world"를 무한정 출력하게 될 것입니다.
만약 특정 횟수만큼 작업을 수행하고 싶으면 어떻게 할까요?

자 이제는 좀 더 프로그래머스럽게 작성을 해보겠습니다.
counter라는 변수는 너무 긴 단어입니다.
그래서 프로그래머들은 무언가를 셀 때 간단하게 정수를 나타내는 i를 사용합니다.
물론 변수명은 맘대로 적어도 문제는 없습니다.
다시 while문으로 돌아가서 이번에는 i<50이라는 조건을 추가해줍니다.
우리는 처음에 i를 0이라고 정해주었고 while는 계속해서 i가 50보다 작은지를 물어볼 것입니다.
따라서 이 코드가 정상적으로 작동하려면 i를 증가시켜야 합니다. (i = i + 1, i += 1, i++ 모두 같은 결과를 냅니다.)
진행 순서를 정리해보자면 아래와 같습니다.
i는 0으로 설정 -> i는 50보다 작은가? -> 작다 -> hello world를 출력한다 -> i를 1증가시킨다 -> i가 50보다 작은가?
-> (반복) -> i가 50보다 작은가? -> 작지 않다 -> 종료
따로 변수를 선언해도 되지만 아래와 같이 for 를 사용하면 for ( ) 안에 각각 (변수 초기화; 변수 조건; 변수 증가) 에 해당하는 코드를 넣어서 간단하게 표현할 수 있습니다.
즉, 가장 먼저 정수 값을 가지는 i라는 변수를 0으로 초기화하고, i가 50인지 매번 검사를 하고, 이를 만족하면 { } 안의 내용을 수행한 후에, i를 1씩 증가시킨다는 의미입니다.

while문과 비교하여 코드가 엄청 간단해진 것을 확인할 수 있습니다.

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

키워드 >> char / long / float / double / % / && / ||.

데이터 타입

아래 목록은 변수의 데이터 타입으로 사용할 수 있는 것들입니다.
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: 부동소수점을 포함한 더 큰 실수

  • int는 대략 40억까지 셀 수 있기 때문에 40억게 이상의 데이터를 가진 일부 거대 기업과 같은 상황이 아닌 일반 사용자들은 대부분 정수에 int를 사용합니다.

CS50 라이브러리 내의 get함수

CS50 라이브러리는 위와 같은 데이터 타입을 입력값으로 받을 수 있는 아래와 같은 함수들을 포함합니다.
(CS50 라이브러리에서 사용되는 함수이기 때문에 가볍게 알고 가시면 됩니다.)
get_char
get_double
get_float
get_int
get_long
get_string

형식 지정자

printf 함수에서는 각 데이터 타입을 위한 형식 지정자를 사용할 수 있습니다.
지난 강의에서 문자열(string)인 answer 변수의 인자를 %s로 불러온 것을 기억하시나요?
이번에는 여러가지 데이터 타입 마다 사용되는 형식 지정자를 알아보도록 하겠습니다.
%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);
}

get_int라는 정수 값을 받아오는 CS50 라이브러리에 있는 함수를 사용합니다.
사용자의 나이는 오른쪽에서 왼쪽으로 복사되어 age라는 변수에 저장됩니다.
그 변수의 종류는 int 정수입니다.
그럼 이 사람의 나이를 일 수로 환산하면 며칠인지 계산해볼까요?
days라는 정수 변수에 age에 365를 곱한 수를 저장해줍니다.
그리고 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);
}

이전에 days에 age에 365를 곱한 값을 저장했습니다.
하지만 엄밀히 말하면 이 행은 필요 없습니다.
days 대신 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);
}

age라는 변수를 없애버리고 age*365 대신에 get_int 함수를 넣어 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);
}

get_float 함수를 사용하여 물건의 가격을 물어보고 가격을 받아 price에 저장해줍니다.
그런 다음 세금을 포함한 값을 계산해서 출력해보겠습니다. (메사추세츠의 부가세는 6.25%입니다.)
총액은 실수(float)이므로 %f를 사용해줍니다.
이제 코드를 실행해서 가격을 100으로 넣어볼까요?

결과 값으로 105.250000이 나온 것을 볼 수 있습니다.
하지만 소수점이 6번째 자리까지 나와 보기에 안 좋습니다.
그럼 이 것을 일부분만 나오게 해볼까요? (소수점 2번째 자리까지 나오게 해보겠습니다.)

printf("Your total is %.2f \n", price*1.0625);

이때는 %f에서 f앞에 '.원하는 자리수'를 넣어 %.2f로 소수점 2번째 자리까지 나오게 할 수 있습니다.

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

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

우선 get_int로 사용자들에게 정수인 숫자를 받아서 n에 저장해보겠습니다.
받은 정수인 숫자가 짝수인지 홀수인지 알아보는 방법에 무엇이 있을까요?
하나하나 적을 수도 있습니다.
만약 숫자가 2이면 짝수다 그리고 숫자가 3이면 홀수다 숫자가 4면 짝수다 .......(무한반복)....... 128129312는 짝수다
이 방법은 숫자는 무한대이기 때문에 절대 완벽한 코드가 될 수 없습니다.
그럼 어떤 방법이 있을까요? 바로 2로 나누어 나머지가 0이냐 1이냐를 보는 것입니다.
짝수면 나머지가 0이 될 것이고 홀수면 1이 될 것입니다.
if ( n % 2 == 0 ) 을 풀어 쓰면 n을 2로 나누었을 때 나머지가 0이면
printf("even\n"); -> even(짝수)을 출력하라 입니다.
홀수를 출력하는 것은 왜 else if (n % 2 == 1)으로 하지 않았을 까요?
왜냐하면 짝수 아니면 홀수 이기 때문에 else로도 충분하기 때문이죠.

주석

C에서는 //로 주석을 달 수 있습니다.

// 주석입니다.

그렇다면 주석은 왜 다는 것일까요? 주석은 이 코드가 무슨 일을 하는지 설명하는 것 입니다.
여러분의 친구, 동료, 혹은 조교 등 여러분들이 짠 코드를 처음보는 사람들에게 설명이 필요하기 때문입니다.
우리는 지금까지 10줄에서 20줄 사이로 코드를 작성해보았는데요,
만약 코드가 수 백, 수 천줄이 되면 주석이 없다면 부분 부분마다 어떤 일을 하는지 찾기 힘들 것입니다.
이 것은 꼭 타인이 아닌 자기 자신에게도 해당합니다.
자신이 짠 코드라고 해도 한달 뒤, 일년 뒤에 보면 새롭기 때문입니다.
그렇기 때문에 주석으로 잘 설명하는 습관이 중요합니다.

#include<cs50.h> 는 무엇인가요?

CS50 수업을 위해 만들어진 라이브러리 입니다. 라이브러리는 여러 함수들을 모아둔 것이라고 볼 수 있습니다. CS50 수업에서는 여러분들께서 좀 더 쉽게 코딩을 짤 수 있게 CS50 라이브러리 안에 여러 함수(get_int, get_double, get_float 등등)를 만들어 두었습니다.
반면에 C에는 표준 라이브러리도 있습니다. 우리가 처음부터 사용한 #inclue<stdio.h>가 표준 라이브러리 중 하나 입니다. 가장 많이 쓰고 가장 보편적으로 사용하는 라이브러리입니다. 그 밖에도 <math.h>, <time.h> 등 자신이 코딩하는데 필요한 함수들을 그때 그때 라이브러리를 불러와서 다른 사람들이 만들어둔 함수를 사용할 수 있습니다. 이 강좌 이후에 직접 코딩을 해보고 좀 더 공부를 하다 보면 배우실 수 있을 것입니다.
그렇다면 sandbox.cs50.io가 아닌 Visual Studio 같은 곳에서 CS50 라이브러리를 바로 사용이 가능할까요? 답은 사용할 수 없습니다. 표준 라이브러리는 기본적으로 설치가 되어 있기 때문에 사용이 가능하지만 앞서 말씀드렸듯이 CS50 라이브러리는 수업을 위해 만들어진 라이브러리 입니다. 그래서 sandbox.cs50.io 처럼 미리 설치가 된 곳이 아니면 따로 설치를 하여야만 사용이 가능합니다. 설치하는 방법은 현재 수준에서 필요한 것이 아니기 때문에 아래 첨부된 CS50 라이브러리 문서를 참고해 주세요.
추가로 CS50 라이브러리의 get_int, get_float 등의 함수로 좀 더 쉽게 입력을 받아 보았는데요, 일반적으로 사용되는 입력을 받는 함수도 아래의 참고자료를 통해 학습하실 수 있게 준비해 두었으니 참고하시면 좋을 것 같습니다.

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

키워드 >> 사용자 정의 함수 / 중첩 루프.

사용자 정의 함수

우리가 스크래치에서 했던 것 처럼 “cough”라고 세 번 말하는 C 프로그램을 작성하고 싶으면 어떻게 해야 할까요?
가장 간단한 방법은 아래처럼 작성하는 것입니다.

#include <stdio.h>
int main(void)
{
    printf("cough\n");
    printf("cough\n");
    printf("cough\n");
}

단순히 printf 를 세 번 반복하면 되지만, 동일한 작업을 반복하는 것이기 때문에 사용자 정의 함수를 이용하면 아래 코드와 같이 더 단순화 할 수 있습니다.
지난번에 배운 방법으로 더 간단하게 만들 수 있습니다. 어떤 것을 사용해야 할까요?
바로 루프입니다. 그 중 for를 사용해보겠습니다.

#include <stdio.h>
int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        printf("cough\n")
    }
}

어렵지 않게 cough을 3번 출력해보았습니다.
그럼 이번에는 우리만의 함수를 만들어 볼까요?

#include <stdio.h>
void cough(void)
{
    printf("cough\n")
}
int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        cough();
    }
}

void를 입력하고 원하는 함수명(cough)을 적은 뒤 괄호 안에 void를 적어줍니다.
그리고 printf로 cough를 출력하는 코드를 작성합니다.
우리가 지금까지 사용하던 'get_int', 'get_string' 등의 함수는 우리가 직접 구현할 필요가 없었습니다.
과거의 어떤 사람들이 모두 구현해두었기 때문입니다.
우리는 함수의 이름을 cough라고 정했습니다.
이제 int main(void)의 안에 cough(함수명)를 사용해보겠습니다.
그럼 우리가 정의해둔 대로 cough가 출력됩니다.
하지만 여기에도 문제가 있습니다.
함수를 1개가 아닌 여러개를 만들수록 main 함수는 아래로 내려가기 때문입니다.
중요한 것이 아래에 있는 것보다 바로 나오는 것이 보기 좋습니다.
그럼 main 함수를 위로 올리고 cough 함수를 내려볼까요?
실행을 해보면 오류가 발생합니다.
main 함수에서 cough() 함수를 사용했습니다.
그런데 cough함수는 아래에 있습니다.
C는 오래되었고 똑똑하지 않기 때문에 아래에 cough라는 함수가 있을 것이라 생각하지 못하는 것이죠.
여러분이 시킨대로만 행동합니다.
이 것을 해결하려면 다시 cough함수를 위로 올려야합니다.
물론 이것은 악순환의 반복일 것입니다. 영원히 새로운 함수를 위에 올릴 수 없으니까요.
그래서 다른 방법이 있습니다.

#include <stdio.h>
void cough(void);
int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        cough();
    }
}
void cough(void)
{

void cough(void)를 세미콜론과 함께 위로 올리는 것입니다.
마치 이전에 cough를 봤던 것처럼 C를 속이는 방법입니다.
cough함수를 전부 본 적은 없어도 이름은 본적이 있으니 main 함수에 나올 때까지 코드를 계속 읽도록 하는 것입니다.
이번에는 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");
    }
}

여기서 맨 아래의 void cough(int n){...} 은 cough 라는 이름의 함수를 우리가 직접 정의한 것입니다.
cough( ) 안의 int n 은 함수가 입력값을 받아서 int 형식을 갖는 n이라는 변수에 저장하겠다는 의미입니다.
그리고 { } 안의 내용을 보면 n번 동안 cough를 출력하는 for 루프가 있습니다.
다시 main 함수 안으로 돌아가보면 cough(3) 이라는 한 줄의 코드를 통해서 3이라는 값을 cough 함수에 전달하고, 궁극적으로는 cough를 세 번 출력할 수 있게 되는 것이죠.
다만 여기서 main 함수를 우리가 정의한 cough 함수보다 위에 위치시키고 싶다면, 예시에서와 같이 void cough(int n);를 먼저 입력해서 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 n;
    do
    {
        n = get_int("Positive Integer: ");
    }
    while (n < 1);
    return n;
}

여기서 get_positive_int 함수는 CS50라이브러리(cs50.h)에 없는 함수 입니다.
아래 int get_positive_int(void)를 보시면 우리가 처음 보는 기능이 있습니다.
이 부분의 논리를 차근차근 확인해 보겠습니다.
여기 get_postive_int라는 함수가 있는데 입력을 받지 않았습니다.
괄호 안에 아무것도 넣을 필요가 없습니다. 아무 양의 정수나 받으면 됩니다.
하지만 이 전에 사용했던 get_int나 get_string 함수처럼 어떤 값을 받아와서 변수에 저장하는 것처럼 이 함수가 뭔가를 반환하게 하고 싶습니다.
그래서 int get_positive_int(void) 파란색 글씨는 void가 아니고 int가 됩니다.
함수 왼쪽에 있는 단어(파란색)는 출력의 종류를 의미 합니다.
int get_positive_int(void) 괄호 안의 빨간색 단어(void)는 입력의 종류를 뜻합니다.
만약 입출력이 없다면 void를 적어주시면 됩니다.
그리고 int n; 이라고 하는 처음 보는 것이 있습니다.
컴퓨터에게 n이라고 하는 변수를 달라는 일종의 힌트입니다.
그 안에 어떤 값을 저장할지 아직 모르기 때문에 그냥 int n;만 적는 것입니다.
아직은 아무것도 할달할 필요가 없습니다.
그럼 n은 쓰레기 값(Garbage Value)이라고 부르는 값을 가지게 됩니다.
n에 무엇이 들었는지는 모르지만 중요하지 않습니다. 나중에 제대로 넣으면 됩니다.
그 다음 do-while의 루프를 알아보겠습니다.
이 불리언 표현 while(n<1); 이 참일때 다음을 수행하라는 뜻입니다.
만약 n이 1보다 작다면 계속해서 질문을 반복하는 것입니다.
while을 단독으로 사용하면 while의 조건이 참이어야만 수행을 합니다.
하지만 do-while은 do에서 무조건 한 번은 먼저 수행하게 해줍니다.
물론 이것은 여러가지 표현 방법 중 하나입니다.

중첩루프

마리오 게임에서 흔히 보는 것 처럼 화면에 여러 개의 이미지를 가로나 세로로 여러 개 이어서 출력하고 싶으면 어떻게 해야 할까요? 아래처럼 for 루프를 사용할 수 있습니다.

#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에 저장되는데요, 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 에서 저장 가능한 비트 수가 유한하기 때문에 다소 부정확한 결과를 내게 되는 것입니다.

정수 오버플로우

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

profile
블로그 이전 : https://medium.com/@jaegeunsong97

0개의 댓글