Home > CS > CS50 > 🏛️ [CS50] Lecture 1 요약: C언어와 프로그래밍의 시작

🏛️ [CS50] Lecture 1 요약: C언어와 프로그래밍의 시작
CS Harvard CS50

🏛️ [CS50] Lecture 1 요약: C언어와 프로그래밍의 시작

하버드 대학교 CS50 두 번째 강의 - David Malan 교수


📋 강의 개요.

Scratch에서 C로의 전환

  • 지난 주 Scratch의 시각적 프로그래밍에서 텍스트 기반 C언어로 전환
  • 기본 개념(함수, 조건문, 루프, 변수)은 동일하지만 문법이 달라짐
  • C언어는 더 전통적이고 강력한 프로그래밍 언어

핵심 철학

  • 문제 해결 도구 확장: 매주 새로운 도구를 툴킷에 추가
  • 프로그래밍의 한계 이해: 컴퓨터가 잘 못하는 것들도 학습
  • 실제 세계의 문제: 메모리와 정확성의 제한으로 인한 실제 문제들

🔧 소스코드에서 기계어까지

컴파일 과정의 이해

소스코드(Source Code) → 컴파일러(Compiler) → 기계어(Machine Code)

소스코드 vs 기계어

  • 소스코드: 인간이 작성하는 고수준 언어
  • 기계어: 컴퓨터가 실행하는 0과 1의 패턴
  • 컴파일러: 소스코드를 기계어로 변환하는 프로그램

개발 환경: VS Code와 클라우드

  • CS50.dev: 클라우드 기반 개발 환경
  • VS Code: 산업계에서 널리 사용되는 무료 코드 에디터
  • Linux 서버: 명령줄 인터페이스(CLI) 환경 제공

💻 첫 번째 C 프로그램

Hello World 프로그램

#include <stdio.h>

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

코드 구성 요소

  • **#include **: 표준 입출력 라이브러리 포함
  • int main(void): 프로그램의 진입점 (“녹색 깃발 클릭”과 동일)
  • printf(): 화면에 텍스트 출력하는 함수
  • \n: 새 줄(줄바꿈)을 의미하는 이스케이프 시퀀스
  • 세미콜론(;): 모든 문장의 끝에 필요

개발 과정의 세 단계

  1. 코딩: code hello.c - 파일 생성 및 편집
  2. 컴파일: make hello - 소스코드를 기계어로 변환
  3. 실행: ./hello - 프로그램 실행

📝 변수와 데이터 타입

기본 데이터 타입

int age = 25;          // 정수형
float price = 19.99;   // 실수형 (32비트)
double pi = 3.14159;   // 실수형 (64비트, 더 정밀)
char grade = 'A';      // 문자형 (단일 문자)
string name = "David"; // 문자열 (CS50 라이브러리)

사용자 입력 받기

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

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

CS50 라이브러리 함수들

  • get_string(): 문자열 입력
  • get_int(): 정수 입력
  • get_float(): 실수 입력
  • get_char(): 문자 입력

형식 지정자(Format Specifiers)

  • %s: 문자열 (string)
  • %i: 정수 (integer)
  • %f: 실수 (float)
  • %c: 문자 (character)

🔀 조건문과 논리 연산

if-else 문의 구조

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

비교 연산자

  • <, >: 작다, 크다
  • <=, >=: 작거나 같다, 크거나 같다
  • ==: 같다 (주의: 할당연산자 =와 구분)
  • !=: 같지 않다

논리 연산자

if (c == 'y' || c == 'Y')  // OR 연산
{
    printf("Agreed\n");
}

if (age >= 18 && citizen)  // AND 연산
{
    printf("Can vote\n");
}

🔄 반복문 (루프)

while 루프

int counter = 3;
while (counter > 0)
{
    printf("meow\n");
    counter--;
}

for 루프 (더 일반적)

for (int i = 0; i < 3; i++)
{
    printf("meow\n");
}

for 루프의 구조

  1. 초기화: int i = 0
  2. 조건: i < 3
  3. 업데이트: i++

do-while 루프

int n;
do
{
    n = get_int("Size: ");
}
while (n < 1);  // 최소 한 번은 실행

⚙️ 함수 정의와 사용

사용자 정의 함수

#include <stdio.h>

void meow(void);  // 함수 프로토타입

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

void meow(void)   // 함수 정의
{
    printf("meow\n");
}

매개변수가 있는 함수

void meow(int n);  // 프로토타입

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

void meow(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("meow\n");
    }
}

반환값이 있는 함수

int add(int a, int b);  // 프로토타입

int main(void)
{
    int result = add(1, 2);
    printf("%i\n", result);
}

int add(int a, int b)
{
    return a + b;
}

💾 명령줄 인터페이스 (CLI)

기본 CLI 명령어

  • ls: 현재 디렉토리의 파일 목록 표시
  • cd: 디렉토리 변경
  • mv: 파일 이동/이름 변경
  • cp: 파일 복사
  • rm: 파일 삭제
  • mkdir: 디렉토리 생성
  • clear: 화면 지우기

실용적인 팁

# 이전 명령어 재사용
↑ 화살표 키

# 자동완성
Tab 키

# 파일 이름 변경
mv old_name.c new_name.c

# 프로그램 실행
./program_name

🎮 실습 예제: 마리오 피라미드

1차원 블록 만들기

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 4; i++)
    {
        printf("#");
    }
    printf("\n");
}

2차원 격자 만들기

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

int main(void)
{
    const int n = get_int("Size: ");
    
    // n×n 격자 출력
    for (int i = 0; i < n; i++)      // 행
    {
        for (int j = 0; j < n; j++)  // 열
        {
            printf("#");
        }
        printf("\n");
    }
}

입력 검증 루프

int n;
do
{
    n = get_int("Size: ");
}
while (n < 1);  // 양수가 입력될 때까지 반복

⚠️ 컴퓨터의 한계

정수 오버플로우 (Integer Overflow)

32비트 정수의 한계

  • 최댓값: 약 21억 (2,147,483,647)
  • 오버플로우: 최댓값을 넘으면 음수로 순환
  • 해결책: long 타입 사용 (64비트)

실제 사례

  • Y2K 문제: 2자리 연도 표현의 한계
  • 2038년 문제: 32비트 시간 표현의 한계 (Unix Timestamp)
  • 팩맨 256레벨: 레벨 카운터 오버플로우로 화면 깨짐

부동소수점 부정확성

float result = 1.0 / 3.0;
printf("%.20f\n", result);  // 0.33333334326744079590...

문제점

  • 유한한 메모리: 무한소수를 정확히 표현 불가
  • 반올림 오류: 가장 가까운 표현 가능한 값으로 근사
  • 해결책: double 사용으로 정밀도 향상 (완전한 해결책 아님)

타입 캐스팅

int x = 1;
int y = 3;
float result = (float) x / (float) y;  // 정확한 나눗셈

🎯 프로그래밍 원칙과 스타일

좋은 코드의 특징

  1. 정확성(Correctness): 의도한 대로 작동
  2. 효율성(Efficiency): 빠르고 메모리를 적게 사용
  3. 가독성(Readability): 이해하기 쉬운 코드

코딩 스타일 가이드라인

  • 들여쓰기: 4칸 공백 사용
  • 변수명: 의미있고 설명적인 이름 사용
  • 주석: //로 코드 설명 추가
  • 상수: const 키워드로 변경 불가능한 값 지정
// 사용자로부터 양수 입력 받기
int n;
do
{
    n = get_int("Size: ");
}
while (n < 1);

// n×n 격자 출력
const int size = n;
for (int i = 0; i < size; i++)
{
    for (int j = 0; j < size; j++)
    {
        printf("#");
    }
    printf("\n");
}

범위(Scope) 개념

  • 변수는 선언된 중괄호 {} 내에서만 사용 가능
  • 함수의 매개변수는 해당 함수 내에서만 접근 가능

🚀 다음 단계

앞으로 배울 개념들

  • 배열(Arrays): 여러 값을 하나의 변수에 저장
  • 포인터(Pointers): 메모리 주소를 다루는 방법
  • 구조체(Structs): 사용자 정의 데이터 타입
  • 파일 입출력: 데이터를 파일로 저장하고 읽기
  • 알고리즘: 더 복잡한 문제 해결 방법

실무 적용

  • 웹 개발: JavaScript, Python과의 연계
  • 시스템 프로그래밍: 운영체제, 드라이버 개발
  • 임베디드 시스템: IoT 기기 프로그래밍
  • 게임 개발: 고성능 게임 엔진

💡 마무리 메시지

C언어는 처음엔 복잡해 보이지만, Scratch에서 배운 논리적 사고 방식이 그대로 적용됩니다. 새로운 문법과 세미콜론, 중괄호에 익숙해지는 데 시간이 걸리지만, 이는 모든 프로그래머가 거치는 과정입니다.

중요한 것은 완벽한 코드를 처음부터 작성하는 것이 아니라, 문제를 단계별로 분해하고, 작은 부분부터 해결해 나가는 것입니다. 컴파일러의 오류 메시지를 두려워하지 말고, 이를 통해 배우는 과정으로 받아들이세요.

컴퓨터의 물리적 한계(정수 오버플로우, 부동소수점 부정확성)를 이해하는 것은 견고한 소프트웨어를 만드는 첫걸음입니다. 보잉 787의 사례처럼, 이러한 이해 부족은 실제 세계에서 심각한 결과를 초래할 수 있습니다.

프로그래밍은 창의적인 문제 해결 도구입니다. C언어로 배운 기초가 향후 어떤 언어를 배우더라도 탄탄한 기반이 될 것입니다.