[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진수로 작성된
머신 코드
로 변환해야 컴퓨터가 이해할 수 있다. 이런 작업을 컴파일러가 수행한다. - 터미널창의 명령어 프롬프트에서
$
기호 옆에 명령어를 입력한다. - 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++;
-
위의 스크래치 조건문을 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개의 비트가 다였기 때문에 그 이상의 숫자는 저장할 수가 없다.
따라서 다루고자 하는 데이터 값의 범위를 유의하며 프로그램을 작성하는 것이 중요하다.
댓글남기기