본문 바로가기
Topos Uranos

요약

이 수업은 C++ 프로그래밍의 첫걸음을 다루는, 강좌의 첫 단원을 연다. 수업은 단원의 목적을 설명한다. 곧, 프로그래밍을 해 본 적이 없는 사람을 일상 언어로 서술된 문제에서 출발하여 컴파일되고 실행되며 데이터를 읽고 결과를 쓰는 프로그램에 이르기까지 이끌되, 그 길에서 각 도구가 무엇을 했는지 알게 하는 것이다. 또 단원의 여섯 수업과, 한국어로 쓴 알고리즘에서 오류의 분류에 이르기까지 그 수업들을 잇는 실을 소개하고, 전제로 하는 얼마 안 되는 지식, 곧 학교 산수, 글로 쓴 지시를 따르는 습관, 파일과 폴더를 다루는 법을 밝힌다. 단원이 해결하는 문제, 곧 손으로 할 줄 아는 계산을 기계가 틀리지 않고 지치지도 않고 실행하는 절차로 바꾸는 문제를 제시하고, 끝으로 이 단원이 뒤의 단원들과 C++ 프로그래밍 II 강좌에서 어디로 이어지는지 보여 준다.

학습 목표

  1. 단원 내용의 순서를 서술하고, 각 수업이 그 뒤의 수업에 무엇을 보태는지 설명한다.
  2. 강좌가 전제로 하는 사전 지식(정수와 소수점이 있는 수의 연산, 몫과 나머지의 계산, 글로 쓴 지시 따르기, 정해진 폴더에 파일 저장하기)을 확인하고, 필요하면 복습한다.
  3. 단원이 가르치는 길, 곧 문제 진술에서 명세로, 명세에서 알고리즘으로, 알고리즘에서 컴파일되고 실행되는 프로그램으로 가는 길을 예를 들어 설명한다.
  4. 단원을 마칠 때 할 줄 알아야 하는 것을 확인하고, 그것을 뒤의 단원들에서 배울 것과 구별한다.

입문 평가

단원을 시작하기 전에 출발점을 재어 두면 좋다. 이 평가는 자기 평가이며 점수를 매기지 않는다. 목적은 오직 학습의 방향을 잡는 데 있다. 이 단원은 강좌의 첫 단원이므로 앞 단원에 관한 문항은 없다. 그 대신 처음 세 문항은 강좌가 전제로 하는 지식을 확인한다. 정수 나눗셈의 몫과 나머지, 절차의 단계 순서, 그리고 파일의 이름과 확장자와 폴더이다. 그 뒤의 일곱 문항은 단원의 여섯 수업의 핵심 개념을 차례로 다룬다. 프로그래밍을 해 본 적이 없는 사람이 정답을 맞히지 못하는 것은 당연하며, 그래도 상관없다. 맞든 틀리든 모든 답이 요점을 설명하고 그 요점을 다루는 수업을 알려 주기 때문이다. 같은 능력은 단원 끝의 종합 수업에서 다시 측정한다.

입문 평가

단원을 시작하기 전에 출발점을 확인해 보세요. 짧은 자기 평가입니다. 점수가 매겨지지 않으며 결과는 브라우저에만 저장됩니다.

  1. 문제 1이전 단원에서종이 47장을 6명에게 똑같이 정수 장씩 나누어 주고, 한 바퀴를 다 돌기에 모자라는 종이는 남겨 둔다. 한 사람이 몇 장을 받고 몇 장이 남는가?

  2. 문제 2이전 단원에서전자 우편으로 파일을 첨부해 보내는 데에는 네 단계가 있다. A, 받는 사람의 주소를 쓴다. B, “보내기”를 누른다. C, 새 메시지를 연다. D, 파일을 첨부한다. 알맞은 순서는 무엇인가?

  3. 문제 3이전 단원에서어떤 파일이 C:\Users\ana\programas\suma.cpp에 저장되어 있다. 그 이름은 무엇이고, 확장자는 무엇이며, 어느 폴더에 있는가?

  4. 문제 4어떤 요리법에 이렇게 적혀 있다. “반죽이 말라 있으면 물을 한 숟가락 넣고 치댄다. 마르지 않을 때까지 반복한다.” 반죽이 마르지 않게 되려면 정확히 세 숟가락이 필요하다. 반죽이 말라 있는지는 몇 번 묻게 되는가?

  5. 문제 5아래의 지시 가운데 누구나, 또는 기계가 똑같은 방식으로 따를 수 있는 것은 무엇인가?

  6. 문제 6어떤 프로그램이 오류도 경고도 없이 컴파일되고, 많은 데이터로 잘 실행되는데, 특정한 데이터 하나로는 “division by zero”라는 메시지를 내고 멈춘다. 오류는 어느 시점에 생겼는가?

  7. 문제 7Visual Studio Code와 C++ 확장을 설치했는데, 터미널에서 g++ --version 명령을 요청하면 터미널이 g++ 명령을 인식하지 못한다고 답한다. 가장 그럴듯한 원인은 무엇인가?

  8. 문제 8컴파일러가 warning: 표시로 시작하는 메시지를 내고, 그런데도 실행 파일을 만든다. 이것은 무엇을 뜻하는가?

  9. 문제 9C++ 프로그램의 실행은 어디에서 시작되는가?

  10. 문제 10어떤 프로그램이 int age{}; 선언으로 변수를 만들고, 나이를 읽으려고 std::cin >> age; 문을 실행하는데, 사용자가 abc 문자열을 입력한다. 무슨 일이 일어나는가?

단원의 내용

이 단원에는 여섯 수업이 있으며, 문제에서 동작하는 프로그램까지의 길을 차례로 걷는다. 각 수업은 앞의 수업을 전제로 하고, 첫 수업은 언어에 대해 아무것도 전제로 하지 않는다.

  • 문제에서 알고리즘으로. 알고리즘이 프로그램보다 앞서므로 강좌는 코드 없이 시작한다. 이 수업은 문제를 입력과 출력과 그 둘 사이의 관계(사전 조건과 사후 조건)로 명세하는 법, 알고리즘을 번호 매긴 단계로 모호함 없이 기술하는 법, 추적표로 그것을 손으로 따라가는 법, 그 안에서 순차, 선택, 반복을 알아보는 법, 그리고 회차마다 줄어드는 양으로 반복이 종료됨을 정당화하는 법을 가르친다.
  • 프로그램, 컴파일러, C++. 기계를 위해 쓴 알고리즘은 프로그램이며, C++ 프로그램은 쓴 그대로 실행되지 않는다. 전처리기, 컴파일러, 링커가 그것을 실행 파일로 바꾼다. 이 수업은 이 세 단계를 거치며, 컴파일할 때 발견되는 것과 실행할 때에만 드러나는 것을 구별하고, 컴파일 방식과 인터프리터 방식을 비교하며, 언어의 표준들을 시간 순서로 자리매김한다. 강좌가 쓰는 C++23까지, 그리고 컴파일러들에서 구현이 진행 중인 C++26까지이다.
  • Windows, macOS, Linux에 환경 설치하기. 시스템마다 하나씩, 세 가지 완전한 안내가 있다. C++23을 지원하는 컴파일러와 Visual Studio Code 편집기 및 그 C++ 확장을 설치하고, 확인용 프로그램으로 모든 것이 동작하는지 확인하며, 흔한 문제를 해결한다. 터미널이 찾지 못하는 컴파일러, 오래된 버전, 요청하지 않은 표준이 그것이다.
  • 컴파일, 실행, 설정. 편집, 저장, 컴파일, 실행, 읽기라는 작업 순환을 터미널과 편집기에서, 강좌의 명령으로 수행한다. 이 수업은 실행 파일을 막는 오류와 의심스러운 코드를 가리키는 경고를 구별하고, 프로그램이 돌아가는 동안 그것을 감시하는 새니타이저(메모리 오류를 잡는 AddressSanitizer와 정의되지 않은 동작을 잡는 UndefinedBehaviorSanitizer)를 켜며, 출력에 한글 대신 이상한 기호가 나타나는 이유를 설명한다.
  • 프로그램의 해부. 최소한의 프로그램을 부품별로 살핀다. 포함 지시문과 헤더, main 함수와 종료 코드, std 네임스페이스, 문, 블록, 주석이다. 이 수업은 컴파일러 메시지를 첫 번째 것부터 읽으면서 구조 오류를 고치는 법을 가르치고, 강좌의 스타일 가이드를 정한다.
  • 입력, 출력, 오류. 프로그램이 바깥과 소통한다. std::print 함수와 std::println 함수로, 너비와 소수 자릿수를 위한 형식 지정을 써서 출력하고, std::cin 객체로 읽는다. 이 수업은 입력이 기대한 형태가 아닐 때 무슨 일이 일어나는지, 읽기를 어떻게 확인하는지 보여 주고, 오류를 드러나는 시점에 따라 컴파일 오류, 링크 오류, 실행 오류, 논리 오류로 분류하는 것으로 단원을 마무리한다.

이 수업들을 잇는 실은 프로그램 자체이다. 첫째 수업은 프로그램을 구상하고, 둘째는 그것이 무엇으로 바뀌는지 설명하고, 셋째는 그것을 바꾸는 도구를 설치하고, 넷째는 그 도구를 쓰는 법과 도구가 하는 말에 귀 기울이는 법을 가르치고, 다섯째는 그것을 해부하고, 여섯째는 그것을 쓰는 사람과 대화하게 한다. 그렇다면, 단원을 마칠 때 학생은 C++에 대해 아직 아는 것이 아주 적지만, C++로 어떻게 작업하는지는 알며, 강좌의 나머지 부분이 전제로 하는 것이 바로 그것이다.

전제로 하는 지식

강좌는 프로그래밍 경험을 전혀 전제로 하지 않는다. 그 대신 세 가지 능력을 전제로 하는데, 입문 평가에서 의문점이 드러났다면 지금 복습해 두는 것이 좋다.

첫째는 학교 산수이다. 정수와 소수점이 있는 수로 계산하고, 연산 순서(괄호가 없으면 곱셈과 나눗셈을 덧셈과 뺄셈보다 먼저)를 지키며, 무엇보다 정수 나눗셈의 몫과 나머지를 구하는 것이다. 이 마지막 것은 보기보다 중요하다. 종이 47장을 6명에게 나누면 한 사람이 7장씩 받고 5장이 남으며, 이것을 47=6⋅7+547 = 6 \cdot 7 + 5로 쓰고, 나머지 55는 나누는 수 66보다 작다. C++에는 몫을 위한 연산자와 나머지를 위한 연산자가 따로 있으며, 정수 계산의 상당 부분(초를 시간으로 바꾸기, 수를 자릿수로 나누기, 수가 짝수인지 판단하기)이 이 둘로 귀결된다. 정수 나눗셈을 자세히 공부하고 싶은 사람은 대수학과 기하학 I: 수, 함수, 평면기하학 강좌에서 증명과 함께 찾을 수 있다. 여기서는 계산할 줄만 알면 된다.

둘째는 글로 쓴 지시를 글자 그대로 따르고, 어떤 지시만으로는 무엇을 해야 할지 알 수 없을 때 그것을 알아차리는 습관이다. 이것은 지식이 아니라 태도의 문제이다. “소금을 입맛대로 넣는다”를 읽고, 이 지시가 실행하는 사람에 따라 달라진다는 것을 알아차리는 것이다. 단원의 첫 수업이 이 태도를 방법으로 바꾼다.

셋째는 컴퓨터의 기본적인 사용이다. 폴더를 만들고, 거기에 고른 이름으로 파일을 저장하고, 이름의 확장자(.cpp, .txt, .exe)를 알아보고, 각 파일이 어느 폴더에 있는지 아는 것이다. 지금부터 확장자를 표시하도록 시스템을 설정해 두는 것이 좋다. Windows처럼 기본적으로 확장자를 숨기는 시스템도 있기 때문이다. 소스 코드 suma.cpp 파일과 실행 파일 suma.exe 파일은 전혀 다른 파일이며, 둘을 혼동하면 터무니없는 오류가 생긴다. 단원은 터미널 사용법도 가르치므로 미리 알 필요는 없다. 버튼을 누르는 대신 명령을 입력할 마음만 있으면 된다. 끝으로, 프로그램을 설치하려면 자기 컴퓨터에서 그렇게 할 수 있어야 한다. 그럴 수 없는 사람을 위해, 설치 수업은 온라인 대안을 알려 준다.

영어는 전제로 하지 않는다. 컴파일러 메시지는 영어로 되어 있지만, 강좌는 그것을 필드별로 읽는 법을 가르치므로, 그 가운데 중요한 것(파일, 줄, 결함의 종류)은 영어를 잘 몰라도 이해할 수 있다. 학교 수준을 넘는 수학도 전제로 하지 않는다. 예제는 나눗셈, 평균, 구구단, 날짜, 시각을 쓴다.

단원이 해결하는 문제

손으로 할 줄 아는 계산은 아직 프로그램이 아니다. 둘 사이의 거리가 바로 단원이 건너는 법을 가르치는 것이며, 간단한 문제 하나로 그 거리를 잴 수 있다.

예시시, 분, 초로 나타낸 시간

스톱워치가 각 활동의 시간을 초 단위의 수로 기록한다. 예를 들어 7384이다. 이것을 분과 초를 언제나 두 자리로 쓰는, 시, 분, 초의 익숙한 형태로 쓰고 싶다. 곧 2:03:04 형태이다. 손으로 하면 계산은 나눗셈의 연쇄이다. 7384=3600⋅2+1847384 = 3600 \cdot 2 + 184에서 2시간과 184초가 나오고, 184=60⋅3+4184 = 60 \cdot 3 + 4에서 3분과 4초가 나온다. 시간 하나라면 일 분이면 된다. 그러나 기록에 있는 천 개의 시간이라면 손 계산은 길고 오류가 나기 쉬우며, 이 일은 기계에 맡기기에 알맞다.

그런데 기계는 위의 설명을 받아들일 수 없다. 기계가 받는 것은 프로그래밍 언어로 쓴 텍스트이며, 그 텍스트를 쓰려면 손 계산이 암묵적으로 남겨 둔 세 가지 문제를 먼저 해결해야 한다. 첫째는 명세이다. 무엇이 들어가고(음이 아닌 정수인 초의 수), 무엇이 나오며(세 정수, 곧 시, 분, 초), 어떤 관계가 그것들을 잇는가. tt가 전체 초의 수라면 t=3600h+60m+st = 3600 h + 60 m + s가 성립하고, 조건 0≤m<600 \leq m < 60과 0≤s<600 \leq s < 60이 붙는다. 둘째는 알고리즘이다. 허용되는 어떤 입력에 대해서도 그 출력을 내는 정확한 단계들이다.

  1. hh에 tt를 36003600으로 나눈 몫을 대입한다.
  2. mm에 6060으로 나눈 몫(나누어지는 수는 tt를 36003600으로 나눈 나머지)을 대입한다.
  3. ss에 tt를 6060으로 나눈 나머지를 대입한다.
  4. hh, 콜론, 두 자리의 mm, 콜론, 두 자리의 ss를 쓴다.

셋째는 언어가 요구하는 정확한 형태로 알고리즘을 C++로 쓰고, 알맞은 도구로 그것을 실행 파일로 번역하는 것이다. 아래 프로그램이 그 일을 한다. 이 프로그램에는 아직 설명하지 않은 것이 많다. 변수(2.1강), 정수 나눗셈의 몫과 나머지를 내는 / 연산자와 % 연산자(3.1강), std::cin 객체를 쓰는 읽기와 형식 지정(1.6강)이 그렇다. 이 프로그램의 {:02} 형식 지정은 수를 두 자리에 쓰되 왼쪽을 0으로 채우는 것으로, 그 수업이 가르치는 너비의 변형이며 3.1강에서도 쓰인다. 지금은 그 안에서 알고리즘의 네 단계를 알아보는 것으로 충분하다.

C++ 프로그램
#include <iostream>#include <print>​int main(){    int total{};  // 변수는 2.1강에서 설명한다    std::print("시간(초): ");    std::cin >> total;    const int hours{total / 3600};    const int minutes{total % 3600 / 60};    const int seconds{total % 60};    std::println("{} s = {}:{:02}:{:02}", total, hours, minutes, seconds);}
프로그램 입력
7384
출력
시간(초): 7384 s = 2:03:04

9번 줄에서 11번 줄까지가 1단계에서 3단계까지이고, 12번 줄이 4단계이다. 결과가 손으로 계산한 것과 일치하는 것은 우연이 아니라, 모든 프로그램이 받아야 할 확인이다. 곧, 답을 알고 있는 데이터로 먼저 시험해 보는 것이다.

프로그램은 문제를 해결하지만, 단원은 그것을 의심하는 법도 가르친다. 사용자가 수 대신 abc 문자열을 입력하면 읽기가 실패하고, total 변수는 0으로 남으며, 프로그램은 아무런 경보 신호도 없이 시간으로 0:00:00 값을 쓴다. 참처럼 보이는 거짓 결론이다. 음수인 시간을 입력하면, 명세가 무엇이 일어나야 하는지 말하지 않으므로 결과는 의미가 없다. total / 360 식을 total / 3600 식 대신 쓰면, 프로그램은 컴파일되고 실행되며 틀린 결과를 내지만, 어떤 도구도 그것을 알려 주지 않는다. 이 결함들은 저마다 다른 종류의 오류에 속하며, 다른 시점에 발견되고 다른 방식으로 찾게 된다. 단원의 마지막 수업이 그것들을 분류한다. 이로부터, 단원이 해결하는 문제는 이 프로그램을 쓰는 것만이 아니라, 그것이 왜 동작하는지, 어떤 데이터에서 동작하지 않게 되는지, 그리고 어떤 도구가 각 결함을 알려 줄지 또는 알려 주지 않을지를 아는 것이라는 결론이 나온다.

따라서 단원을 마치면 학생은 이런 종류의 프로그램을 쓰고, 컴파일하고, 실행할 수 있게 된다. 몇 개의 입력 데이터, 차례로 이어지는 몇 가지 계산, 형식을 갖추어 쓴 몇 가지 결과로 이루어진 프로그램이다. 아직은 C++로 선택을 하거나 문을 반복할 수 없고(한국어로 쓴 알고리즘에서는 할 수 있지만), 각 데이터의 자료형을 근거를 갖고 고를 수도 없다. 그것은 뒤의 단원들이 가져다준다.

어디로 이어지는가

이 단원은 강좌의 문턱이다. 여기서 설명 없이 쓰는 것은 나중에 아래의 순서로 설명된다. 2단원 “데이터와 자료형”은 변수와 그것이 메모리에 저장되는 방식, 정수의 한계(그리고 올바른 계산이 왜 오버플로가 날 수 있는지), 소수점이 있는 수의 반올림, 자료형 사이의 변환을 설명한다. 3단원 “식과 연산자”는 음수의 몫과 나머지를 포함한 C++의 산술, 조건의 논리, 그리고 식에서 표준이 정의하지 않고 남겨 두는 것을 설명한다. 4단원 “제어 흐름”은 첫 수업이 한국어로 쓰는 선택과 반복을 C++로 옮기고, 반복문이 올바르며 종료함을 정당화하는 법을 가르친다. 5단원은 프로그램을 함수로 나누고, 6단원은 복합 데이터(배열, 벡터, 표, 레코드)를 탐색과 정렬 알고리즘 및 그 비용과 함께 도입하며, 7단원은 텍스트, 견고한 입력 읽기, 파일을 다루고, 8단원은 여러 파일로 된 프로그램, 체계적인 테스트, 디버거를 다룬다.

이 강좌를 마친 뒤에는 C++ 프로그래밍 II 강좌가 이 강좌가 일부러 밖에 남겨 둔 것을 이어 다룬다. 포인터와 동적 메모리, 메서드를 갖춘 클래스, 직접 만드는 템플릿, 익명 함수, 예외이다. 이 주제들 가운데 어느 것도 이 강좌의 문제를 푸는 데 필요하지 않으며, 그것들을 미루면 어느 언어에서나 통하는 것에 집중할 수 있다. 명세하고, 알고리즘을 설계하고, 그것을 명료하게 쓰고, 그것이 올바름을 확인하는 일이다. 이 단원의 종합 수업은 바로 이 구별, 곧 C++에 고유한 것과 다른 어느 언어로든 가져갈 것 사이의 구별을 정리할 것이다.