1장. 소개

 
본서의 2 장에서는 C 프로그래밍의 기초를 다룹니다. 여러분이 주목해야 할 사항은 C 언어의 기초를 다루는 것이 아니라 “C 프로그래밍”의 기초를 다룬다는 점입니다. C언어의 기초를 다룬다면 2 장의 내용은 주로 C언어의 문법과 구문을 위주로 설명하게 될 것입니다. 그러나, 본서는 C 프로그래밍의 기초를 다루기 때문에 C 언어를 사용해서 어떻게 프로그래밍하는가를 설명합니다. 단순히 연산자, 데이터타입, 형 변환, static 키워드, 포인터, 배열, 함수 등과 같은 C언어의 문법을 설명하기 위한 내용을 다루지 않습니다. 이러한 내용은 C 언어를 이해하는데 기본적인 내용이 되지만 실제 프로그래밍을 배우기 위해서는 C언어의 표현력이 필요합니다. 표현력은 글로 말하면 문장력이 되겠지요. 글을 쓸 때 단어와 문법만 안다고 글이 이루어지는 것이 아닙니다. 글에 글쓴이의 의도가 분명히 담기고 글쓴이가 말하고자 하는 바가 잘 표현되어야 합니다. 그것이 바로 표현력이요 문장력입니다.
본서에서도 C 프로그래밍을 통해서 바로 이러한 C언어를 사용하여 프로그램을 작성하기 위한 표현력과 문장력을 다룹니다. 물론, C언어의 문법적 내용과 기본적으로 제공되는 내장함수(built-in function)에 대해서도 잘 알고 계셔야 합니다. 하지만, 여기에 머무르지 마시고, 보다 발전된 방향으로 나가셔야 합니다. 그 길을 제시하기 위해 본서는 존재하는 것입니다. 물론, 많은 양의 내용을 통해서 프로그래밍의 길을 제시하는 것이 좋을 수 있지만, 본서는 그렇게 많은 양을 포함하고 있지 않습니다. 즉, 핵심적이고 함축적으로 꼭 필요하다고 생각하는 바를 축약해서 본 서의 2 장에서 C 프로그래밍을 설명하고 있습니다.
사실, 본서는 알고리즘(Algorithm)이라고 하는 전산학의 한 분야와 데이터구조라고 하는 관련 분야를 공부하기 위한 기초 단계라고 생각하시면 됩니다. 어떤 면에서는 본서가 C언어의 문법과 구문 공부에서 알고리즘과 데이터구조를 공부하기 위해 교각이 될 수 있지만, 실제로 알고리즘과 데이터구조를 이론적으로 배우는 것보다는 이렇게 구체적인 프로그래밍 언어의 하나인 C언어를 통해서 배움으로써 실무적으로나 실용적으로 많은 도움이 될 것입니다. 흔히 거론되고 있는 Practical C Programming이라는 말이 있는데, 이는 본서보다 한 번 더 진전된 과정을 다룹니다. 즉, 실제 응용분야에서 적용되고 사용되는 C 프로그래밍을 다룹니다.
본서는 C 언어 공부의 관점에서 살펴보면 다음과 같은 위치에 있습니다.
1. C 언어의 기초(문법과 구문) 공부
2. C 프로그래밍의 기초 ==> 본서
3. 알고리즘과 데이터구조
4. Practical C Programming(실용적 C 프로그래밍)
 
 
위와 같이 순번을 매겨서 C 언어 학습과정을 묘사했는데, 실제 이러한 순번은 큰 의미가 없습니다. 왜냐하면 학습자마다 공부하는 순서가 다룰 수 있기 때문입니다. 필자의 경우는
3. 알고리즘과 데이터구조 공부 ->
1. C언어의 기초(문법과 구분) 공부 ->
2. C 프로그래밍의 기초와 4. Practical C Programming 동시 학습
 
순으로 공부하였습니다. 필자의 생각으로는 1. 2. 3. 4. 순서대로 공부하는 것이 정순(正巡)이라 생각합니다. 물론, 여러분은 다른 방법으로 공부하고자 할지도 모릅니다. 하지만, 공부 순서가 어떠하든 간에 분명한 것은 C언어의 학습과정이 크게 이상의 네 가지 분야로 분류된다는 점입니다. 필자의 주관적인 판단이 들어있지만 그래도 필자가 경험한 바에 의하면 상기의 네 가지 구분은 C언어 학습에 대부분 유효하게 적용된다고 생각합니다.
네 번째, Practical C Programming은 기술적인 프로그래밍을 포함하고 있어, 실제 적용하고자하는 시스템이나 업무 및 수학적 논리에 대한 어느 정도의 지식을 갖추고 있어야 가능합니다. 기술적인 프로그래밍이란 말을 들어보셨나요? Technical Programming(TP)이라고 하는데, 이 TP는 하드웨어 시스템과 관련된 지식을 바탕으로 프로그램을 작성하는 분야입니다. 어셈블리어 수준에서의 프로그래밍이 사실 TP라고 할 수 있지요. 물론 TP는 반드시 어셈블리어를 사용하여 프로그래밍 할 경우만을 말하는 것은 아닙니다. 이 TP 또한 Practical C Programming의 한 분야라고 할 수 있습니다. 사실, Practical C Programming은 매우 광범위한 분야를 다룹니다. 혹시 Design Pattern(DP)이란 말을 들어본 적이 있나요? DP는 C++로 프로그래밍하는 기법을 담고 있는데, 실무 프로그램을 작성하는데 매우 요긴하게 사용된다고 합니다. C++ 프로그램의 구조를 결정짓고 C++ 프로그램의 유사성을 연구하여 프로그램을 작성하기 위한 기법을 체계화한 것이 DP라고 할 수 있습니다. 사실, DP는 재사용 가능한 프로그램을 작성하기 위한 객체지향 프로그래밍 언어의 기법입니다. 그러므로, C++ 뿐만아니라 JavaSmalltalk와 같은 언어로도 DP가 가능합니다. 이 모두가 Practical C Programming의 분야에 해당합니다.
실제 Practical C Programming은 여러분이 직장에서 직업을 갖고 프로그래밍하게 되는 영역 전체를 포괄합니다. 즉, 아마추어가 아닌 프로이자 베테랑(전문가)로 나아가기 위한 영역이 됩니다. 실제적이고 가장 도움이 되는 지식은 바로 이 Practical C Programming을 통해서 얻을 수 있습니다.
본서에서는 Practical C Programming의 기초가 되는 C 언어를 구사하는 문장력과 표현력을 배울 겁니다. 다소 알고리즘과 데이터구조가 도입되지만 그렇게 어려운 알고리즘과 로직을 가지고 있지는 않습니다. 다만, 한가지 알고리즘과 로직을 여러 방향으로 사고할 수 있도록 해주고 다양한 방식으로 표현하도록 여러분을 도와줄 겁니다. 여러분은 한가지 문제가 어떻게 다양한 방식으로 해결되는지를 지켜봐야할 겁니다. 가장 중요한 것은 여러 가지 해법들 중에서 문제에 가장 적합한 해법을 찾아내는 것입니다. 그리고 그 해법에 맞추어 프로그래밍을 해나가는 것입니다.
필자는 쉬운 문제를 통해서 여러 가지 다양한 사고를 할 수 있도록 문제를 약간씩 변형시켰습니다. 따라서, 여러분은 각각의 문제들이 어떤 차이를 지니고 있는지 확인하시고, 그 문제가 어떻게 다양한 해법으로 풀이되는지를 터득하십시오. 그러니까 이를 도표로 나타내면 다음과 같습니다.
 
문제 1. -------------> 해법 1 -------------> 해법 1의 변형1 -------------> 해법 1의 변형2. ... -------------> 해법 1의 변형n1 문제 1의 변형1 -------------> 해법 2 -------------> 해법 2의 변형1 -------------> 해법 2의 변형2. ... -------------> 해법 2의 변형n2 ... ... 문제 1의 변형M-1 -------------> 해법 M -------------> 해법 M의 변형1 -------------> 해법 M의 변형2. ... -------------> 해법 M의 변형nn
 
위와 같이 광범위하게 다양한 문제와 해법이 존재하게 됩니다. 여러분은 이와 같은 많은 경우의 수를 이해하셔야 합니다. 최소한 어느 정도 파악하고 계시고 어느 문제가 중요하고 어느 해법이 필요하다는 것을 이해하셔야 합니다. 그리고, 위에 도시한 경우의 수는 단지 문제 1에 대한 경우의 수를 생각한 것입니다. 만약 문제의 종류가 많아지면 더 광범위한 경우의 수가 존재할 겁니다.
가능한 총 문제의 종류와, 그 중 한 문제에 대한 변형된 문제의 종류 그리고 이에 대한 해법 그리고 이 해법들 중의 한 해법에 대해 파생되는 아류적 변형 해법들... 이와 같이 생각해 보면 이론적으로 문제의 유형을 문제의 궁극적 시원으로 무한 전개해 나갈 수 있으며, 해법의 유형을 궁극적 종착지로 무한 전개해 나갈 수 있습니다. 위에 도시해 놓은 경우의 수는 복잡한 경우의 수들 중에서 한 부분을 포착하여 단순히 간략하게 표시한 것일 뿐입니다.
문제를 얼마나 잘 이해하는가는 어떤 면에서 정해진 규칙(Rule)에 입각하여 문제를 얼마나 자유자재로 이해할 수 있는가에 의해 결정된다고 할 수 있습니다. 그리고, 다양한 시각으로 문제를 이해하는 것이 바로 다양한 해법을 도출해 내는 시작점이라 할 수 있지 않을까 합니다. 문제로부터 다양성과 변화(Variety)를 얼마나 잘 이해하고 파악하느냐에 따라 무궁무진한 해법이 존재합니다. 이런 측면에서, 문제와 해법은 일체라고 할 수 있습니다. 문제에 해법이 함유되어 있고, 해법에 문제가 함유되어 있다는 뜻입니다.
특히 실무에서는 정형화된(Formalized) 문제는 거의 찾아보기 힘듭니다. 어느 정도 휴리스틱(탐험적 응용) 방법이 필요합니다. 휴리스틱을 위해서는 문제의 다양성과 해법의 다양성을 알고 있어야 합니다. 그리고 현재 직면하고 있는 문제를 가장 잘 해결해 주는 해법을 찾아야 합니다. 다양한 문제를 공부하는 것은 실무에서 해결해야 할 문제가 정형화되어 있지 않고 서적에서 다루는 것보다는 한층 미묘하고 복잡하게 나타나기 때문입니다. 필자의 경우, 실무에서 프로그래밍을 하다보면 책에서 다루는 문제들보다 더 큰 복잡성과 미묘함에 의해 어려웠던 적이 많았습니다. 물론, 프로젝트의 규모도 크고 다루는 분야도 기술적 프로그래밍(TP)을 요구하기 때문에 어려웠지만 가장 중요한 것은 알고리즘과 로직을 세우는 작업이었습니다. 프로젝트의 규모가 커질수록 문제의 범위가 커지기 때문에 복잡성이 증대되고 이러한 복잡성에 의해 약간의 요인 변화에 의해서도 문제의 특성이 매우 미묘하게 변화된다는 점이었습니다.
이러한 복잡성과 미묘함에 의해 어려움을 겪게되는 이유는 평상시 한가지 문제를 다양한 방식으로 사고하는 연습을 하지 않았기 때문입니다. 이러한 문제점을 해소하기 위해서 필자는 본서 “C 프로그래밍의 기초”를 통해서 나름대로 여러분에게 문제를 다양하게 만들어보고 해법 또한 다양하게 연구하고 공부하시도록 하고자 합니다.
본서에서 다루는 예제들 보다 더 많은 문제들과 해법이 존재할 수 있으므로, 여러분께서도 공부하시면서 더 다양한 변화와 응용을 생각해 보십시오. 하나의 새로운 변화를 연구해 내고 새로운 시도(해법)를 모색하는 것이 바로 공부의 시작입니다. 그러나, 주의하실 것은 너무 허황된 문제와 해법을 찾으려고 하지는 마시기 바랍니다. 그러니까, 실용적으로나 이론적으로 별 도움이 되지 않은 문제로부터 해법을 찾고자 시간을 허비하지는 말라는 것입니다. 공부를 최적화하시고자 한다면 나름대로 이론적으로나 실용적으로 도움이 되는 문제와 해법의 유형을 파악하셔야 합니다. 만약 위험성이 있거나 무익한 문제나 해법 유형에 시간을 낭비하게 되면 무력감과 심리적 혼미함이 일어나기 때문에 문제와 해법 유형을 신중하게 선택하는 것이 중요합니다. 막연한 호기심에 의해 무턱대고 새로운 문제 유형과 해법 유형을 모색하는 것은 자칫 방만한 연구열을 불러 일으킬 수 있어 자제해야 합니다.
본서는 실무 프로그래밍에 진입하기 전에 밟아야 할 기초 과정입니다. C 언어 문법 서적 한 권을 병행으로 함께 가지고 본서를 진행해 나간다면 많은 도움을 얻으실 겁니다. 배열이란 무엇인가를 묻고 답을 구하기 보다는 배열이 왜 필요하며 어떻게 사용되는지를 공부하게 될 겁니다. 그리고 배열의 응용을 공부함으로써 배열 자체에 대한 특성과 의미를 더 진지하게 고찰할 수 있게 될 겁니다. 필자가 생각하고 의도한 내용보다도 많은 것을 본서에서 얻을 수 있기를 바랍니다. 분명, 풍부하게 생각한다면 그리고 보다 진지하게 연구한다면 다양한 문제와 다양한 해법을 찾을 수 있게 될 것이며, 그럼으로써 여러분이 다양한 문제에 대해서 자신감을 가지고 어려움을 헤쳐나갈 수 있는 기반을 어느 정도 마련해 나갈 수 있지 않을까 기대해 봅니다. 그러나, 여기서 다루는 30여가지 예제만으로는 부족하므로, 더 많은 문제들을 다루어 보고 본서보다 더 다양한 난이도의 문제에도 도전해 보고 다양한 해법을 골고루 터득해 나가시기 바랍니다.
그리고 개념을 이해할 때는 보다 광범위하게 이해하도록 하십시오. 어느 상황에서는 이렇게 광의적으로 이해하시는 것이 도움이 될 겁니다. 예를 들어, 함수는 영어로 fucntion이라고 합니다. function이란 단어는 크게 두 가지 뜻을 지니고 있는데, 첫째가, 기능이라는 뜻이고, 둘째가 함수라는 뜻입니다. 사실, 프로그래밍 언어에서 함수라는 것은 일종의 기능을 하는 집합체입니다. 수학적으로 말하게 되면 특정 값에 상응하는(매핑되는) 값을 구할 때 사용되는 도구(Instrucment)입니다. 그러나 컴퓨터에서 함수는 어떤 매개변수를 받아 특정한 처리를 한 후 그 결과를 돌려주는 루틴의 모임을 뜻합니다. 제가 말씀드리고 싶은 것은 함수의 기본적 이치가 수학에서 나왔으며 그것이 프로그램 작성에도 적용된다는 점을 아시기 바랍니다. 즉, 하나의 개념을 보다 유연하게 여러 가지 경우로 확장해서 적용해 보고 그 의의와 가능성을 고찰하고 반성해 보는 것은 매우 바람직하지 않나 생각해 봅니다.
본서를 읽어보시면 아시겠지만 일반적으로 C 언어 서적에서 다루는 내용을 탈피하여 C 언어의 응용력을 향상시키는데 중점을 두어 본서를 구성했습니다. 모든 프로그래밍에서 특별하게 중요시하는 알고리즘과 자료구조의 근간이 되는 내용을 설명함으로써 여러분의 의식 속에 C 프로그래밍을 하기 위한 기본적인 스킬(Skill)을 갖추도록 하였습니다. 필자는 어떤 프로그래밍 언어이건 간에 프로그래밍에 근간이 되고 기본이 되는 요소들이 있다고 생각하고 있으며 그러한 요소들을 효과적으로 사용하는 능력이 훌륭한 프로그래머로 나아가는 발판이 된다고 확신하고 있습니다. 필자는 그러한 프로그래밍의 요소들을 요소 기술(Element Skill)이라고 말하며 이러한 요소 기술들을 잘 결합하여 완성된 프로그램을 만들어 낼 수 있을 때 프로그래머는 긍지와 자부심을 가지게 되고 현업에서 생산성 높은 프로그래머로 인정받게 되는 것입니다. 사실, 프로그램이라는 것은 요소 기술들이 결합되어 집대성된 거대한 집합체와 비슷합니다. 프로그래밍의 여기 저기에 숨어있는 자잘한 요소 기술들을 파악한 후 그들을 정리하여 자기 것으로 소화한다면 응용력이 증대되고 프로그램을 생산하는 능력이 향상되며 프로그램 개발에 자신감을 얻게 됩니다. 프로그래밍을 하면서 특별한 이치를 발견하게 되면 그냥 지나치지 마시고 그 이치를 잘 파악하고 이해한 후 자신의 것으로 소화해서 의식 속에 정리하고 체계화시키는 작업을 해두시기 바랍니다. 처음에는 그것의 가치를 잘 인식하지 못하는데 그러한 이치들이 누적되고 축적되었을 때 프로그래밍에 커다란 힘이 됩니다.
우리는 본서를 통해서 간단하고 다양한 유형의 문제들을 풀어나감으로써 프로그램에 내재되어 있는 각양각색의 요소 기술들을 파악한 후 그러한 요소 기술이 전체 프로그램에 어떤 영향을 주고 있는지 확인하고자 합니다. 그리고 요소 기술을 어떻게 프로그래밍에 적용하는지 그 방법에 대해서도 알아보고자 합니다.
요소 기술이란 구조체 정의하는 방법, 배열 초기화 방법, 포인터 사용하는 방법, 구조체를 함수의 매개변수로 넘기는 방법, static 스코프 키워드를 사용하는 방법, ++ 연산자 사용법, printf() 함수 사용법 등 단편적이지만 핵심적인 지식들을 의미합니다. 예를 들어, 중첩 루프도 하나의 요소 기술이라고 말할 수 있습니다. 물론, for 반복문 안에 if 조건문을 내포하는 방식이나 do while 반복문 안에 switch 문을 내포하는 방식도 요소 기술이라고 할 수 있습니다. 예를 들어, 문장의 끝에는 세미콜론(;)을 찍어야 한다는 사실도 단편적인 지식이지만 이 것 역시 요소 기술의 하나라고 할 수 있습니다. 요소 기술의 개념은 이러한 단편적인 지식에서부터 알고리즘의 로직이나 데이터 구조의 구성에까지 확장될 수 있습니다. 여러분은 보다 풍요로운 사고 속에서 이러한 요소 기술의 개념을 확대 응용해서 여러분의 소프트웨어 기술로 축적시켜 나가시기 바랍니다.
소프트웨어 공학에서는 예전부터 빌딩블록(Building Block/건축돌)이라고 하는 용어를 자주 사용하였습니다. 이것은 소프트웨어 구축 작업이 마치 건물을 짓는 것과 비슷하다는 취지에서 건축분야의 용어를 빌려온 것이라 할 수 있습니다. 실제 소프트웨어를 작성하는 데 있어서 요소 기술은 건축분야의 빌딩블록 역할을 합니다. 요소 기술이 모여져서 하나의 유기적인 프로그램이 만들어지는 것입니다. 또한 중요한 것은 유기적으로 완성된 프로그램을 통해서 각각의 요소 기술이 어떤 역할을 하고 있는지 이해하는 것입니다. 실제로 요소 기술의 전반적인 의미를 이해하기 위해서는 요소 기술이 어떻게 어우러져서 하나의 완성된 프로그램으로 통합되는가와 그리고 완성된 프로그램 속에서 요소 기술이 어떤 역할을 하는지를 이해하는 것입니다.
한편, C 언어 프로그래밍 학습은 무엇보다 C언어가 제시하는 법칙(Rule)을 잘 알고 있어야 합니다. 법칙은 사회나 모든 조직에서 지켜져야할 매우 중요한 행동규약입니다. 프로그래머가 프로그램을 작성할 때도 반드시 지켜야 할 법칙이 있는데 그것이 바로 Rule입니다. 자기 마음대로 프로그램을 작성한다면 그것은 프로그램이 아닙니다. C 언어가 제시하고 규정하는 법칙에 근거하여 프로그램을 작성해야 합니다. 예를 들어, 포인터를 선언하는 방법, 포인터를 함수의 매개변수를 전달하는 방법, 2차원 배열을 선언하는 방법, 함수를 사용하여 구조체를 반환하는 방법 등 많은 법칙들이 있습니다. 이 법칙들은 기본적으로 C 언어가 프로그래머들이 준수할 것을 당부하는 법칙입니다. C 프로그래밍에 있어서 준수해야 할 사항이라고 생각하면 됩니다. 훌륭한 시민이 사회의 법칙과 법을 잘 지키는 것처럼 훌륭한 프로그래머는 프로그래밍 언어가 제시하는 법칙을 잘 지킵니다. 정말, 프로그램을 오류 없이 잘 동작하도록 작성하기 위해서는 프로그래밍 언어가 제시하는 법칙(Rule)을 잘 지켜야 합니다. 물론, 엄격하게 구분하자면 프로그램에 관련된 법칙은 크게 두 가지로 분류됩니다. 첫 째가 프로그래밍 언어를 컴파일해주는 컴파일러에 의해 정의된 규칙이고, 둘째가 프로그래머가 프로그램을 작성하기 위해 정의하는 규칙입니다. 이 두 가지 규칙 모두 다 잘 지켜야 정상적으로 작동하는 올바른 프로그램이 완성됩니다. 프로그래머가 정의한 규칙에 오류가 있을 경우, 알고리즘적인 오류를 갖게 됩니다. 프로그래머가 정의하는 규칙은 주어진 문제를 정확하게 이해하지 못하거나 아니면 문제 파악을 제대로 하지 못해 해법을 올바르게 설정하지 못했기 때문에 발생합니다.
프로그래밍의 오류를 줄이기 위해서는 컴퓨터 앞에 앉아 화면상에 직접 코딩을 하기보다는 종이 위에 연필로 핸드 디자인(손으로 설계)을 하는 것이 좋습니다. 머리에 들어있는 생각들을 연필을 사용해서 종이에 작성하다보면 작성해야 할 프로그램의 논리적 구조를 쉽게 파악할 수 있으며, 전체적인 윤곽을 잡은 후 점진적으로 세부적 설계에 진입할 수 있습니다. 물론, 종이 위에 세부적인 코딩까지 완성한 후 컴퓨터 상에 코딩하는 방법이 좋은 방법이지만, 그렇게까지 할 인내심이 없다면 대강의 윤곽이라도 핸드 디자인으로 완성하는 것이 좋습니다.
프로그래머가 갖추어야할 소양들 중에는 대표적으로 자긍심과 겸허라는 것이 있습니다. 이 자긍심은 자만과는 다릅니다. 자만은 스스로 만족해서 더 이상 발전하려고 노력하지 않는 것을 뜻합니다. 겸허하고도 다른 뜻이지요. 겸허하다면 오히려 더 노력하겠지요. 이 자긍심과 겸허는 프로그래머의 실력을 향상시켜 주게 하는 거름이 됩니다. 자긍심은 자신의 일을 즐겨하면서 자신의 일에 긍지를 갖는 것입니다. 일을 할 때 즐겁게 하면 더 효율이 오르는 것처럼, 프로그래밍할 때도 즐거운 마음으로 프로그래밍을 하면 더 효율을 올릴 수 있습니다. 물론, 진지한 마음을 병행해야 합니다. 반면, 자신이 프로그래밍한 것이 반드시 옳다고 섣불리 주장해서는 안되며 남의 의견을 주의 깊게 들어보는 자세가 필요합니다. 이것이 바로 겸허입니다. 자긍심과 겸허는 바로 동전의 양면과 같습니다. 자신의 프로그래밍 해법에 대해서 긍지를 갖고 자신감 있게 자신의 주장을 피력하는 자세와 한편으로는 자신이 작성한 프로그래밍 해법만큼 남들의 해법에 존중하고 다른 해법이 존재할 수 있다는 생각을 가짐으로써 마음을 겸허하게 할 수 있습니다. 긍지는 확신을 갖게 하고 자신감을 형성시켜줍니다. 반면, 겸허는 다른 해법을 존중하고 다른 해법을 배울 수 있는 자세를 갖게 함으로써 학문에 대한 도량을 넓힐 수 있다 하겠습니다. 프로그래밍은 단순히 기술(Skill)을 배우는데 있는 것이 아니라 더 나아가 기술을 배우는 올바른 자세를 얻는데 있다고 할 수 있습니다. 단순히 기술에 얽매이게 되면 몇 가지 기술을 배웠을 때 만족하게 되어 더 이상의 발전을 가져오지 못하지만, 기술을 배우는 자세를 배우게 되면 몇 가지 기술을 배웠다고 만족하거나 교만해지지 않습니다. 더욱 분발하여 새로운 기술을 습득하고 남에게 아낌없이 전수하고자 노력하기도 합니다. 프로그래밍을 위한 자세와 프로젝트 수행에 관해 자세히 다룬 문헌이 있는데 그것은 Kent Beck이라는 분이 주창한 Extreme Programming입니다. 이 Extreme Programming은 4대 가치와 10대 practice(실무)를 다룹니다. 여러분이 위에서 설명한 다음의
1. C 언어의 기초(문법과 구분) 공부
2. C 프로그래밍의 기초 ==> 본서
3. 알고리즘과 데이터구조
4. Practical C Programming(실용적 C 프로그래밍)
 
네가지 C 언어 공부 과정을 무난히 수행하였다면 한 번쯤 Extreme Programming과 같은 프로젝트 수행 방법론 분야를 바라볼 필요가 있습니다. Extreme Programming은 소프트웨어 공학의 새로운 물결이며 기존 소프트웨어 공학 패러다임과 차별화된 방식을 따릅니다. 소프트웨어 공학의 신사조라고 할 수 있습니다. 그러니까, C 언어 과정의 다섯 번째 과정은 바로 소프트웨어 공학의 공부가 되겠습니다.
5. 소프트웨어 공학
 
Extreme Programming은 이 소프트웨어 공학 범주에 속하는 한 가지 양태라고 생각하시면 됩니다. UML이니 CASE Tool이니 하는 것은 모두가 소프트웨어 공학에 관한 이야기입니다. 여러분이 C 언어에서 한 단계 진일보하여 나간다면 C++이나 Java 언어를 공부해야 할 것이며, 그렇게 되면 자연스럽게 프로그래밍만이 아닌 “프로그램 설계”를 공부하시게 될 겁니다. 프로그램 설계 분야는 개념 설계가 주축을 이루고 있으며, 전산학에서 다루는 소프트웨어 기술을 현실세계에 접목시키는 작업을 잘 해낼 수 있어야 합니다. 따라서, 프로그램 설계는 실무 프로젝트 분야에 대한 전문적인 지식을 요구합니다. 따라서, 프로그래머는 항상 끊임없이 새로운 것을 배워야 합니다. 시대의 발전과 소프트웨어 기술은 병행하여 지속적으로 진화해 나가기 때문에 프로그래머는 항상 새로운 것을 배우는데 익숙해져야 합니다. 새로운 변화에 익숙하게 자신의 모습을 변화시키는 것도 프로그래머가 갖추어야 할 자세라고 할 수 있습니다.
본서는 5 개의 장으로 구성되었습니다. 물론 프로그래밍과 관련된 공부론에 대한 제 1장 소개를 포함해서 말입니다. 제 2 장에서는 30여 가지의 문제를 풀어나갑니다. 문제를 풀면서 문제의식을 쌓고 문제를 해소시키는 방법을 증득해 나가게 됩니다. 문제는 어렵지 않고 쉽고 평이한 내용입니다. 다만, 문제의 변화와 해법의 변화에 주목하시면 공부에 도움이 될 겁니다. 어려움은 획일적이고 일관적인 내용에서 발생하기보다는 다양한 변화를 통해서 발생하는 경향이 있습니다. 따라서, 문제와 해법의 변화를 궁리하고 연구하는 것이 실전 프로그래밍이나 실무 프로젝트에서 그 공부의 효험을 발휘할 수 있습니다. 제 2 장은 바로 이러한 내용을 다룹니다.
제 3 장은 객관식 문제를 제시하고 이 문제를 풀어나가는 것입니다. 프로그램의 논리구조를 파악하고 제어흐름을 이해하는데 목표를 두었습니다. 크게 어렵지 않으므로 종이와 연필로 이용하면 충분히 해결할 수 있는 내용입니다. 이 두 번째 장의 내용은 실전에서 선임자가 작성한 프로그램 코드를 분석하는 능력을 배양하고 프로그램의 논리구조를 보다 잘 이해하도록 실력을 향상시키는데 목적을 두었습니다. 자신이 프로그래밍을 잘하는 것도 좋지만 남이 작성해 놓은 프로그램을 보다 좋게 개선하고 향상시키는 작업도 중요합니다. 소프트웨어 공학에서는 이를 유지보수(Maintenance) 또는 유지관리 과정이라고 합니다. 소프트웨어의 품질을 향상시키는데 중요한 역할을 합니다. 왜냐하면 소프트웨어는 펌웨어처럼 한번 만들어져서 고착화되기 보다는 일반적으로 업데이트되는 경향이 강하기 때문입니다. 대부분의 SI 분야에서는 그렇지요.
제 4 장에서는 코드패턴을 다루는데, 이 코드패턴을 필자의 경험으로 나름대로 생각해 낸 것입니다. 물론, GoF의 디자인 패턴의 영향을 받았으며, 사실 디자인 패턴의 개념보다 먼저 숙지해야할 사항이라고 생각합니다. 다양한 코드패턴을 학습하고 암기해 둠으로써 프로그래밍의 생산성을 높이고 논리적 오류를 줄여나갈 수 있으며, 코드에 대한 친숙도를 높일 수 있습니다. 그리고, 논리적 구성력을 높일 수 있는 방편이 되기도 합니다.
마지막 제 5장 장에서는 전산학 및 프로그래밍에 대한 학습론을 다루고 있습니다. 필자는 그렇게 많은 프로그램을 작성한 적이 없습니다. 하지만 몇 년간 C 언어나 기타 언어를 사용하여 프로젝트를 수행하면서 느낀 바와 학문을 공부하는 방법에 대해서 경험한 바를 나름대로 정리해서 하나의 장에 담았습니다. 비록 주관적이고 사변적인 경향이 있으나, 프로그램이라는 완성품을 얻기 위해서는 단지 프로그래밍 언어의 문법구조만을 이해하는 것만으로는 안 된다는 점을 이야기하고 있습니다. 그 외 많은 제반지식을 공고히 하고 실제 경험을 통해 프로젝트를 완성할 수 있도록 자신의 능력을 함양시켜야 합니다. 5 장의 내용을 통해 프로그래밍 학습론에 대한 사고를 보다 넓히는데 도움이 되었으면 합니다.
본서의 강의 요령은 문제를 제시하고 해법을 제시하고 다시 새로운 해법을 제시하고, 그 다음 다른 변형된 문제를 제시하고 이에 대한 해법을 제시하고 다시 변형된 새로운 해법을 제시하는 방식으로 이어집니다. 본서의 내용을 예의주시하면 제가 빠뜨린 부분도 있고 미처 생각하지 못한 내용도 있을 겁니다. 따라서 여러분이 본서를 읽으시면서 빠진 부분이나 미약한 부분 등이 있으면 보충하시고 나름대로 연구해 보십시오. 모든 경우의 수의 문제와 모든 경우의 수의 해법을 다 제시하고 설명할 수 없습니다. 본인의 실력에 한계가 있기도 하고 문제와 해법의 가능성은 무한하기 때문입니다. 여러분은 꾸준히 연습하고 연구하시면서 문제와 해법을 공통된 법칙과 일정한 원리로 범주화시키는 작업을 하는 것도 도움이 될 겁니다. 물론, 이렇게 범주화시키는 것이 학습방법을 획일화시키게 될 수 있으므로 이와 동시에 유연성을 부여하고 항상 포용력과 확장성을 지닌 채 문제와 해법을 대면하는 것이 중요하다고 생각합니다.
Design Patterns 개념의 기저에는 이러한 소프트웨어의 보편타당한 경향성을 추론하고 정리, 체계화하는 창안자들의 노력이 서려있습니다. 알고리즘이나 자료구조도 마찬가지로 Design Patterns 개념이 적용되고 있다고 생각합니다. 만약 컴퓨터가 고장나면 왜 고장이 났는지 그 원인을 규명하고 그 다음 고장을 고치는 방법을 연구해야 합니다. 문제를 모르고서는 답을 얻을 수 없습니다. 하지만 문제를 안다고 해서 답을 완전히 얻을 수 있는 것도 아닙니다. 이러한 문제와 해법에 대한 이원적 관계를 하나의 연구의식으로 세워두고 끊임없이 프로그래밍 작업 동안 매진해 나간다면 언젠가 문제 해법의 이원적 상호관계의 심오하고 무궁무진한 의미를 이해할 수 있지 않을까 생각합니다. 제 자신도 다른 훌륭한 프로그래머들의 생각과 공부론 그리고 듀이 등의 교육 철학자들의 교육론들을 약간이나마 접하면서 이러한 아이디어들이 중요하다는 것을 인식하게 되었습니다. 물론 이러한 이원적 세계를 극복하고 초월하여 문제와 해답이 일원화(일체가)되는 세계까지 나아가야 하겠지만 말입니다. 하하. 저의 개인적인 생각이었습니다.
각설하고, 여하튼 본서의 핵심은 문제를 제시하고 제시된 문제를 해결하는 방법에 관해 진지하게 성찰해 보는데 있습니다.
예를 들어 다음의 문제를 고찰해 봅시다.
 
 
문제 1) N개의 정수 값을 갖는 배열 중에서 최대 값을 구하는 프로그램을 작성하시오.
 
해법 1-1) 다음은 함수를 사용하지 않은 해법입니다.
#include <stdio.h> #define N 10 void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int i, MaxValue = 0; for(i = 0; i < N-1; i++) if( vect[i] > MaxValue) MaxValue = vect[i]; printf("MaxValue : %d",MaxValue); }
 
 
프로그래밍 로직은 어렵지 않아 간단하다는 것을 알 수 있습니다. 다음은 함수를 사용한 해법입니다.
 
해법 1-2)
#include <stdio.h> #define N 10 int max(int vect[], int size) { int MaxValue = 0; int i; if ( size <= 0 ) return -1; for(i = 0; i < size; i++) if( vect[i] > MaxValue) MaxValue = vect[i]; return MaxValue; } void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int MaxValue = 0; MaxValue = max(vect, N-1); printf("MaxValue : %d",MaxValue); }
 
max()란 함수를 정의하였는데, 매개변수를 두 가지를 사용하고 있습니다. 즉, 배열 요소들을 담고 있는 vect[] 배열과 배열의 크기를 담고 있는 size가 그 것입니다. 함수를 사용하는 주된 이유는 중복을 최소화하기 위해서이며, 또한 함수를 사용하면 프로그램 루틴의 크기를 축소해 주는 이점을 가지고 있기 때문입니다.
다음은 반환해야할 매개변수가 많은 해법을 나타냅니다. 일명 이 함수는 전산학의 프로그래밍 언어론에서 함수가 아니라 프로시쥬어로서 알려진 것입니다. 즉, 함수를 사용하면 부가적으로 복잡한 로직을 하나의 함수에 작성함으로써 복잡한 로직을 분리하여 관리하기가 편리합니다. 사실 이러한 부가적인 개념은 프로시쥬어를 의미합니다.
 
해법 1-3)
#include <stdio.h> #define N 10 void max(int vect[], int size, int *MaxValue) { int i; if ( size <= 0 ) *MaxValue = -1; for(i = 0; i < size; i++) if( vect[i] > *MaxValue) *MaxValue = vect[i]; } void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int MaxValue = 0; max(vect, N-1, &MaxValue); printf("MaxValue : %d",MaxValue); }
 
void max() 함수는 반환 값이 없으며 전달받거나 전달되는 내용이 모두 함수의 매개변수에 표시됩니다. 이와 같이 매개변수를 사용해서 내용을 전달받을 때 프로시쥬어(절차) 적인 성향을 지닌 함수가 됩니다. PASCAL이란 언어에서는 이를 함수라고 하지 않고 프로시쥬어라고 명칭 합니다.
다음은 오류를 포함하는 함수(프로시쥬어)입니다. 전달하는 값을 다시 전달받기 위해서는 CALL BY REFERENCE 방식인 포인터를 사용해서 MaxValue 값을 전달하고 전달받아야 합니다.
 
해법 1-4) 오류!
#include <stdio.h> #define N 10 void max(int vect[], int size, int MaxValue) { int i; if ( size <= 0 ) MaxValue = -1; for(i = 0; i < size; i++) if( vect[i] > MaxValue) MaxValue = vect[i]; } void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int MaxValue = 0; max(vect, N-1, MaxValue); printf("MaxValue : %d",MaxValue); }
 
이제, 새로운 문제를 제시 해 봅시다. 지금까지는 최대 값만을 구했는데, 이제는 최소 값과 최대 값을 모두 구하는 프로그램을 작성합니다.
 
문제 2) 최대 값과 최소 값을 구하는 프로그램을 작성하십시오.
 
다음 해법은 최소 값과 최대 값을 하나의 함수 안에서 구하는 기법인데 비효율적인 요소를 지니고 있습니다.
 
해법 2-1)
#include <stdio.h> #define N 10 enum T { Min = 0, Max }; int MinMax(T type, int vect[], int size) { int MinValue = 100, MaxValue = 0; int i; if ( size <= 0 ) return -1; // 오류 for(i = 0; i < size; i++) { if( vect[i] > MaxValue) MaxValue = vect[i]; if( vect[i] < MinValue) MinValue = vect[i]; }; if ( type == Min ) return MinValue; if ( type == Max ) return MaxValue; } void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int MinValue, MaxValue = 0; MinValue = MinMax(Min, vect, N-1); MaxValue = MinMax(Max, vect, N-1); printf("MinValue : %d, MaxValue : %d", MinValue, MaxValue); }
 
문장 if ( size <= 0 ) return -1; // 오류 는 대표적인 방어적 프로그래밍을 말합니다. 데이터의 타당성을 검증하는 루틴을 의미하는데 이러한 루틴을 작성함으로써 사용자의 잘못된 입력 값이나 부적절한 값을 미연에 방지할 수 있습니다.
비효율적인 부분은 MinMax() 함수의 for 루프입니다. for 루프는 반복문으로 MinValueMaxValue를 모두 구하고 있습니다. 이 방식은 다음의 해법으로 개선됩니다.
다음은 최소값과 최대 값을 하나의 함수 안에서 구하는 기법으로 앞의 해법보다 효율적입니다.
 
해법 2-2)
#include <stdio.h> #define N 10 enum T { Min = 0, Max }; int MinMax(T type, int vect[], int size) { int MinValue = 100, MaxValue = 0; int i; if ( size <= 0 ) return -1; // 오류 if ( type == Min ) { for(i = 0; i < size; i++) if( vect[i] < MinValue) MinValue = vect[i]; return MinValue; } else if ( type == Max ) { for(i = 0; i < size; i++) if( vect[i] > MaxValue) MaxValue = vect[i]; return MaxValue; } } void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int MinValue, MaxValue = 0; MinValue = MinMax(Min, vect, N-1); MaxValue = MinMax(Max, vect, N-1); printf("MinValue : %d, MaxValue : %d", MinValue, MaxValue); }
 
MinMax() 함수의 for 루프에서 MinValueMaxValue들 중에서 하나만 구합니다. 앞의 해법에서는 비효율적으로 두 경우에 대해서 모두 구했는데, 여기서는 조건에 맞는 경우에 대해서 한 가지만 구합니다.
 
다음은 앞의 두 가지 해법의 변형으로 최소 값과 최대 값을 하나의 함수 안에서 동시에 구하는 기법입니다. 일명 프로시쥬어를 사용합니다.
해법 2-3)
#include <stdio.h> #define N 10 void MinMax(int vect[], int size, int *MinValue, int *MaxValue) { int i; //*MinValue = 100; //*MaxValue = 0; if ( size <= 0 ) return; // 오류 for(i = 0; i < size; i++) { if( vect[i] > *MaxValue) *MaxValue = vect[i]; if( vect[i] < *MinValue) *MinValue = vect[i]; }; } void main() { int vect[N] = { 2, 3, 1, 7, 6, 4, 5, 1, 9}; int MinValue = 100, MaxValue = 0; MinMax(vect, N-1, &MinValue, &MaxValue); printf("MinValue : %d, MaxValue : %d", MinValue, MaxValue); }
 
// 기호는 C++에서 단행 주석 표시이며 main() 함수에서 MinValueMaxValue를 초기화하지 않을 경우 주석 기호를 풀어주시면 됩니다.
앞에서 설명한 문제와 해법을 다시 C 언어 차원이 아닌 C++ 차원에서 구현하면 보다 다양한 가능성이 전개됩니다. C++ 언어에서 클래스와 Template를 사용할 경우 새로운 해법들이 모색될 수 있습니다.
이제 여러분이 인식할 수 있을 테지만 문제와 해법이 다양하게 발전하고 전개됨에 따라 새로운 요소 기술과 논리 구조를 대면하게 됩니다. 이와 같이 많은 문제들과 해법을 공부하다보면 아마도 어느샌가 소프트웨어 기술에 대한 나름대로의 이론이 정립되고 실력이 늘어날 것입니다. 그리고 중요한 것은 왜 이 요소 기술이 필요한가? 왜 이 요소 기술을 사용하지 않으면 안 되는가? 만약 다른 방법을 없을까? 다른 방법이 있다는 기존 방법과 차이는 무엇인가? 어느 요소 기술을 사용했을 때 더 성능 면에서나 가독성 면에서 좋은가? 비슷하다면 어떤 상황에서 어느 요소 기술을 사용한 해법이 더 효과적인가? 우리는 이와 같은 끊임없는 물음을 제시할 수 있어야 합니다. 그러나 주의할 사항은 실전 프로그래밍에서는 이러한 방만한 문제의식보다는 한가지 문제를 집중적으로 심화시키는 것이 중요합니다. 그래야 정확한 해답을 얻을 수 있기 때문입니다. 본서의 내용은 다분히 이론적입니다. 따라서, 실전에 본서의 내용을 활용하기 위해서는 프로젝트 수행과 관련된 제반 지식을 잘 알고 있어야 하며, 한 가지 문제 의식을 심화시켜 나가는 능력을 길러야 합니다. 즉, 근면과 인내가 필요합니다.
이제 본서의 내용에 대해서 대강의 의미를 파악하셨으리라 생각합니다.