파스칼 프로그래밍/기본서

위키책, 위키책
둘러보기로 가기 검색하러 가기

구조[+/-]

구조도[+/-]

간단한 구조.

program 프로그램이름; // 이 소스코드가 프로그램이라는것을 의미한다.
label                 // Goto문의 지표가 된다.
  지표1, 지표2;

uses                  // 외부유닛의 참조목록이다.
  유닛1, 유닛2;

type                  // 자료형 선언부이다.
  형명칭: 자료형; 

var                   // 변수 선언부이다.             
  변수명: 자료형;

const                 // 상수 선언부이다.
  상수명=상수값;

  procedure 프로시저이름; // 프로시저를 만들었다.
  begin                   // 시작
    명령;
  end;                    // 끝 - 프로시저

begin                 // 시작
  프로시저이름;
  명령;                     // end의 앞에는 원칙적으로는 ;이 오면 안된다. 이 문제는 나중에 다룬다.
end.                  // 끝 - 프로그램

예제[+/-]

일반적인 HelloWorld이다.

program Helloworld;
begin
  WriteLn('HelloWorld');
end.

단순히 문자열 HelloWorld를 출력하고 끝난다. (begin과 end 사이에 WriteLn만 있는걸 발견할 수 있다.)

설명[+/-]

  • Program
사용: Program(input, output) [프로그램이름]
프로그램이 이곳에서 시작한다는 것을 해당 파일이 프로그램이라는 것을 의미한다. Input은 입력파일을, Output은 출력파일을 지정한다. 키보드와 모니터로 기본설정이 되어 있다. Program 뒤의 이름은 프로그램의 이름을 지정하는 기능을 했다. 보통 파일 이름으로 자동설정되므로 신경쓸 필요가 없다.
  • begin
  1. 프로그램, 유닛, 서브루틴의 시작을 알린다.
  2. 블록의 시작이라는 뜻이다. 이때 beginend로 묶인 내부는, 수학연산에서 내용이 마치 괄호로 묶인것과 같은 효과를 냅니다. 2 * 2 + 2 에서 2 + 2 를 먼저 계산하기 위해 2 * ( 2 + 2 ) 처럼 ( ) 를 도입하는 기법과 같다고 생각하면 된다.
    • 물론 프로그램, 유닛, 서브루틴의 내용이 한 블럭을 포함한다고 생각할 수도 있다. 단 이 경우 블럭을 생략할 수는 없습ㄴ다.
  • end
  1. 프로그램, 유닛, 서브루틴의 끝을 알린다.
  2. 블록의 끝이라는 뜻이다. begin 참조.
  • ;
  1. 문장의 끝을 알린다.
  2. 파스칼 소스코드에서 두칸 이상의 공백으로 보이는 문자들(공백, 아스키 9, 아스키 10)은 아무런 의미도 없다.
  3. ;을 써야만 그 문장이 끝나게 된다.
    • 처음 파스칼을 시작하는 사람이 쉽게 저지르는 실수가 ;를 빼먹는 것이다.
  4. 모든 문장은 ;으로 끝나게 된다. 이때 end는 블록 단위이기 때문에 문장이 아니고, end;와 같은 문장은 성립할 수 없다. 따라서 블럭의 마지막 문장에 ;을 쓰지 않는 것은 end까지가 그 문장을 나타내기 때문이다.
  • 주석
  1. 내용에 영향을 끼치지는 않지만 사람이 메모해둘 내용이 있을 때 사용한다.
  2. { } (* *) : 괄호 가운데의 내용은 컴파일할 때 없는 것으로 간주한다. 각각 시작한것과 쌍을 이루어 끝난다. 단 { }는 컴파일러 지시자와 충돌을 일으킬 수 있으므로 경우에따라 잘 사용해야 한다.
  3. // : 오른쪽의 내용은 컴파일할 때 없는 것으로 간주한다. 위의 구조란에서 썼다.

구성[+/-]

  • 구성 항의 내용물은 본격적으로 파스칼에 대해 서술하기 위한 준비작업이다.
  • 구성부분은 건너뛰고 그 다음의 설명을 먼저 읽으며 필요한 부분을 구성 부분에서 참조하는 것이 쉽고 편하다.

식별자[+/-]

식별자는 어떤 대상을 구별할 수 있는 이름이다. 사용자가 정의해 쓰는 모든 변수, 서브루틴, 상수, 자료형 등의 이름을 포함하며 영문자, 숫자, 밑줄만 사용해야 한다.

첫문자는 반드시 알파벳으로 시작하고 63자를 초과할 수 없다.

단 유닛 이름 파일 이름이기 때문에 운영체제에 따라 대소문자를 구분할 수도 있다.

예약어는 식별자로 쓸수 없으며 지시어는 권장되는 기법은 아니지만 쓰일 수는 있다.

이에 대한 자세한 내용은 파스칼 프로그래밍/문법#식별자를 참고하라.

한편 파스칼이 기본적으로 정의하고 있는 자료형, 기본함수, 기본프로시저, 기본상수 등의 명칭이 있다.

기본함수와 기본프로시저에 대해서 자세한 내용은 파스칼 프로그래밍/함수에서 확인하라.

연산자[+/-]

대입연산[+/-]

  • := : 대입연산자이다. 왼쪽의 변수에 오른쪽의 값을 대입한다.
var
  a, b: Integer;

begin
  a := 40; // a = 40
  b := a;  // b = 40 : 40이 저장된 a의 값을 b에 대입했으므로 b의 값은 40이다.
end

대수연산[+/-]

대수연산의 연산자는 일반적 수학표현과 마찬가지로, 괄호 우선, 곱셈나눗셈 우선이다.

  • + : 덧셈
var
  intA, intB: Integer;
  strA, strB: String;

begin
  intA := 1;
  intB := intA + 4; // intB = 5
  strA := 'Hello';
  strB := StrA + 'World'; // strB = 'HelloWorld'
end
  • - : 뺄셈. 표현식 앞에 붙어 음수를 표현하기도 한다.
var
  a, b: Integer;

begin
  a := 100;
  b := -a; // b = -100
end
  • * : 곱셈
  • / : 실수 나눗셈
  • div : 정수 나눗셈의 몫
  • mod : 정수 나눗셈의 나머지
var
  int: Integer;
  float: real;

begin
  int := 26 div 7; // int = 3 : 몫을 구했다.
  int := 26 mod 7; // int = 5 : 나머지를 구했다.
  float := 10 / 4; // float = 2.5 : 실수범위에서 나누었다.
  {int := 10 / 2;} // 나누면 5로 떨어지므로 int에 대입할 수 있을듯 하지만 /연산자는 실수값을 반환하므로 대입할 수 없다.
  int := trunc( 26 / 7 ); // int = 3 : 함수를 이용해 소수점 이하를 버렸다. 따라서 몫을 구한 것과 같다.
end

논리연산[+/-]

논리연산의 결과는 항상 Boolean형이다. 논리연산자의 양쪽에는 Boolean형이 놓여야만 한다. Boolean의 값은 True는 양수, False는 0이라고 생각하면 쉽다.

  • not : 부정연산을 하는 단항 연산자이다. 뒤의 값의 역을 반환한다.
  • and : 논리곱이다. 양쪽이 모두 참일때만 True값을 반환한다.
  • or : 논리합이다. 양쪽이 모두 거짓일때만 False값을 반환한다.
  • xor : 배타논리연산이다. 양쪽이 다를때만 True값을 반환한다.

비교연산[+/-]

크기 비교는 서수형 자료형에서만 가능한다. 비교연산의 결과는 항상 Boolean형이다.

var
  is_bigger, is_in: Boolean;

begin
  is_bigger := 3 > 1; // is_bigger = True
  is_bigger := 100 > 100; // is_bigger = False
  is_in := 1 in [1, 3, 5, 10]; // is_in = True
end
  • = : 같다.
  • > : 왼쪽이 더 크다.
  • < : 왼쪽이 더 작다.
  • >= : 왼쪽이 크거나 같다.
  • <= : 왼쪽이 작거나 같다.
  • <> : 다르다.

집합 연산[+/-]

  • = : 같다.
  • > : 오른쪽이 왼쪽의 진부분집합이다.
  • < : 왼쪽이 오른쪽의 진부분집합이다.
  • >= : 오른쪽이 왼쪽의 부분집합니다.
  • <= : 왼쪽이 오른쪽의 부분집합이다.
  • <> : 다르다.
  • in : 왼쪽의 값이 오른쪽 집합에 포함된다. 반드시 오른쪽에는 집합형이 놓여야 한다.
  • - : 차집합을 구한다.
  • + : 합집합을 구한다.
  • * : 교집합을 구한다.

문자열 연산[+/-]

  • + : 문자열을 이어붙일 때 + 를 이용할 수 있다.

비트연산[+/-]

var
  Num: Integer;

begin
  Num := 5; // 5, 00000101
  Num := Num SHL 2; // 20, 00010100, 왼쪽으로 두비트 밀리고 오른쪽이 0으로 채워졌다.
  Num := Num SHR 3; //  2, 00000010, 오른쪽으로 세비트 밀리고 왼쪽이 0으로 채워졌다. 마지막 1은 사라졌다.
end
  • SHL : 왼쪽으로 밀어낸다.
  • SHR : 오른쪽으로 밀어낸다.

예제의 사용을 참고하라.

자료형[+/-]

자료형 정의[+/-]

아래에서는 자료형을 임의로 정의해서 쓰는 경우가 많다.

type
  NewInteger = Integer;

위와 같이 정의하면 NewInteger라는 자료형이 새로 생기게 된다.

단순형[+/-]

서수형[+/-]
  • 정수형
    • 부호있는정수: Integer, 일반적인 정수이다.
    • 부호없는정수: Cardinal, 0 이상의 정수만 표현한다.
var
  Int1, Int2: Integer;
  Card1, Card2: Cardinal;

begin
  Int1 := 10;
  Card1 := 5;
  Int2 := Int1 + Card1; // Integer 형으로 바뀌어 대입된다.
  Card := Int1 + Card2; // Cardinal 형으로 바뀌어 대입된다.

  Int1 := -3;
  Card1 := -10; // Cardinal은 음수를 가질 수 없으므로 컴파일 오류 발생.
  Card1 := 3;
  Int2 := Int1 + Card1; // 0 저장
  Card2 := Int1 + Card1; // 어쨌든 결과값이 0이므로 0이 저장된다.

  Int1 := -4;
  Card2 := Int1 + Card1; // 범위를 벗어나서 실행 오류 발생.
end
// 범위 불일치는 오류가 일어날 여지가 있으므로 이 예제의 모든 문장에서 경고가 발생한다.
  • 논리형 : Boolean, 참/거짓 자료형이다. 참과 거짓은 True와 False라는 상수로 정의되어 있다. 1바이트이다. True나 False값만 가질 수 있다.
  • 문자형 : Char, 문자 한개를 가지는 1바이트 자료형이다.
  • 열거형 : 어떤 모임에 순서를 부여하기 위해 쓴다.
type
  Wiki = (wikipedia, wikibooks, wiktionary, wiknews);

var
  Work: Wiki;

begin
  Work := wikibooks;
  if Work in [wikipedia, wikibooks, wiktionary] then // 열거형은 주로 집합형(Set)과 함께 쓰인다.
    writeLn('이 작업은 한국어 위키미디어 프로젝트가 존재한다.');
end
  • 범위형 : 범위형은 독립적인 자료형이라기 보다는 다른 자료형의 범위를 필요한 만큼으로 제한하는 것이다. 제한형이라고도 부른다.
type
  Alphabet = 'A'..'z';
  NumberKey = 0..9;

var
  Chr: Alphaber; // 영문자의 대소문자만 가질 수 있다.
  IntKey: NumberKey; // 0부터 9까지의 한자리 숫자만 가질 수 있다.

begin
  IntKey := 5; // 일반적
  IntKey := 10; // 오류, 범위초과
end
실수형[+/-]

Real로 선언한다.

실수 값을 가지며 유효자리와 자릿수 정보를 저장한다. 서수형과 호환성이 없으며, 정수형과 섞어 쓰면 자동으로 실수형으로 바뀌어 처리된다.

자료형끼리 연산을 하면 연산 결과를 포함할 수 있는 자료형으로 자동으로 바뀌게 된다.

var
  Int1, Int2: Integer;
  Float: Real;

begin
  Int1 := 5;

  Float := Int1; // 정수에서 실수로 대입은 가능하다.
  Int1 := Float; // 오류 발생! 같은경우처럼 보일지 모르나 실수형 자료는 정수에 대입할 수 없다. Float이 가진 값은 15000이지만 실수 형태로 저장되어 있다.
  Int2 := Int1 + Float // 위와 같은 이유로 안된다.

  Int2 := Int1 / Int1; // 오류 발생! / 연산자는 무조건 실수값을 반환하므로 오류가 발생한다.
  Int2 := Int1 div Float; //오류 발생! div 연산자는 정수의 연산만 가능하므로 오류가 발생한다.

  Float := Int1 / Int1; // 바른 연산이다.
end

문자열형[+/-]

문자열형은 ' 따옴표 두개로 둘러싸 나타낸다.

그리고 문자의 배열형으로 생각할 수 있다.

var
  str: String;
  chr: Char;
 
begin
  str := 'Hello World';
  chr := str[1]; //chr = 'H' 
end

문자열과 문자 간에도 자동 변환이 일어난다.

var
  Chr1, Chr2: Char;
  Str: String;

begin
  Str := Chr1 + Chr2; // 문자를 합치면 무조건 문자열이 된다.
 
  Str := '';
  Chr1 := 'a';
  Chr2 := Str + Chr1; // 오류가 발생한다. 문자열과 문자의 합은 문자열이다. 실제 값(''+'a'='a')과는 관계없다.
  //문자열에서 +는 concat 함수와 같은 역할일 뿐이기 때문에 위와 같은 현상이 일어난다.
end

구조형[+/-]

배열형[+/-]
type
  이름 = Array[시작값..끝값] of 자료형;

크기가 정해진 자료형만 배열형으로 만들 수 있다. 따라서 문자열이나 파일, 집합 등은 만들수 없다.

type
  IntArray = Array[1..10] of Integer;

var
  intMass: IntArray; // intMass[1], intMass[2], ... intMass[10] 까지의 변수를 모두 선언한 것과 같다.
  count: Integer;

begin
   For count := 1 to 10 do // 반복문을 이용해 배열 전체에 접근한다.
     IntMass[count] := 0;
end

다차원 배열은 배열의 배열과 같다.

배열의 크기를 미리 지정해 두지 않고 필요한 만큼 정의해 쓰는 동적 배열을 만들 수도 있다.

다차원배열과 동적 배열에 관한 자세한 것은 파스칼 프로그래밍/문법#배열형을 참고하라.

구조체[+/-]
  • Record : 여러개의 자료형을 묶는 틀과 같은 자료형이다.
type
이름 = Record
  필드1 : 자료형;
  필드2 : 자료형;
  필드3 : 자료형;
end;
type
  Subject = Record
    Pascal, PHP, Assembly : Integer;
  end;

  Student = Record  
    Score: Subject;
    Name: Array[0..8] of Char;
    Grade: Char;
  end;

var
  Student1, Student2: Student;

begin
  Student1.Name := 'student';
  with Student1.Score do
    begin
      readln(Pascal, PHP, Assembly);
      if Pascal + PHP + Assembly > 200 then
        Student1.Grade := 'A'
      else
        Student1.Grade := 'F';
    end;

  Student2.Score := Student1.Score; // Student2의 Score를 한꺼번에 대입했다.
end
집합형[+/-]

Set 으로 다루는 집합형이다. 서수형 자료형을 원소로 가질 수 있다. 수학의 집합을 생각하면 쉽다.

type
  이름 = Set of 자료형; //단 자료형의 선택 가능한 수에 제약이 있다.

집합형은 연산의 독특한 성질을 이해해야 쓰기 쉽다.

type
  wiki = (wikipedia, wikibooks, wiktionary, wikinews);
  wikis = Set of wiki;

var
  wikis1, wikis2: wikis;
  Check: Boolean;

begin
  wikis1 := [wikibooks, wikipedia, wiktionary];
  wikis2 := [wikipedia..wikibooks];          //서수형을 다루므로 구간 내의 원소를 한번에 넣을 수 있다.
  Check := wikis1=wikis2;                    // Check = True, 순서에 관계없이 내부 값만 확인한다.
  wikis1 := wikis1 + [wiktionary, wikinews]; // wikis1 = [wikipedia..wikinews] 이다. 합집합 연산.
  wikis2 := wikis1 - wikis2;                 // wikis2 = [wikinews] 이다. 차집합 연산.

  Check := wikis2 in wikis1                  // 오류! in 연산자는 원소를 위한 것이다.
  Check := wikibooks in wikis1               // Check = True
  Check := wikibooks in wikis2               // Check = False
  Check := wikis2 <= wikis1;                 // Check = True, 부분집합을 크기 비교 연산으로 구할 수 있다.
  wikis2 := wikis1*wikis2;                   // wikis2=[wikinews], 교집합을 구하는 연산이다.
end

포인터[+/-]

파일[+/-]

  • File : File of (자료형) 꼴로 선언해 쓴다. File형을 다루는 고유의 함수가 많다.
    • Textfile : 텍스트 파일을 제어할 때는 File of Char를 쓰지 않고 Textfile자료형을 쓰는 경우가 많다. Textfile 자료형에서는 WriteLn, ReadLn을 쓸 수 있으며 표준입출력장치를 쓰는것과 같은 효과를 낸다. 자료형을 쓰는 경우가 많다. Textfile 자료형에서는 WriteLn, ReadLn을 쓸 수 있으며 표준입출력장치를 쓰는것과 같은 효과를 낸다.

문법[+/-]

조건문[+/-]

if[+/-]

if문은 Boolean형 자료의 참거짓 값에 따라 프로그램 진행을 분기하고자 할때 쓴다.

if Bool then
  명령
else
  명령;

Bool이 참값이라면 then 뒤의 명령을, 거짓이라면 else뒤의 명령을 실행할 것이다.

일반적으로는 한 명령 뒤에는 반드시 ; 을 써야 하지만 if문의 then 뒤의 명령에는 ; 를 쓰지 않는다. 왜냐하면 if문 전체가 하나의 문장이기 때문에, if문에는 if문의 종결을 알리는 ; 만 필요하기 때문이다. 따라서 if문에서 ; 은 if문의 맨 끝에 적는다. 물론 else가 존재하지 않는 if문의 경우 then 뒤의 명령 뒤에도 ; 이 붙는다.

여러 명령을 실행하고 싶으시다면, 위에서 설명한 beginend를 이용한다.

if Bool then
  begin
    명령1;
    명령2;
    명령3;
  end
else
  begin
    명령1;
    명령2;
    명령3;
  end;

역시 thenbegin ~ end 뒤에는 ; 을 쓰지 않았다.

case[+/-]

case 서수형 of
  경우1 :
    명령1;
  경우2 :
    명령2;
  경우3 :
    명령3;
  else
    명령;
end;

case문은 서수형 자료형의 값에 따라 여러 분기문을 만들때 쓴다. 서수형의 자료값이 각 경우에 해당되면 그 경우의 명령을 실행한다. 만일 조건 중 아무것도 충족하지 못한다면 else의 문장을 실행할 것이다.

반복문[+/-]

for[+/-]

var
  Index: Integer;

begin
  for Index := 1 to 100 do
    WriteLn(Index);
end

위의 for문은 Index에 1부터 100까지 순서대로 대입하고 do 의 다음 문장을 실행하는 것이다. 결과로는 1부터 100까지의 수가 매 줄마다 출력될 것이다.

var
  Index: Integer;

begin
  for Index := 100 downto 1 do
    WriteLn(Index);
end

첫 예제의 거꾸로이다. 100부터 1까지 순서대로 대입했다.

일부 언어와 달리 파스칼에는 1 이외의 차로 for문을 설정할 수는 없다. 하지만 만일 for문을 이용해 2의 차를 얻고싶다면 간단히 실행줄에서

Index*2

를 쓰는것으로 충분한다.

while[+/-]

while문은 조건이 맞으면 반복하는 반복문이다.

while bool do
  명령;

bool이 True라면 명령을 실행하고 다시 bool을 검사한다. bool이 False라면 while문 밖으로 빠져나간다.

var
  a: Integer;

begin
  a := 1;
  while a < 100 do
    a := a + 1;
end // a = 100

이 예제에서는 a가 100보다 작은지 검사하고, 작다면 a의 숫자를 1씩 늘리고 있다. 반복할수록 a가 점점 커지기 때문에, 조건을 만족하는순간 while문을 빠져나오게 된다.

repeat ~ until[+/-]

repeat ~ until문은 조건이 맞을때까지 반복하는 반복문이다.

repeat
  명령1;
  명령2;
until bool;

repeat문은 일단 명령1, 명령2를 실행하게 된다. 그리고 until의 bool을 검사해 True면 순환을 빠져나가고, False면 repeat부터 새로 반복하게 된다. while과는 다음과 같은 차이가 있다.

  1. while문은 조건을 비교하고 반복하지만, repeat~until문은 실행하고 조건을 비교한다.
  2. while문은 조건이 맞으면 반복하지만, repeat~until문은 조건이 맞으면 빠져나온다.
  3. while문은 한문장을 실행하지만, repeat~until문은 여러문장을 실행한다.

하지만 세번째 이유 때문에 while 대신 repeat~until을 쓸 필요는 없다. beginend로 내용을 묶어주면 된다.

반복문의 프로시저[+/-]

var
  Index : Integer;
begin 
  for count := 1 to 20 do
    begin
      if Index mod 2 = 0 then
        WriteLn(count)
      else
        Continue;
      if Index = 9 then
        Break;
      if Index = 12 then
        Break;
    end;
end
  • Continue는 반복문의 맨 끝으로 이동하게 한다. 반복문의 끝에 도달했으므로 다시 처음값으로 돌아가 반복문을 계속 실행하게 된다.
  • Break는 반복문을 빠져나가게 한다.

위의 실행 결과는 아래와 같다.

2
4
6
8
10
12

Index mod 2 가 0일때만 WriteLn을 썼으므로 짝수만 출력된다는것은 쉽게 파악될 것이다. Index = 9 일 때 Break를 실행하도록 했지만, Index = 9 일때는 이미 앞의 조건문 때문에 continue가 되어 뒤의 문장이 실행되지 않았다. 따라서 Index에는 무사히 10이 대입되었다. Index = 12 일 때는 break가 실행되므로 for문은 아직 끝나지 않았지만 for문을 빠져나오게 된다. while과 repeat문에서도 break와 continue의 용법은 마찬가지이다.

With[+/-]

유닛, 구조체, 클래스 등 하부구조를 가지는 자료를 다룰 때 불필요한 반복을 막기 위해 사용한다.

type
  RecSample = Record
    Chr1, Chr2: Char;
    Int1, Int2: Integer;
  end;

  TSample = class
    Chr1, Chr2: Char;
    Int1, Int2: Integer;
  end;

var
  varRecord: RecSample;
  varClass: TSample;

begin
  varRecord.Chr1 := 'a';
  varRecord.Chr2 := 'b';

  with varRecord do begin
    Int1 := 1;
    Int2 := 2;
  end;

  varClass := TSample.Create; // 이 부분은 클래스에서 설명한다.
  varClass.Chr1 := 'c';
  varClass.Int1 := 3;

  with varClass do begin
    Chr2 := varRecord.Chr2;
    Int2 := varRecord.Int2;
  end;

  varClass.Free; // 이 부분은 클래스에서 설명한다.
end

결과는 다음과 같다.

varRecord
  Chr1 = 'a'
  Chr2 = 'b'
  Int1 = 1
  Int2 = 2
varClass
  Chr1 = 'c'
  Chr2 = 'b'
  Int1 = 3
  Int2 = 2

With문이 어떤 역할을 하는지 예제를 참고하라.


부프로그램[+/-]

부프로그램은 서브루틴이라고도 한다. 다른 언어에서는 함수(function)로 통합해서 다루는 경우가 있으며, 실제로 개념도 다르지 않다. 인자가 없을때는 호출할때 인자 넣는 공간의 기호인 ( ) 를 생략해도 된다. WriteLn 프로시저가 인자가 있을때와 없을 때 어떻게 쓰였는지 생각해보시면 쉬울 것이다.

함수[+/-]

함수는 함수명에 지정된 부프로그램을 실행한 후 자신의 이름에 연산의 결과를 반환한다.

function 이름(인자: 인자자료형): 자료형;
begin
  연산;
  연산;
  Result := 함수값;
end;
Program Callfunction;
  function PlusMinus(a, b: Integer; Operator: Char): Integer;
    function Infunction: Integer;
    begin
      // 서브루틴 안에도 서브루틴을 만들 수 있다.
      Result := 함수값;
    end // of Infunction
  begin
    Result := 0; // 기본값을 0으로 지정한다.
    case Operator of
      '+' :
        Result := a + b;
      '-' : 
        Result := a - b;
    end; // of case 연산자가 '+'나 '-'가 아니면 기본값으로 남는다.
  end; // of PlusMinus
begin
  PlusMinus(1, 2, '+'); // 함수는 실행되었고 실제로 하드웨어에서 연산도 일어났다. 하지만 결과를 이용하지 않아 아무런 결과도 나타나지 않다.
  WriteLn(PlusMinus(1, 4, '+'));
end.

위와 같이 함수는 변수처럼 자료형만 맞추어 쓰면 어디든지 쓸 수 있다.

프로시저[+/-]

프로시저는 자료형이 지정되지 않고, 반환값을 지정하지 않는 함수와 같다. 단 반환하는 자료형이 없는 함수를 만들 수는 없다.

procedure 이름(인자: 인자자료형);
begin
  연산;
  연산;
end

프로시저는 함수사용법의 첫번째처럼 그냥 써주면 된다.

범위규칙[+/-]

변수의 지역성을 규정하는 법칙이 있다.

Program 이름;
var
  A: 자료형; // 전역변수를 선언한다.
  procedure 이름;
  var
    A, B: 자료형; // 지역변수를 선언한다.
  begin
    명령; // A를 참조할 경우 프로시저 내의 A를 가리키게 된다. 이름이 중복될 경우 좁은지역의 변수가 우선한다.
  end;
begin
  명령; // B를 참조할 수 없다. B는 프로시저 내에서만 통용된다.
end.

참조방법[+/-]

함수에 인자를 넘겨줄 때, 넘겨주는 방식에 따라 약간씩 특징이 바뀌게 된다.

값 참조[+/-]

값 참조 방식은 일반적 방식이다. 아무련 표식도 하지 않았을 때 함수는 인자를 자동으로 값참조 방식으로 넘긴다. 서브루틴 내에 인자 이름에 해당하는 지역변수가 있는것과 같다.

  • 인자 이름에 값을 대입할 수 있다.
  • 인자에 대입한 값은 함수를 빠져나가는 순간 사라진다.
주소 참조[+/-]

주소 참조 방식은 변수의 인자의 포인터를 인자이름의 포인터에 대입시키는 것과 같다.

function 이름(var 인자: 자료형): 자료형;
begin
  Result := 반환값;
end;

아래와 같은 차이가 있다.

  • 인자 이름에 값을 대입할 수 있다.
  • 인자에 대입한 값은 실제의 인자로 넘겨진다.
  • 서브루틴 실행 결과가 인자를 변형시켜야 할 때 쓰인다.
  • 포인터를 대입시킨것과 같다는 말이 잘 이해가 되지 않는다면 자료형에서 포인터의 설명을 읽어보라.
상수 참조[+/-]

상수 참조 방식은 변수의 값만 인자로 넘겨주는 것과 같다.

function 이름(const 인자: 자료형): 자료형;
begin
  Result := 반환값;
end;

인자 이름은 하나의 상수처럼 취급된다.

  • 인자에 어떤 값을 대입할 수 없다.
  • 서브루틴이 인자의 값만 필요할 때 쓸 수 있다.
  • 실행속도가 가장 빠릅니다.

순환호출[+/-]

간혹 재귀호출이 필요할때와 마찬가지로, 두개 이상의 부프로그램을 순환호출해야 할 때도 있다.

procedure Proc1;
begin
  Proc2; // 정의되지 않은 식별자 오류
end;

procedure Proc2;
begin
  Proc1;
end;

위와 같이 만들게 되면 Proc1에서는 Proc2를 찾을 수 없게 되어 컴파일 할 수 없다.

반대로 만들면 반대의 상황이 발생하므로 역시 불가능한다.

procedure Proc2; forward;

procedure Proc1;
begin
  Proc2;
end;

procedure Proc2;
begin
  Proc1;
end;

위와 같이 만들면 Proc1에서도 Proc2를 참조할 수 있고, 실제로 Proc2를 호출했을때는 위의 Proc2를 부르게 되지만, 위의 Proc2는 실 선언부인 아래로 이동하게 되므로 문제가 발생하지 않다.

단 위의 예제는 무한순환구조에 빠지게 되므로 그대로 사용하면 안된다.

미리 선언할때는 forward를 붙여둔다.

제어[+/-]

  • exit : 서브루틴에서 빠져나갈 때 쓴다. exit를 실행하면 한번에 서브루틴의 end로 이동하게 된다.

컴파일러 지시자[+/-]

유닛[+/-]

파스칼의 유닛은 일부 언어와 달리 소스코드를 보호할 수 있다. 유닛을 컴파일하면 interface 부분은 접근 가능한 텍스트로 남고, implementation 부분은 컴파일된다. 컴파일러는 버전마다 독특한 컴파일된 유닛을 생성하기 때문에 유닛을 컴파일하는 컴파일러와 유닛을 참조하는 컴파일러는 같은 버전이어야 한다.

  • interface : 유닛의 외부에서 접근할 수 있는 파일의 머리부분이다.
  • implementation : interface에서 선언한 유닛의 내용을 서술한다.
unit Unitname;

interface

var
  int1: Integer; // 이 유닛을 참조하면 interface에 선언한 내용은 접근할 수 있다.

procedure Proc1; // interface에서는 이름만 선언해 준다.

implementation

var
  int2: Integer; // implementation에 선언한 내용은 유닛 내에서만 접근할 수 있다. 이 기능으로 소스코드를 보호할 수 있다.
  procedure Proc1; // interface에서 선언한 부분을 implementation에서 구현한다.
  begin
    
  end;
end.

선언부에서 이름만 선언한 것은 앞에서 다루었듯 뒤의 내용에 대해 미리 선언하는 것이다. 유닛은 컴파일하면 구현부에 접근할 수 없기 때문에 구현부와 연결되는 선언부를 만들어 두는 것이다.

흑체 원리[+/-]

흑체 원리는 유닛과 클래스에 공통으로 적용되는 원리이다. 강제하는 원리는 아니지만 외부에서 접근을 보다 명확하게 유지하고 내부 자료를 보호하기 위해 권장하는 기법이다.

  • interface에는 상수는 선언하지만 변수를 선언하는 않는다. 변수에 접근할때는 필요한 기능에 적합한 이름을 붙인 프로시저나 함수로 접근한다. (클래스에서 property를 관리하는 기법이 사용자 입장에서 가장 단순하게 구현하는 방법이다.)

클래스[+/-]

클래스는 type에 선언한다.

type
  TClassName = class
    int1: Integer; // 일반적 변수 선언
  private
    int2: Integer;
  protected
    int3: Integer;
  public
    int4: Integer;
    constructor create;
    destructor destroy;
  end;
type
  TClassName = class
  private
    procedure Proc1; virtual; // private이므로 virtual로 선언할 필요가 없다.
  protected
    procedure Proc2; virtual;
  public
    procedure Proc3; virtual;
  end;

  TClassName2 = class(TClassName)
  public
    procedure Proc1: override; // private로 선언한것은 override할 수 없다.
    procedure Proc2: override;
    procedure Proc3: override;
  end;

var
  ClassExam: TClassName;

procedure Proc4;
begin
  ClassExam := TClassName.Create;
  Proc1; // Private로 선언한 것을 호출할 수 없다.
  Proc2; // Protected로 선언한 것을 호출할 수 없다.
  Proc3; // Public으로 선언했으므로 호출할 수 있다.
  ClassExam.Destroy; // 일반적으로 TObject를 제거할때는 Free를 호출한다.
end;

클래스는 항상 Constructor로 만들고 Destructor로 제거한다. Destructor로 제거해주지 않으면 클래스의 찌꺼기가 램에 남아 메모리 누수를 일으킵니다.

위키백과 한국어 위키백과에 수록된
메모리 누수 문서 참고.

첫번째 경우에서

var
  ClassExam: TclassName;

을 선언했다면,

ClassExam.Create;

라고 쓸 수는 없다. ClassExam이 생성자인 Create를 갖고 있기는 하지만, 변수로 선언한 ClassExam에는 단순히 TclassName 속성의 포인터라는 표시를 해 둔 것이기 때문이다. 변수를 선언한 시점에서 ClassExam은 실체가 없는 빈 포인터일 뿐이다. 따라서 저 포인터에 어떤 TClassName의 값을 대응시켜주어야 사용할 수 있고, 그것이 TClassName.Create로 새 클래스를 생성해 대입해 주는 과정이다.

가시성[+/-]

상속성[+/-]

  • 클래스는 서로 상속할 수 있다.
type
  TClassParent = Class
  public
    int1: Integer;
    procedure Proc1; virtual;
  end;

  TClassName = Class(TClassParent)
  public
    procedure Proc2: override;
  end;

procedure TclassParent.Proc1;
begin
  
end;

procedure TclassName.Proc1;
begin
  inherited; // inherited 자리에서 이 클래스가 상속한 TClassParent.Proc1이 실행된다.
  
end;

클래스를 선언할 때 ( ) 안에 클래스의 이름을 적으면 그 클래스의 모든 속성을 상속하게 된다. 위의 예에서 TClassName에는 int1을 선언하지 않았지만 int1을 쓸 수 있다.

같은 이름의 서브루틴을 내용을 상속하며 새로 쓸 때는 override를 이용한다. TClassName에서 TclassParent의 Proc1을 상속했다. inherited로 상속한 함수를 원하는 시점에 실행해주면 이를 이용해 추가처리를 할 수 있다.