Now Loading ...
-
🏛️ [CS50] Lecture 2 요약: 메모리와 배열
🏛️ [CS50] Lecture 2 요약: 메모리와 배열
하버드 대학교 CS50 세 번째 강의 - David Malan 교수
📋 강의 개요
텍스트 읽기 수준 분석
1학년 수준: “One fish, two fish, red fish, blue fish”
3학년 수준: “Congratulations. Today is your day…”
10학년 수준: 조지 오웰의 1984 첫 구절
텍스트 특성 분석: 단어 길이, 문장 길이, 구두점으로 읽기 수준 판단 가능
이번 주 핵심 주제
컴파일 과정의 심화 이해: 소스코드에서 기계어까지의 4단계
메모리 구조: 컴퓨터가 데이터를 저장하는 방법
배열과 문자열: 연속된 데이터 구조의 이해
암호화 기초: 데이터 보안의 기본 원리
🔧 컴파일 과정의 4단계
기존 이해: make의 진실
# 기존 방식
make hello
./hello
# 실제로는 이렇게 동작
clang hello.c -o hello
./hello
Clang 컴파일러 직접 사용하기
# 기본 컴파일 (a.out 생성)
clang hello.c
# 출력 파일명 지정
clang -o hello hello.c
# CS50 라이브러리 링크
clang -o hello hello.c -l cs50
컴파일의 4단계 분해
1단계: 전처리 (Preprocessing)
#include <stdio.h> // 이 라인이
// 실제로는 printf의 프로토타입으로 교체됨:
// int printf(const char *format, ...);
#include <cs50.h> // 이 라인이
// 실제로는 get_string의 프로토타입으로 교체됨:
// string get_string(const char *prompt);
전처리기 역할:
#include 지시문을 찾아 해당 파일 내용을 복사-붙여넣기
헤더 파일의 함수 프로토타입을 소스코드에 삽입
2단계: 컴파일 (Compiling)
C 소스코드를 어셈블리 언어로 변환
어셈블리: 기계어보다는 읽기 쉽지만 여전히 저수준 언어
과거에는 프로그래머가 직접 어셈블리로 코딩
3단계: 어셈블링 (Assembling)
어셈블리 코드를 기계어(0과 1)로 변환
a.out 파일명의 유래: “assembler output”
4단계: 링킹 (Linking)
여러 개의 기계어 파일을 하나로 결합
hello.c, cs50.c, stdio.c의 기계어를 연결
최종 실행 가능한 파일 생성
🐛 디버깅 기법
1. printf 디버깅 (기초)
for (int i = 0; i <= 3; i++) // 버그: <= 사용
{
printf("i is %i\n", i); // 디버깅용 출력
printf("#\n");
}
// 출력: i=0,1,2,3 총 4번 (의도: 3번)
printf의 한계:
매번 재컴파일 필요
임시 코드 추가/삭제의 번거로움
복잡한 버그에는 비효율적
2. debug50 사용 (권장)
# 디버거 실행
debug50 ./program_name
디버거 사용법:
브레이크포인트 설정: 코드 왼쪽 여백 클릭
Step Over: 현재 라인 실행 후 다음 라인으로
Step Into: 함수 내부로 들어가기
변수 모니터링: 실시간으로 변수 값 확인
디버거의 장점:
코드 수정 없이 실행 상태 확인
변수 값의 실시간 추적
단계별 실행으로 논리 오류 발견
3. 러버덕 디버깅
개념: 무생물(고무 오리)에게 코드 설명하기
효과: 말로 설명하는 과정에서 논리적 오류 발견
CS50 Duck: cs50.ai에서 AI 도움 받기
💾 메모리와 데이터 타입
데이터 타입별 메모리 크기
bool // 1 byte (비효율적: 1 bit면 충분하지만)
char // 1 byte
int // 4 bytes (32 bits)
float // 4 bytes
double // 8 bytes
long // 8 bytes
string // 가변 크기 (문자 수 + 1)
메모리 주소 개념
메모리 = 거대한 격자 (캔버스)
각 바이트는 고유한 주소(인덱스)를 가짐
주소: 0 1 2 3 4 5 6 7 ...
데이터: H e l l o \0 72 73 ...
점수 프로그램 예제
// 나쁜 방법: 변수 개별 선언
int score1 = 72;
int score2 = 73;
int score3 = 33;
float average = (score1 + score2 + score3) / 3.0;
// 개선된 방법: 배열 사용
int scores[3];
scores[0] = 72;
scores[1] = 73;
scores[2] = 33;
📊 배열 (Arrays)
배열의 기본 개념
// 배열 선언과 초기화
int scores[3]; // 크기 3인 정수 배열
scores[0] = 72; // 첫 번째 요소 (인덱스 0)
scores[1] = 73; // 두 번째 요소 (인덱스 1)
scores[2] = 33; // 세 번째 요소 (인덱스 2)
// 사용자 입력으로 배열 채우기
for (int i = 0; i < 3; i++)
{
scores[i] = get_int("Score: ");
}
배열의 장단점
장점:
관련 데이터를 논리적으로 그룹화
반복문으로 효율적 처리
메모리에 연속적으로 저장되어 빠른 접근
단점:
C에서는 배열 크기를 자동으로 알 수 없음
배열 경계를 벗어나는 접근 시 위험
크기가 고정적 (컴파일 시점에 결정)
상수와 전역 변수 활용
#include <stdio.h>
const int N = 3; // 전역 상수 (대문자 관례)
int main(void)
{
int scores[N];
// 배열 입력
for (int i = 0; i < N; i++)
{
scores[i] = get_int("Score: ");
}
// 평균 계산
float average = 0;
for (int i = 0; i < N; i++)
{
average += scores[i];
}
average /= N;
printf("Average: %.2f\n", average);
}
함수에 배열 전달하기
float calculate_average(int array[], int length)
{
int sum = 0;
for (int i = 0; i < length; i++)
{
sum += array[i];
}
return (float) sum / length;
}
int main(void)
{
int scores[3] = {72, 73, 33};
float avg = calculate_average(scores, 3);
printf("Average: %.2f\n", avg);
}
🔤 문자열 (Strings)
문자의 본질 이해
// 문자는 숫자다
char c1 = 'H'; // ASCII 72
char c2 = 'I'; // ASCII 73
char c3 = '!'; // ASCII 33
printf("%c%c%c\n", c1, c2, c3); // HI!
printf("%i %i %i\n", c1, c2, c3); // 72 73 33
문자열 = 문자 배열
// 문자열의 실제 구조
string s = "HI!";
// 메모리: ['H']['I']['!']['\0']
// 72 73 33 0
// 배열로 접근 가능
printf("%c\n", s[0]); // H
printf("%c\n", s[1]); // I
printf("%c\n", s[2]); // !
printf("%c\n", s[3]); // '\0' (null terminator)
NUL 종료 문자 (\0)
목적: 문자열의 끝을 표시
값: 8개의 0 비트 (ASCII 0)
중요성: 문자열 길이 결정, 메모리 오버런 방지
크기: 문자열 길이 + 1바이트
문자열 길이 계산
#include <string.h>
// 수동으로 길이 계산
int string_length(string s)
{
int length = 0;
while (s[length] != '\0')
{
length++;
}
return length;
}
// 라이브러리 함수 사용 (권장)
int main(void)
{
string name = get_string("Name: ");
int len = strlen(name); // string.h의 strlen 함수
printf("Length: %i\n", len);
}
문자열 처리 예제
#include <string.h>
// 문자열 각 문자 출력
string s = get_string("Input: ");
printf("Output: ");
for (int i = 0, n = strlen(s); i < n; i++)
{
printf("%c", s[i]);
}
printf("\n");
효율성 개선:
// 비효율적: strlen을 매번 호출
for (int i = 0; i < strlen(s); i++)
// 효율적: 한 번만 계산
for (int i = 0, n = strlen(s); i < n; i++)
🔄 문자열 조작
대문자 변환 프로그램
#include <ctype.h>
// 방법 1: ASCII 값 직접 조작
for (int i = 0, n = strlen(s); i < n; i++)
{
if (s[i] >= 'a' && s[i] <= 'z')
{
printf("%c", s[i] - 32); // 소문자를 대문자로
}
else
{
printf("%c", s[i]); // 그대로 출력
}
}
// 방법 2: ctype 라이브러리 사용 (권장)
for (int i = 0, n = strlen(s); i < n; i++)
{
printf("%c", toupper(s[i])); // 자동으로 대문자 변환
}
2차원 배열: 문자열 배열
// 문자열 배열 선언
string words[2];
words[0] = "HI!";
words[1] = "BYE!";
// 2차원적 접근
printf("%c", words[0][0]); // 'H' (첫 번째 단어의 첫 번째 문자)
printf("%c", words[1][2]); // 'E' (두 번째 단어의 세 번째 문자)
⌨️ 명령줄 인수 (Command Line Arguments)
기본 구조
#include <stdio.h>
int main(int argc, string argv[])
{
// argc: argument count (인수 개수)
// argv: argument vector (인수 배열)
}
간단한 인사 프로그램
int main(int argc, string argv[])
{
if (argc == 2)
{
printf("Hello, %s!\n", argv[1]);
}
else
{
printf("Hello, world!\n");
}
return 0;
}
실행 예시:
./greet David # Hello, David!
./greet # Hello, world!
./greet David Malan # Hello, world! (argc = 3이므로)
argv 배열 구조
명령어: ./greet David Malan
argv[0] = "./greet" (프로그램 이름, 항상 포함)
argv[1] = "David" (첫 번째 인수)
argv[2] = "Malan" (두 번째 인수)
argc = 3 (총 인수 개수)
모든 인수 출력하기
int main(int argc, string argv[])
{
for (int i = 0; i < argc; i++)
{
printf("argv[%i]: %s\n", i, argv[i]);
}
return 0;
}
종료 상태 (Exit Status)
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("Missing command-line argument\n");
return 1; // 오류 상태
}
printf("Hello, %s!\n", argv[1]);
return 0; // 성공 상태
}
종료 상태 확인:
./status David
echo $? # 0 (성공)
./status
echo $? # 1 (실패)
🔐 암호화 기초 (Cryptography)
암호화의 개념
평문(Plaintext) → [암호화 알고리즘 + 키] → 암호문(Ciphertext)
핵심 요소:
평문: 원래 메시지 (예: “HI!”)
암호문: 암호화된 메시지 (예: “IJ!”)
키: 암호화/복호화에 사용되는 비밀 값
알고리즘: 암호화 방법 (예: Caesar cipher)
Caesar Cipher (시저 암호)
// 암호화: 각 문자를 키만큼 이동
char plaintext = 'H'; // ASCII 72
int key = 1;
char ciphertext = plaintext + key; // 'I' (ASCII 73)
// 복호화: 키만큼 역방향 이동
char decrypted = ciphertext - key; // 'H'
ROT13 암호화
#include <ctype.h>
void rot13(string text)
{
for (int i = 0, n = strlen(text); i < n; i++)
{
if (isalpha(text[i]))
{
if (islower(text[i]))
{
// 소문자 처리 (a=97, z=122)
printf("%c", (text[i] - 'a' + 13) % 26 + 'a');
}
else
{
// 대문자 처리 (A=65, Z=90)
printf("%c", (text[i] - 'A' + 13) % 26 + 'A');
}
}
else
{
printf("%c", text[i]); // 문자가 아닌 경우 그대로
}
}
}
암호화 프로그램 예제
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("Usage: ./caesar key\n");
return 1;
}
int key = atoi(argv[1]); // 문자열을 정수로 변환
string plaintext = get_string("Plaintext: ");
printf("Ciphertext: ");
for (int i = 0, n = strlen(plaintext); i < n; i++)
{
if (isalpha(plaintext[i]))
{
// 알파벳 문자만 암호화
char base = isupper(plaintext[i]) ? 'A' : 'a';
printf("%c", (plaintext[i] - base + key) % 26 + base);
}
else
{
printf("%c", plaintext[i]); // 그 외는 그대로
}
}
printf("\n");
return 0;
}
🎯 프로그래밍 원칙과 모범 사례
효율적인 코드 작성
// 비효율적: 함수를 반복 호출
for (int i = 0; i < strlen(s); i++)
{
// strlen이 매번 호출됨
}
// 효율적: 한 번만 계산
for (int i = 0, n = strlen(s); i < n; i++)
{
// strlen이 한 번만 호출됨
}
상수 사용하기
// 나쁜 예: 매직 넘버
int scores[3];
for (int i = 0; i < 3; i++) { /* ... */ }
float average = sum / 3.0;
// 좋은 예: 상수 사용
const int N = 3;
int scores[N];
for (int i = 0; i < N; i++) { /* ... */ }
float average = sum / (float) N;
범위(Scope) 관리
// 전역 변수 (모든 함수에서 접근 가능)
const int MAX_STUDENTS = 50;
int main(void)
{
// 지역 변수 (main 함수에서만 접근 가능)
int scores[MAX_STUDENTS];
for (int i = 0; i < MAX_STUDENTS; i++) // i는 루프에서만 유효
{
scores[i] = get_int("Score: ");
}
// printf("%d", i); // 오류: i는 여기서 접근 불가
}
🚀 다음 단계
이번 주차에서 배운 핵심 개념
컴파일 과정: 전처리 → 컴파일 → 어셈블 → 링킹
메모리 구조: 바이트 단위의 주소 체계
배열: 연속된 메모리에 같은 타입 데이터 저장
문자열: null 종료 문자 배열
디버깅: printf, debugger, rubber duck
명령줄 인수: 프로그램 실행 시 매개변수 전달
암호화: 데이터 보안의 기본 원리
앞으로 배울 내용
포인터: 메모리 주소 직접 조작
동적 메모리 할당: malloc과 free
자료구조: 연결 리스트, 스택, 큐
알고리즘: 정렬, 검색, 재귀
파일 입출력: 데이터 영속성
실무 연결점
시스템 프로그래밍: 운영체제, 드라이버 개발
임베디드 시스템: IoT, 마이크로컨트롤러
게임 개발: 메모리 최적화, 성능 튜닝
보안: 암호화, 해시 함수, 디지털 서명
💡 마무리 메시지
이번 강의에서는 컴퓨터의 내부 동작 원리를 깊이 있게 살펴보았습니다. 소스코드가 어떻게 실행 가능한 프로그램이 되는지, 메모리에서 데이터가 어떻게 저장되고 관리되는지 이해하게 되었습니다.
배열과 문자열은 프로그래밍의 기초 중의 기초입니다. 이들을 잘 이해하면 더 복잡한 자료구조도 쉽게 이해할 수 있습니다. C언어에서 문자열이 단순히 문자 배열이라는 사실을 이해했다면, 다른 언어의 문자열 처리도 훨씬 명확해질 것입니다.
디버깅은 프로그래밍 실력의 핵심입니다. printf로 시작해서 전문적인 디버거 사용법까지 익혔다면, 복잡한 버그도 체계적으로 해결할 수 있습니다. 러버덕 디버깅처럼 간단한 기법도 놀라울 정도로 효과적임을 기억하세요.
메모리 관리와 효율성 고려는 좋은 프로그래머의 필수 덕목입니다. strlen을 반복 호출하지 않기, 상수 사용하기 등의 작은 습관들이 모여 큰 차이를 만듭니다.
마지막으로 암호화는 현대 디지털 세상의 필수 요소입니다. 간단한 Caesar cipher부터 시작했지만, 이것이 RSA, AES 같은 현대 암호화 기술의 기초가 됩니다. 데이터 보안의 중요성을 항상 염두에 두고 프로그램을 작성하세요.
-
🏛️ [CS50] Lecture 1 요약: C언어와 프로그래밍의 시작
🏛️ [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: 새 줄(줄바꿈)을 의미하는 이스케이프 시퀀스
세미콜론(;): 모든 문장의 끝에 필요
개발 과정의 세 단계
코딩: code hello.c - 파일 생성 및 편집
컴파일: make hello - 소스코드를 기계어로 변환
실행: ./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 루프의 구조
초기화: int i = 0
조건: i < 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; // 정확한 나눗셈
🎯 프로그래밍 원칙과 스타일
좋은 코드의 특징
정확성(Correctness): 의도한 대로 작동
효율성(Efficiency): 빠르고 메모리를 적게 사용
가독성(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언어로 배운 기초가 향후 어떤 언어를 배우더라도 탄탄한 기반이 될 것입니다.
-
🏛️ [CS50] Lecture 0 요약: 컴퓨터 과학과 프로그래밍의 예술
🏛️ [CS50] Lecture 0 요약: 컴퓨터 과학과 프로그래밍의 예술.
📋 강의 개요.
CS50이란?
하버드 대학교의 컴퓨터 과학 입문 강의.
컴퓨터 과학의 지적 기업과 프로그래밍의 예술을 다루는 수업
컴퓨터 과학의 지적 기업의 의미:
컴퓨터 과학은 인류가 함께 수행하는 거대한 지적 탐구이자, 학문적 모험이다.
수강생의 2/3가 컴퓨터 과학 경험이 전혀 없는 초보자들
핵심 철학
상대적 성장이 중요 :
다른 학생들과의 비교가 아닌, 자신의 시작점 대비 얼마나 성장했는지가 중요
불편함에 익숙해지기 :
새로운 것을 배울 때 불편함을 느끼는 것은 당연하며, 이를 받아들여야 함
일반적 사고 방식 :
컴퓨터 과학은 문제를 해결하는 범용적 사고 방식
🔢 정보의 표현.
이진법(Binary)의 이해
단진법(Unary)에서 이진법(Binary)으로
단진법(Unary) :
손가락으로 1, 2, 3, 4, 5까지 세는 방식
이진법(Binary) :
손가락의 위치(올림/내림)를 활용하면 최대 31까지 표현 가능
비트(Bit) :
Binary Digit의 줄임말
0 또는 1의 값을 가짐
이진법의 원리
10진법 기준:
- 123 = 100x1 + 10x2 + 1x3
- 각 자리는 10의 거듭제곱 (10⁰, 10¹, 10²)
이진법 기준:
- 101 = 4x1 + 2x0 + 1x1 = 5
- 각 자리는 2의 거듭제곱 (2⁰, 2¹, 2²)
바이트(Byte)와 정보량
1 바이트 = 8 비트
8비트로 표현 가능한 수: 0~255 (총 256가지)
컴퓨터는 전기의 유무(켜짐/꺼짐)로 이진법을 구현
📝 문자와 텍스트 표현
ASCII 코드 시스템
A = 65: 대문자 A는 이진법으로 65에 해당
ASCII: American Standard Code for Information Interchange
영어 알파벳과 기본 문자들을 숫자로 매핑
유니코드(Unicode)
ASCII의 한계: 8비트로는 영어 외 언어 표현에 부족
유니코드: 16, 24, 32비트까지 사용하여 전 세계 언어 지원
이모지: 유니코드 문자의 한 종류, 최대 40억 개 문자 표현 가능
이모지의 조합 원리
기본 이모지 + 수정자로 다양한 표현 생성
예: 👍 (기본) + 피부톤 수정자 = 다양한 피부톤의 엄지척
Zero-Width Joiner를 통해 여러 이모지 결합
🎨 멀티미디어 표현
색상 표현 (RGB)
RGB: Red, Green, Blue 값으로 색상 표현
각 색상은 0~255 값을 가짐 (1바이트)
예: (72, 73, 33) = 노란색 계열
이미지와 픽셀
픽셀: 화면의 각 점, 3바이트(RGB)로 색상 정보 저장
이미지 크기가 클수록 더 많은 픽셀과 용량 필요
음악과 비디오
음악: 음높이, 지속시간, 음량을 숫자로 표현
비디오: 초당 30프레임의 이미지를 빠르게 재생하여 동작 착시 효과
🔍 알고리즘과 문제 해결
문제 해결의 구조
입력(Input) → 알고리즘(Algorithm) → 출력(Output)
전화번호부 검색 알고리즘 비교
1. 선형 검색 (Linear Search)
첫 페이지부터 순차적으로 검색
최악의 경우: 1,000번의 시도 필요
시간복잡도: O(n)
2. 2페이지씩 검색
2페이지씩 넘기며 검색
최악의 경우: 500 + 1번의 시도
여전히 O(n)이지만 2배 빠름
3. 이분 탐색 (Binary Search)
중간을 찾아 반씩 제거하며 검색
최악의 경우: 약 10번의 시도 (log₂ 1000)
시간복잡도: O(log n) - 획기적 개선
효율성의 중요성
데이터가 2배 증가할 때:
선형 검색: 2배 더 오래 걸림
이분 탐색: 단 1번만 더 필요
💭 의사코드(Pseudocode)
전화번호부 검색 의사코드
1. 전화번호부를 집는다
2. 중간 페이지를 연다
3. 페이지를 본다
4. 만약 찾는 사람이 페이지에 있다면
5. 전화를 건다
6. 아니면 만약 찾는 사람이 앞쪽에 있다면
7. 왼쪽 절반의 중간을 연다
8. 3번으로 돌아간다
9. 아니면 만약 찾는 사람이 뒤쪽에 있다면
10. 오른쪽 절반의 중간을 연다
11. 3번으로 돌아간다
12. 아니면
13. 포기한다
프로그래밍의 기본 구성 요소
함수(Functions): 동작을 수행하는 기능
조건문(Conditionals): 분기를 만드는 if-else
불린 표현식(Boolean Expressions): 참/거짓 질문
루프(Loops): 반복 실행
🤖 인공지능 소개
전통적 프로그래밍의 한계
if student says "hello":
chatbot says "hello"
elif student says "goodbye":
chatbot says "goodbye"
elif student says "how are you":
chatbot says "I'm well"
# 모든 경우를 미리 프로그래밍해야 함
대규모 언어 모델(LLM)
입력: 인터넷의 모든 텍스트 데이터
학습: 언어 패턴을 통계적으로 학습
출력: 확률적으로 가장 적절한 응답 생성
CS50 AI (CS50 Duck)
CS50 전용 AI 튜터
답을 직접 주지 않고 학습을 유도
24시간 이용 가능한 개인 튜터 역할
🎮 Scratch 프로그래밍
Scratch 소개
MIT에서 개발한 시각적 프로그래밍 언어
드래그 앤 드롭으로 코드 작성
복잡한 문법 없이 프로그래밍 개념 학습
기본 프로그램: “Hello, World”
when green flag clicked
say [Hello, World]
상호작용 프로그램
when green flag clicked
ask [What's your name?] and wait
say (join [Hello, ] (answer))
함수와 추상화
추상화: 복잡한 구현을 숨기고 간단한 인터페이스 제공
사용자 정의 함수: 반복되는 코드를 함수로 만들어 재사용
예: 고양이 울음소리 함수
define meow (n) times
repeat (n)
play sound [meow]
wait (1) seconds
이벤트와 조건문
when green flag clicked
forever
if <touching [mouse-pointer]> then
play sound [meow]
🎯 핵심 개념 정리
프로그래밍의 4가지 기본 요소
함수(Functions): 특정 작업을 수행하는 코드 블록
조건문(Conditionals): 상황에 따른 분기 처리
루프(Loops): 반복 작업 수행
변수(Variables): 데이터 저장 및 관리
좋은 프로그램의 특징
정확성(Correctness): 의도한 대로 작동
효율성(Efficiency): 빠르고 자원을 적게 사용
가독성(Readability): 이해하기 쉬운 코드
프로그래밍 학습 접근법
점진적 개발: 한 번에 완성하지 말고 단계별로 구현
추상화 활용: 복잡한 부분을 함수로 분리
반복 개선: 동작하는 버전을 만든 후 개선
🚀 다음 단계
앞으로 배울 언어들
C: 저수준 프로그래밍 언어, 컴퓨터 구조 이해
Python: 데이터 과학, 웹 개발에 널리 사용
SQL: 데이터베이스 관리 언어
JavaScript: 웹 개발의 핵심 언어
학습 목표
특정 언어에 국한되지 않은 프로그래밍 사고력 배양
새로운 언어를 스스로 학습할 수 있는 능력 개발
문제 해결 능력과 논리적 사고력 향상
💡 마무리 메시지
컴퓨터 과학은 단순히 코딩을 배우는 것이 아니라, 체계적이고 논리적으로 문제를 해결하는 사고 방식을 기르는 것입니다. 이러한 사고력은 기술 분야뿐만 아니라 인문학, 사회과학, 자연과학 등 모든 분야에 적용될 수 있는 범용적 도구입니다.
불편함을 느끼는 것은 성장의 신호이며, 실수와 시행착오를 통해 배우는 과정이 바로 진정한 학습입니다.
Touch background to close