레이블이 최종보고서인 게시물을 표시합니다. 모든 게시물 표시
레이블이 최종보고서인 게시물을 표시합니다. 모든 게시물 표시

2019년 2월 28일 목요일

[컴개실 최종보고서 5] 파일 읽기 및 쓰기와 PostScript


5. 파일 읽기 및 쓰기와 PostScript
1. StreamReader와 StreamWriter
우리가 한글과컴퓨터 등등의 프로그램을 다루면서 설정을 만지작거리고, 다시 프로그램을 껐다가 켜면 그 설정이 그대로 남아있는 것을 확인할 수 있다. 이는 그 설정 값이 휘발성 메모리가 아니라 영구적인 메모리에 할당되기 때문인데, 거의 반영구적인 메모리로는 하드 디스크, SSD 등을 들 수 있다. 우리가 설정해 준 것을 프로그램이 파일 형태로 하드 디스크 등등에 남기고, 우리가 나중에 프로그램을 다시 켤 때 프로그램이 그 파일을 불러와서 파일에 기록된 설정 값 대로 움직여 주기 때문에, 프로그램을 껐다가 다시 켜더라도 우리가 설정해 준 대로 프로그램이 잘 돌아가는 것이다. 이를 실현하기 위해서는 우선 파일을 읽고 쓰는 방법을 알아야 한다.
우리는 여기서 StreamReader와 StreamWriter를 이용하여 파일을 읽고 쓸 것이다. 이러한 것들은 네임스페이스 System.IO 안에 들어있으므로, 코드 상위에 다음과 같이 using System.IO라는 문구를 추가해야 한다.
그림 1 네임스페이스를 추가한 모습
우리가 실행하는 프로그램이 있는 폴더 안에 input.txt라는 텍스트 파일이 있다고 하자. 우리는 input.txt를 읽고 싶다. 그렇다면 StreamReader를 선언해야 하는데, 선언하는 꼴은 다음과 같다.
StreamReader sr = new StreamReader(“input.txt”);
sr은 StreamReader의 이름으로 사용자가 임의대로 설정할 수 있다. 그리고 new StreamReader 옆의 괄호에 파일 이름을 쓰면, StreamReader은 이제 그 파일을 읽을 수 있는 준비 상태가 된다. 텍스트 파일의 경우, 우리는 이 파일을 ‘한 줄 한 줄’ 읽어 나가야 하는데, 이때 한 줄을 읽고 읽은 것을 string temp에 담기 위해서는 다음과 같이 써야 한다.
string temp = sr.ReadLine();
종종 우리는 텍스트 파일로 작성된 표를 만나볼 수 있다. 표의 칸을 구분하기 위해 칸마다 데이터마다 띄어쓰기가 되어 있거나, 반점이 찍혀 있는 것을 볼 수 있는데, 이렇게 칸을 구분하여 하나의 배열에 담는 방법이 있다. 공백으로 구분되어 있는 칸을 나눠 데이터를 저장하는 배열의 이름을 tempArr로 한다면,
string[] tempArr = temp.Split(‘ ‘);
이렇게 하면 배열의 원소마다 우리가 읽은 줄, 즉 표의 행에 있는 칸의 내용이 담긴다.
이제 이러한 방식으로 우리는 파일을 파일이 끝날 때까지 읽고 싶다. 파일이 끝났는지 알려주는 지표가 StreamReader 클래스 안에 이미 있는데, 이는 EndOfStream이다. EndOfStream이 true이면 파일이 끝났다는 말이 된다. 이를 활용하면 다음과 같이 쓸 수 있다.
while(!sr.EndOfStream)
{
  파일을_읽는_코드;
}
파일을 쓰는 방법도 StreamReader를 쓰는 방법과 별반 다를 게 없다. 파일을 쓰기 위해서는 StreamWriter라는 것을 써야 하는데, 만약 output.txt에 데이터를 작성하고 싶다면, 다음과 같이 선언하면 된다. (StreamWriter의 이름은 sw로 임의로 지정함)
StreamWriter sw = new StreamWriter(output.txt);
이도 똑같이 파일을 한 줄 한 줄 쓰는데, 만약 “Hello World!”라고 한 줄 쓰고 싶다면, 다음과 같이 코드를 작성하면 된다.
sw.WriteLine(“Hello World!”);
StreamReader와는 달리, StreamWriter는 쓰기 작업이 모두 끝난 뒤에 반드시 StreamWriter을 닫아야 한다. 그렇지 않으면 오류가 발생하는데, 닫는 코드는 다음과 같다.
sw.Close();
2. PostScript
위의 StreamReader와 StreamWriter을 사용하여 도형을 그려보자. PostScript는 실시간으로 도형을 그릴 수는 없지만, 난이도가 낮기 때문에 많이 사용된다. PostScript로 작성된 파일은 .ps 확장자를 가진 파일로 저장되며, 여러 가지 프로그램으로 이 파일을 열어서 확인할 수 있는데, 필자는 Rampant Logic Poscript Viewer을 이용하였다.
PostScript가 작동하는 방식은 우리가 일상생활에서 도형을 그리는 방법과 동일하다. 만약에 우리가 삼각형을 그리고자 하면, 우리는 도형을 그리기 위해 연필을 집고, 연필을 종이의 어떤 점에 찍은 뒤, 연필을 종이에 댄 상태에서 연필을 다른 두 점으로 이동시키고, 다시 원래의 점으로 연필을 이동시킨 다음에, 종이에서 연필을 뗀다. PostScript도 이와 비슷하게 움직인다.
우리가 연필을 집는 과정을 PostScript에서는 “newpath”라고 한다. newpath라고 명령하면 PostScript는 도형을 그리는 ‘연필’을 집게 된다. 그리고 우리가 연필을 종이에 찍을 때 ‘어떤 특정한 점’에 찍듯이, PostScript가 종이를 찍게 될 좌표를 우리가 명령으로 알려주어야 한다. 그렇게 하기 위해서 우리는 다음과 같이 명령을 내려야 한다.
(x좌표) (y좌표) moveto
연필을 찍었으면, 연필을 종이에 댄 상태에서 다른 점으로 옮겨야 종이에 선이 그려질 것이다. 우리가 연필을 점 P로 옮기기 위해서 우리는 다음과 같이 명령해 줘야 한다.
(P의 x좌표) (P의 y좌표) lineto
이렇게 선을 긋고 다시 연필이 맨 처음에 연필을 찍었던 점으로 되돌아온 상태라고 하자. 대개 도형을 그리면 도형은 열린 상태가 아닌, 닫힌 상태가 되는데, 도형을 완전히 닫힌 상태로 만들기 위해서 우리는 다음과 같이 명령해줘야 한다.
closepath
마지막으로, 우리는 연필을 종이에서 떼어야 한다. 이는 다음과 같이 명령해 주면 할 수 있다.
stroke
하지만 이렇게만 하면 우리는 도형의 테두리, 즉 선만을 그리게 된다. 도형을 색칠하기 위해서는, 우선 색을 결정해야 하고, 그 다음에 선 안을 채워야 한다. 이를 수행하려면 위에서 closepath와 stroke 사이에 다음의 명령어를 쓰면 되는데, 색은 검정색으로 하겠다.
1 setgray
fill
setgray가 흑백으로 색을 결정하는 명령어인데, setgray 앞의 0과 1 사이의 값이 0에 가까울수록 흰색에 가까우며, 1에 가까울수록 검정색에 가깝다. fill은 지정한 색으로 닫힌 선 안을 채우는 명령어이다.
우리는 흑백 말고 화려한 색으로 도형을 채우고 싶을 수 있다. 이때 우리는 빛의 삼원색을 이용하여 색을 지정해주는데, 그 명령어는 다음과 같다.
(R 비중) (G 비중) (B 비중) setrgbcolor
이 비중을 모두 1로 하면 흰색, 이 비중을 모두 0으로 하면 검정색이 나온다. 그리고 RGB에 해당되는 비중 중 하나만 1로 하고 나머지를 0으로 하면, 1의 비중을 넣은 그 색이 출력된다.
이제 이 명령어들을 가지고 실제로 삼각형을 그려보자.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace helloworld2
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter("output.ps");
            sw.WriteLine("100 100 moveto");
            sw.WriteLine("200 100 lineto");
            sw.WriteLine("100 200 lineto");
            sw.WriteLine("100 100 lineto");
            sw.WriteLine("closepath");
            sw.WriteLine("1.0 0.7 0 setrgbcolor");
            sw.WriteLine("fill");
            sw.WriteLine("stroke");
            sw.Close();
        }
    }
}

이렇게 하면 (100,100), (200,100), (100,200)을 꼭짓점으로 가지는 주황색 삼각형이 그려진다. 이를 실제로 파일을 열어 확인해보자.
그림 2 PostScript으로 그린 결과
우리는 기하와 벡터 문제와 같은 수학 문제를 풀 때 3차원 입체 도형을 2차원에 나타내어 푼다. 그 이유는 간단하게도, 우리가 종이에 그릴 수 있는 것은 2차원 상의 도형밖에 없기 때문이다. 그래서 우리는 투영도를 그려서 3차원 도형을 2차원으로 표현하여 그 모습을 짐작하는 것이다.
이것 또한 PostScript로 구현할 수 있는데, 이를 위해서는 미리 생각해 줘야 하는 장치들이 있다. 첫 번째로, 입체 도형을 바라보는 카메라가 필요하다. 이때 편의성을 위해 우리는 카메라를 한 점으로 생각할 것이다. 두 번째로, 카메라와 입체 도형 사이에 있는 평면이 필요하다.
그림 3 장치
그리고 카메라와 입체 도형의 꼭짓점을 잇는 직선을 생각하고, 직선과 평면 사이의 교점을 모두 구한다. 교점과 꼭짓점은 일대일 대응이 이뤄지는데, 입체 도형의 모서리가 어떤 점과 어떤 점을 잇는 선분인지 고려하여 대응에 맞게 교점과 교점 사이를 선분으로 잇는다. (카메라와 A를 잇는 직선과 평면 사이의 교점이 A’이고, B를 잇는 직선과 평면 사이의 교점이 B’라면, A-B를 잇는 선분 → A’-B’를 잇는 선분에 대응이 되도록) 그러면, 거리에 따른 순서가 없는 입체도형의 투영도가 그려진다.
하지만 일상생활에서 우리가 보는 입체 도형의 경우, 뒤에 가려진 부분은 우리 눈에 보이지 않는다는 것을 발견할 수 있다. 그렇다면 가장 우선 해야 할 것은 꼭짓점과 카메라 사이의 거리를 알아내는 것이고, 두 번째로 해야 할 것은 그 거리에 따라 꼭짓점을 ‘정렬’하는 일이다. 그리고 PostScript로 평면을 그릴 때, 거리가 가장 먼 꼭짓점으로 이루어진 평면부터 차례차례 그려서 마지막엔 거리가 가장 가까운 꼭짓점으로 이루어진 평면을 그려서 뒤에 가려진 부분은 보이지 않도록 처리하면 된다. 문제는 꼭짓점을 ‘정렬’하는 일인데, 이는 부록에서 살펴보도록 한다.

2019년 2월 27일 수요일

[컴개실 최종보고서 4] 클래스와 메소드


4. 클래스와 메소드
1. 메소드
C나 C++ 언어에서 부르는 방식에선 함수라고 부르는 메소드는 간단히 말하면 매개 변수를 통해 Input을 넣었을 때, 일련의 작업을 수행하여 Output을 내놓는, 말 그대로 함수이다. 다음은 메소드를 선언하는 형식이다.

class 클래스의_이름
    {
        한정자 반환_형식 메소드의_이름( 매개_변수_목록 )
        {
            실행하고자_하는_코드;
            return 메소드의_결과;
        }

    }

한정자는 이 메소드가 이 클래스 외부에서도 쓰일 수 있는지를 결정짓는다. 외부에서 쓰일 수 있도록 하려면 한정자를 public으로 잡으면 된다. 반환 형식은 함수가 Output으로 내놓는 값의 데이터 형식이 어떤 것인지를 결정짓는다. 만약 Output으로 내놓고자 하는 것이 정수형이면, 반환 형식을 int로 잡으면 된다. 메소드의 이름은 말 그대로 개발자가 임의로 결정하는 메소드의 이름이다. 그 옆의 괄호에는 매개 변수의 목록이 자리잡게 되는데, 이는 메소드를 호출하면서 동시에 메소드에게 전달하는 Input 값들의 목록이다. 그 아래에는 메소드를 호출했을 때 실행하게 될 코드가 있고, return이라는 것이 자리잡고 있다. return 옆에 오는 값은 메소드가 Output으로 내놓는 값이다. 아래는 이에 대한 예시이다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloWorld
{
    class Test
    {
        public static int Add(int a, int b)
        {
           return a + b;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
           Console.WriteLine(Test.Add(3, 4));
        }
    }

}

우선 클래스 Test가 새로 선언되고, 그 안에는 외부에서도 쓰일 수 있으며, static으로 한정되고, 정수형 값을 Input으로 받는 메소드 Add가 선언된다. Add는 Input으로 받은 정수를 더한 값을 return 값으로 내보내며, Main 메소드에서는 3과 4를 Input으로 집어넣은 Add 메소드의 return 값을 콘솔 창에 출력한다. 이때, 눈 여겨봐야 할 것이 바로 Main 메소드에서 Test의 Add 메소드를 호출하는 방식인데, static 한정자를 가진 메소드의 경우,
클래스_이름.메소드_이름( 매개변수 )
의 형식으로 호출해야 한다.
static 한정자를 가지지 않은 메소드의 경우는 클래스를 다룰 때 살펴 본다.
한 가지 알아 두어야 할 점은, 매개 변수를 아무리 메소드 안에서 수정한다고 해도 메소드의 작업이 끝나게 되면 그 수정된 값은 남아있지 않고 원래대로 돌아가게 된다는 것이다. 이는 메소드에서 매개 변수의 값을 ‘복사’하여 다른 메모리에 저장한 뒤 그 값을 쓰게 되기 때문인데, 만약 메소드 안에서 매개 변수를 수정할 수 있도록 변경하려면 매개 변수 앞에 ref를 붙이면 된다.
2. 클래스
우리는 여태까지 이런 형식의 변수 선언을 많이 봐 왔다.
int a = 0;
우리는 int를 데이터 타입이라고 불러 왔으나, 여기서는 int를 ‘클래스’라고 부를 것이다. int라는 클래스는 a라는 ‘객체’가 어떠한 데이터를 담을 것인지를 알려주는 ‘청사진’이 되어주는 역할을 한다. 우리가 칼을 만들려고 할 때, 쇳물을 붓는 거푸집을 클래스라고 한다면, 거기에서 굳어서 나온 실제 칼을 객체라고 하는 것이다. 이를 예제를 통해 더 살펴보도록 하자.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloWorld
{
    class Student
    {
public string Name;

        public void Print()
        {
           Console.WriteLine(Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
           Student kim = new Student(); //1
           kim.Name = “Kim Ku”;
           kim.Print();
        }
    }

}

여기서는 클래스 Student가 선언되었다. 그 안에는 string 타입 변수 Name이 선언되었고, Name을 콘솔 창에 출력하는 메소드 Print가 선언되었다. 저번과 다른 점은 메소드에 static 한정자가 붙지 않았다는 것이다.
그리고 Main 메소드 중 오른쪽에 //1 이라고 쓰여 있는 부분에서Student 클래스의 형태를 띤 변수 kim에 대한 메모리 할당이 이뤄진다. 그 후 kim 안의 내용물을 수정하는데, kim의 이름을 Kim Ku로 바꿔 버리는 것이다. 다음에는 Print 메소드가 쓰이는데, 저번과는 다르게 클래스 이름 뒤에 .Print()를 붙이지 않고 변수 kim의 뒤에 .Print()를 붙인다. 이는 static 한정자가 메소드에 붙지 않았기 때문이다. 메소드가 철저하게 개인화된다.
만약 Student라는 클래스가 생성될 때부터 아예 Name까지 정해지도록 하고 싶다면 어떻게 해야 할까? 다음 코드를 보자.

namespace HelloWorld
{
    class Student
    {
public string Name;

        public void Print()
        {
           Console.WriteLine(Name);
        }

        public Student (string name)
        {
           Name = name;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
           Student kim = new Student(“Kim Ku”); //1
kim.Print();
        }
    }

}

여기서 무엇이 바뀌었는지 보자. public Student라고 쓰인 파트가 추가되었다. 옆의 괄호 안에는 매개 변수 같이 생긴 것이 있고, 중괄호 안에서는 Name에 매개 변수로 받은 string 변수 name을 대입하는 코드가 있다. 다음과 같이 써 주면, Main 메소드에서 Student kim을 새로 선언할 때, 괄호 안에 학생의 이름을 같이 써 주면 kim.Name에 따로 string 변수를 대입해주지 않더라도 Name에 “Kim Ku”라는 이름을 저장할 수 있다.
그리고 이러한 public Student(…)를 클래스 내에 여러 개를 설정할 수 있는데, 이때 괄호 안에 오는 매개 변수의 타입이 이미 클래스 내에서 설정된 public Student(…)와는 달라야 한다.

2019년 2월 25일 월요일

[컴개실 최종보고서 3] 연산자와 구문


3. 연산자와 구문
1. 연산자
수학에서 우리가 계산식을 표기할 때에도 기호라는 것을 이용해서 덧셈식, 뺄셈식 등등을 표기하듯이, 프로그래밍 언어에서도 계산 작업을 수행할 때에는 연산자를 이용해서 계산하게 된다. 그 연산자의 종류로는 다음이 있다.
분류
연산자
산술 연산자
+, -, *, /, %
증가/감소 연산자
++, --
관계 연산자
<, >, ==, !=, <=, >=
조건 연산자
?:
논리 연산자
&&, ||, !
비트 연산자
<<, >>, &, |, ^, ~
할당 연산자
=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
표 1 연산자의 종류
산술 연산자는 덧셈, 뺄셈 등 우리가 일상생활에서 가장 많이 사용하는 연산을 수행하는 연산자이다. 예시를 들어보자.
int a = 1 + 2;
int b = 4 – 1;
int c = 4 * 2;
int d = 4 / 3;
int e = 4 % 3;
double f = 5 / 2;
예상할 수 있듯이, 정수형 변수 a에는 3이 저장되고, b에는 3이 저장되며, c에는 8이 저장된다. 하지만 d는 정수형 변수인데, 4 / 3을 어떻게 저장할까? 소수점 뒷부분을 전부 빼 버린, 결국 나눗셈의 몫, 1만 남게 된다. 그리고 e에는 4 나누기 3의 나머지인 1을 저장하게 된다. 반면, 소수점이 붙을 수 있는 변수 f에는 5 나누기 2의 결과인 2.5가 전부 저장될 수 있게 된다.
증가 연산자와 감소 연산자는 의미가 간단하나, 가장 많이 쓰이는 연산자 중 하나이다. 증가 연산자인 ++를 변수 뒤에 붙이면 (예 : number++;) 그 변수의 수가 1 증가하며, 감소 연산자 --를 변수 뒤에 붙이면 (예 : number--;) 그 변수의 수가 1만큼 감소하게 된다.
관계 연산자는 조건문에 빠지지 않고 등장하는 단골 손님이다. <는 왼쪽이 오른쪽보다 작은 지를 확인, >는 오른쪽이 왼쪽보다 작은 지를 확인, <=는 오른쪽이 왼쪽보다 크거나 같은 지를 확인, >=는 왼쪽이 오른쪽보다 크거나 같은 지를 확인, 그리고 ==는 왼쪽과 오른쪽이 같은 지를 확인, !=는 왼쪽과 오른쪽이 다른 지를 확인하여 참이면 TRUE, 거짓이면 FALSE를 내놓는다.
논리 연산자는 말 그대로 논리 연산을 하는데, &&는 왼쪽 조건과 오른쪽 조건이 동시에 확인하여 참이면 참을 내놓고, ||는 왼쪽 조건과 오른쪽 조건 중 하나라도 만족하면 참을 내놓는다.

2. 구문
분기문은 프로그램의 흐름을 조건에 따라 여러 갈래로 나누는 흐름 제어 구문이다. 이 중에서 가장 많이 쓰게 될 것은 if문이다. 다음은 if 문의 사용 형식이다.
if ( 조건식 )
{
  실행할 내용;
}
if 옆의 괄호 안에 있는 조건식이 성립하게 되면, if문 아래 중괄호 안의 내용을 실행하게 된다. 이때 조건식에는 방금 봤던 관계 연산자가 아주 많이 쓰이게 된다. 예시를 보자.
if ((10 % 2) == 0)
{
  Console.WriteLine(“10은 짝수다”);
}
10을 2로 나눴을 때의 나머지와 0을 우선 조건식에서 비교한다. 결과는 TRUE이므로, if문은 중괄호 안의 내용을 실행하게 된다. 그럼 콘솔 창에 “10은 짝수다”라고 출력되고 끝날 것이다. 그리고 이와 같이 중괄호 안의 코드가 한 줄 밖에 되지 않는 경우, 그냥 중괄호를 생략하고 코드만 써 줄 수 있다.
우리는 if 문 안의 조건문이 참이 아닌 경우에서는 다른 작업이 수행되도록 하고 싶고, 아니면 여러 가지 경우에 대해서도 분기문을 실행시켜 주고 싶을 때가 있다. 이를 해결할 수 있는 방법이 아래 예시에 나와있다.
int a = 10;
if ( a > 1)
  Console.WriteLine(“a is bigger than 1”);
else if ( a == 1 )
  Console.WriteLine(“a is 1”);
else
  Console.WriteLine(“a is smaller than 1”);
분기문 실행은 if부터 시작된다. 우선 a가 1보다 큰 지 확인해서, 1보다 크다면 그 아래의 코드를 실행시키고, 만약 그렇지 않다면 else if로 넘어가게 된다. 그리고 else if의 조건문에서 명시해 두었듯이, a가 1과 같은 지 확인하고, 참이라면 else if 바로  아래의 코드를 실행시키고, 만약 그렇지 않다면 else로 넘어가서 else 바로 아래의 코드를 실행시키게 된다. 위 예시의 경우 a는 1보다 크므로 if 문의 코드가 실행될 것이다.
이렇게 다양한 분기점에 대해서 if문을 붙여줄 수 있는데, else if문의 개수는 무한정으로 늘일 수 있다. 하지만 이 순서 배정에 있어서 생각을 해 줘야 하는데, 조건을 확인하는 순서는 코드에 쓰여 있는 순서대로 진행되기 때문이다. 그래서 가장 참일 확률이 높은 조건문을 가지는 분기문을 가장 상위에 띄워야 하고, 가장 확률이 낮은 것은 가장 아래에 띄워야 한다.
반복문은 특정 조건을 만족하는 동안 코드를 계속 반복해서 실행하도록 하는 문장이다. 그 종류는 4가지가 있는데, 모두 매우 자주 쓰이는 구문들이다.
첫 번째는 while 문이다. while 문은 다음의 형식을 가진다.
while ( 조건식 )
{
  반복_실행할_내용;
}
while 문은 가장 먼저 조건식을 체크한다. 조건식이 참이면 중괄호 안의 코드를 수행하고, 조건식이 거짓이면 코드를 수행하지 않는다. 조건식을 먼저 체크하기 때문에 맨 처음부터 조건식이 거짓이면 while 문 안의 코드는 전혀 수행되지 않을 것임을 명심해야 한다.
두 번째는 do while 문이다. do while 문은 다음의 형식을 가진다.
do
{
  반복_실행할_내용;
}
while ( 조건식 );
이 반복문의 특성은 조건식을 체크하기 전에 먼저 do 안의 코드를 실행한다는 것이다. 그래서 조건식이 맨 처음부터 거짓이더라도 반복 실행할 내용을 무조건 한 번은 수행하게 돼 있다.
세 번째는 for 문이다. for 문은 다음의 형식을 지닌다.
for ( 초기화식; 조건식; 반복식;)
{
  반복_실행할_내용;
}
초기화식은 반복을 실행하기 전에 단 한 번만 실행되는 코드이며, 조건식은 반복문을 계속 수행할 지 체크하는 코드이다. 그리고 반복식은 반복이 한 번씩 끝날 때마다 실행되는 코드로, 주로 초기화식에서 선언한 변수를 변화시키는데 쓰이는 코드이다. 예시를 통해 확인해보자.
for ( int i = 0; i < 10; i++)
{
  Console.WriteLine(i);
}
우선 초기화식에서 새로운 변수 i를 선언하고 값을 0으로 초기화한다. 이때 초기화하는 변수는 굳이 초기화식에서 새로 선언하지 않고 외부에서 가져와도 괜찮다. 초기화식을 처리한 후, 바로 조건식을 체크하는데, 만약 맨 처음부터 조건식이 성립되지 않으면 for 문 안의 코드는 수행되지 않고 끝난다. 조건식 체크 후에 조건식이 참이면, 반복식 말고 for 문 중괄호 안의 코드가 먼저 실행된다. 코드의 실행이 끝나면 그 후에 반복식을 실행한다. 이 예시의 경우, 결과는 다음과 같을 것이다.
0
1
2
3
4
5
6
7
8
9
이러한 for 문 안에 다시 for문을 집어넣어 중첩된 for 문을 쓸 수 있음을 알고 있으면 좋다.
네 번째는 foreach 문이다. foreach 문은 배열 또는 컬렉션을 순회하며 각 데이터 요소에 차례대로 접근하는 반복문이다. 형식은 다음과 같다.
foreach ( 데이터형식 변수명 in 배열_또는_컬렉션 )
{
  반복_실행할_코드;
}
그리고 이에 대한 예시는 다음과 같다.
int[] arr = {0,1,2};
foreach (int a in arr)
  Console.WriteLine(a);
정수형 배열 arr에는 0, 1, 2가 저장되고, foreach 문이 이 원소를 차례차례 살펴가며 각 원소를 콘솔 창에 출력하게 된다. 결과는 다음과 같을 것이다.
0
1
2
이 반복문은 다른 것에 비해서 상대적으로 사용 빈도가 적은 편이나, 쓰이면 유용한 경우가 있기 때문에 잘 알아 두는 것이 좋다.

2019년 2월 22일 금요일

[컴개실 최종보고서 2] 데이터의 보관

2. 데이터의 보관

1. 변수

수학에서와 마찬가지로, 프로그래밍에도 변수라는 것이 존재한다. 변수를 코드의 면모서 살펴보면 수학에서의 의미와 비슷하게 값을 대입시켜 변화시킬 수 있는 요소로 볼 수 있으나, 메모리의 관점에서 살펴보면 ‘데이터를 담는 일정한 크기의 공간’이라고 볼 수 있다. 이러한 변수를 ‘만든다’는 것을 개발자들은 ‘변수를 선언한다’라고 말한다. 변수를 선언한다는 말은 이 변수에 필요한 메모리 공간을 예약해달라는 말로 바꾸어 이해할 수 있다.
그 변수의 형식들은 다음과 같이 나열할 수 있다. 여기서 중요한 형식들 몇 가지만을 살펴보도록 한다.
그림 1 C# 변수의 형식
형식 중에는 정수 계열 형식이 있는데, 이 중에서 우리가 중점적으로 다룰 것은 int이다. int는 정수 계열 형식이라는 이름에 걸맞게 부호가 있는 정수를 담을 수 있는 변수 타입이다. 그림 1을 자세히 들여다보면 uint라는 이름이 비슷하게 생긴 형식을 볼 수 있는데, 이것은 부호가 없는 정수, 즉 0 이상의 정수를 저장하는 변수 타입이다. 이들이 저장할 수 있는 값의 최소와 최댓값을 보면 다름을 알 수 있는데, 이는 정수 계열 형식 변수가 부호 있는 정수를 표현하는 방식으로 인한 것이다. 정수 계열 형식 변수는 2의 보수를 이용하여 부호를 표현하는데, 앞의 비트를 부호 비트로 사용하는 이유로 최소와 최댓값의 범위가 위와 같이 달라지게 된 것이다. 또한, int에는 소수점이 붙은 수는 표현할 수 없는데, 만약 1/2의 계산 결과를 int에 저장하라고 명령하면 int에는 0만 저장되게 된다.
그 다음으로는 부동 소수점 형식이 있다. 여기에는 우리가 중점적으로 다루게 될 double 형식이 있는데, 이것을 이용해서 비교적 소수점이 많이 붙은 유리수를 표현할 수 있게 된다. 또한, 문자열 형식인 string 타입도 있는데, 이 타입을 이용하여 말 그대로 문자의 열을 저장할 수 있게 된다.
이러한 변수를 선언하는 방법은 다음과 같다. 여기서는 x라는 이름의 변수를 선언하려고 한다.
int x;
x = 10;
다음과 같이 변수의 이름 앞에 변수 형식을 먼저 쓴 뒤, 등호 연산자를 이용하여 x에 10이라는 값을 대입한다. 그리고 이렇게 명령문 하나가 끝날 때마다 뒤에 세미콜론(;)을 붙여 명령문이 끝났음을 알린다.
위를 아래와 같이 줄여 쓸 수 있다.
int x = 10;
2. 배열
위처럼 변수를 선언하다 보면 같은 타입의 변수를 아주 많이 선언한 다음, 나열해서 쓰고 싶을 때가 있다. 하지만 하나하나 선언하다 보면 욕이 먼저 튀어나올 것이다. 그래서 만들어진 것이 배열이다. 만약에 100개의 변수를 선언하고 싶다면, 100개의 용량을 가진 배열 한 개만 선언하면 되는 것이다. 배열은 다음과 같은 형식으로 선언한다.
데이터형식[ ] 배열이름 = new 데이터형식[ 용량 ];
만약 int 형식 변수 100개를 배열이름 array 아래에 선언하고 싶으면, 다음과 같이 쓰면 된다.
int[] array = new int[100];
다음과 같이 하면100개의 int 변수의 용량을 메모리에 잡아 놓을 수 있다. 이제 선언한 데이터의 배열에 데이터를 저장해야 할 것이다. 만약 1, 2, 3, 4, 5를 정수형 배열에 저장하고 싶으면 아예 변수를 선언할 때부터 다음과 같이 선언할 수 있다.
int[] array1 = { 1, 2, 3, 4, 5 };
아니면, new로 선언해서 먼저 메모리를 잡아 놓은 뒤에 그 메모리에 데이터를 집어 넣을 수도 있다. 만약에, 전전에서 선언한 array라는 배열에 첫 번째 원소로 1을 집어넣고 싶다면 다음과 같이 써야 한다.
array[0] = 1;
대괄호 사이에 들어가는 것이 원소의 순서를 의미한다. 여기서는 첫 번째 원소여서 대괄호 사이에 1이 들어가야 할 것이라고 생각할 수 있으나, 1이 아니라 그보다 1 작은 0이 들어가야 한다. 그리고 array의 n번째 원소에 m이라는 정수를 저장하려면 다음과 같이 써야 할 것이다.
array[n-1] = m;
이렇게 대괄호 안에 있는 인덱스(index)는 0부터 시작한다.
사실 이렇게 배열을 여러 개 설정하다 보면 이것을 가지고 표를 만들고 싶어하는 욕구도 생긴다. 행렬처럼, 2차원으로 원소를 나열해서 정리하는 것이다. 다음과 같은 행렬을 matrix라는 2차원 배열에 저장한다고 해 보자.
우선 메모리에 예약을 해 놓는다.
int[,] matrix = new int[2,2];
그리고 다음과 같이 데이터를 집어넣을 수 있다.
matrix[0,0] = 1;
matrix[0,1] = 2;
matrix[1,0] = 3;
matrix[1,1] = 4;
또는 다음과 같이 짧게 쓸 수 있다.
int[,] matrix = { { 1, 2 }, { 3, 4 } };
이보다 다차원의 배열을 설정할 수 있다. 다만, 이렇게 배열을 선언하면 2차원 배열의 어떤 행에는 다섯 개의 요소를, 다른 행에서는 세 개의 요소를 발견하는 일 없이 모두 같은 개수의 요소를 발견할 수밖에 없을 것이다. 하지만, 이렇게 쓰면 그렇게 할 수 있다.
데이터형식[][] 배열이름 = new 데이터형식[가변배열의 용량][];
int 형식의 배열을 설정하고자 한다면 다음과 같이 쓸 수도 있다.
int[][] jagged = new int[3][];
jagged[0] = { 1, 2, 3, 4, 5 };
jagged[1] = { 1, 2, 3 };
jagged[2] = { 1, 2 };
이와 같이 쓸 수 있는 배열을 가변 배열이라고 한다.

3. 리스트
이번엔 행의 개수를 마음대로 조절할 수 있는 것이 등장하는데, 그것을 리스트라고 한다. 리스트에는 원소를 더하는 함수를 쓸 수도 있고, 원소를 제거하는 함수를 쓸 수도 있다. 다음은 정수인 원소를 리스트에 더하고 제거하는 코드를 보인 것이다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            //1
            List<int> list = new List<int>();
            list.Add(0);
            list.Add(1);
            list.Add(2);

            //2
            list.Remove(2);

            //3
            list.Add(2);
            list.RemoveAt(1);

            //4
            list.Insert(1, 2);
        }
    }
}

//1 아래의 파트를 살펴보자. 우선 이름이 list인 정수형 원소를 저장하는 리스트가 선언되었다. 위와 같이 List<데이터형식>을 써서 리스트를 선언한다. 그리고, 리스트 산하에 있는 리스트이름.Add(원소) 함수를 써서 list에 차례차례 더해간다. 그러면 리스트에 있는 데이터는 다음과 같을 것이다.
0 1 2
//2 아래의 파트에서는 리스트이름.Remove(원소) 함수를 썼다. 이 함수는 괄호 안에 있는 것과 같은 원소를 찾아내 해당 리스트에서 삭제하는 역할을 하는 함수이다. 그러면 결과는 다음과 같을 것이다.
0 1
//3 아래의 파트에서는 먼저 방금 지웠던 원소 2를 다시 더한 뒤, RemoveAt 함수를 쓴다. 여기서는 RemoveAt(1)이라고 쓰여 있는데, 그러면 리스트 중 인덱스가 1인 원소, 정수 1을 리스트에서 삭제하게 된다. 그럼 결과는 다음과 같을 것이다.
0 2
//4 아래의 파트에서는 Insert 함수가 등장한다. 여기서는 Insert(1,2)라고 쓰여 있는데, 그럼 리스트의 인덱스 1에 해당하는 자리에 정수 2를 집어넣고, 그 뒤의 원소들은 한 자리 씩 뒤로 밀어버리는 작업을 함수가 하게 된다. 이것이 실행된 후 리스트는 다음과 같을 것이다.
0 2 2