본문 바로가기
Topos Uranos

요약

이 수업은 강좌의 첫 단원을 이루며, 강좌의 학습 계획 전체를 제시한다. 강좌의 목적이 어떤 언어가 아니라 프로그래밍 그 자체임을 밝히고, 그 기초를 학습 곡선이 더 완만한 언어가 아니라 기계를 숨기지 않는 언어인 C++로 가르치는 이유를 설명한다. 또 강좌가 대상으로 하는 학생, 전제로 하는 얼마 안 되는 지식, 강좌가 이어지는 강좌들을 짚고, 일곱 가지 전체 학습 목표와 각 목표에 기여하는 단원을 진술하며, 여덟 개의 내용 단원과 그것을 이루는 수업을 기술한다. 나아가 강좌가 요구하는 전체 학습량, 그 단원별 배분, 권장 학습 속도, 학점을 추정하고, 수업이 어떻게 만들어졌는지 설명하며, 끝으로 이 강좌를 공부하는 방법, 인공지능 어시스턴트의 사용, 버전 관리에 대해 조언한다.

학습 목표

  1. 강좌의 여덟 단원의 순서를 설명하고, 각 단원이 이후 단원에 무엇을 주는지 설명한다.
  2. 강좌의 일곱 가지 전체 학습 목표를 진술하고, 어느 단원이 각 목표에 기여하는지 알아본다.
  3. 이 강좌가 프로그래밍의 기초를 자료형과 메모리를 숨기는 언어가 아니라 C++로 가르치는 이유를 예를 들어 설명한다.
  4. 단원별 추정 시간에서 출발하여 강좌 전체나 그 일부가 요구하는 학습량을 계산하고, 그것으로 자신의 학습 속도를 계획한다.

목적

이 강좌는 올바른 프로그램을 처음부터 작성하는 법을 가르친다. 말로 진술된 문제를 정확한 명령의 순서로 바꾸고, 데이터를 어떻게 표현할지 고르고, 작성한 것이 실행되는 순서를 다스리고, 코드를 명확한 인터페이스를 가진 함수로 조직하는 법이다. 그렇다면 이 강좌의 대상은 하나의 언어가 아니라 프로그래밍 그 자체, 곧 알고리즘을 구상하고, 데이터를 표현하고, 문제를 분해하고, 작성한 것의 정확성과 비용에 대해 추론하는 일이다. 그래서 이 강좌의 이름이 “프로그래밍의 기초”이고, 그래서 C++의 모든 구문 요소는 그것이 해결하는 문제와 그것에 대해 추론하는 방법과 함께 제시되며, 결코 문법 목록으로 제시되지 않는다.

따라서 C++는 이 강좌의 도구이지 목적이 아니다. 그런데 도구는 그것으로 배우는 것과 무관하지 않으며, 언뜻 보기에 가장 어려워 보이는 이 도구를 고른 것은 의도된 선택이다. 그 이유는 뒤의 절에서 밝힌다. 다만 그 가장 눈에 띄는 결과를 미리 말해 둘 만하다. 각 단원의 종합 수업에는 “어느 언어에서나 통하는 것”이라는 고정된 절이 있어, 배운 것을 C++에서의 형태와 떼어 놓고, 다른 언어에서는 그것이 어떻게 표현되는지, 또는 무엇이 달라지는지를 말한다. 이로부터, 이 강좌를 마친 학생은 C++로 작성할 줄 알 뿐 아니라, 어느 언어에서든 옷만 갈아입은 같은 생각을 알아볼 수 있게 된다.

왜 더 친절한 언어가 아니라 C++인가

Python처럼 학습 곡선이 더 완만한 언어들이 있다. 그런 언어에서는 쓸모 있는 첫 프로그램을 자료형을 선언하지도, 컴파일하지도, 데이터가 어디에 사는지 묻지도 않고 몇 줄로 작성한다. 목적이 동작하는 프로그램을 빨리 작성하는 것이라면 그것이 갈 길일 것이다. 그러나 이 강좌의 목적은 프로그램이 무엇을 하는지 이해하는 것이고, 그러려면 기계를 숨기지 않는 언어가 알맞다. C++는 더 편한 언어들이 조용히 처리해 버리는 여섯 가지를 보게 만든다.

  • 자료형. 모든 데이터는 자료형과 함께 선언되며, 자료형은 그 데이터가 가질 수 있는 값, 차지하는 크기, 허용하는 연산을 정한다. 컴파일러는 무엇이든 실행하기 전에 맞지 않는 것을 거부한다.
  • 컴파일. 작성, 컴파일, 링크, 실행은 서로 다른 단계이며, 모든 오류는 그 가운데 하나에 속한다. 어느 단계에 속하는지 아는 것이 고치는 일의 절반이다.
  • 메모리. 변수는 수명과 유효 범위를 가진, 이름 붙은 메모리 칸이다. 참조는 같은 칸의 다른 이름이며, 더는 존재하지 않는 칸을 가리키는 참조는 실제 오류이다.
  • 복사의 비용. 벡터를 함수에 전달하면 벡터 전체가 복사될 수 있다. 값 전달과 참조 전달 가운데 무엇을 고를지는 기계가 아니라 프로그래머가 내리는 결정이다.
  • 수의 한계. 32비트 정수는 2 147 483 6472\,147\,483\,647까지만 담을 수 있고, 부동소수점 수는 0.10.1의 값을 정확히 저장하지 못한다.
  • 정의되지 않은 동작. 표준이 정의하지 않는 것(배열의 범위를 벗어나는 접근, 부호 있는 정수의 오버플로, 초기화되지 않은 변수의 읽기)은 보장된 오류가 아니라 무슨 일이든 낳는다. 그래서 이 강좌는 언제나 경고와 새니타이저를 켜고 컴파일한다.

다음 예가 그것을 작은 규모로 보여 준다. 이 프로그램은 11부터 1515까지의 팩토리얼을 int 타입의 변수에서 계산한다. 정수가 한없이 커지는 Python이라면 같은 계산이 아무 불평 없이 13!=6 227 020 80013! = 6\,227\,020\,800을 출력할 것이다. C++에서는 12!12! 곱하기 1313의 값이 int 타입에 담기지 않으며, 이 강좌가 모든 예제를 컴파일할 때 쓰는 정의되지 않은 동작 새니타이저가 바로 그 줄에서 프로그램을 멈춘다.

C++ 프로그램
#include <print>​int main(){    int factorial{1};    for (int n{1}; n <= 15; ++n) {        factorial = factorial * n;   // 곱이 int에 담기는가?        std::println("{}! = {}", n, factorial);    }}
출력
1! = 12! = 23! = 64! = 245! = 1206! = 7207! = 50408! = 403209! = 36288010! = 362880011! = 3991680012! = 479001600main.cpp:7:31: runtime error: signed integer overflow: 479001600 * 13 cannot be represented in type 'int'

프로그램이 코드 1(으)로 종료되었습니다.

그렇다면 이 언어는 다른 언어라면 숨겼을 질문을 던지게 만든 것이다. 고른 자료형에 얼마까지 담기는지, 담기지 않을 때는 어떻게 할지라는 질문이다. 두 대답 가운데 어느 것도 추상적으로 더 낫지는 않다. 중요한 것은 그 질문이 있다는 것을 아는 일이다. 정수와 그 표현 수업이 이 질문에 답하고, 각 종합 수업에서 정의되지 않은 동작을 다루는 절이 이 질문으로 다시 돌아온다.

이로부터 하나의 비대칭이 생기며, 그것이 이 선택의 근본 이유이다. 기초를 C++로 배운 사람은 나중에 더 편한 언어들이 자기 대신 무엇을 해 주는지 이해한다. 가비지 컬렉터, 한계가 없는 정수, 인덱스 검사, 자료형의 암시적 변환이다. 반면 그런 언어 가운데 하나로 먼저 배운 사람은 이 층들을 보지 못한 채 지나가고, 나중에 실제 프로그램이 언어가 숨겨 두었던 원인 가운데 하나로 실패할 때 그것에 걸려 넘어진다. 따라서 이 강좌가 고르는 길은 처음에는 더 힘들지만 더 잘 옮겨 간다. 여기서 배운 것은 어느 언어에서나 통하지만, 그것을 숨기는 언어에서 배운 것은 여기서 늘 통하지는 않는다.

그런데 C++는 큰 언어이고, 기초 강좌는 그것을 전부 가르치고 싶은 유혹을 견뎌야 한다. 이 첫 강좌는 C++23 표준의 구조적 프로그래밍(데이터, 식, 제어 흐름, 시퀀스, 텍스트, 함수)으로 범위를 좁히고, 그 현대적인 형태를 쓴다. std::println 함수로 하는 출력, std::array 타입과 std::vector 타입으로 다루는 시퀀스, std::string 타입으로 다루는 텍스트, 범위(ranges) 형태의 라이브러리 알고리즘이다. 이 토대를 필요로 하는 것, 곧 포인터와 동적 메모리, 멤버 함수를 가진 클래스, 직접 만드는 템플릿, 익명 함수, 예외는 C++ 프로그래밍 II로 넘긴다.

대상과 사전 지식

이 강좌는 프로그래밍을 한 번도 해 보지 않은 사람을 대상으로 하며, 어느 학위 과정에서든 1학년부터 들을 수 있다. 자료 구조와 시스템 강좌에 앞서 탄탄한 토대가 필요한 컴퓨터 계열 학생, 컴퓨터를 맹목적으로 믿지 않으면서 계산에 써야 하는 공학과 자연과학 계열 학생, 자신의 알고리즘이 실행되는 프로그램으로 바뀌는 것을 보고 싶은 수학 계열 학생이다.

이 강좌는 수준 1의 강좌로, 선수 과목이 없고 그 자체로 완결되어 있다. 전제하는 것은 두 가지뿐이다. 정수 나눗셈과 그 나머지를 포함한 학교 산수, 그리고 컴퓨터의 기초적인 사용(폴더 만들기, 파일 저장하기, 프로그램 열기)이다. 이진 표현, 경우를 나누는 추론, 조건의 논리, 귀납법 가운데 쓰는 것은 필요한 자리에서 가르친다. 이진법은 정수 수업에서, 드모르간 법칙은 관계와 논리 수업에서, 귀납법은 재귀 수업에서 다룬다. 따라서 수업에 나오는 사이트의 다른 강좌로의 참조는 심화이지, 선수 조건이 아니다.

어디로 이어지는가

앞의 내용으로부터 사이트에서 이 강좌가 차지하는 자리가 정해진다. 이 강좌는 선수 과목이 없고, “정보학과 컴퓨팅”과 “공학”이라는 두 학위 과정의 목표 강좌이다. 두 과정 모두에서 프로그래밍은 도구이면서 동시에 대상이기 때문이다. 그러므로 이 강좌는 그 과정들의 첫 강좌 가운데 하나로, 학술적 글쓰기를 위한 LaTeX, 수리논리학 I: 명제 논리와 함께 듣는 것이 좋다.

이 강좌에는 보완 강좌가 하나 있으며, 이 강좌 전에, 동시에, 또는 후에 들을 수 있다. 수리논리학 I: 명제 논리는 여기서 실천으로 쓰는 것을 이론으로 공부한다. 3단원의 복합 조건, 진리표, 드모르간 법칙, 그리고 절차가 올바르다는 것을 증명한다는 생각인데, 이 생각은 이 강좌에서 반복문의 불변식과 재귀에 대한 귀납법의 형태를 띤다. 한편 대수학과 기하학 I은 이 강좌가 프로그램으로 옮기는 수에 관한 결과들을 증명한다. 유클리드 나눗셈, 유클리드 호제법, 기수법, 호너의 방법이다. 그리고 학술적 글쓰기를 위한 LaTeX은 그 강좌의 공동 집필 수업에서 Git을 이용한 버전 관리를 가르치는데, 이 강좌는 첫 프로그램부터 그것을 쓰기를 권한다.

그런데 이 강좌는 자신의 토대를 필요로 하는 것을 일부러 밖에 남겨 둔다. 예정된 후속 강좌는 C++ 프로그래밍 II로, 포인터와 동적 메모리, 클래스와 객체, 템플릿, 익명 함수, 예외, 그리고 표준 라이브러리를 깊이 다룰 것이다. 그러므로 이 강좌는 C++ 안내서의 첫 부분이 아니라, 여기서 마칠 수 있는 완결된 기초 강좌이다. 반면 계속 나아가는 사람은 강좌 II에서 같은 규율이, 이제는 그것 없이 지낼 수 없을 만큼 큰 프로그램에 적용되는 것을 보게 될 것이다.

전체 학습 목표

다음 목표들은 강좌를 마친 학생이 할 수 있어야 하는 일을 기술한다. 각 목표는 관찰할 수 있는 수행으로 진술되어 있어 구체적인 과제로 확인할 수 있다. 내용을 다루는 각 수업도 저마다 고유한 목표를 밝히며, 그 목표는 하나 이상의 전체 목표에 이바지한다. 마무리 수업은 각 목표가 어느 단원과 어느 수업에서 다루어지는지 보여 준다. 일곱 목표에는 인용할 수 있도록 번호를 붙인다.

  1. 목표 1. 프로그램으로 문제를 해결한다. 무엇이 들어오고 무엇이 나가는지 명세하고, 알고리즘을 설계하고, 그것을 손으로 따라가 보고, 작성하고, 컴파일하고, 실행하고, 디버깅하면서 컴파일러의 메시지와 경고를 해석한다.
  2. 목표 2. 각 데이터에 알맞은 자료형을 고르고, 그 표현의 한계에 대해 추론한다. 정수의 값의 범위, 오버플로, 부동소수점의 반올림, 텍스트의 인코딩이다.
  3. 목표 3. 우선순위, 형 변환, 부수 효과, 표준이 정의하지 않은 채 남겨 두는 것을 알고서 올바른 식을 만든다.
  4. 목표 4. 올바르고 종료하는 조건문과 반복문으로 제어 흐름을 설계하고, 그 정확성을 불변식으로 정당화한다.
  5. 목표 5. 문제를 명확한 인터페이스를 가진 함수들로 분해하고, 매개변수를 전달하는 방식을 고르며, 재귀로 문제를 해결한다.
  6. 목표 6. 복합 데이터(시퀀스, 표, 레코드, 텍스트, 파일)를 기본 알고리즘과 표준 라이브러리로 표현하고 처리하며, 그 비용을 추정한다.
  7. 목표 7. 더 큰 프로그램을 여러 파일로 조직하고, 사전 조건과 사후 조건으로 명세하며, 체계적으로 테스트하고, 그 정확성을 논증한다.

다섯 목표에는 저마다의 단원이 있다. 목표 2는 데이터와 자료형 단원, 목표 3은 식 단원, 목표 4는 제어 흐름 단원, 목표 5는 함수 단원, 목표 6은 복합 데이터 단원인데, 이 단원은 텍스트와 파일 단원으로 이어지고, 거기서 목표 2가 UTF-8 인코딩과 함께 다시 나타난다. 반면 나머지 두 목표는 강좌 전체를 관통한다. 목표 1은 명세하고, 작성하고, 컴파일하고, 실행하는 작업 순환을 가르치는 첫 단원과 마지막 단원의 디버깅 수업을 주된 자리로 삼는다. 그러나 강좌의 모든 프로그램이 컴파일되고, 실행되고, 수정되므로 모든 단원에서 연습된다. 목표 7은 마지막 단원, 곧 더 큰 프로그램 단원을 주된 자리로 삼는다. 그러나 첫 단원의 스타일 가이드에서 시작하여 둘째 단원의 상수로 이어지고, 다섯째 단원의 계약(그때부터 모든 함수에 따라붙는다)과 넷째와 여섯째 단원의 불변식에서 모습을 갖춘다. 이로부터 이 두 목표의 성취는 한 단원의 몫으로 돌릴 수 없으며, 강좌 전체에 걸쳐 길러 가는 것이 좋다는 결론이 나온다.

내용

이 강좌는 열 개의 단원으로 이루어진다. 이 소개, 여덟 개의 내용 단원, 그리고 마무리이다. 각 내용 단원은 도입 수업으로 시작하는데, 도입 수업은 단원의 수업들, 전제로 하는 지식, 단원이 해결하는 문제를 소개하고, 입문 평가로 시작한다. 단원은 종합 수업으로 끝나는데, 종합 수업은 단원의 개념의 사슬을 다시 세우고, 어느 언어에서나 통하는 것을 C++에서의 형태와 떼어 놓고, 단원의 정의되지 않은 동작을 모으고, 마무리 평가에서 입문 평가의 기능을 다시 묻고, 수업의 문제보다 어려운 도전 과제를 낸다.

0단원. 강좌 소개

이 수업 하나로 이루어지며, 강좌의 목적, 언어의 선택, 전체 목표, 내용, 학습량, 설계를 정한다.

1단원. 첫걸음

이 단원의 목표는 말로 진술된 문제에서 출발하여 컴파일되고, 실행되고, 데이터를 읽고, 결과를 쓰는 프로그램에 이르기까지 학생을 이끌되, 그 길에서 각 도구가 무엇을 했는지 알게 하는 것이다. 알고리즘이 프로그램에 앞서므로 코드 없이 시작한다. 단원을 마칠 때 학생은 C++에 대해 아직 아는 것이 적지만, C++로 어떻게 작업하는지는 안다.

  1. C++ 첫걸음: 도입.
  2. 문제에서 알고리즘으로: 입력과 출력의 명세, 번호 매긴 단계, 손으로 하는 추적, 순차, 선택, 반복.
  3. 프로그램, 컴파일러, C++: 전처리, 컴파일, 링크. 컴파일 언어와 인터프리터 언어의 대비. 언어의 표준.
  4. Windows, macOS, Linux에 환경 설치하기: 세 가지 완전한 안내, 컴파일러, Visual Studio Code와 그 C++ 확장, 설치 확인, 흔한 문제.
  5. 컴파일, 실행, 설정: 이 강좌의 옵션, 오류와 경고, 새니타이저, 편집기의 빌드 작업, 콘솔의 인코딩.
  6. 프로그램의 해부: 포함 지시문, main 함수, 문, 블록, 주석, 이 강좌의 스타일 가이드.
  7. 입력, 출력, 오류: std::cin 객체, std::println 함수와 형식 지정, 기대와 다른 입력, 오류의 분류.
  8. C++ 첫걸음: 종합과 도전 과제.

2단원. 데이터와 자료형

이 단원의 목표는 모든 데이터가 자료형과 함께 메모리에 살며, 자료형이 그 데이터가 가질 수 있는 값, 차지하는 크기, 허용하는 연산을 정한다는 것, 그리고 프로그램의 수는 수학의 수가 아니라 한계가 있는 표현이라는 것을 가르치는 것이다.

  1. 데이터와 자료형: 도입.
  2. 변수와 메모리: 선언과 중괄호 초기화, 대입, 상태의 추적, 이름, 초기화되지 않은 변수.
  3. 정수와 그 표현: 밑 2와 16, 각 자료형의 값의 범위, 2의 보수, 부호 있는 정수와 부호 없는 정수의 오버플로.
  4. 부동소수점 수: 0.10.1의 값이 왜 정확하지 않은지, 누적되는 반올림, 허용 오차를 둔 비교, 무한대와 비수(NaN).
  5. 문자, 불리언, 형 변환, 상수: 문자의 문자 코드, 암시적 형 변환과 명시적 형 변환, 정보를 잃는 형 변환의 거부, const 키워드, constexpr 키워드, auto 키워드.
  6. 데이터와 자료형: 종합과 도전 과제.

3단원. 식과 연산자

이 단원의 목표는 값을 확실하게 예측할 수 있는 식을 만드는 법을 가르치는 것이다. 어느 연산이 먼저 이루어지는지, 결과가 어떤 자료형인지, 어떤 부수 효과가 있는지, 어떤 식이 의미를 갖지 않는지이다. 이 단원에는 아직 조건문도 반복문도 없다. 조건은 계산되어 출력될 뿐이다.

  1. 식과 연산자: 도입.
  2. 산술 연산: 음수가 있을 때의 몫과 나머지, 식 트리, 우선순위, 결합 방향, 결과의 자료형.
  3. 관계와 논리: 복합 조건, 드모르간 법칙에 따른 그 부정, 단락 평가, 1 < x < 5 식의 오류.
  4. 대입, 부수 효과, 정의되지 않은 동작: 식의 값과 효과, 증가 연산, 평가 순서, 표준이 정의하지 않는 것의 목록.
  5. 비트와 수학 라이브러리: 비트 연산자, 시프트, 마스크, <cmath> 헤더, <numbers> 헤더의 상수.
  6. 식과 연산자: 종합과 도전 과제.

4단원. 제어 흐름

이 단원의 목표는 조건문과 반복문으로 실행 순서를 다스리고, 그 정확성에 대해 추론하는 법을 가르치는 것이다. 모든 경우가 처리되는지, 모든 반복문이 해야 할 일을 하는지, 그리고 종료하는지이다. 이 단원은 강좌의 첫 수업이 예고한 것, 곧 선택과 반복을 이제 코드로 작성하고 검증된 형태로 실현한다.

  1. 제어 흐름: 도입.
  2. 조건문: 연쇄되고 중첩된 if 문과 else 문, 겹치지 않고 빠짐없는 경우, enum class 열거형을 쓰는 switch 문과 그 함정.
  3. 반복문: while 문, do 문, for 문, 반복문의 추적, 형태들 사이의 변환, 중첩 반복문.
  4. 올바른 반복문 설계: 카운터, 누산기, 최댓값, 보초의 패턴, 불변식, 종료, 디버거와의 첫 만남.
  5. 수치 알고리즘: 소수 판정, 유클리드 호제법, 진법 변환, 호너의 방법, 이분법. 각각 그 정당화와 함께 다룬다.
  6. 제어 흐름: 종합과 도전 과제.

5단원. 함수

이 단원의 목표는 문제를 저마다 인터페이스와 계약을 가진, 이름 붙은 부분들로 분해하는 법과 재귀로 문제를 해결하는 법을 가르치는 것이다. 이 단원은 4단원의 긴 프로그램을 하나씩 검증되는 부분들로 이루어진 프로그램으로 바꾼다.

  1. 함수: 도입.
  2. 함수: 선언, 정의, 호출, 호출 스택, 사전 조건과 사후 조건, 값을 반환하지 않는 함수.
  3. 매개변수와 유효 범위: 값 전달, 참조 전달, 상수 참조 전달, 구조적 바인딩을 이용한 여러 결과, 변수의 유효 범위와 수명.
  4. 함수로 설계하기: 단계적 세분화, 본문보다 먼저 인터페이스, “한 함수에 한 가지 일”이라는 규칙, 오버로딩.
  5. 재귀: 기저 사례와 재귀 사례, 귀납법에 의한 정확성과 종료, 스택과 재귀의 비용.
  6. 함수: 종합과 도전 과제.

6단원. 복합 데이터

이 단원의 목표는 데이터의 모음을 표현하고, 그것을 손으로 작성한 알고리즘과 라이브러리에서 가져온 알고리즘으로 처리하며, 그 비용을 추정하는 법을 가르치는 것이다. 이 단원은 낱개의 데이터에서 컬렉션으로, “올바른가?”라는 질문에서 “얼마나 드는가?”라는 질문으로 이끈다.

  1. 복합 데이터: 도입.
  2. 배열: std::array 타입, 인덱스를 이용한 순회와 범위 기반 for 문을 이용한 순회, 범위 안의 인덱스, 대조로서의 C 배열.
  3. 벡터: std::vector 타입, 길이를 모르는 일련의 값 읽기, 누적, 필터링, 변환, 크기와 용량.
  4. 알고리즘의 비용: 지배적 연산의 개수 세기, 증가 차수, 시간의 측정.
  5. 탐색과 정렬: 선형 탐색과 이진 탐색, 불변식을 갖춘 선택 정렬과 삽입 정렬, 라이브러리의 알고리즘.
  6. 표: 이차원 표, 행 단위와 열 단위의 순회, 전치, 곱, 평탄화한 벡터 안의 표.
  7. 레코드: struct 키워드, 지정 초기화자, 매개변수로서의 레코드, 한 필드에 따른 정렬.
  8. 복합 데이터: 종합과 도전 과제.

7단원. 텍스트와 파일

이 단원의 목표는 텍스트를 데이터로 다루는 법과, 형식이 잘못된 입력 앞에서도 무너지지 않고 파일을 읽고 쓰는 프로그램을 만드는 법을 가르치는 것이다. 단원을 마칠 때 학생은 악센트 부호와 오류까지 담긴 실제 데이터를 처리하는 프로그램을 작성할 수 있다.

  1. 텍스트와 파일: 도입.
  2. 문자열: std::string 타입과 그 연산, npos 상수, std::string_view 타입의 뷰, 대조로서의 C 문자열.
  3. 텍스트는 ASCII가 아니다: UTF-8 인코딩, 바이트와 문자, 텍스트를 깨뜨리는 연산.
  4. 입력을 제대로 읽기: std::getline 함수, 스트림 상태, 잘못된 데이터 뒤의 복구, std::from_chars 함수를 이용한 검증.
  5. 텍스트 파일: 열기, 한 줄씩 읽기, 쓰기, 덧붙이기, 그리고 쉼표로 구분한 값의 파일 처리.
  6. 텍스트와 파일: 종합과 도전 과제.

8단원. 더 큰 프로그램

이 단원의 목표는 이제 한 파일에도, 머릿속에도 담기지 않는 프로그램을 조직하고, 검증하고, 디버깅하는 법을 가르치는 것이다. 이 단원은 첫 수업이 연 순환, 곧 명세하고, 만들고, 검증하는 순환을 닫는다. 그 종합 수업은 강좌 전체를 아우르는 최종 프로젝트로 끝난다.

  1. 더 큰 프로그램: 도입.
  2. 여러 파일과 자체 라이브러리: 헤더와 구현, 인클루드 가드, 단일 정의 규칙, 분할 컴파일.
  3. 프로그램 테스트: 명세에서 이끌어 낸 테스트 케이스, 자동 테스트, assert 매크로와 static_assert 선언.
  4. 프로그램 디버깅: Visual Studio Code의 디버거, 중단점, 호출 스택, 오류의 위치를 찾는 체계적인 전략.
  5. 더 큰 프로그램: 종합과 도전 과제.

9단원. 강좌 마무리

C++ 프로그래밍 I: 최종 결산 수업으로 이루어진다. 이 수업은 일곱 전체 목표를 되새기고, 각 목표가 어느 단원과 어느 수업에서 다루어지는지 그림으로 보여 주고, 마무리 평가의 첫 시도를 목표별로 모으고, 공부가 어디로 이어지는지 가리킨다.

학습량과 학점

강좌의 학습량은 강의 시간으로 재지 않는다. 비동기 강좌에는 강의 시간이 없기 때문이다. 재는 것은 학생의 전체 작업, 곧 읽기, 예제를 직접 입력하고 컴파일하기, 문제 풀이, 복습이다. 추정은 이 사이트의 모든 강좌에서 같은 방법을 따른다. 각 수업의 본문(예제 코드 포함)을 분당 백 단어로 읽는 시간, 예제 하나에 이십 분, 연습 문제 하나에 삼십오 분, 도전 과제 하나에 사십오 분, 입문 평가나 마무리 평가 한 번에 십이 분을 세고, 복습을 위해 십분의 일의 여유를 더한다.

결과

이 강좌의 수업들에는 모두 합해 예제 105105개, 연습 문제 105105개, 도전 과제 5252개가 있고, 그 밖에 단원 평가 열여섯 번과 사이트가 즉시 확인하는 짧은 연습 일흔 개가 있다. 단원별 추정 학습량을 시간 단위로 반올림하면 다음과 같다.

  • 0단원, 강좌 소개: 11시간.
  • 1단원, 첫걸음: 2828시간.
  • 2단원, 데이터와 자료형: 2222시간.
  • 3단원, 식과 연산자: 2222시간.
  • 4단원, 제어 흐름: 2222시간.
  • 5단원, 함수: 2222시간.
  • 6단원, 복합 데이터: 3030시간.
  • 7단원, 텍스트와 파일: 2121시간.
  • 8단원, 더 큰 프로그램: 2020시간.
  • 9단원, 강좌 마무리: 11시간 미만.

합계는 학생의 작업으로 약 188188시간이다. 이 수치는 있는 그대로 읽는 것이 좋다. 모든 예제를 입력하고 컴파일하며, 모든 문제와 도전 과제를 푸는 학생을 기준으로 한 추정이라는 것이다. 다른 언어로 프로그래밍을 해 본 사람은 처음 단원들에서 시간이 꽤 덜 들 것이다. 프로그램을 설치해 본 적도, 오류 메시지를 읽어 본 적도 없는 사람은 더 필요할 수 있는데, 특히 1단원에서 그렇다. 이 단원은 복합 데이터 단원과 함께 가장 분량이 많은 단원이기도 하다.

그런데 이 수치는 명목상의 것이다. 강좌의 학점을 정하는 것이 바로 이 수치이며, 일정한 속도의 읽기와 도전 과제 하나에 사십오 분을 전제로 한다. 실제 학생들로 재기 전까지는 현실적인 범위를 함께 제시하는 것이 좋다. 읽기가 가정한 속도의 절반으로 진행되고(예제마다 손으로 입력하고, 컴파일하고, 무엇이 바뀌는지 보려고 고쳐 보면 그렇게 된다), 도전 과제 하나에 사십오 분이 아니라 칠십오 분이 든다면, 같은 방법으로 약 250250시간이 나온다. 따라서 예상되는 학습량은 약 188188시간에서 약 250250시간 사이에 놓인다. 위쪽 끝은 신중한 상한이지 측정값이 아니다. 이 범위는 강좌를 공부하는 사람들이 실제로 쓰는 시간이 알려지면 수정될 것이며, 그때까지 학점은 명목상의 수치로 계산한다.

권장 속도

학습 속도를 부르기 위해, 이 계획은 사이트의 다른 학습 계획들과 같은 기준을 쓴다. 전일제는 6060학점의 한 학년, 곧 약 16501650시간을 약 마흔 주에 나눈 것, 다시 말해 주당 약 4040시간이고, 시간제는 그 절반인 주당 약 2020시간이다. 이로부터 권장 속도는 열여섯 주의 한 학기, 주당 약 1212시간의 공부가 되는데, 이는 전일제의 삼분의 일에 조금 못 미친다. 현실적인 범위로는 주당 1212시간에서 1616시간이다. 이 학기는 수리논리학 I: 명제 논리와 함께 들을 수 있는데, 그 강좌의 진리표와 증명의 생각은 이 강좌의 조건과 불변식에서 알아볼 수 있다.

주당 약 2020시간, 곧 시간제의 집중적인 속도라면 강좌를 아홉 주나 열 주에 마친다. 주당 약 66시간의 느긋한 속도라면 두 학기에 걸치며, 두 학기는 거의 절반씩 나뉜다. 알고리즘에서 제어 흐름까지인 0단원에서 4단원까지가 약 9595시간, 함수에서 최종 프로젝트까지인 5단원에서 9단원까지가 약 9393시간이다. 이 나눔은 자연스럽다. 4단원을 마칠 때 학생은 이미 하나의 main 함수 안에 완전한 프로그램을 작성하며, 후반부는 그것을 조직하는 법을 가르친다.

학점

이 사이트의 학점은 강좌가 공부하는 사람에게 요구하는 학습의 총량(읽기, 문제, 도전 과제, 복습)을 재는 것이지, 수업 시간을 재는 것이 아니다. 한 학점은 약 27.527.5시간의 학습에 해당하고, 전일제 일 년은 6060학점, 곧 약 16501650시간에 해당한다. 이는 앞 항목에서 학습 속도를 부를 때 쓴 것과 같은 기준이다.

그런데 추정한 188188시간을 27.527.5로 나누면 6.8을 얻으며, 이를 반올림하면 77이 된다. 학점 하나의 범위를 2525시간에서 3030시간의 학습으로 인정하면, 이 강좌의 학점 수 CC의 값은 6.3과 7.5 사이로 범위가 정해진다.

(18830)≤C≤(18825)\left( \frac{188}{30} \right) \leq C \leq \left( \frac{188}{25} \right)

따라서 이 강좌는 77학점이며, 이는 전일제 일 년의 구분의 일을 조금 넘는다. 이 수치는 실수가 아니다. 강좌는 기초를 가르치는 데 필요한 만큼의 길이를 가지며, 딱 떨어지는 학점 수에 맞추려고 줄이지 않았다.

학점의 쓸모는 실제적이다. 학점으로 사이트 강좌들의 부담을 서로 비교하고, 쓸 수 있는 시간을 넘지 않으면서 몇 개의 강좌를 함께 공부하는 것이 좋은지 정할 수 있다. 예를 들어 열여섯 주의 한 학기 동안 시간제로, 곧 약 320320시간을 쓸 수 있는 사람은 합계 약 1212학점의 강좌들을 다룰 수 있고, 같은 학기를 전일제로 쓸 수 있다면 약 2323학점이다.

이 학점은 학습을 계획하기 위해 이 사이트가 자체적으로 쓰는 척도로, 어떤 공식 학점 제도에도 해당하지 않으며 어떤 기관의 학업 인정도 뜻하지 않는다.

강좌의 설계

형태

이 강좌는 비동기, 온라인, 자기 주도 속도로 진행된다. 날짜도, 실시간 수업도, 마감도 없으며, 학생은 저마다 자신의 준비와 시간이 허락하는 속도로 나아간다. 이 강좌는 사이트의 열네 개 언어로 제공될 것이다. 프로그램의 식별자는 영어로 되어 있고 모든 판에서 같다. 반면 주석과 프로그램이 보여 주는 메시지는 페이지의 언어로 되어 있어, 각 판이 저마다의 출력으로 컴파일되고 실행된다. 수업마다 먼저 공부해 두면 좋은 다른 수업을 밝혀 두므로, 권장 순서가 명시적이다.

수업의 구성

내용을 다루는 모든 수업은 같은 구성을 따른다. 요약과, 관찰할 수 있는 수행으로 진술된 고유한 학습 목표의 목록으로 시작한다. 이어서 설명이 나오며, 그 핵심은 프로그램이다. 이 강좌의 완전한 프로그램은 모두 사이트를 빌드하기 전에 C++23 표준으로, 모든 경고를 켜고, 메모리 오류와 정의되지 않은 동작의 새니타이저를 붙여 컴파일되고 실행되었다. 코드 아래에 실린 것은 그 실제 출력이거나, 가르치는 것이 오류나 경고일 때는 컴파일러의 실제 메시지이거나, 프로그램이 정의되지 않은 동작을 저지를 때는 새니타이저의 실제 보고서이다. 그렇다면 이 강좌의 출력 가운데 손으로 쓴 것은 하나도 없다. 사이트는 빌드할 때 모든 프로그램에 현재 판의 출력이 저장되어 있는지 확인하며, 하나라도 빠져 있으면 공개되지 않을 것이다. 다른 그림들은 본문이 추론하는 것을 단계별로 움직여 보여 준다. 이름과 값을 가진 메모리 칸, 호출 스택, 반복문의 진행, 수의 비트이다.

각 수업은 단계별 풀이가 딸린 예제 세 개와 연습 문제 세 개로 끝난다. 연습 문제마다 접힌 힌트가 있고, 그다음에 답이 있으며, 때로는 상세 풀이가 함께 있는데, 상세 풀이는 자신의 풀이를 쓴 뒤에만 여는 것이 좋다. 문제는 하나의 착상을 요구하지, 결코 예제의 기계적인 반복을 요구하지 않으며, 그 수업까지 가르친 것만 쓴다. 각 수업에는 그 밖에 사이트가 즉시 확인하는 짧은 연습이 두 개 있다. 하나에서는 프로그램의 정확한 출력을 예측하고, 다른 하나에서는 코드 조각을 채우거나 고친다. 이 확인이 무엇을 하고 무엇을 하지 않는지 알아 두는 것이 좋다. 사이트는 학생의 코드를 컴파일하지 않고, 핵심이 들어 있는지와 찾는 오류가 사라졌는지를 패턴으로 확인한다. 따라서 사이트에서 통과한 연습도 여전히 자신의 컴퓨터에서 컴파일하고 실행해야 하며, 그것이 유일한 최종 심판이다.

도입 수업과 종합 수업은 고유한 구성을 가진다. 도입은 단원, 그 수업들과 그것들을 잇는 흐름, 전제로 하는 지식, 단원이 해결하는 문제를 소개하고, 입문 평가를 담는다. 종합은 단원의 개념의 사슬을, 각 고리가 세워지는 수업과 각 고리가 쓰는 것과 함께 다시 세운다. 종합에는 “어느 언어에서나 통하는 것”과 “단원의 정의되지 않은 동작”이라는 두 고정된 절, 마무리 평가, 도전 과제 여섯 개(마지막 단원에서는 열 개이며, 그 최종 프로젝트는 여러 단계로 나뉜다)가 있고, 도전 과제마다 힌트와 답이 있으며, 추론이 그럴 만할 때는 접힌 상세 풀이가 붙는다.

음성 읽기

모든 수업은 기기 자체의 음성을 쓰는 사이트의 음성 읽기로 소리 내어 들을 수 있다. 본문은 쓰인 그대로 읽히고, 본문에 인용된 코드는 뜻이 통하게 발음되며, 코드 예제는 알리기만 하고 한 글자씩 읽지는 않는다. 덕분에 화면에서 떨어져서도 설명을 복습할 수 있다. 반면 프로그램에는 언제나 편집기가 필요하다.

평가

이 강좌는 점수를 매기지 않는다. 이 강좌의 평가는 형성적이며 자기 평가에 바탕을 둔다. 연습 문제로 각 수업의 목표를, 도전 과제로 각 단원의 목표를 이루었는지 확인할 수 있다. 그 모두에서 최후의 심판은 경고와 새니타이저를 갖춘 컴파일러이다. 경고 없이 컴파일되고, 자신의 테스트 케이스를 통과하고, 어떤 새니타이저도 깨우지 않는 프로그램은 논란의 여지가 없는 증거이기 때문이다. 다만 테스트 수업이 가르치듯, 그것이 증명은 아니다.

여기에 단원 평가가 더해진다. 도입 수업에 있는 입문 평가는 출발점을 재는 짧은 자기 평가이다. 종합 수업에서 도전 과제 앞에 있는 마무리 평가는 이룬 것을 재며, 입문 평가와 비교된다. 둘 다 브라우저 안에서 바로 채점된다. 각 답에는 설명이 붙고, 전형적인 오류마다 그것을 짚어 주는 논평이 붙으며, 각 문항은 복습하면 좋은 수업을 가리킨다. 어느 쪽도 점수를 매기거나 인증하지 않으며, 결과는 학생의 브라우저에만 저장된다. 습득한 기능의 인증은 나중에 다른 플랫폼에서 이루어질 것이다. 최종 결산은 각 마무리 평가의 첫 시도를 전체 목표별 프로필로 모은다.

인공지능 어시스턴트

오늘날에는 이 강좌의 거의 모든 문제의 풀이를 몇 초 만에 써 내는 인공지능 어시스턴트가 있다. 그것들에 어울리는 자리를 분명히 말해 두는 것이 좋으며, 그 이유는 다른 모든 것을 정당화하는 이유와 같다. 이 강좌가 훈련하는 것은 프로그램의 생산이 아니라 하나의 능력, 곧 문제를 끝까지 생각하여 올바른 프로그램으로 바꾸는 능력이다. 그렇다면 연습을 어시스턴트에게 맡기는 것은 연습을 없애는 일이며, 시도하기 전에 문제의 답을 보면 그 문제가 읽을거리가 되는 것과 똑같다. 그렇게 얻은 프로그램은 올바를 수도 있다. 그러나 그것을 제출하는 사람은 그것을 작성하는 법을 배우지 못했고, 어시스턴트가 틀릴 때, 곧 그 능력이 가장 필요한 바로 그때 작성하지 못할 것이다.

이로부터 나오는 것은 금지가 아니라 사용 규칙이다. 어시스턴트는 이해되지 않는 오류 메시지를 설명하거나, 생각하지 못한 테스트 케이스를 제안하거나, 이미 완성한 자신의 풀이를 다른 풀이와 비교하거나, 설명 속 생각을 다른 각도에서 밝히는 데 쓸모가 있다. 연습, 문제, 도전 과제를 작성하는 데는 쓸모가 없다. 그리고 어떤 경우에나, 어시스턴트에게서 왔든 포럼에서 왔든 동료에게서 왔든 모든 남의 코드에 통하는 원칙이 적용된다. 받아들이기 전에 읽고, 테스트하고, 이해한다는 것이다. 어시스턴트는 컴파일되고 올바르게 보이지만 정의되지 않은 동작을 저지르거나 언어의 낡은 형태를 쓰는 코드를 내놓을 수 있다. 이 강좌를 해낸 학생은 그것을 알아볼 수 있겠지만, 강좌를 남에게 맡긴 학생은 그러지 못한다.

이 강좌를 공부하는 방법

이 강좌는 순서대로 공부하도록 짜여 있다. 각 단원은 앞의 단원들을 전제로 한다. 식은 둘째 단원의 자료형으로 작성되고, 반복문은 셋째 단원의 식으로 작성되며, 함수는 넷째 단원의 반복문을 감싸고, 복합 데이터는 함수로 처리된다. 수업을 하나 건너뛰면 머지않아 그 수업으로 돌아가야 한다.

주된 규칙은 하나뿐이다. 모든 예제를 직접 입력하고 컴파일하는 것이다. 프로그래밍은 읽어서 배우는 것이 아니라, 작성하고, 컴파일하고, 컴파일러가 돌려주는 답을 읽으면서 배운다. 그래서 학생은 Windows, macOS, Linux에 환경 설치하기 수업을 따라 자신의 환경을 설치하고, 컴파일, 실행, 설정 수업이 정하는 법을 가르치는 옵션으로 모든 프로그램을 자신의 Visual Studio Code에서 재현한다. 프로그램을 설치할 수 없는 컴퓨터를 쓰는 사람은 임시로 온라인 컴파일러를 쓸 수 있으며, 그것은 설치 수업에서 설명한다. 예제는 복사하지 말고 손으로 입력하는 것이 좋다. 눈이 지나치는 것을 손은 붙잡기 때문이다. 각 프로그램의 출력은 실행하기 전에 예측하는 것이 좋다. 예측한 것과 얻은 것의 차이가 바로 아직 배워야 할 것이기 때문이다. 그리고 컴파일러의 경고는 하나하나 참고 넘길 잡음이 아니라 대답할 만한 질문으로 읽는 것이 좋다. 프로그램이 동작한 뒤에는 일부러 고쳐 보고(세미콜론을 지우고, 자료형을 바꾸고, 벡터의 범위를 벗어나고) 컴파일러나 새니타이저가 무엇이라고 하는지 읽는 것이 좋다. 일부러 일으킨 오류는 모두 나중에 알아볼 수 있는 오류가 되기 때문이다.

각 단원에는 두 개의 점검 지점이 있다. 도입 수업에 있는 입문 평가는 출발점이다. 단원을 공부하기 전에 아무것도 찾아보지 않고 답하고, 오류가 가리키는 수업을 적어 두는 것이 좋다. 종합 수업에서 도전 과제 앞에 있는 마무리 평가는 대조이다. 수업을 마친 뒤 역시 아무것도 찾아보지 않고 답하고, 입문 평가와 비교한다. 두 평가 모두 첫 시도가 기준이며, 비교되는 것도, 최종 결산의 프로필로 모이는 것도 첫 시도이다. 연습 문제와 도전 과제에서는 시도가 바닥났을 때에만 힌트를 열고, 확인을 위해 맨 마지막에만 답을 여는 것이 좋다. 자신의 풀이를 컴파일하기 전에 답을 본 문제는 푼 것이 아니라 읽은 것이다.

버전 관리

끝으로, 첫 프로그램부터 강좌의 작업을 버전 관리 시스템으로 보관하는 것이 좋다. Git은 작업 폴더의 모든 상태를 기록하고, 그 가운데 어느 상태로든 돌아갈 수 있게 하며, 두 상태 사이에서 무엇이 바뀌었는지 보여 준다. 프로그래머에게 이는 동작하던 것을 잃을 걱정 없이 실험할 수 있고, 판들을 비교하여 오류를 끌어들인 변경을 찾아낼 수 있다는 뜻이며, 이는 디버깅 수업이 가르치는 이분 탐색을 프로그램의 이력에 옮겨 적용하는 일이다. 이 강좌는 Git을 가르치지 않는다. 사이트의 다른 강좌가 이미 가르치기 때문이다. 학술적 글쓰기를 위한 LaTeX 강좌의 공동 집필 수업이 최소한의 작업 흐름, 어떤 파일을 보관하고 어떤 파일을 제외하는지, 충돌을 어떻게 해결하는지를 설명한다. 거기서 문서의 소스에 대해 통하는 것은 프로그램의 소스에 대해서도 그대로 통한다. LaTeX의 보조 파일 대신 컴파일이 다시 만들어 내는 실행 파일과 오브젝트 파일을 제외하기만 하면 된다.