본 포스트는 boostcourse의 '모두를 위한 컴퓨터 과학 (CS50 2019)'를 바탕으로 작성되었습니다.
프로그래밍 언어는 컴퓨터와 사람이 대화하기 위한 하나의 수단이다.
컴퓨터는 0과 1로 구성된 기계어만을 이해하는데, 사람은 이러한 기계어를 구사하기는 힘드니 둘 사이에는통역가가 필요하다. 그 통역가가 바로 기계어와 프로그래밍 언어를 구사하는 컴파일러이고, 사람은 컴파일러에게 프로그래밍 언어로 의사를 전달하며 비로소 컴퓨터와 대화할 수 있게 되는 것이다.
여기서 우리가 작성한 코드는 "소스코드"라고 부르며, 컴파일러를 통해 2진수를 사용하는 "머신코드"로 변환되는 것이다.
C언어는 전통적인 프로그래밍 언어 중 하나로, 구체적인 문법을 사용하여 작성할 것을 요구하는데 앞으로는 그 문법에 대해 배워볼 것이다.
#include <stdio.h>
int main(void)
{
printf("Hello world!"\n);
return 0;
}
위 프로그램을 통해 C언어의 기본구조를 알아보자.
#include <stdio.h
stdio.h라는 헤더파일을 선언하였다.
헤더파일은 메뉴판과 같은 존재로, 어떠한 함수를 사용하려면 그 함수가 적힌 메뉴판이 먼저 필요하다. 그래서 가장 먼저 등장한 것이다.
int main(void)
{
printf("Hello world!\n");
return 0;
}
출력(반환)형태 함수이름 (입력형태)
{-> 함수의 시작(중괄호)
함수의 기능; -> (문장의 끝-세미콜론)
} -> 함수의 끝(대괄호)
이러한 구조로 이루어져있다.
함수를 정의하려면 위 구조를 따라야 한다.
이 구조를 토대로 위 함수를 읽어본다면
'int(정수)를 반환하는 입력 형태는 void인 main이라는 함수'가 된다.
다음은 함수의 몸체(body)를 살펴보자.
printf("Hello world!\n");
스크래치에서 say와 같은 기능을 하는 함수가 printf이다.
큰 따옴표 안에 있는 문자열을 모니터에 출력해주는 함수이다.
print뒤에 붙은 f는 formatted의 약자로 서식이 지정된 형태를 출력한다는 의미다.
뒤에 붙은 \n은 키보드의 Enter와 같은 줄바꿈의 기능을 한다.
return 0;
이 return문은 함수를 호출한 부분에 값(0)을 반환하면서
함수에서 빠져나가는 역할(종료)을 한다.
보통 함수가 정상적으로 일을 하고 종료할 때는 0, 그렇지 않은 경우에는 1을 반환하면서 끝낸다.
"Hello world"가 아닌 이용자의 이름을 입력받아 그 사람에게 인사하는
프로그램을 만들어 보자.
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string answer = get_string("What's your name?\n");
printf("Hello %s \n", answer);
}
(get_string함수는 CS50 수업을 위해 만들어진 함수로, CS50 라이브러리를 필요로 한다.)
string answer = get_string("What's your name?\n");
string은 문자열, 문자들의 집합을 말한다.
answer은 사용자의 이름이 저장되는 변수의 이름인데,
앞에 string이 붙은 이유는 C언어에게 변수의 자료형을 알려주기 위해서이다.
=는 오른쪽에서 왼쪽으로 가는 화살표와 같은 존재로, 할당연산자이다.
get_string을 통해 입력받은 값이 answer이라는 변수에 저장되는 것을 의미한다.
printf("Hello %s \n", answer);
%s는 형식지정자다. Hello뒤에 오는 변수의 자료형이 string이기 때문에 %뒤에 s를 붙여 문자열이 올 것을 알려준다. (각 데이터의 종류마다 형식지정자가 다르다.)
콤마를 쓰고 뒤에 변수의 이름을 써서 어떤 변수의 값이 들어올 건지 알려줘야 한다.
C언어에서 변수란 정해지지 않는 어떠한 값이 저장되는 메모리 공간을 의미한다.
변수를 선언하고(만들고) 값을 할당(저장)하는 방법을 알아보자.
int counter = 0;
"자료형 변수이름(int counter)"을 통해 int(정수)형의 자료를 저장하기 위한 메모리 공간을 할당한다.
=(할당 연산자)로 값을 대입한다. 오른쪽에 변수에 저장할 값을 쓴다.
마지막에 세미콜론을 붙이는 것도 잊지 않아야 한다!
이 결과, counter라는 int형의 변수에 0이 초기화된다.
(변수에 초깃값을 저장하는 것을 '초기화'라고 한다.)
변수의 값을 1씩 증가시키려면 어떻게 해야할까.
counter = counter + 1;
=의 의미가 오른쪽의 값을 왼쪽에 지정한다는 것을 다시 한번 떠올리면 쉽게 이해가 된다.
이는 두 가지 방법으로 더 간단하게 표현할 수도 있다.
counter += 1;
counter++;
'+='은 +연산자와 =연산자의 결합 형태로 복합 대입 연산자라고 한다.
'변수++'의 형태는 속한 문장을 먼저 연산한 후 값을 1씩 증가한다는 의미다.('++변수'인 ++가 변수보다 먼저 오는 형태는 변수의 값을 1 증가 후, 연산한다는 의미다.)
조건문은
if(검사하려는 조건)
{
조건을 만족할 때까지 수행할 코드
}
의 형태이다.
if (x < y)
{
printf("x is less than y\n");
}
x가 y보다 작을 경우(조건이 True인 경우) x is less than y가 출력된다.
(조건문 뒤에는 세미콜론이 붙지 않은 것을 확인할 수 있다.)
조건을 추가해보자.
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");
}
'=='는 일치연산자인데 '='가 이미 할당연산자로 쓰이고 있기 때문에 같다라는 표시를 등호 두개로 하기로 약속하였다.
여기서
else if (x == y)
{
printf("x is equal to y\n");
}
는 x가 y보다 작지도 크지도 않다면 남은 유일한 가능성이 x와 y가 같다이기 때문에 더 간략하게 수정할 수 있다.
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");
}
마지막으로 루프(반복문)를 구현해보자.
"Hello world"를 오십 번 출력하고 싶을 경우 어떻게 작성해야할까.
int 1 = 0;
while (i < 50)
{
printf("Hello world\n");
i = i + 1;
}
while 뒤 소괄호 안에 조건을 넣고, 중괄호 안에 조건이 만족하는 동안 어떤 작업을 할 것인지 넣으면 된다.(중괄호는 반복하는 작업이 하나의 문장으로 이뤄진 경우 생략할 수도 있다.)
만약 무한 반복하고 싶은 경우 true, 5==5와 같은 항상 참(True)가 되는 조건을 넣으면 루프는 영원히 수행된다.
while문의 진행 순서는 다음과 같다.
i를 0으로 초기화 -> i는 50보다 작은가? -> 작다 -> Hello world 출력 -> i를 1증가 -> i가 50보다 작은가? -> (반복) -> i가 50보다 작은가? -> 작지 않다 -> 종료
for문을 사용하여 루프를 만들 수도 있다. for문의 문법은 다음과 같다.
for (변수 초기화; 변수 조건; 변수 증가)
{
수행할 작업
}
위의 while문을 for문으로 바꿔보자.
for (int = 0; i < 50; i = i + 1)
{
printf("Hello world\n");
}
이미 정의되어있는 라이브러리함수 외에도 사용자가 직접 함수를 만들어 사용할 수 있다. 이를 사용자 정의함수(user define)라고 한다.
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; i++)
{
printf("cough\n")
}
}
cough를 세 번 반복하는 프로그램이다. cough라는 이름의 사용자 정의함수를 따로 만들어 출력해보자.
#include <stdio.h>
void cough(void)
{
printf("cough\n")
}
int main(void)
{
for (int i = 0; i < 3; i++)
{
cough();
}
}
main 함수 안에 위에 만든 cough 함수를 사용했다.
그런데, C 언어는 절차지향 프로그래밍 언어로 위에서부터 아래로 가는 실행흐름을 중요시여기므로 main 함수를 위에 선언해야 한다.
#include <stdio.h>
void cough(void);
int main(void)
{
for (int i = 0; i < 3; i++)
{
cough();
}
}
void cough(void)
{
printf("cough\n");
}
main 함수를 cough 함수보다 위로 올렸다.
하지만 C언어는 main함수를 읽을 때, 아래에 있는 cough 함수를 아직 못 봤기 때문에 오류가 날 수 있다. 그래서 main 함수보다 위에 함수 원형(function prototype)을 적어, "cough라는 함수가 있을 것이다"라고 미리 귀뜸을 해줘야 한다.
void cough(void);
main 함수 위에 이 부분이 함수 프로토타입이다.
cough 함수에 3번 반복하는 기능을 추가해 더욱 잘 디자인된 코드로 바꿀 수도 있다.
#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");
}
}
cough 함수는 정수(int)형의 입력값을 받는다고 나와있다.
main 함수에서 3이라는 값을 전달하고 결과적으로 세 번 반복하여 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;
}
CS50라이브러리의 정수값을 입력 받는 get_int 함수를 이용한 사용자 정의함수 예제이다.
get_positive_int 함수를 차례대로 살펴보자.
int get_positive_int(void)
입력은 받지않고, 정수가 출력되는 get_positive_int라는 이름의 함수라는 것이다.
int n;
정수형 변수 n를 선언했다. 아직 어떤 값을 저장할지 모르니 값을 적지 않았는데, 이때 n은 임의의 쓰레기 값(Garbage Value)을 갖고 있게 된다.
do
{
n = get_int("Positive Integer: ");
}
while (n < 1);
return n;
do-while 루프문이다. 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");
}
}
반복문은 중첩되어 사용될 수 있다. 가장 밖에 있는 외부 반복문(outer loop)가 한 번 수행될 때마다 안에 있는 내부 반복문(inner loop)은 새로 수행된다.
앞서 배운 for문을 중첩하고, do-while루프를 같이 사용한 프로그램이다.
사용자가 입력한 값만큼의 가로, 세로에 "#"을 출력한다.
컴퓨터가 할 수 있는 일에도 한계가 있다. 프로그램이 실행되는 동안 필요한 정보가 저장되는 RAM(Random Access memory)은 유한한 저장공간이다. 그러므로 유한한 크기의 비트만 저장될 수 있어 가끔씩 부정확한 결과를 내게 된다.
컴퓨터는 실수를 0과 1만을 가지고 저장하기 위해 "부동소수점(floating potint) 표현 방식"이라는 것을 사용한다. 이 방식은 적은 비트로 넓은 범위의 실수를 표현할 수 있게 하지만, 정확성을 떨어트린다. 모든 실수를 정확히 표현할 수 없고 표현하려는 값의 아주 근사치로 실수를 표시하기 때문에 오차가 생기기 때문이다.
정수 자료형 역시 오류가 일어날 수 있다. 각 데이터타입의 자료형마다 저장의 최대 최소 범위가 정해져있다. 예를 들어 unsigned char의 최댓값은 255인데 그 값을 넘으면 오버플로우(overflow)가 발생하여 쓰레기값이 출력된다. (최솟값 이하로 넘어가는 경우는 언더플로우(underflow)가 발생한다.)
이렇게 float의 정확성이나 정수의 크기에는 한계가 있다는 것을 기억하자!