Skip to content

Instantly share code, notes, and snippets.

@Gumball12
Last active April 25, 2019 01:18
Show Gist options
  • Save Gumball12/07b1b5ece1a41fb36d311fbe8c3921a9 to your computer and use it in GitHub Desktop.
Save Gumball12/07b1b5ece1a41fb36d311fbe8c3921a9 to your computer and use it in GitHub Desktop.
시험용

Introduce

참고로 c#은 console과 winform 둘 다 개발이 가능.

Introduction to cs

전체적으로 한 번 훑어보자.

operators

일반적으로 C와 비슷함. 다만 다음이 있음.

  • type testing operator
    • is: 호환 가능한가? (inheritance)
    • as: 해당 type으로 변환
      • obj as string 뭐 이렇게

Array

선언 및 초기화는 각각 다음과 같음

  • int [] arr1;
    • 1차원 배열 선언
    • int[] arr1 = new int[10]; arr[0] = 1; 이렇게 하거나
    • int[] arr1 = {0, 1, 2, 3}; 뭐 이렇게 할 수가 있다.
  • short[,] arr2;
    • 2차원 배열 선언. 사용은 다음과 같음.
      • short[,] arr2 = new short[2, 10];
    • int[,,] arrarr; 이따위로도 가능
  • int[][] arr3;
    • 가변 배열. 사용은 다음과 같음.
      • int[][] arr3 = new int [3][];
      • arr3[0] = new int[5];
    • 2차원 배열과는 다른것이... 각각이 서로 다른 길이를 가질 수 있다는 것.

String

cs에서 string은 Object이다. 대부분 마찬가지기는 하지만...

참고로 stringString 객체에 대한 alias이다. 리터럴은 ""

class

  • c#은 특이하게 indexer, override operator가 가능하다.
  • Destructor도 있음. 진행하며 보겠지만 ~<class name> 으로 정의 가능

property

getter/setter 정의하는 애

class A {
  private int data = 0;

  public int Data { // 당연히 반드시 public일 필요는 없음. 그냥 얘도 멤버임
    // 반드시 둘 다 정의할 필요는 없다.
    get { return data; }
    set { data = value; } // value: setter value
  }
}

/* ... */

A a = new A();

Console.WirteLine(a.Data); // 0

a.Data = 10;
Console.WriteLine(a.Data); // 10

operator overloading

연산자 재정의. 말 그대로 연산자의 기능을 재정의.

class A {
  private int data = 0;
  public int Data {
    get { return data; }
    set { data = value; }
  }

  // public static 이어야 함
  public static A operator + (A a1, A a2) {
    A a = new A();
    a.Data = a1.Data + a2.Data;
    return a;
  }
}

/* ... */

A a1 = new A();
A a2 = new A();

a1.Data = 10;
a2.Data = 20;

a3 = a1 + a2;

Console.WriteLine(a3.Data); // 30

구조를 잘 알아두자. public static <class name> operator <operator> 이거임.

웬만한 애들은 다 overload 가능한데, 반환은 연산자 의미에 걸맞게 해줘야함. 가령, == 이걸 overload 한다고 했을 때 반환이 bool 이어야겠지.

Delegate

function pointer. 그냥 callback이라고 생각하는게 편함. 실제로도 그렇고.

delegate string Dele (int a);

public class A {
  public string print (int a) {
    return $"value is {a}";
  }
}

/* ... */

A a = new A();

Dele d = new Dele(a.print); // print 메서드를 Dele 델리게이트 사용해 d로 가리키도록 함

Console.WriteLine(a.print(1)); // value is 1
Console.WriteLine(d(1)) // value is 1

보이다싶이 다음의 조건이 맞아야 한다.

  • return type, arguments의 length와 type이 모두 동일해야 한다.
    • 이것만 동일하다면 가리키는 함수가 static이든 instance이든 상관없음.

어렵게 생각하지는 말자. 참고로 lambda와 함께 Dele d = new Dele(a => $"value is {a}") 이렇게도 되지 않을까? 해보지는 않아서 잘 모르겠다.

Thread

System.Threading 을 using 해야만 한다.

ThreadStart 델리게이트에 메서드 연결한 뒤, 이 ThreadStart 델리게이트 객체를 Thread 클래스 생성자로 보내주면 되는거.

using System;
using System.Threading;

class T {
  static void Body () {
    Console.WriteLine("in threading...");
  }

  public static void Main () {
    ThreadStart ts = new ThreadStart(Body);
    Thread t = new Thread(ts);

    /* ... */
  }
}

Generic

다음과 같이 정의 가능

class Stack<T> {
  public void Method<E> () { /* ... */ }

  /* ... */
}

이따위로 메서드에도 제네릭 사용이 가능하다는 것을 알아두자.

Compile

중간 코드가 있다. 확장자가 *.il 이거임.


언어 구조

plt에서도 봤던 것들

items

Token

다음으로 구성되어있다.

  • keyword(지정어): abstract, asn, ...
    • @ 붙이면 지정어(변수명)로 사용이 가능하다.
    • @int 이렇게
  • operator(연산자): +, -, ...
  • delimiter(구분자): ., :, ...
  • identifier(명칭): sum, ptr, ...
    • 변수이름 이런거 말하는거
    • 반드시 문자로 시작해야하고, 특수문자는 _만 사용가능
      • 맨앞에는 @ 사용가능한데, 지정어 덮어씌울때만임. 그외에는 불가능
  • literal(리터럴)

여기서 keyword는 개많이있으며 여기서 하나 나올 것 같긴 한데 아..

i

Unicode

참고로 놀랍게도 다음이 정상실행된다.

using System;
					
public class Program
{
	public static void Main()
	{
		double π = 3.1415926535897;
		Console.WriteLine(π);
	}
}

유니코드 쓰기에 되는거. 근데 여기서 pi 기호를 문자취급하나봄.

Literal

  • c#에서는 8진수 지원안하고, 16진수는 앞에 0X를 붙인다.
  • true, false가 1, 0을 의미하지 않음
  • 다음과 같이 @ 을 이용해 \ 표현 가능
    • "hello \\t world"; 원래는 이랬는데
    • @"hello \t world"; 이렇게 사용할 수 있다.
    • 둘 다 hello      world 를 표현함

comments

/// 이거 세 개 쓴거는 docs 작성용이라고 한다. XML 사용한다고 함.

Data types

뭐 개많다.

  • value type

    • number
    • string
    • boolean
    • enum
    • structure
  • ref type

    • calss
    • interface
    • delegate
    • array

웃긴게 CTS type이라고 다음과 같이도 선언이 가능하다는거

System.Int32 x;
int x;

Convert 하면서 Int32 뭐 이런이름들 종종 봤을텐데, 이게 이거다.

i

자료형의 크기도 나올까? 그러지는 않을 것 같긴 한데...

Pointer

c#의 pointer 연산은 반드시 다음과 같은 unsafe 블록 안에서 정의되어 있어야 하며,

class Program {
  unsafe public static void swap (int *px, int *py) { // unsafe 키워드가 포함된 블록
     int tmp = *px;
     *px = *py;
     *py = tmp;
  }

  public static void Main (string[] args) {
    int x = 1, y = 2;

    Console.WriteLine($"BEFORE >> x: {x}, y: {y}"); // BEFORE >> x: 1, y: 2

    unsafe {
      swap(&x, &y);
    }

    Console.WriteLine($"AFTER >> x: {x}, y: {y}"); // AFTER >> x: 2, y: 1
  }
}

컴파일 시 /unsafe 옵션을 추가해주어야만 한다.

csc /unsafe Program.cs

Operators

  • 보수는 ~x 와 같이 ~ 키워드를 사용
  • XOR는 ^x 와 같이 ^ 키워드를 사용
  • overflow 검사 또는 무시에 따라 연산자를 지정할 수도 있다.
    • overflow 검사 연산자는 checked
    • overflow 무시 연산자는 unchecked

Type conversion

형 변환 시 범위가 작은 타입에서 큰 타입으로는 묵시적으로 변환되나, 범위가 큰 타입에서 작은 타입으로는 명시적으로 해 줘야만 한다. 이 때 cast operator를 사용하겠지.

참고로, bool은 다른 타입으로의 conversion이 불가능하다.

Boxing, Unboxing

  • Boxing
    • value를 ref로 변환하는 것. 묵시적으로 됨
    • int to object
  • Unboxing
    • ref를 value로 변환하는 것. 반드시 명시적으로
    • object to int

unboxing 시 원래의 타입으로만 unboxing이 가능하다. 그 외에는 exception

int foo = 10;

object bar = foo; // boxing (implicit)

int i1 = bar; // ERROR
int i2 = (int) bar; // unboxing (explicit)
double d = (double) bar; // ERROR

c# 문장

대충 지금까지 봤던것과 겹치는게 많다.

문장들

Loop

  • foreach 문이 처음 등장하기는 하는데, 뭐 이름에서 뭐하는애인지는 잘 나오니까...
    • foreach (<type> <name> in <array>) { ... }

Branch

  • goto는 다음이 불가
    • 블록 안으로 비집고 들어갈 수 없다.
    • 메서드 밖으로 벗어날 수 없다.
    • finally 밖으로 벗어날 수 없다.

Check overflow

다음 두 형태가 있음.

checked {
  /* overflow 발생 여부 확인 문장 */
}

checked ( /* overflow 발생 여부 확인 수식 */ )

만약 위의 수식 또는 문장에서 overflow 발생 시, System.OverflowException 예외가 발생된다. 무시하려면 unchecked 키워드 사용해서 똑같이 쓰면 됨.

Standard I/O

  • Input
    • Console.Read(): 문자 하나를 읽어 정수형으로 반환
    • Console.ReadLine(): 하나의 line을 읽어 string으로 반환
  • Output
    • Console.Write(): 출력
    • Console.WriteLine(): 출력 후 line break

Formmated output

printf와 비슷한 것을 사용할 수 있다. 포맷은 다음과 같음

{N[,W][:format]}

  • N: 위치 지정(0부터 시작)
  • W: 폭 지정(- 붙으면 좌측정렬)
  • format: 형식 지정 문자

여기서 형식 지정 문자는 다음과 같다.

지정자 의미
c 통화
d 10진수
e 지수
f 고정 소수점
g 고정 소수점 또는 지수. 이 둘 중에서 더 간략한 형태를 선택함
n 자릿수 구분(,) 들어간 10진수
p 백분율. 여기서 % 기호 들어간다.
r 좀 더 길게 뽑는 애인듯? 원 형태로 출력하는 것이랜다.
x 16진수

여기서 지정자는 대소문자 포함된거다. 구분안함.

public class Program
{
	public static void Main()
	{
		double d = Math.PI;
		Console.WriteLine("1) " + d);
		Console.WriteLine("2) {0}", d);
		Console.WriteLine("3) {0:C}", d);
		Console.WriteLine("4) {0:E}", d);
		Console.WriteLine("5) {0:F}", d);
		Console.WriteLine("6) {0:G}", d);
		Console.WriteLine("7) {0:P}", d);
		Console.WriteLine("8) {0:R}", d);
		Console.WriteLine("9) {0:X}", 255);
	}
}

/* OUTPUT

1) 3.14159265358979
2) 3.14159265358979
3) $3.14
4) 3.141593E+000
5) 3.14
6) 3.14159265358979
7) 314.16 %
8) 3.1415926535897931
9) FF

*/

Class

자료 추상화의 한 방법

Modifier

대부분 본 애들.

  • public
  • internal
    • 같은 네임스페이스 안에서만 사용이 가능
  • static
  • abstract
  • sealed
    • 아래에서 다룸
  • protected
    • 파생 클래스에서만 사용이 가능
    • protected internal 같이 두 개 쓰면 같은 네임스페이스 또는 파생 클래스 둘 다에서 사용이 가능해짐
  • private
    • 수정자 생략 시 적용되는 수정자
  • new
    • 중첩 클래스에서 사용되며, parent(base) 클래스의 멤버를 숨김 (override)
    • 좀 아래에서 다룸
  • readonly
    • 읽기전용
    • runtime 시 값이 결정됨
  • const
    • 읽기전용
    • compile 시 값이 결정됨

Method modifier

메서드의 수정자들이랜다.

  • public, protected, internal, private
    • 뭐 이런 접근 수정자 있고
  • static
  • abstract, extern
    • 둘 다 추상메서드이긴 한데, 차이점은 다음과 같음.
    • abstract: 메서드가 하위 클래스에 정의됨
    • extern: 메서드가 외부에 정의됨
  • new, virtual, override, sealed
    • 뒤에서 봄. 근데 대충 의미는 해석이 가능하고

Call by ref, Call by value

이 둘이 뭘 의미하는지는 알 것이고, 이거에 따라서도 수정자가 있더라.

  • ref: 매개변수 전달 시 반드시 초기화 되어야 함
  • out: 매개변수 전달 시 지멋대로 가능
class Program {
  static void swap (ref int x, ref int y) {
    int tmp = x;
    x = y;
    y = tmp;
  }

  public statc void Main (string[] args) {
    int x = 1, y = 2;

    Console.WriteLine($"x: {x}, y: {y}"); // x: 1, y: 2

    swap(ref x, ref y);

    Console.WriteLine($"x: {x}, y: {y}"); // x: 2, y: 1
  }
}

둘 다 ref 적어줘야 한다. 또는 다음과 같이도 가능

class Program {
  static void swap (Integer x, Integer y) {
    int tmp = x.i;

    x.i = y.i;
    y.i = tmp;
  }

  public static void Main (static[] args) {
    Integer x = new Integer(1);
    Integer y = new Integer(2);

    Console.WriteLine($"x: {x.i}, y: {y.i}"); // x: 1, y: 2

    swap(x, y);

    Console.WriteLine($"x: {x.i}, y: {y.i}"); // x: 2, y: 1
  }
}

뭐 그렇다. 객체쓰니까.

param array

다음과 같이 가변적으로 받을 수 있다.

void func (params int[] args) {
  /* ... */
}

그렇다. 배열이기에 int[] 로 들어가는걸 주의.

Signature

메서드 구분 정보. 이걸 기준으로 override인지 overload인지 구분한다.

  • 이름
  • 매개변수 개수와 타입

반환 타입은 포함되지 않음. 주의하자.

Constructor

static 수정자를 갖는 생성자를 정의할 수도 있다고 함. 클래스의 static fields를 초기화 할 때 사용되며, Main() 보다 먼저 실행됨.

new로 부르는 것이 아니니, 매개변수와 접근 수정자를 가질 수 없다고 한다.

다시말하자면, static fields를 init 하는 방법에는 다음 두 개가 있다는 것.

  • 선언과 동시에 초기화
  • static constructor를 통해 초기화

Destructor

소멸 시 호출됨.

class Program {
  ~Program () { /* ... */ }
}

이렇게 하면 compile 시 Finalize() 메서드가 된다고 한다. 참고로 얘는 override 불가능.

Dispose() 메서드

scope 벗어나면 즉시 호출됨

Indexer

property의 매개변수 받기? 뭐 이렇게 볼 수가 있을까?

class Data {
  private int[] data = new int[100];

  public Data this[int i] {
    get { return data[i]; }
    set { data[i] = value; }
  }
}

/* ... */

Data d = new Data();

d[0] = 10;
Console.WriteLine(d[0]); // 10

뭐 이렇게 사용하는 애다. 참고로 indexer의 modifier는 static사용할 수 없음. this 컨텍스트를 사용하니까 그런듯.

Operator overload

재정의는 위에서 봤을 것이고, type conversion도 overload가 가능하다. 이걸 user-defined type conv. 라고 함.

  • public static explicit operator <type-name> ( ... ) { ... }
    • 명시적
  • public static implicit operator <type-name> ( ... ) { ... }
    • 묵시적

수정자가 개많이붙는다는걸 알아두자. public, static, explicit/implicit, operator. 총 네 개가 붙는다.

Multicast delegation

델리게이션 자체는 위에서 다뤘고, 델리게이션에 여러 메서드를 연결할 수도 있다고 함. 이렇게 연결한 경우 한 번에 연결된 애들을 호출한다고 한다. 약간 함수형? 이런느낌

  • + 연산자로 연결
  • - 연산자로 제거

연결 순서로 호출된다.

물론 연결되는 애든 모두 동일한 return type, argument length/type을 가져야겠지.

Event

delegate를 통해 event를 처리한다고 한다. 성격이 비슷하긴 한데...

// 델리게이트
delegate void Dele (int a);

class Program {
  // 이벤트 델리게이트
  public event Dele evt; // 이벤트의 타입은 델리게이트임

  // 등록할 메서드
  public void check (int n) {
    if (n % 2 == 0) { // 만약 조건에 부합하면
      evt(n); // 파라메터와 함께 이벤트 호출
    }
  }

  // 이벤트 처리기
  static void isEven (int i) {
    Console.WriteLine($"{i}는 짝수임");
  }

  static void Main (string[] args) {
    Program p = new Program();

    p.evt += new Dele(isEven); // add event

    p.check(1) // nothing
    p.check(2) // 2는 짝수임 (emit event)
  }
}

이해되나? 이벤트 추가/제거는 델리게이트와 똑같다.

  • evt += new Dele(method);: 추가
  • evt -= new Dele(method);: 제거

이걸 System.EventHandler 델리게이트 사용해 윈폼 이벤트 핸들링이 가능하다.

Struct

클래스와 동일하게 객체의 구조와 행위를 명시(정의)하는데, 차이점은 다음과 같다.

  • class: ref.
    • heap에 저장됨
    • param같은걸로 넘길 때, ref가 넘어감
  • struct: value
    • stack에 저장됨
    • param같은걸로 넘길 때, value(내용)가 넘어감
    • 상속불가
    • destructor 구현불가
    • member는 초기값 못가짐

다음과 같은 형태를 가지며,

public struct StructName {
  // member
}

수정자는 다음과 같다.

  • public
  • protected
  • internal
  • private
  • new

이 차이점을 알아두자.


Inheritance and Interface

상속은 재사용성 증가시키지. c#은 클래스 하나만 상속할 수 있는 단일 상속 만을 지원한다.

class Parent {
  private int data = 0; // 만약 상속받는 클래스에서 'data' 필드가 없다면 얘가 상속됨

  public Parent (int a) { /* ... */ }

  public void print () { /* ... */ }
}

class Child: Parent {
  private int data = 0; // 상속받는 클래스에서 부모 클래스의 필드인 'data'가 다시 정의되었으니, 부모의 'data'는 숨겨짐

  public Child (int a, int b): base (a) { // 부모 클래스의 생성자로 변수 a를 넘김
    /* ... */
  }

  public void print () {
    base.print(); // 부모 클래스의 print() 메서드 호출

    /* ... */
  }
}

Methods

virtual method

구현은 했는데, 파생 클래스에서 재정의가 필요. 여기서 어떤 지정어를 사용해 재정의했는지에 따라 동작이 달라진다.

  • new 지정어로 재정의: 선언 type에 따라 호출됨
  • override 지정어로 재정의: 구현 type에 따라 호출됨
public class Program {
	public static void Main() {
		A ab = new B();
		A ac = new C();
    C cc = new C();
		
		ab.print(); // B
		ac.print(); // A
    cc.print(); // C
	}
}

abstract class A {
	public virtual void print () {
		Console.WriteLine("A");
	}
}

class B: A {
	public override void print () {
		Console.WriteLine("B");
	}
}

class C: A {
	public new void print () {
		Console.WriteLine("C");
	}
}

재정의 시 modifier는 항상 일치 해야만 한다.

virtual method가 포함된 클래스는 반드시 abstract 키워드를 붙여야만 한다.

Abstract method

얘는 virtual과 비슷하긴 한데, 몸체가 없는애.

Sealed method

재정의 못하는애. 이걸 한 번에 모든 메서드에 대해 적용하는 것이 sealed class.

Class conversion

자식 클래스가 부모 클래스로 conversion은 가능한데, 부모 클래스가 자식 클래스로 conversion은 불가능하다. 이유는 자식 클래스는 부모 클래스에서 추가적으로 기능들이 구현된 것이기 때문. 즉, 해당 기능이 구현되어있지 않으니 Exception이 발생되는 것.

Interface

interface 키워드 사용하는 애. 멤버로는 다음이 올 수 있다.

  • method
  • property
  • indexer
  • event

주의해야 할 것은, 모두 구현부가 없다는 것과 모두 public 이라는 것. 얘네들 중 하나라도 구현하지 않으면 해당 클래스는 abstract class이다.

때문에 다중 상속이 가능하다. 여러개의 interface를 받을 수 있다는 것. 물론 클래스 extends와 함께 인터페이스의 implement도 가능하다.

class Program: BaseClass, Interface_A {
  /* ... */
}

class A: Interface_A { /* ... */ }
class B: A, Interface_B { /* ... */ }

수정자는 다음과 같다.

  • public
  • protected
  • internal
  • private
  • new

고급 프로그래밍 기법

Generic

위에서 설명했지. 참고로 여러개 받을 수 있다.

// generic class
class Program<T1, T2, T3> { /* ... */ }

// generic interface
interface IF<T1, T2> { /* ... */ }

class Impl<T1, T2>: IF<T1, T2> { /* ... */ }

// generic method
class M {
  static void swap <T> (ref T a, ref T b) {
    T tmp = a;
    a = b;
    b = tmp;
  }

  public static void Main (string[] args) {
    int a = 1, b = 2;

    Console.WriteLine($"a: {a}, b: {b}"); // a: 1, b: 2

    swap<int>(ref a, ref b);

    Console.WriteLine($"a: {a}, b: {b}"); // a: 2, b: 1
  }
}

JAVA와는 달리 generic이 꼭 class일 필요는 없다. alias라서 그런듯?

제한

제네릭의 범위를 어떤 서브클래스로 제한할 수 있다.

class Parent { /* ... */ }
class Child: Parent { /* ... */ }

class Program<T> where T: Parent { /* ... */ }

/* ... */

Program<Child> p1 = new Program<Child>(); // OK
Program<int> p2 = new Program<int>(); // ERROR

Parent 부분에는 클래스 말고도 다양한 애들이 들어갈 수 있다.

  • where T: struct: T는 value
  • where T: class: T는 ref(참조형)
  • where T: new(): T는 매개변수가 없는 생성자가 있어야 함
  • where T: MyClass: TMyClass 클래스의 파생 클래스
    • 물론 여기서의 '파생 클래스'는 abstract class가 아니겠지
  • where T: MyInterface: TMyInterface 인터페이스를 구현한 클래스

Attribute

실제 동작 코드는 아니고, JAVA의 annotation(@)과 같은애. 개발자들끼리 소통?그런거 하려고 쓰는애임.

정확히는 해당 클래스, 필드, 메서드, 프로퍼티 뭐 이런애들의 속성에 대한 정보를 주고받기 위해 사용하는 애다. assemble에 metadata 형식으로 추가된다는데... 뭐 이건 그렇다고 치자.

attribute에는 두 가지 종류가 있다.

  • 표준 attribute (.NET FW에서 제공)
  • 사용자 정의 attribute

Standard attribute

조건적으로 실행하고 안하고 할 Conditional attribute 와, deprecated 예정인 애를 나타낼 Obsolete attribute 두 개를 소개하고 있다.

참고로 Conditional attribute는 System.Diagnostics 를 사용해야 한다니 주의하도록 하자.

class ObsoloAttr {
  [Obsolete("경고, Deprecated 예정인 애")]
  public static void LegacyMethod () { /* ... */ }

  /* ... */
}

LegacyMethod 사용하는 코드를 컴파일하면 뭐라뭐라 저 메시지와 함께 warning이 뜬다.

User-defined attribute

System.Attribute 클래스에서 파생시켜 정의가 가능. 이름 형태는 XxxAttribute 이렇게 하라는데... 강제일까? 그건 잘 모르겠다.

정의나 뭐 그런건 똑같은 클래스이며 attribute라서 큰 차이는 없다.

class TestAttribute: Attribute { /* ... */ }

[TestAttribute( /* ... */ )]

Exception

runtime 때 발생되는 에러. 이런 애를 처리해줘야(exception handling) 안전한 프로그램이겠지. 물론 이런 방법들은 언어 자체에서 제공해준다.

Exception instance

예외도 하나의 객체다. 따라서, 예외를 위한 클래스를 정의해줘야 할 것. 그냥 일반적인 클래스로 취급해주면 된다.

class MyException: ApplicationException {
  public MyException (string s): base(s) { }
}

class Program {
  public static void Main (string[] args) {
    try {
      throw new MyException("My exception");
    } catch (MyException e) {
      Console.WriteLine(e.Message); // My exception
    }
  }
}

constructor로 string 넘겨주면 된다. 뭐 이렇게 해 주면 됨.

참고로 우리가 정의해주고 안해주고에 따라 컴파일러의 동작이 달라지는데, 일반적인 시스템 예외의 경우 컴파일러가 try-catch 로 잡아주지 않아도 신경안쓰지만, 우리가 정의해준 예외의 경우 try-catch로 잡아주지 않으면 컴파일러가 컴파일하지 않는다고 한다.

또한 시스템에 발생된 의도치않은 예외를 implicit exception 이라고 하고,

throw 를 통해 의도적으로 발생시킨 예외를 explicit exception 이라고 한다.

System exceptions

다음이 있다.

  • ArithmeticException: 산술 연산
  • IndexOutOfRangeException: 이름 그대로 인덱스 벗어날 때 발생됨
  • ArrayTypeMismatchException: 타입에러
  • InvalidCastException: casting(explicit type conversion) 실패 시
  • NullReferenceException: null 객체 참조 시
  • OutOfMemoryException: 메모리 할당 실패 시

Exception handling

다음과 같이 try-catch-finally 구문을 사용ㅎ나다.

try {
  /* ... */
} catch (ExceptionType exceptionName) {
  /* ... */
} catch (ExceptionType exceptionName) {
  /* ... */
} finally {
  /* ... */
}

알고있듯이, finally 블록은 반드시 실행된다. goto문 이런걸로도 빠져나올 수 없음.

Exception propagation

예외 처리가 해당 메서드에 없으면, 해당 메서드를 호출한 메서드로 예외를 전파함. 예외 이후 코드는 모두 무시된다.

이렇게 try-catch 문을 찾을 때까지 전파한다. 스택타고 올라간다 생각하면 됨.

Thread

프로세스 단위로 자원공유하며 여러가지 행동을 한 번에 진행할 수 있다는 것은 OS에서 배웠지. c#도 물론 스레드를 지원한다.

c#에서의 thread는 method 단위로 실행하며, 이를 위해 Thread 클래스와 System.Threading.ThreadStart 델리게이트를 지원한다.

class Program {
  static void ThreadBody () {
    Console.WriteLine("Thread body");
  }
  public static void Main () {
    ThreadStart ts = new ThreadStart();
  }
}

시험용

bool 대수 축약

기본 정리 는 다음과 같음

OR AND
A + 0 = A A * 1 = A
A + 1 = 1 A * 0 = 0
A + A = A A * A = A
A + A' = 1 A * A' = 0
A + B = B + A A * B = B * A
A + (B + C) = (A + B) + C A * (B * C) = (A * B) * C
A + (B * C) = (A + B) * (A + C) A * (B + C) = (A * B) + (A * C)
A + (A * B) = A A * (A + B) = A
(A + B)' = A' * B' (A * B)' = A' + B'
(A')' = A ­

우선 순위 는 다음과 같음

( ) > NOT > AND > OR

bool 축약은 기본 정리를 이용하는 것임. 축약 후 카르노 맵 이용해 검산이 가능할 것.

기본적인 진행은 다음과 같다.

  1. 먼저 괄호같은 것 있으면 모두 펼쳐준다.
    • AND로 묶인 OR들을 OR로 묶인 AND로 만들어줌 (sum of product, canonical/normal/standard form. 용어는 아래에서 다룸.)
    • 가령 (a + b) * (a + c) 이런걸 aa + ac + ab + bc 이렇게 만들라는 말
  2. 그리고 공통 인수로 묶은 뒤
    • 가장 많이 묶이는 걸로 묶는다
  3. 기본 정리 이용해 중복 제거해준다.

예시 문제들 및 풀이

  • a + a' * b = a + b

    1. 분배법칙 이용해 푼다.
    2. a + (a' * b) = (a + a') * (a + b)
    3. 1 * (a + b) = a + b
  • ab + a'c + bc = ab + a'c

    1. 변형시켜서 푼다.
      • 좀 더 자세히 말하자면, 결과에 영향을 끼치지 않는 범위 안에서 없는 애들을 만들어주도록 하자.
    2. ab + a'c + bc * 1 = ab + a'c + bc * (a + a')
    3. ab + a'c + abc + a'bc = ab + abc + a'c + a'bc
    4. ab(1 + c) + a'c(1 + b) = ab + a'c
  • a(a + b) + b(a + b) = a + b

    1. 분배법칙을 이용함
      • 정 헛갈린다면 a + b 를 z 이런거로 치환한다음에 진행해보자
    2. (a + b) * (a + b) = a + b
  • ab + (a' + b')c + ab = ab + c

    1. ab + (a' + b')c + ab = ab + (a' + b')c
    2. ab + (ab)'c
      • 드모르간
      • 여기서 ab = z 로 치환가능
    3. z + z'c
      • 분배법칙 이용
    4. (z + z')(z + c) = z + c
    5. z + c = ab + c
  • ab + a(b + c) + b(b + c) = b + ac

    1. ab + ab + ac + bb + bc = ab + ac + b + bc
      • ab + ab = ab
      • bb = b
    2. 보면 b로 묶는게 가장 많이 묶이니 b로 묶음
    3. b(a + 1 + c) + ac = b + ac
  • [a + b'c + d + ef][a + b'c + (d + ef)'] = a + b'c

    1. 치환을 이용한다.
      • X = a + b'c
      • Y = d + ef
    2. (X + Y)(X + Y') = X
    3. X = a + b'c
  • a'b'c' + a'bc + abc + ab'c' + ab'c = ab' + b'c' + bc

    1. 먼저 가장 많이 묶이는 것으로 묶어준다 (b')
    2. b'(a'c' + ac' + ac) + a'bc + abc
      • 여기서 그냥 진행하면 됨
    3. b'(a'c' + ac' + ac) + a'bc + abc = b'(a'c' + a) + a'bc + abc
    4. b'(a + c') + a'bc + abc = ab' + b'c' + bc
  • (b' + c')(b' + c) + a' + b' + c' = a' + b' + c'

    1. b' + b'c' + b'c + 0 + a' + b' + c' = a' + b' + c' + b'c + b'c'
    2. a' + b' + c' + b' = a' + b' + c'
  • (x + y)(x' + y') + xy + x'y' = 1

    1. 치환을 이용할 수 있는데, 먼저 드모르간으로 서로 연관성있게? 묶어줘야함
    2. (x + y)(xy)' + xy + (x + y)'
      • A = x + y
      • B = xy
    3. AB' + B + A' = A + B + A' = 1
  • x'yz + xy'z + xyz + y'z = z

    1. 가장 많이 묶이는 z로 묶음
    2. z(x'y + xy' + xy + y') = z(y + xy' + y')
    3. z(1 + xy') = z
  • x'y'z + x'yz + xy' = x'z + xy'

    1. x'y'z + x'yz + xy' = x'z + xy'
  • xy + x'z + yz = xy + x'z

    1. xy + x'z + yz * 1 = xy + x'z + yz * (x' + x)
    2. xy + x'z + x'yz + xyz = xy + xyz + x'z
    3. xy + x'z
  • (x + y)(x' + z)(y + z) = x'y + xz

    1. (y + x)(y + z)(x' + z) = (y + xz)(x' + z) = x'y + yz + xx'z + xzz
      • (x + y)(x + z) = x + yz 를 이용한다.
    2. x'y + yz + xz = x'y + yz * (x' + x) + xz
    3. x'y + x'yz + xyz + xz = x'y + xz

기타 사항들

  • sum of product 형식으로 만들어줄 때, (x + y)(x + z) = x + yz 분배법칙을 이용하는게 좀 더 효율적이다.

    • (a + bc)(a + d + e) = (a + bc)(a + (d + e)) = a + (bc)(d + e) = a + bcd + bce
  • 변수 여러개인 드모르간 풀 때는 그냥 하나씩 들어가면서 풀자

최소항 축약

카르노맵?써서? 축약하는 것 말하는 것 같은데? 그전에 standard form으로 만들어줘야한다.

참고로 standard form은 이름이 여러가지임. canonical form, normal form, 정준식, 표준식... 헛갈리지 말도록 하자.

bool 축약할 때 한 번 본 것인데, 다음과 같은 식에 대해

f = x * y * (z + z') + x' * z * (y + y') + y * z' * (x + x')

다음과 같이 sum of product 형태로 펼쳐주는 것을 의미함.

f = xyz + xyz' + x'yz + x'y'z + xyz' + x'yz'

이 형태를 standard form 이라고 한다. 여기서 카르노맵 써서 축약해주면 되는거.

Min term

3변수는 다음과 같이 나타나는데

ind x y z
0 0 0 0 x' y' z'
1 0 0 1 x' y' z
2 0 1 0 x' y z'
3 0 1 1 x' y z
4 1 0 0 x y' z'
5 1 0 1 x y' z
6 1 1 0 x y z'
7 1 1 1 x y z

이걸 기준으로 다음의 식을

f = xyz + xyz' + x'yz + x'y'z + xyz' + x'yz'

다음과 같이 나타낼 수 있다.

f = ∑(1, 2, 6, 7)

  • xyz = 7
  • xyz' = 6
  • x'y'z = 1
  • xyz' = 6
  • x'yz' = 2

1 되는 애들 묶은 것이다. 중복은 제외됨. 이렇게 나타낸 것을 min-term(최소항) 이라고 함.

또는 다음과 같이 표기되기도 한다.

f = Π(0, 3, 4, 5)

그냥 f' 나타낸거임(0 되는 애들). 나머지도 뭐 이런 방식으로 해 주면 된다.

카르노 맵

최소항과 gary code를 이용해 다음과 같은 맵으로 나타낼 수 있음.

3-variables kmap

gray code라는 것을 주의하자. 아무튼 이렇게 만들었으면 다음과 같이 묶을 수가 있다.

3-variables kmap 2

이렇게 묶은 것을 각각 다음과 같이 나타낼 수 있을 것이다.

  • A = x'y'z'
  • B = yz'
  • C = xy

그냥 성립되는 x, y, z 사용해 나타내면 되는 것. 아무튼 이렇게 각각을 나타낸걸 OR로 묶으면 축약이 끝난다.

f = xyz + xyz' + x'yz + x'y'z + xyz' + x'yz' = x'y'z + yz' + xy

솔직히 개신기하지않은가. 이런 방식으로 진행하면 된다.

묶기

여러가지 방법으로 묶을 수 있다. 주의해야 할 것은, 보이듯이 3개를 묶을 수는 없다는 거. 왜냐하면... 나타낼 수가 없으니까!

4변수

kmap 1

kamp 1-1

5변수

kamp 2

5변수는 헛갈린다면 그냥 이렇게 입체적으로 생겼다고 생각하면 된다.

kmap 3

입체적으로 생겼다는 것 말고는 4변수 묶듯이 묶으면 됨. 물론 입체 니까 z축으로도 묶을 수 있겠지.

이렇게 진행하면 된다.

Code conv.

먼저 각각의 코드는 다음과 같음

  • weighted code: BCD 8421, 2421, 84-2-1, 5043210
    • 가중치가지는애들
  • self-complement code: excess-3, 84-2-1, 2421
    • 자기보수
  • error detection: 5043210
    • 오류검출코드. 정정은 안됨. 검출법은 코드보면서 말해주겠다.

8421 BCD

0~9 까지만 표현하는 코드. 가중치가 8421 이어서 8421 BCD 임.

digit을 8421 BCD로 바꾸는 것은 다음과 같다. 그냥 10진수를 2진수로 바꾸는 것임.

digit 8421
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001

얘를 다시 digit으로 바꾸는 것도 2진수를 10진수 계산하듯이 하면 되겠지.

여기서 가중치(weight)는 digit에 대한 값이라고 생각하면 된다.

2421 BCD

8421이랑 똑같긴 한데, 가중치가 2421 이어서 2421 BCD 임.

digit to 2421 BCD 는 그냥 가중치 보고 바꾸면 된다.

digit 2421
0 0000
1 0001
2 0010
3 0011
4 0100
5 1011
6 1100
7 1101
8 1110
9 1111

반대로 가는것도 똑같이 가중치 보고 값 주면 되겠지.

84-2-1 BCD

똑같이 가중치 줘서 해주면 된다.

digit 84-2-1
0 0000
1 0100
2 0110
3 0101
4 0100
5 1011
6 1010
7 1001
8 1000
9 1111

5043210 code

설명할 것도 없다. 첫번째는 weight, 두번째는 even parity, 세번째부터 weight 이렇게 생각하면 된다.

5 0 4 3 2 1 0
0 0 0 0 0 0 0
5 even 4 3 2 1 0

여기서 even은 1이 짝수인지 홀수인지 를 나타낸다. 짝수이면 1, 홀수이면 0 이렇게. 경우에 따라 odd parity도 있는데, 얘도 마찬가지로 홀수이면 1, 짝수이면 0 이렇게 되는 애.

이걸 통해 1이 몇개 와야하는지를 알 수 있으니 오류 검출이 가능한 것. 단, 어디서 오류난건지는 모르니 정정은 불가능하다.

변환된 것 보면 무슨 말인지 쉽게 이해가 갈 것이다.

digit 5043210
0 0100001
1 0100010
2 0100100
3 0101000
4 0110000
5 1000001
6 1000010
7 1000100
8 1001000
9 1010000

솔직히 말해 어려울 건 없다고 봄.

excess-3 code

8421 BCD로 표현된 값에 3을 더한 것. 다시말해, 10~15는 don't care 라는 말임.

digit을 3초과로 바꾸는 것은 다음과 같다. 그냥 3 더해주면 됨.

digit excess-3
0 0011
1 0100
2 0101
3 0110
4 0111
5 1000
6 1001
7 1010
8 1011
9 1100

자기 보수의 관계에 있음. 뭔 말이냐? 4, 5 기준으로 서로 보수관계 갖고있다고.

반대로 excess-3에서 digit으로 바꾸는 것은 일단 BCD로 바꿔준 뒤, 3을 빼 주면 된다. 크게 어려운 것은 없을거라 생각함.

Gray code

다음 수로 넘어갈 때 비트 하나만 바뀌는 코드. 먼저 digit을 gray로 바꿔주는 것은 다음과 같다.

digit 8421 gray
0 0000 0000
1 0001 0001
2 0010 0011
3 0011 0010
4 0100 0110
5 0101 0111
6 0110 0101
7 0111 0100
8 1000 1100
9 1001 1101

바꿔주는건 그냥 8421 하나씩 참고하면서 앞의 숫자와 다르면 1 같으면 0 을 넣으면 된다.

bcd to gray

gary에서 BCD로 바꿔주는건 다음과 같다.

gray 8421 digit
0000 0000 0
0001 0001 1
0011 0010 2
0010 0011 3
0110 0100 4
0111 0101 5
0101 0110 6
0100 0111 7
1100 1000 8
1101 1001 9

마찬가지로 gary 하나씩 참고하며 1의 갯수 보고 홀수면 1 짝수면 0 을 넣으면 된다.

gary to bcd

Code converter

코드를 변환해보고, 이걸 하는 회로를 설계해보겠다.

8421 BCD to 3-excess

d

먼저 8421 BCD는 다음과 같음

index 8421
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
10 don't care
11 don't care
12 don't care
13 don't care
14 don't care
15 don't care

이러게 나온 8421을 그냥 3-excess에 대입하면 됨

index 8421
ABCD
3-excess
wxyz
0 0000 0011
1 0001 0100
2 0010 0101
3 0011 0110
4 0100 0111
5 0101 1000
6 0110 1001
7 0111 1010
8 1000 1011
9 1001 1100
10 don't care don't care
11 don't care don't care
12 don't care don't care
13 don't care don't care
14 don't care don't care
15 don't care don't care

input이 don't care인 경우 output도 don't care가 되겠다. 아무튼 이렇게 해서 다음을 만들 수 있다.

  • w = ∑(5, 6, 7, 8, 9)
  • x = ∑(1, 2, 3, 4, 9)
  • y = ∑(0, 3, 4, 7, 8)
  • z = ∑(0, 2, 4, 6, 8) = D'
    • 이렇게 나타낼 수도 있음. 카르노맵 진행하면 이해될 것.

이걸 카르노맵으로 그리면 다음과 같다.

k-w

k-x

k-y

z는 말했듯이 그냥 D' 이니 카르노 맵을 그릴 필요조차 없다. 실제로 그려도 D'이 나오기도 함.

아무튼 이를 통해 다음과 같다.

  • w = A + BC + BD = A + B(C + D)
  • x = BC'D' + B'D + B'C = B(C + D)' + B'(C + D)
  • y = CD + C'D' = CD + (C + D)'
  • z = D'

여기서 C + D가 중복되는게 보이는가? 설계 시 이걸 이용해 중복을 제거할 수도 있겠지. 아무튼 해 보면 다음과 같다.

circuit

잘 안보이면 여기 참고.

3-excess to 8421 BCD

똑같이 진행하면 된다. 단, don't care 위치가 달라진다는 것을 주의하자.

index excess-3
ABCD
8421
wxyz
0 0000 don't care
1 0001 don't care
2 0010 don't care
3 0011 0000
4 0100 0001
5 0101 0010
6 0110 0011
7 0111 0100
8 1000 0101
9 1001 0110
10 1010 0111
11 1011 1000
12 1100 1001
13 1101 don't care
14 1110 don't care
15 1111 don't care

주의할 것은 excess-3가 0000부터 시작한다는 것. minterm은 다음과 같다.

  • w = ∑(11, 12)
  • x = ∑(7, 8, 9, 10)
  • y = ∑(5, 6, 9, 10)
  • z = D'

이걸로 진행하면 된다. 카르노맵 그리고... 축약하고... 중복합쳐서 설계하고...

BCD to 5043210

이 경우 input bit은 4개, output bit은 7개가 되어야 한다.

index 8421
ABCD
5043210
abcdefg
0 0000 0100001
1 0001 0100010
2 0010 0100100
3 0011 0101000
4 0100 0110000
5 0101 1000001
6 0110 1000010
7 0111 1000100
8 1000 1001000
9 1001 1010000

BCD나 5043210이나 똑같이 09 표현하는 것이기에 don't care는 1015가 된다. 일단 각각 다음과 같음.

  • a = ∑(5, 6, 7, 8, 9)
  • b = ∑(0, 1, 2, 3, 4)
  • c = ∑(4, 9)
  • d = ∑(3, 8)
  • e = ∑(2, 7)
  • f = ∑(1, 6)
  • g = ∑(0, 5)

카르노 맵을 그려보면...

k-a

k-b

k-c

k-d

k-e

k-f

k-g

뭐가 개많은데 이걸로 축약하고, 중복잡고 설계하면 된다. 더 하는건 의미 없다고 생각해서 여기서 멈추겠음.

5043210 to 8421 BCD

이 경우 input bit은 7개. output bit은 4개가 되야 한다.

5043210
abcdefg
8421
wxyz
0100001 0000
0100010 0001
0100100 0010
0101000 0011
0110000 0100
1000001 0101
1000010 0110
1000100 0111
1001000 1000
1010000 1001

근데 보면 don't care가 개많음

k

이게 진짜 가능은 한걸까? 안나오기만을 빌어야지 나오면 어쩔 수 없이 억지로 해보는거고..

Adder

다음의 식을 계산한다고 했을 때

i

첫 번재 자리는 올림 없이 그냥 두 수 A, B만 더하기만 하면 되기에 다음과 같이 구현이 가능하다.

i

여기서 C를 C-out 이렇게로도 표기함. 아래 그림보면 나온다.

두 번째 자리부터는 A, B 그리고 이전 단계에서 넘어온 올림수 C를 더해야 하기에 다음과 같이 구현해야 한다.

i

설계도 뭐 크게 어려울 건 없다. 그냥 연결해주면 될 테니까.

근데 문제가, 설계해보면 첫 번째 자리부터 끝자리까지 하나하나씩 올라가며 계산해야 한다는 것 이다. 속도가 개중요한건데 만약 32bit 결과를 갖는 계산을 진행한다면 32번을 꼼작없이 계산해야 한다는 것. 이러면 정말 답이 없다.

look ahead C-out generator

뭐냐 이거 했냐?

half-adder

반가산기는 다음과 같이 동작한다.

A B Sum C-out
0 0 0 0
0 1 1 0
1 0 1 0
1 1 1 1

다시말해 다음과 같다는 것

  • sum = ∑(1, 2) = A'B + AB' = A xor B
  • C-out = ∑(3) = AB

이를 회로로 만들면 다음과 같다.

i

full-adder

전가산기는 다음과 같다.

A B C-in Sum C-out
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1
  • sum = ∑(1, 2, 4, 7) = A'B'C + A'BC' + AB'C' + ABC = C xor (A xor B) = A xor B xor C
  • C-out = ∑(3, 5, 6, 7) = A'BC + AB'C + ABC' + ABC = AB + BC + AC

뭐 이런데, 이 full-adder는 두 개의 half-adder로 만들 수 있다. 왜냐고? 그냥 A + B + C-in 이거니까.

i

설계해보면 다음과 같다.

c

참고로 여기서 C-out에 OR가 걸린 이유는 C-out이 다음과 같기 때문.

C-out = ∑(3, 5, 6, 7) = A'BC + AB'C + ABC' + ABC = (A xor B) * C + A * B

  • A'BC + AB'C = (A xor B) * C
  • ABC' + ABC = A * B

NAND

XOR

시험용

모르는 것만 정리

Java Graphics

javax.swing.* 라이브러리와 java.awt.* 라이브러리를 이용한다.

javax.swing.JFrame

가장 위에 있는 애다. 얘를 기반으로 Swing graphic을 구현하는 것.

여기 안에 javax.swing.JPanel 뭐 이런 요소들이 add() 메서드를 통해 들어간다.

class Frame extends JFrame {
    public Frame () {
        JPanel panel = new JPanel;

        add(panel);

        setSize(250, 200);
        setTitle("Title");
        setVisible(true); // 얘가 없으면 안나타나더라
    }

    public static void main (String[] args) {
        Frame f = new Frame();
    }
}

뭐 이런 방식으로 구현된다.

Animate

Swing timer, std util timer, std util thread 세 가지 방법으로 구현이 가능한데, 여기서는 Swing timer를 이용한다.

사용은 대략 다음과 같음.

Timer t = new Timer(DELAY, evt -> { // ActionListener.actionPerformed::ActionEvent
    /* ... */
});

솔직히 말해 그래픽스에서는 뭐 안낼 것 같다. 구현에 필요한 라이브러리를 다 외우라는 말은 아닐것이고..

PaintComponent

각각의 render loop마다 호출되는 메서드. 정확히는 repaint() 메서드로 호출되는 애다.

java.awt.Grpahics 객체가 넘어와서 transformation을 할 수 있도록 한다.

@Override public void paintComponent (Graphics g) {
    super.paintComponent(g);

    /* ... */
}

super.paintComponent() 를 호출했냐면, 자신의 부모 클래스에서도 어떤 그래픽적인 처리를 할 수 있기 때문. 웬만하면 해 주도록 하자.

Event

Graphics에서 나오는 것이긴 한데, 포괄적으로 다루기 위해 따로 빼냈다. Java에서의 event handling processor는 다음과 같음.

  1. 이벤트 객체에 대해 어떠한 동작을 실행
    • 가령 마우스로 클릭한다거나
  2. emit event
  3. 해당 event에 대해 등록한 코드(event handler callback) 실행

여기서 event handler callback 부분은 다음과 같이 여러 부분에 구현이 가능하다.

  • 독립적인 클래스로 작성
    • 해당 클래스의 인스턴스를 이용하나? 그럴듯
  • 내부 클래스로 작성
    • this로 참고하겠지
  • 무명 클래스 사용
    • new Runnable() { /* ... */ } 이렇게
  • lambda expression
    • 위에서 Timer 말할 때 봤던 것

Keyboard events

키보드 입력은 KeyListener 인터페이스로 핸들링이 가능한데, 이 인터페이스는 다음 세 가지를 모두 구현해야만 한다.

  • keyPressed(KeyEvent e): 키보드 눌렀을 시
  • keyReleased(KeyEvent e): 키보드 떼었을 시
  • keyTyped(KeyEvent e): 글자 입력 시

세 개 구현해야하기에 lambda expression은 사용이 불가하다 는 것을 알아두자.

이렇게 구현했다면, addKeyListener() 메서드로 등록이 가능하다. 아래에서 예시 한 번에 볼 것임.

Action event

위에서 본 Timer이런 것들을 핸들링하는 이벤트. 하나만 있는 경우 보통 이걸로 받는듯? ActionListener 인터페이스를 구현하면 된다.

  • actionPerformed(ActionEvent e)

하나만 구현하면 되기에 위의 Timer 예제에서처럼 lambda expression으로도 구현이 가능하다.

eg

위의 것들을 구현해보면 다음과 같음.

class Board extends JPanel implements KeyListener {
    private Timer t;

    public Board () {
        addKeyListener(this);

        t = new Timer(10, evt -> { // lambda 사용
            /* ... */
        });

        t.start();
    }

    @Override public void keyPressed (KeyEvent evt) {
        /* ... */
    }

    @Override public void keyReleased (KeyEvent evt) {
        /* ... */
    }

    @Override public void keyTyped (KeyEvent evt) {
        /* ... */
    }
}

여기서 Board 에 대해 키보드 이벤트를 처리해야 하니, addKeyListener() 메서드로 자신을 넘긴 것이다.

Thread

Java에서 스레드는 다음과 같이 구현 가능하다.

Thread t = new Thread();
t.start(); // 여기서 branch

다 비슷비슷한듯?

Thread class

다음과 같이 상속받을 수도 잇다. 이 때 run()를 구현하면 되며, 얘는 JVM에 호출된다.

class MyThread extends Thread {
    public void run () {
        /* ... */
    }

    public static void main (String[] args) {
        Thread t = new MyThread();
        mt.start();
    }
}

또는 다음과 같이 Runnable 인터페이스를 넘길 수도 있음.

class MyRun implements Runnable {
    public void run () {
        /* ...    */
    }
}

class Program {
    public static void main (String[] args) {
        Runnable r = () -> {
            /* ... */
        };

        Thread t1 = new Thread(new MyRun());
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(() -> {
            /* ... */
        });

        /* ... */
    }
}

보통 Runnable 인터페이스를 사용하곤 한다.

Synchronized methods

스레드를 동기화시킴으로써 해당 메서드는 최대 하나의 스레드만이 사용가능하도록 지정할 수가 있다.

class Counter {
    private int value = 0;

    public synchronized void increment () {
        value++;
    }

    public synchronized void decrement () {
        value--;
    }

    public void print () {
        System.out.println(value);
    }
}

뭐 이렇게 가능하다.

Collection

collection은 객체들의 모임이라고 생각하면 됨. 여러가지 자료구조로 되어있다. 구조는 대략 다음과 같음.

i

무엇이 interface이고, 무엇이 class인지 잘 구별하자.

  • interface
    • Iterable
    • Collection: 객체의 모임을 의미
    • List: 순서가 있는 자료 구조. 중복 원소(hash 기준)를 가질 수 있다.
    • Set: 중복없는 집합 자료구조
    • SortedSet
    • Queue: FIFO 자료구조
    • Deque

Methods

메서드는 다음이 있다.

  • public boolean add(E e)
  • public boolean addAll(Collection<? extends E> c)
  • public boolean remove(Object el)
  • public boolean removeAll(Collection<?> c)
  • default boolean removeIf(Predicate<? super E> filter)
  • public boolean retainAll(Collection<?> c)
    • 교집합
  • public int size()
  • public void clear()
    • 모든 원소 제거
  • public boolean contains(Object el)
  • public boolean containsAll(Collection<?> c)
  • public Iterator iterator()

대충 의미는 파악이 갈 것이고, 각각의 상황에 어떠한 메서드가 적절한지 알아두면 될 것이다.

Iterate

다음의 방법이 있는데,

  1. Iterator interface
  2. for-each loop
  3. ListIterator interface
  4. for loop
  5. forEach() method
  6. forEachRemining() method

여기서는 for, for-each, Iterator interface, forEach() method만을 사용한다.

ArrayList<Integer> arr = new ArrayList<Integer>();

arr.add(10);
arr.add(12);
arr.add(4);

// for loop
for (int i = 0; i < arr.size(); i++) {
    System.out.println(list.get(i));
}

// for-each loop
for (Integer i : arr) {
    System.out.println(i);
}

// Iterator interface
Iterator it = arr.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// forEach() method (using method ref)
arr.forEach(System.out::println);

주의할 것은, Iterator 사용 시 it.next() 반환으로는 Object가 반환된다는 것.

List interface

순서있는 중복가능한 컬렉션

Vector

초기 버전에 구현된 컬렉션 클래스. 제네릭 사용하지 않고 그냥 모두 object로 받는 가변 크기 배열이다.

import java.util.Vector;

public class Program {
    public static void main (String[] args) {
        Vector v = new Vector();

        v.add("string");
        v.add(10);
        v.add(new Integer(12));

        String s = (String) v.get(0); // type conv
    }
}

모두 object로 들어가기에 보이다싶이 type conversion을 해 줘야 한다.

ArrayList

제네릭 사용하는 Vector 라고 생각하면 된다. Random access가 가능하기에 한 번에 인덱스 참조할 때 적절함.

단, 중간에 요소 변경 시 앞뒤 인덱스 모두 변경해줘야 하기에, 빈번한 insert와 remove에 적절하지 않음을 알아두자.

참고로 다음과 같이 java.util.Arrays.asList() 메서드를 통해 일반 배열을 컬렉션으로 만들 수 있는데,

String[] ss = {'a', 'b', 'c'};

List<String> list = Arrays.asList(ss); // java.util.Arrays.ArrayList

이렇게 나온 컬렉션은 java.util.Arrays.ArrayList 이기에, 원래 기대했던 java.util.ArrayList와는 다르다는 것을 주의하자.

이 둘의 차이점은 java.util.Arrays.ArrayList의 경우 원소 추가나 제거가 불가능해 length를 바꿀 수가 없다는 것.

만약 만약 java.util.ArrayList로 사용하길 원한다면 다음과 같이 해 주면 된다.

ArrayList<String> arr = new ArrayList<String>(Arrays.asList(ss));

LinkedList

앞뒤 노드만 변경해주면 되기에, 빈번한 insert와 remove가 일어나는 경우 적절하다.

단, Random access는 불가능하다는 것을 알아두자.

Set interface

원소 중복 허용않는 순서없는 컬렉션. 때문에 addAll() 메서드는 다음의 특징을 갖는다.

s1.addAll(s2): s1s1s2의 합집합으로 만듦

중복이 없으니 addAll() 해도 합집합이 되는 것.

HashSet

순서가 일정하지 않지만, hash table을 이용하기에 성능이 가장 뛰어나다. 찾는것도 개빠르고.

TreeSet

red-black tree(binary-tree보다 좀 더 성능이 좋은 애라고 생각하면 됨) 사용하기에, 순서 알 수 있음. 그러나 HashSet보다는 느리다.

순서를 알 수 있기에 일부만 가져올 수가 있다.

  • headSet(): 처음부터 잘라옴
  • subSet(): 중간 잘라옴
  • tailSet(): 끝부분부터 잘라옴

보면 인자가 값과 boolean이 들어가는데, 여기서 boolean은 해당 요소 포함 여부를 의미한다.

LinedHashSet

hash table과 linked list를 결합한 것. 일단 hash table대로 insert 하는데, linked list를 사용해 각각의 공간에 집어넣기에 중복 요소도 넣을 수 있다.

Queue interface

말했듯이 FIFO. First In First Out.

PriorityQueue

우선순위큐. heap 자료구조를 사용해 무작위로 값이 inserted 되었더라도 정렬된 상태로 컬렉션을 유지한다. 다시말해, remove() 시 가장 작은 원소부터 나간다는 말.

PriorityQueue<Integer> q = new PriorityQueue<Integer>();

q.add(10);
q.add(12);
q.add(4);

System.out.println(q.remove()); // 4
System.out.println(q.remove()); // 10
System.out.println(q.remove()); // 12

Map

dictionary. key와 value 가지고 노는 자료구조.

java에서는 java.util.Map 인터페이스를 구현한 java.util.HashMap이 있다.

Map<String, Integer> map = new HashMap<String, Integer>();

map.put("k1", 1);
map.put("k2", 2);
map.put("k10", 3);

map.forEach((k, v) -> System.out.println(k + ":" + v));

map.remove("k2");

map.forEach((k, v) -> System.out.println(k + ":" + v));

/** OUTPUT

k1:1
k2:2
k10:3

k1:1
k10:3

*/

그외 기타 요소들

Method reference

lambda를 사용할 수도 있으나, static method의 경우 다음과 같이 :: 연산자를 이용해 메서드를 직접 가져올 수 있다.

ArrayList<Integer> arr = new ArrayList<String>(Arrays.asList(integerArray));

// using lambda expression
arr.forEach(i -> System.out.println(i));

// using method reference
arr.forEach(System.out::println);

String 비교

String은 불변 객체이기에 다음과 같이 비교해야만 한다.

"string".equals("string"); // true

Collections

java.util.Arrays가 있었듯이, sorting/shuffling/searching 이런걸 지원하는 java.util.Collections 클래스가 있다. 여러 유용한 알고리즘 지원하는 애라고 생각하면 된다.

// sorting
String[] ss = {"i", "walk", "the", "line"};

List<String> list = Arrays.asList(ss); // [i, walk, the, line]
Collections.sort(list); // [i, line, the, walk]

// searching
Collections.binarySearch(list, "i"); // return index(0)

뭐 이렇게 쓰면 된다.

stream() method

다음과 같이 stream() 메서드를 통해 chaining할 수가 있다.

int[] ia = {10, 20, 30, 40};
List<Integer> list = Arrays.asList(ia);

list.stream()
    .filter(e -> e.value !== 30)
    .forEach(System.out::println);

/** OUTPUT

10
20
40

*/

이렇게 사용할 수 있다.

Iterator interface

Iterator 인터페이스를 직접 구현할수도 있다. 이를 통해 interable 한 클래스를 구현할 수 있음.

구조는 다음과 같다.

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove(); // optional
}

알아서 적절히 구현하면 될 것이다. 이렇게 iterator pattern을 통해 객체를 순회할 수 있도록 하면, 순회 방법을 클래스 내부적으로 구현할 수 있으니 좀 더 우아한 코드가 될 것이다.

line drawing algorithm

DDA

기울기 값을 이용해 다음 픽셀의 위치를 유추하여 선을 그리는 알고리즘입니다.

[x1, y1, x2, y2] = [40, 20, 10, 30] 이 들어온 경우를 예로 들도록 하겠습니다.

1. 먼저 다음과 같이 필요한 상수를 정의합니다.

  • dx = x2 - x1; // dx := 10 - 40 = -30
  • dy = y2 - y1; // dy := 30 - 20 = 10
  • distance = max(abs(dx), abs(dy)) // distance := max(abs(-30), abs(10)) = 30

여기서 distance 상수는 dominant에 따른 distance를 의미합니다.

각각의 distance step마다 픽셀을 계산해주어야 하며, 예제 기준 총 30번의 픽셀 계산을 진행해야 함을 의미합니다. (x dominant)

2. 각 축에 대한 증가율(increment ratio)을 계산하기 위해 다음과 같이 xIncrement, yIncrement 상수를 정의합니다.

  • xIncrement = dx / distance; // xIncrement := -30 / 30 = -1
  • yIncrement = dy / distance; // yIncrement := 10 / 30 = 0.333

3. 시작 위치를 setPixel() 함수의 인자로 넘겨 시작 위치 픽셀을 그리도록 합니다.

  • setPixel(x1, y1); // setPixel(40, 20)

4. 1부터 distance까지 1씩 증가하며 다음을 반복합니다.

  1. 각 축에 대한 증가율 xIncrement, yIncrementx1y1에 더합니다.
    • x1 = x1 + xIncrement; // x1 := 40 - 1 = 39
    • y1 = y1 + yIncrement; // y1 := 20 + 0.333 = 20.333
  2. 이 값을 round() 함수로 반올림한 뒤, 각각의 값을 rx, ry 변수에 저장합니다.
    • rx = round(x1); // rx := round(39) = 39
    • ry = round(y1); // ry := round(20.333) = 20
  3. 이렇게 구해지는 rx, ry 값은 다음 픽셀 위치가 됩니다. 이를 setPixel() 함수의 인자로 넘겨 해당 픽셀을 그리도록 합니다.
    • setPixel(rx, ry); // setPixel(39, 20);

예제 기준으로 총 30번의 반복을 진행하며, 이를 통해 [40, 20]부터 [10, 30]까지 선이 그려지게 됩니다. (x dominant)

Bresenham

선이 어디에 위치하느냐에 따라 다음 픽셀의 위치를 유추하여 선을 그리는 알고리즘입니다.

[x1, y1, x2, y2] = [40, 30, 10, 30] 이 들어온 경우를 예로 들도록 하겠습니다.

1. 먼저 다음과 같이 필요한 상수를 정의합니다.

  • dx = abs(x2 - x1); // dx := abs(10 - 40) = 30

  • dy = abs(y2 - y1); // dy := abs(30 - 20) = 10

  • dx2 = dx << 1; // dx2 := 60

  • dy2 = dy << 1; // dy2 := 20

  • isXDominant = dx > dy; // isXDominant := 30 > 10 = true

  • xIncrement = sign(x2 - x1); // xIncrement := -1

  • yIncrement = sign(y2 - y1); // yIncrement := 1

2. 그 다음, 선 위치에 대한 정보를 제공할 p 변수의 첫 번째 값(p_0)을 계산합니다.

  • p = isXDominant ? dy2 - dx : dx2 - dy; // p := 20 - 30 = -10

3. 각각의 dominant에 따라 다음을 반복합니다.

  1. 먼저 x1, y1 픽셀을 setPixel() 함수의 인자로 넘겨 해당 픽셀을 그립니다.
    • setPixel(x1, y1); // setPixel(40, 20)
  2. 만약 x dominant인 경우, // isXDominant = true 이기에 이 과정을 진행합니다.
    1. x1x2와 동일한 값인지 확인합니다. // x1 = 40, x2 = 20의 값을 갖기에 아래 작업을 거치지 않고 넘어갑니다.
      • 만약 동일한 경우, 다음 작업을 진행하지 않고 반복문을 빠져나옵니다.
    2. p 값이 0보다 같거나 큰지 확인합니다. // p = -10의 값을 갖기에 아래 작업을 진행하지 않고 넘어갑니다.
      • 같거나 클 경우, y1의 값에 yIncrement 값을 더해주고 p의 값에 dx2 값을 빼줍니다.
        • y1 = y1 + yIncrement;
        • p = p - dx2;
    3. x1xIncrement를, p에는 dy2를 더해줍니다.
      • x1 = x1 + xIncrement; // x1 := 40 - 1 = 39
      • p = p + dy2; // p := -10 + 20 = 10
  3. 그렇지 않을 경우,
    1. y1y2가 동일한 값인지 확인합니다.
      • 동일한 경우 다음 작업을 진행하지 않고 반복문을 빠져나옵니다.
    2. p 값이 0보다 같거나 큰지 확인합니다.
      • 같거나 클 경우, x1 값에 yIncrement 값을 더해주고 p의 값에 dy2를 빼줍니다.
        • x1 = x1 + yIncrement;
        • p = p - dy2
    3. y1xIncrement를, p에는 dx2를 더해줍니다.
  4. 3-1을 반복합니다.

예제 기준으로 총 30번의 반복을 진행하며, 이를 통해 [40, 20]부터 [10, 30]까지 선이 그려지게 됩니다. (x dominant)

참고로, 여기서 y dominant인 경우 xy가 서로 반전된다는 것을 주의하여 구현해야 합니다.

general transformation matrix

Translation

[
    [1, 0, 0],
    [0, 1, 0],
    [T_x, T_y, 1]
]

Rotation

[
    [cos t, sin t, 0],
    [-sin t, cos t, 0],
    [0, 0, 1]
]

만약 원점이 (x_r, y_r) 인 경우

[
    [1, 0, 0],
    [0, 1, 0],
    [-x_r, -y_r, 1]
]
.
[
    [cos t, sin t, 0],
    [-sin t, cos t, 0],
    [0, 0, 1]
]
.
[
    [1, 0, 0],
    [0, 1, 0],
    [x_r, y_r, 1]
]

Scaling

[
    [s_x, 0, 0],
    [0, s_y, 0],
    [0, 0, 1]
]

만약 원점이 (x_r, y_r) 인 경우

[
    [1, 0, 0],
    [0, 1, 0],
    [-x_r, -y_r, 1]
]
.
[
    [s_x, 0, 0],
    [0, s_y, 0],
    [0, 0, 1]
]
.
[
    [1, 0, 0],
    [0, 1, 0],
    [x_r, y_r, 1]
]

General transformation matrix

[
    [1, 0, 0],
    [0, 1, 0],
    [-x_r, -y_r, 0]
]
.
[
    [s_x, 0, 0],
    [0, s_y, 0],
    [0, 0, 1]
]
.
[
    [cos t, sin t, 0],
    [-sin t, cos t, 0],
    [0, 0, 1]
]
.
[
    [1, 0, 0],
    [0, 1, 0],
    [T_x, T_y, 1]
]
.
[
    [1, 0, 0],
    [0, 1, 0],
    [x_r, y_r, 1]
]

원점조정 -> scaling -> rotation -> translation -> 원점조정

이 순서임.

약자모음

  • CAD: Computer Aided Design

  • CAM: Computer Aided Manufactoring

  • CRT: Cathod Ray Tube

  • LED: Light Emitting Diode

  • LCD: Liquid Crystal Display

  • PDP: Plasma Display Panel

  • DVST: Direct View Storage Tube

  • TFED: Thin Film Electoluminescent Displays

  • DDA: Digital Differented Analystics

번역 (Input devices)

Keyboards

An alphanumeric keyboard on a graphics system is used primarily as a device for entering text strings.

그래픽 시스템에서 영숫자 키보드는 주로 문자열을 입력하는 데 사용됩니다.

The keyboard is an efficient device for inputting such nongraphic data as picture labels associated with a graphic display.

키보드는 그래픽 디스플레이와 연관된 사진의 레이블과 같은 비-그래픽 데이터를 입력하는 데 효과적인 장치입니다.

Keyboards can also be provided with features to facilitate entry of screen coordinates, menu selections, or graphics functions.

키보드는 화면의 좌표, 메뉴 선택 또는 그래픽 기능을 쉽게 입력할 수 있는 기능 또한 제공할 수 있습니다.

  • facilitate: 용이하게 하다

=

Cursor-control keys and function keys are common features on general-purpose keyboards.

커서-제어 키와 기능 키는 보통 범용 목적의 키보드의 기능입니다.

Function keys allow users to enter frequently used operations in a single keystroke, and cursor-control keys can be used to select displayed objects or coordinate positions by positioning the screen cursor.

기능 키는 한 번의 키 입력으로 사용자가 자주 사용하는 명령들을 실행할 수 있도록 하며, 커서-제어 키를 사용해 화면의 커서를 움직여 객체 또는 좌표의 위치를 선택할 수 있도록 할 수도 있습니다.

Other types of cursor-positioning devices, such as a trackball or joystick, and included on some keyboard.

트랙볼이나 조이스틱같은 다른 종류의 커서-포지셔닝 장치는 일부 키보드에 포함되어 있습니다.

Additionally, a numeric keypad is often included on the keyboard for fast entry of numeric data.

또한, 숫자 키패드는 빠른 숫자 데이터 입력을 위해 종종 키보드 안에 들어가기도 합니다.

Typical examples of general-purpose keyboards are given in Figs. 2-1, 2-33, and 2-34.

범용 목적 키보드의 전형적인 예시는 그림 2-1, 2-33 그리고 2-34에 나와 있습니다.

Fig. 2-40 shows an ergonomic keyboard design.

그림 2-40은 인체 공학적으로 디자인된 키보드를 보여줍니다.

  • allow: ~을 지급하다
  • frequently: 자주
  • entry: 기입
  • additionally: 또한
  • often: 종종
  • typical: 전형적인
  • purpose: 목적
  • figure: 그림
  • ergonomic: 인체 공학적

=

For specialized applications, input to a graphics application may come from a set of buttons, dials, or switches that select data values or customized graphics operations.

전문적인 응용 프로그램의 경우, 그래픽 응용 프로그램에 대한 입력은 버튼, 다이얼 또는 데이터 값이나 커스터마이즈된 그래픽 작업을 선택할 수 있는 스위치로 구성될 것입니다.

Figuer 2-41 gives an example of a button box and a set of input dials.

그림 2-41은 버튼 박스와 입력 다이얼의 집합에 대한 예시를 보여줍니다.

Buttons and switches are often used to input predefined functions, and dials are common device for entering scalar values.

버튼과 스위치는 종종 미리 정의된 기능을 입력하는 데 사용되고, 다이얼은 스칼라 값 입력을 위한 일반적인 장치입니다.

Real numbers within some defined range are selected for input with dial rotations.

다이얼의 회전으로 입력되는 값은 일부 범위가 지정된 실수로 선택됩니다.

Potentiometers are used to measure dial rotations, which are then converted to deflection voltages for cursor movement.

전위차계는 이 다이얼이 얼마나 회전하였는지를 측정하는 데 사용되며, 커서를 움직이기 위한 편향 전압으로 변환됩니다.

  • specialized: 전문적인
  • Potentiometers: 전위차계
  • deflection: 편향
  • voltages: 전압

Mouse

A mouse is small hand-held box used to position the screen cursor.

마우스는 화면 위 커서를 움직이기 위해 사용되는 소형의 작은 상자입니다.

Wheels or rollers on the bottom of the mouse can be used to record the amount and direction of movement.

마우스 밑에 달려있는 휠이나 롤러를 사용해 움직인 양과 방향을 기록할 수가 있습니다.

Another method for detecting mouse motion is with an optical sensor.

마우스가 움직인 것을 감지하는 또 다른 방법은 광학 센서입니다.

For these systems, the mouse is moved over a special mouse pad that has a grid of horizontal and vertical lines.

이 시스템에서, 마우스는 수직선과 수평선의 격자로 되어있는 특수한 마우스 패드 위를 움직이는 것입니다.

The optical sensor detects movement across the lines in the grid.

격자 위의 선을 가로지르는 이동을 광학 센서가 감지하는 것입니다.

  • hand-held: 소형의
  • optical sensor: 광학 센서
  • detecting: 감지하는
  • across: 가로지르는

=

Since a mouse can be picked up and put down at another position without change in cursor movement, it is used for making relative changes in the position of the screen cursor.

커서의 움직이 없이 다른 위치에 마우스를 들었다가 내려놓을 수 있기 때문에, 상대적으로 화면의 커서를 변경하는 데 사용되기도 합니다.

One, two, or three buttons are usually included on the top of the mouse for signaling the execution of some operation, such as recording cursor position or invoking a function.

보통 마우스 상단에는 커서의 위치를 기록하거나 함수 호출이 실행되었음을 알리기 위해 하나, 둘 또는 세 개의 버튼이 포함되어 있습니다.

Most general-purpose graphics systems now included a mouse and a keyboard as the major input devices, as in Figs. 2-1, 2-33 and 2-34.

그림 2-1, 2-33 그리고 2-34에 나와있듯이, 대부분의 범용 목적의 그래픽 시스템은 이제 마우스와 키보드가 주요 입력 장치로 포함되어 있습니다.

  • since: ~때문에
  • relative: 상대적인
  • invoking: 호출하다
  • signaling: 알리다
  • most: 대부분의
  • major: 주요의

=

Additional devices can be included in the basic mouse design to increase the number of allowable input parameters.

허용된 입력의 수를 늘릴 수 있도록 기본적인 마우스 디자인에 부가 장치를 포함할 수 있습니다.

The Z mouse in Fig. 2-42 includes three buttons, a thumbwheel on the side, a trackball on the top, and a standard mouse ball underneath.

그림 2-42의 'Z 마우스'에는 총 세 개의 버튼, 측면의 썸휠, 상단의 트랙볼, 하단의 표준 마우스 볼이 포함되어 있습니다.

This design provides six degrees of freedom to select spatial positions, rotations, and other parameters.

이 디자인은 공간 위치, 회전 그리고 기타 매개변수를 선택할 수 있는 여섯 개의 자유도를 제공합니다.

With the Z mouse, we can pick up an object, rotate it, and move it in any direction, or we can navigate our viewing position and orientation through a three-dimensional scene.

'Z 마우스'를 사용하면 물체를 들어 회전시키거나, 아무 방향으로 움직이거나 또는 3-차원 장면을 통해 우리의 뷰 위치와 방향을 조종할 수가 있습니다.

Applications of the Z mouse include virtual reality, CAD, and animation.

'Z 마우스'의 응용 프로그램은 VR, CAD 그리고 애니메이션이 포함됩니다.

  • Additional: 부가적인
  • underneath: 하단의
  • freedom degrees: 자유도
  • spatial: 공간의
  • navigate: 조종하다
  • origentation: 방향

Trackball and Spaceball

As the name implies, a trackball is a ball that can be rotated with the fingers or palm of the hand, as in Fig. 2-43, to produce screen-cursor movement.

이름에서 알 수 있듯, 트랙볼은 그림 2-43에서 볼 수 있듯이 화면-커서를 손가락이나 손바닥으로 움직일 수 있도록 하는 볼을 의미합니다.

Potentiometers, attached to the ball, measure the amount and direction of rotation.

볼에 붙여진 전위차계가 회전의 방향과 양을 측정합니다.

Trackballs are often mounted on keyboards (Fig. 2-15) or other devices such as the Z mouse (Fig. 2-42).

트랙볼은 종종 키보드나 Z 마우스같은 다른 장치에 장착됩니다.

  • palm of the hand: 손바닥
  • attached: 붙여진
  • mounted: 장착되다
  • implies: 함축하다

=

While a trackball is a two-dimensional positioning device, a spaceball (Fig. 2-45) provides six degrees of freedom.

트랙볼은 2-차원 위치 변환 장치이지만, 스페이스볼은 여섯개의 자유도를 제공합니다.

Unlike the trackball, a spaceball does not actually move.

트랙볼과는 달리, 스페이볼은 실제로 움직이지는 않습니다.

Strain gauges measure the amount of pressure applied to the spaceball to provide input for spatial positioning and orientation as the ball is pushed or pulled in various directions.

스트레인 게이지는 다양한 방향으로 공이 밀리거나 당겨질 때 공간 위치와 방향을 제공하기 위해 스페이스볼에 가해지는 압력의 양을 측정헙니다.

Spaceballs are used for three-dimensional positioning and selection operations in virtual-reality systems, modeling, animation, CAD, and other applications.

스페이스볼은 VR, 모델링, 애니메이션, CAD 및 기타 응용 프로그램에서 3-차원 공간과 방향을 선택할 때 사용됩니다.

  • while: ~동안
  • unlike: ~와 달리

Joysticks

A joystick consists of a small, vertical lever (called the stick) mounted on a base that is used to steer the screen cursor around.

조이스틱은 화면의 커서를 조종하는 데 사용되는 (스틱이라고 불리는) 작은 수직 레버로 이루어져 있다.

Most joysticks select screen positions with actual stick movement; others respond to pressure on the stick.

대부분의 조이스틱은 실제 스틱의 움직임으로 화면 위치를 결정한다; 또는 스틱의 압력에 대응되는.

Figure 2-44 shows a movable joystick.

그림 2-44는 움직일 수 이쓴ㄴ 조이스틱을 보여준다.

Some joysticks are mounted on a keyboard; others function as stand-along units.

일부 조이스틱은 키보드에 장착되어있다; 나머지는 독립적인 장치로 작동한다.

  • consists: 이루어져 있다
  • steer: 키
  • respond to: 대응되는
  • function: 기능, 작동

=

The distance that the stick is moved in any direction from its center position corresponds to screen-cursor movement in that direction.

스틱이 중심으로부터 어떤 방향으로든 움직이는 거리는 해당 방향으로의 화면-커서의 움직임에 해당합니다.

Potentiometers mounted at the base of the joystick measure that amount of movement, and spring return the stick to the center position when it is released.

조이스틱에 장착된 전위차계는 움직인 양을 측정하고, 스틱이 풀리면 값을 다시 되돌립니다.

One or more buttons can be programmed to act as input switches to signal certain actions one a screen position has been selected.

하나 또는 여러 버튼은 입력된 스위치의 신호에 따라 선택된 동작을 알리도록 프로그래밍 될 수 있습니다.

  • correspond to: ~에 해당하다
  • certain: 확실하다고 생각되는

=

In another type of movable joystick, the stick is used to activate switches that cause the screen cursor to move at a constant rate in the direction selected.

다른 유형의 이동식 조이스틱에서, 스틱은 화면 커서가 선택된 방향으로 일정한 속도로 움직이게하는 스위치를 활성화시키는 데 사용됩니다.

Eight switches, arranged in a circle, are sometimes provided, so that the stick can select any on of eight directions for cursor movement.

원 안에 배치 된 8 개의 스위치가 제공되어 스틱이 커서 이동을 위해 8 방향 중 하나를 선택할 수 있습니다.

Pressure-sensitive joysticks, also called isometric joystics, have a nonmovalble stick.

'아이소 메트릭 조이스틱'이라고도하는 압력 감지 조이스틱은 움직이지 않는 막대기가 있습니다.

Pressure on the stick is measured with strain gauges and converted to movement of the cursor in the direction specified.

스틱의 압력은 스트레인 게이지로 측정되고 지정된 방향으로 커서의 움직임으로 변환됩니다.

그외기타

Region code

용어들

  • Simulate: 가상으로 구동

  • Emulate: 실제 데이터를 이용해 구동

  • World Coordinate: 실제 세계에서의 coord

  • Normalized Coordinate: 규격화된 coord

  • Device Coordinate: 출력 시의 디바이스 coord

언어의 기본적인 사항들

언어론을 왜 배우는가?

여섯 가지 이유가 있음

  • 표현 능력 향상

    • 언어마다 제어 구조, 데이터 구조, 추상화 유형을 제한함 => 알고리즘 제한
    • 때문에 하나의 언어에만 fit 된 경우, 표현 능력이 그 언어의 틀에 박힐 수 있음
  • 적절한 언어 선택 가능

    • 아는 언어만 사용하려는 경향이 실제로 존재함
    • 언어의 특징에 대해 이해하다 보면, 문제에 적절한 언어를 선택할 수 있겠지
  • 새로운 언어 습득 능력 향상

    • 주력으로 사용되는 언어는 계속 바뀜
    • PL의 essential한 개념을 파악하고 있으면, 새로운 언어를 배우더라도 어떤 개념들이 녹아들었는지 쉽고 빠르게 파악이 가능
      • 가령 JAVA의 OOP 라던가
  • 구현에 대해 더 깊은 이해 가능

    • 언어가 설계된 의도대로 사용이 가능
    • 언어 구조가 어떻게 실행될지, 어떤 결과를 가질지 이해 가능
  • 이미 알고 있는 언어에 대해서도 더 나은 사용이 가능

    • 이전에 몰라서 사용하지 못했던 특성에 대해서 사용이 가능
  • 전반적인 전자계산분야에 대한 이해 향상

    • 이 언어가 어떠한 특징을 가졌길래 왜 사용되었는지 이해할 수 있다거나

프로그래밍 영역

다양한 분야가 있음

  • 과학응용: Fortran (1956)

    • 간단한 데이터 구조, 그러나 상당한 양의 부동소수점 산술연산
    • 보통 배열/행렬 데이터 구조가 사용됨
    • 보통 루프/선택문 제어 구조가 사용됨
    • 효율성 따짐
  • 사무응용: COBOL (1960)

    • 상세한 보고서 위한거
    • 십진수, 문자 데이터의 정확한 표현/저장
    • 십진수 산술연산
  • 인공지능: LISP (1959), Scheme (1975), Prolog (1970)

    • 기호(연산자) 위주의 계산
      • 추상화시켰다고
      • 많은 융퉁성을 갖도록 구현되었겠지
    • 배열 과 같은 구조를 주로 사용
    • 재귀/조건식 으로 주로 제어 구조 사용
  • 시스템 프로그래밍: C (1978)

    • HW 제어할거니까 low한 특징을 갖는 언어를 사용해야겠지
    • 효율적이고, 깐깐한 C를 사용했다.
  • 웹: HTML, JS, PHP 등

    • 다양한 언어들 있음

언어 평가기준

개발, 유지보수에 언어가 미치는 영향이 무엇이 있는가

https://i.imgur.com/ZVZ2mgS.jpg

  • Readability (판독성)

    • 프로그램을 얼마나 쉽게 이해할 수 있는가
    • 유지보수에도 중요하다
    • Simplicity (단순성)
      • 규모가 크면(단순하지 않으면) 일부만 배우려는 경향을 보임
        • 서로 아는게 달라질 수가 있겠지
      • 특정 연산이 한 가지 이상 방법으로 실행되면 약간 복잡해지겠지
      • 또는 연산자가 두 개 이상의 의미를 가져도 약간 복잡해지겠지
        • operator overloading
      • 그렇지만 지나치게 단순해도 문제다. 적절히 단순해지도록 하자
    • Orthogonality (직교성)
      • 독립된 기본 구조들이 조합되어 새로운 제어 구조(if 이런거)나 데이터 구조(array 이런거)를 생성할 수 있는 능력
        • 물론 합리적이고 적법하고 의미가 있어야겠지
      • 너무 떨어져도 문제고 너무 높아도 문제다.
        • 떨어지면 예외를 하나하나 알고 있어야 하고
          • 되는줄 알았는데 안되는 애가 생길 수 있음
        • 너무 높으면 가능한 조합이 너무 많아짐
          • 원래 되면 안되는데 되는 애가 생길 수 있음
      • C는 array를 반환할 수 없다는 점에서 Un-Orthogonality를 갖는다고 할 수 있음
    • Data types (데이터 타입)
      • 언어가 충분히 타입을 제공하거나
      • 타입을 만들 수 있는 장치를 제공하면 Readability에 큰 도움이 된다.
    • Syntax design (구문 설계)
      • 특수어 (Special word)
        • while, class, for 이런애들이 어떤 구조를 갖는가
        • 가령 if (cond) { ... }로도 될 수가 있고, if (cond) ... end if 이렇게 될 수도 있고
        • 물론 이런 이름들이 변수명으로 사용 가능하면 개복잡해지겠지
      • 형식과 의미
        • 문장의 형태가 그 의미를 적절히 나타내는지
        • 형태는 동일한데 위치에 따라 의미가 달라지면 Readability가 좀 떨어지겠지
          • C에서의 static같이 말이지
          • global variables에 대해 static을 붙이면 locally하게 바뀌어서 여러 파일에서 해당 global variables를 declaration할 수 없고
          • local variables에 대해 static을 붙이면 globally하게 바뀌어서 heap에 할당되어 전역 변수처럼 사용이 가능하고
  • Writability (작성력)

    • 언어가 문제에 대해 얼마나 쉽게 사용(작성)될 수 있는가
    • Readability에 영향을 끼치는 특성 대부분이 Writability에도 영향을 끼침. 이는 작성된 프로그램을 종종 다시 읽기 때문.
    • 단순성/직교성
      • 적은 수의 기본 구조들의 조합으로 일관된 규칙을 갖는 언어가 더 좋음
      • 언어가 너무 크고 복잡하면 모르는 특징을 우연찮게 오용할 가능성이 있음
    • Support for abstraction (추상화 지원)
      • 복잡한 데이터 구조나 연산을 간략히 정의하여 사용할 수 있는 것을 의미
      • 프로세스(제어) 추상화: if, for, while, function 등
      • 데이터 추상화: variables, array 등
    • Expressivity (표현력)
      • 연산자, 미리 정의된 함수의 개수/능력을 의미
      • 표현력이 높으면 상대적으로 편리해짐
        • 가령 APL의 연산자와 같이
  • Reliability (신뢰성)

    • 모든 조건 하에서 주어진 명세대로 수행되는 경우 Reliable하다고 함
    • 마찬가지로 Readability와 Writability 둘 다 Reliability에 영향을 끼침
    • 판독성/작성력
      • 판독이 어려운 프로그램은 올바르게 작성하거나 수정하기 어려움
      • 쉽게 작성할 수 있다면 올바르게 동작할 가능성이 높음
    • Type checking (타입 검사)
      • 컴파일 또는 run-time에서 타입 오류 검사 가능
      • C(KnR)에서는 매개변수 타입을 제대로 검사해주지 않았기에 탐지가 힘든 수 많은 오류가 발생되었음
    • Exception handling (예외 처리)
      • run-time 중 프로그램의 오류를 올바르게 수정하고 끊김없이 계속 진행할 수 있는 능력
    • Restricted aliasing (제한된 별칭)
      • 동일한 기억장소에 대해 2개 이상의 다른 접근(참조) 방법을 가질 수 있음을 의미
      • 보통 위험한 특징이라고 함
  • Cost (비용)

    • 교육 비용
    • 작성 비용
      • 특정 목적에 얼마나 부합한가
    • 컴파일 비용
      • 얼마나 optimization이 되는가
      • optimization이 강력할수록 컴파일 비용이 올라갈 것임
    • 실행 비용
      • 얼마나 optimization이 되는가
      • optimization이 강력할수록 실행 비용이 낮아질 것임
    • 언어 구현 시스템 비용
      • 당연히 컴파일러가 공개된 경우 구현 시스템 비용이 낮아질 것임
    • 신뢰성 부족에 따른 비용
      • 원자력 이런곳에 쓰는 프로그램의 신뢰성은 굉장히 중요할 것임. 이런 것에 따라오는 비용을 말함
    • 유지보수 비용
    • : Cost에 기여하는 특징들 중 개발/유지보수/신뢰성, 이 세 요소가 큰 영향을 끼침
      • 얘네들은 언어 평가 요소 중 Writability와 Readability에 의해 결정됨
  • 그 외 평가요소

    • Portability (이식성)
      • 코드변경 없이 다른곳에서도 사용이 가능한가
      • 언어의 표준화가 잘 되어있을수록 이식성이 높겠지
    • Generality (일반성)
      • 응용 분야가 광범위한가
    • Well-definedness (분명성)
      • document가 완전하고 정밀한가

언어 설계 절충

이러한 평가기준은 설계에 대한 어떤 framework를 제공함. 언어 설계 시 타협과 절충이 필요.

설계 절충 예.

  • 신뢰성/실행비용: 배열 원소에 대한 scope 검사

    • Java에서는 검사하지만 C에서는 안 함
    • Java는 느리지만 신뢰적 / C는 빠르지만 비신뢰적
  • 판독성/작성력: APL의 연산자

    • APL은 다양하고 강력한 연산자를 지원해 작성력이 굉장히 높음
    • 그러나 함축된 의미를 알고 있어야 하기에 판독성이 굉장히 떨어짐
  • 적성력/신뢰성: C의 Pointer

    • C에서의 pointer는 강력하고 융퉁성이 있어 작성력이 굉장히 높음
    • 다만, 잘 써야 함. 때문에 비신뢰적

언어 설계에 미친 영향

  • 컴퓨터 구조

    • 폰 노이만 구조
      • 명령어/데이터가 메모리에 저장되 있는 구조
      • 메모리가 CPU부터 분리되어 있음
      • CPU의 인출-해석-실행 세 주기로 순차적으로 실행됨
        • 메모리에서 인출하고
        • 이를 해석한 뒤
        • 실행하는 것
    • 명령형 언어 (imperative languages)
      • 폰 노이만 구조에 기반한 언어를 의미
      • 변수, 배정문, 반복문이 있다는 특징이 있음
        • 참고로 반복문은 그저 하나의 명령이기에 빠르게 실행됨
      • 이 때 Program Counter 라는 레지스터를 이용해 다음 명령어 주소를 기억하고 있음
    • 함수형 언어(= 적용 언어)
      • 명령형 언어에서 사용되는 변수/배정문/반복문을 사용하지 않고 이루어 질 수 있음
      • 명령형 언어에 비해 비효율적이긴 함
  • 설계 방법론

    • 초반(50, 60)에는 HW 효율성이 중시됨
      • SW는 단순 응용 프로그램
    • 60년대 후반에는 SW 효율성이 중시됨
      • 판독성이나 더 나은 제어 구조를 생각하게 됨
      • 구조를 가진 프로그래밍을 생각하게 됨
        • top-down design
        • stepwise refinement (단계적 세분화)
      • 타입 검사나 제어구조 지원
        • 과도한 goto문은 제한
    • 70년대 후반
      • 프로세스-지향에서 데이터-지향으로 이동
      • 데이터 추상화 지원
      • SIMULA 67
    • 80년대 중반
      • OOP
        • 데이터 추상화(캡슐화)
        • 상속
        • 동적 메서드 바인딩(다형성)
      • Java, C++, C#, F#, Prolog++
    • 최근
      • 프로세스-지향에서 동시성(thread같은)으로 가려는 경향
      • Java, C#

언어 부류

  • 명령형 언어 (imperative lang)

    • 폰 노이만 구조에 기반된 언어들
    • 변수/배정문/반복문이 있음
    • OOP, Visual, Script 언어는 얘에 속함
  • 함수형 언어 (functional lang)

    • 수학의 함수에 기반된 언어들
      • 의미를 명확하게 정의할 수 있음
    • 특정 컴퓨터 구조에 독립적
    • 변수/배정문/반복문 없음
    • recursion 사용
  • 논리형 언어 (logic lang)

    • 논리 기호에 기반된 언어들
      • 규칙-기반 언어의 한 예
    • 제어 구조가 없음
    • 특정 순서 없이 규칙이 기술됨
    • 기술된 규칙이 실행 순서를 선택
  • 마크업/하이브리드 언어

    • 마크업은 HTML 말하는거고
    • 하이브리드는 말 그대로 여러개 섞는거. JSTL 같은거

Layered view

https://i.imgur.com/sBOHEeX.png

여기서 OS, 언어 구현 시스템 이런애들이 가장 상층부를 구성함.

가령 가상 C 컴퓨터의 경우 컴파일러가 달라지는 것에 대해 OS가 이를 구현해줘서 이식성을 높이듯이 말이지.

언어 구현 방법

  • 컴파일 (Compiler implementation)

    • 소스를 기계어로 번역하는 것
    • linking / loading
      • 모듈화했을 때, 모듈 합치는거
      • 이를 모두 합한것을 load module, executable image 라고 함
    • 폰 노이만 병목 (von Neumann bottleneck)
      • 기억공간에 저장된 명령과 프로세서간의 연결을 의미
      • 이 연결 속도가 프로세서의 속도보다 느리기에
      • 전체적으로 컴퓨터 속도를 제한하는 요소가 됨
    • https://i.imgur.com/WC3jMnx.png
      • Lexical analyzer (어휘 분석기)
        • 문자들을 어휘 단위(식별자/특수어/연산자/구분자)로 모음
        • 당연히 주석은 무시됨
      • Syntax analyzer (구문 분석기)
        • 어휘 단어들에 대해 parse tree 라는 계층적 구조를 생성
          • 실제로 tree 자체가 생성되기보다는 관련 정보가 생성됨
      • Intermediate code generator (중간 코드 생성기)
        • source code(원시 코드)와 ML 사이의 중간 수준에 해당되는 언어로 표현된 프로그램 생성
        • 타입오류 이런 오류 검사
      • Symbol table (심볼 테이블)
        • 컴파일 과정에서 DB로 사용되는 애
        • 주로 user-defined namespace에 대한 타입과 속성의 정보를 저장
      • Code generator (코드 생성기)
        • 여러 과정 거쳐 나온 중간 코드를 ML로 번역
  • 순수 해석 (Pure interpretation)

    • 번역 없이 인터프리터에 의해 해석되며 실행되는 방식
    • 단순한 구조에 적합
      • LISP, JS, PHP, ...
    • 장점
      • 구현, 디버깅 용이
    • 단점
      • 느림
        • 실행시마다 계속 해석해야하니
      • 더 많은 기억공간 요구 (source 자체니까)
    • https://i.imgur.com/DJSHocL.png
  • 혼합형 구현 시스템 (Hybrid implementation sytem)

    • 위 둘을 절충한 것
      • 먼저 중간 코드(바이트 코드)로 번역한 뒤, 이를 인터프리터로 해석
    • Perl, 초창기의 Java가 사용했음
    • 또는 둘 다 제공하기도 함
      • 인터프리터는 개발/디버깅 시에 사용
      • 완성된 경우 컴파일
    • https://i.imgur.com/ZJQEVy3.png
  • JIT (Just-In-Time)

    • 먼저 중간 코드로 번역한 뒤, 해당 메서드 호출 시마다 중간 코드를 ML로 번역하는 것
      • 이렇게 번역된 애는 caching함
      • 때문에 delayed compiler라고도 함
    • 현재의 Java, .NET

사전처리기

번역 직전에 처리하는 애들. C의 매크로를 예로 들 수 있음

#define max(A, B) ((A) > (B) ? (A) : (B))

x = max(2 * y, z / 1.73);

x = max(2 * y++, z / 1.73);

이런애들은 실행 직전에 다음과 같이 그대로 바뀜. 때문에 위에서 bracket 쳐준거.

x = ((2 * y) ? (z / 1.73) ? (2 * y) : (z / 1.73));

x = ((2 * y++) ? (z / 1.73) ? (2 * y++) : (z / 1.73));

두 번째 예제같은 경우를 주의하자. 그대로 바뀌는 것이기에 ++도 그대로 둘 다 들어감.

프로그래밍 환경

개발 도구 집합을 으미. IDE 이런거.

  • 파일시스템, 텍스트 편집기, 링커, 컴파일러, 디버거, ...

복습문제

  1. 프로그래머가 프로그래밍 언어를 실제로 설계하지는 않았을지라도, 언어 설계에 대한 배경을 갖는 것이 유용한 이유

    • 생각을 표현할 수 있는 능력 향상
      • 이해에 따른 추상화의 깊이가 달라짐
    • 적합한 언어를 선택할 수 있는 배경 향상
      • 언어의 기본적인 개념들을 습득하면 새롭게 배우는 언어가 어떻게 설계되었는지 파악이 용이함
    • 구현의 중요성에 대해 더 깊은 이해 가능
      • 언어가 설계된 의도를 이해하여 더 지능적으로 언어를 사용 가능
    • 이미 알고 있는 언어에 대해 더 깊은 이해 가능
      • 이전에 사용하지 않았던 특징에 대한 사용 가능
    • 전반적인 전자계산분야에 대한 이해 가능
      • 2번 문제에서 좀 더 자세히 나옴
  2. 프로그래밍 언어 특성을 아는 것이 전반적인 전산학 분야에 어떻게 도움이 되는가

    • 각각의 언어에 대한 특징을 이해함으로써 왜 이러한 방향으로 설계되었고, 사용되었는지 이해할 수 있기에 전반적인 전산학 분야에 도움이 될 수 있다.
  3. 지난 50년 동안 과학 계산 분야를 주도해온 프로그래밍 언어

    • Fortran (1956)
  4. 지난 50년 동안 사무 응용 분야를 주도해온 프로그래밍 언어

    • COBOL (1960)
  5. 지난 50년 동안 인공지능 분야를 주도해온 프로그래밍 언어

    • LISP (1959)
  6. UNIX의 대부분이 어떤 언어로 작성되었는가

    • C 언어
  7. 언어가 너무 많은 특징을 갖는 것에 따른 단점은 무엇인가

    • Simplicity(단순성)가 낮아질 경우 Readability(판독성)가 떨어져 자칫하면 작성자와 판독자가 서로 알고 있는 부분이 달라지는 상황이 발생될 수 있음
  8. 사용자-정의 연산자 중복이 어떻게 프로그램의 판독성에 해를 끼치는가

    • 분별력 있게 사용하지 않으면 잘못 사용될 우려가 있음
    • 가령 matrix A, B에 대해 A + B가 matrix addition이 아닌, dot product을 의미한다면 사용에 혼란이 생길 수 있음
  9. C의 설계에서 직교성이 결핍된 한 가지 예

    • Array를 return type으로 선언할 수 없음
  10. 주요 설계 기준으로 직교성을 사용한 언어

    • ALGOL68
    • 모든 구조가 어떠한 데이터 타입이기 때문에, 조합이 기하급수로 증가하게 됨. 때문에, 무의미한 복잡성을 초래하게 됨.
  11. -

  12. 프로그래밍 언어의 어떤 구조가 프로세스 추상화를 제공하는가

    • 프로세스, 데이터
  13. 프로그램이 신뢰성이 있다는 것은 무엇을 의미하는가

    • 모든 조건 아래에서 주어진 명세대로 동작하는 경우 Relable하다고 함
  14. 부프로그램의 매개변수에 대한 타입 검사가 왜 중요한가

    • 들어온 인자에 대한 타입 검사가 제대로 이루어져야 올바른 값이 반환될 것이기 때문
  15. 별칭이란 무엇인가

    • 하나의 변수에 2개 이상의 다른 참조로 접근할 수 있는 것을 의미
  16. 예외 처리란 무엇인가

    • 오류(예외)가 발생되어도 멈추지 않고 올바르게 수정 후 계속 진행하는 것을 의미
  17. 판독성이 작성력에 왜 중요한가

    • 프로그램을 작성 하는 과정 안에서, 프로그래머는 작성된 코드를 다시 판독 하기 때문
  18. 주어진 언어에 대한 컴파일러 비용이 그 언어의 설계와 어떻게 관련되는가

    • 만일 언어 설계에서 run-time type check에 대해 많은 시간을 소요하도록 설계되었다면, 컴파일러 성능과는 관계없이 컴파일러 비용이 높게 나올 것임
  19. 지난 50년 동안 프로그래밍 언어 설계에 가장 크게 영향을 미친 것은 무엇인가

    • 폰 노이만 구조
  20. 언어 구조가 폰 노이만 컴퓨터 구조에 기반하고 있는 프로그래밍 언어 부류의 이름은 무엇인가

    • 명령형 언어
  21. 1970년대 소프트웨어 개발에 관한 연구 결과로서 발견된 프로그래밍 언어의 두 가지 결함은 무엇인가

    • 타입 검사의 불완전성
    • 제어문(goto와 같은)의 부적절성
  22. 객체-지향 프로그래밍 언어가 갖는 세 가지 기본적 특징은 무엇인가

    • Encapsulation (캡슐화)
    • Inheritance (상속)
    • Dynamic method binding (동적 메서드 바인딩)
  23. 객체-지향 프로그래밍 언어의 세 가지 기본적 특징을 지원한 첫 번째 언어는 무엇인가

    • Smalltalk
  24. 서로간에 직접 상충되는 두 가지 언어 설계 기준의 예는 무엇인가

    • 신뢰성 <-> 실행비용
      • JAVA에서는 적절한 scope에 속하는지 검사함
      • 그러나 C에서는 그런거 없음
      • => JAVA는 느리지만 신뢰적, C는 빠르지만 비신뢰적
    • 판독성 <-> 작성력
      • APL은 다양하고 강력한 연산자들을 제공하기에 작성력이 굉장히 뛰어남
      • 그러나 함축된 의미를 이해하고 있어야 하기에, 판독성이 굉장히 떨어짐
  25. 프러그래밍 언어를 구현하는 세 가지 일반적인 방법

    • 컴파일 구현
    • 순수해석 (pure interpretation)
    • 혼합형 구현 시스템
  26. 컴파일러와 순수 인터프리터 중에서 어느 것이 더 빠르게 실행되는 프로그램을 생성하는가

    • 컴파일러
  27. 컴파일러에서 심볼 테이블의 역할은 무엇이낙

    • 컴파일 과정에서 DB로 사용됨
    • 주로 사용자 정의 이름에 대한 타입과 속성의 정보가 들어감
  28. 바이트 코드의 유용성

    • pure interpretation보다 빠름
    • 바이트 코드 인터프리터와 연관된 run-time system을 갖는 기계에 대해서 이식성을 제공함
  29. 혼합형 구현 시스템이란

    • 고급 언어 프로그램을 먼저 해석이 용이한 중간언어(바이트코드)로 번역(=>컴파일)한 뒤, 이를 해석(=>인터프리터)하는 방식
    • 원시 프로그램 => 어휘 분석 => 구문 분석 => 중간코드 생성 => 인터프리터 => 결과

구문과 의미론

chapter2는 생략됨. 그러나 한 번 읽고 정리해보자.

문법과 유도

각각 다음과 같음

  • 문법
    • 언어를 정의하기 위한 생성장치
  • 유도 (derivation)
    • 문장들은 일련의 규칙을 통해 생성되는데, 이를 derivation 이라고 함

e.g 문법

<program> -> begin <stmt_list> end
<stmt_list> -> <stmt>
                | <stmt>; <stmt_list>
<stmt> -> <var> = <expression>
<var> -> A | B | C
<expression> -> <var> + <var>
                | <var> - <var>
                | <var>

문법 G를 통해 begin A = B + C; B = C end의 문장이 생성되는지 볼 수 있다.

e.g 유도

다음과 같이 유도 과정을 통해 가능함

<program>   => begin <stmt_list> end
            => begin <stmt>; <stmt_list> end
            => begin <var> = <expression>; <stmt> end
            => begin A = <var> + <var>; <var> = <expression> end
            => begin A = B + C; B = <var> end
            => begin A = B + C; B = C end       // accept!

참고로 실제 유도는 한 번에 하나의 N 씩만 진행한다는 것을 알아두자. 위의 유도는 좀 짧게 진행한 것이다.

아무튼 위 문장이 생성되었으니, 문법 G는 위의 문장을 생성할 수 있다고 한다.

유도

문법의 시작 기호(S)로 시작되며, 각각의 단계를 A => B로 표현함. 참고로 말했듯이 한 번에 하나의 N을 해당 N의 definition으로 대체해야 한다.

이렇게 각 단계의 문자열을 sentential form(문장 형태) 라 하고, N이 존재하지 않을 때 까지 유도를 진행하며, 이렇게 해서 나온 것을 sentence(문장) 이라고 한다. 다르게 말하자면, sentence는 T만을 포함하고 있다고도 할 수 있음.

예제 하나 더 보자. 위 문법으로부터 begin A = B + C; end 문장은 생성이 가능할까? 답부터 보자면, 생성되지 않는다.

<program>   => begin <stmt_list> end            // 여기서 세미콜론이 있어야 하기에 다음과 같이 진행해야만 한다.
            => begin <stmt>; <stmt_list> end    // <stmt_list>는 반드시 expression이 되어야 하기에... reject

이러한 이유로 생성되지 않는다.

유도 방식

유도 방식은 방향에 따라 두 가지로 나뉜다.

  • leftmost derivation (최좌단)
  • rightmost derivation (최우단)

이 둘은 그냥 유도 과정에서 해당 방향의 N부터 풀라는 말이다.

<assign>    -> <id> = <expr>
<id>        -> A | B | C
<expr>      -> <id> + <exec>
            | <id> * <exec>
            | ( <exec> )
            | <id>

이 문법으로 A = B * (A + C) 라는 문장을 leftmost derivation 한다고 했을 때, 다음과 같다.

<assign>    => <id> = <expr>
            => A = <expr>
            => A = <id> * <expr>
            => A = B * <expr>
            => A = B * ( <expr> )
            => A = B * ( <id> + <expr> )
            => A = B * (A + <expr> )
            => A = B * (A + <id> )
            => A = B * (A + C)

가장 왼쪽의 N부터 풀어나갔음을 볼 수 있다. rightmost도 이와 똑같이 가장 오른쪽의 N부터 풀어나가면 된다. 굳이 하지는 않겠음.

구문과 의미론

언어 기술을 위한 방법은 다음 두 개가 있음

  • Syntax (구문): 식, 문장, 프로그램 단위의 형태 또는 구조에 대한 형식
  • Semantics (의미론): 식, 문장, 프로그램 단위의 의미

형식이냐 의미냐 이 차이가 있다. 가령 C의 if를 예로 들어보자면...

// syntax

if (/* condition */) /* statement */

여기서 if문의 semantic은 /* condition */true 일 때, <statement>가 실행된다 는 것이 된다.

여기서 syntax는 범용적인 표기법이 존재한다. 뭐 아무튼 이런 애들이 모여 언어의 정의가 가능함.

참고로 언어는 여러 어휘항목들이 모여 정의된 것인데, 어휘항목을 구현하는 목록을 token 이라고 함. 이는 위에서 다루긴 했으나...(chapter 1)

언어의 형식적인 의미

언어는 두 가지 방식으로 형식적으로 정의가 가능

  • 인식기
    • 언어 L의 알파벳으로 구성된 입력 문자열을 읽어들여 L에 속하는지를 판단(accept/reject)하는 인식 장치 R을 고려하여야 한다.
    • 이런 RL의 모든 문자열을 수락(accept)하면, RL의 기술이라 할 수 있다.
    • ex) 컴파일러의 syntax 분석: 해당 어휘가 해당 언어에 속하는가? 를 판단.
  • 생성기
    • 문장들을 생성하는데 사용될 수 있는 장치
    • 특정 문장의 굼누이 올바른지를 판단하기 위해 생성기로 생성된 문장과 해당 문장을 비교해 판단할 수도 있다.

구문 기술의 형식적인 정의

  • Context-free grammers (문맥-자유 문법)
    • 1950년, Noam chomsky
  • BNF (Backus-Naur form)
    • 구문 기술 표기법
    • BNF는 context-free grammer와 동일(비슷)함

BNF basic

프로그래밍 언어를 기수랗기 위한 언어(meta lang.)

추상화를 통해 구문 구조 유형을 표현했다. 이렇게 정의한 추상화는 구문 변수로 사용함.

추상화 여부에 따라 각각 다음으로 불린다.

  • 추상화: 논터미널, 논터미널 기호
  • 규칙에 포함된 어휘항목(token): 터미널, 터미널 기호
<if stmt>   -> if ( <logic_expr> ) <stmt>
            | if ( <logic_expr> ) <stmt> else <stmt>

여기서 LHS가 non-terminal(N), RHS가 terminal(T) 이라고 보면 된다. 정확히는 RHS는 terminal과 nonterminal로 구성됨.

보이듯이 N은 OR(|)로 구분되는 여러개의 statements를 가질 수 있다.

문법은 다음과 같다.

  • 한 개 이상의 규칙들로 구성
  • 시작은 문법에 포함된 N으로

G = (N, T, S, P)

  • G: 문법
  • N: 논터미널 집합
  • S: 시작 기호 (∈ N)
  • P: 생성 규칙 집합

참고로 다음과 같이 recursive formula 통해 list를 명시할 수도 있다.

<ident_list>    -> ident
                | ident, <ident_list>

물론 이러한 표기는 비효율적이기에 아래서 더 효율적인 표기법(EBNF)을 본다.

이렇게 BNF는 단순하지만 구문 대부분을 기술할 수가 있다.

  • list
  • 구조들의 순서
  • 깊이 제한 없는 중첩 구조
  • 연산자 우선순위
  • 결합법칙 등

강력하다.

Parse tree

문법은 계층적 구조를 갖는다는 것을 느낌상으로라도 알아챘을 것이다. 이를 표현한 것이 바로 parse tree.

가령 바로 위에서 진행했던

<assign>    -> <id> = <expr>
<id>        -> A | B | C
<expr>      -> <id> + <expr>
            | <id> * <expr>
            | ( <expr> )
            | <id>

이 문법으로 생성되는 문장 A = B * (A + C)의 parse tree는 다음과 같다.

p

모호성

위 문법을 통해 문장 A = A + B * C를 생성하는 경우, 다음과 같이 parse tree가 두 개 이상 나오는 것을 볼 수 있는데, 이러한 애들을 '모호하다(ambiguous)'고 한다.

p

p

또 하나 예를 볼까. 5 - 3 / 2 문장에 대해 다음의 문법은 모호한가?

<expr>  -> <expr> <op> <expr> | const
<op>    -> / | -

물론 모호하다. 다음 두 개의 tree가 나온다.

p

p

이런 모호성은 다음과 같이 우선순위 를 명세함으로서 모호하지 않도록 문법을 정의할 수 있다.

<expr>  -> <expr> - <term> | <term>
<term>  -> <term> / const | const

이 문법은 다음 하나의 parse tree만을 갖는다.

p

여기서 하나 알아두어야 할 것이 있는데, parse tree의 낮은 위치에 위치할수록 높은 연산자 우선순위를 갖는다 는 것이다. 다시말해 먼저 연산된다는 것. ('-' < '/')

이런 방식으로 우선순위를 표현하면, 문법의 모호성을 제거할 수 있다.

마지막으로 예제 하나만 더 보자. 우선순위가 '=' < '+' < '*' 로 표현되도록 다음의 문법을 수정해보면...

<assign>    -> <id> = <expr>
<id>        -> A | B | C
<expr>      -> <expr> + <expr>
            | <expr> * <expr>
            | ( <expr> )
            | <id>

크게 어렵지 않게 구할 수 있을 것이다.

<assign>    -> <id> = <expr>
<id>        -> A | B | C
<expr>      -> <expr> + <term> | <term>
<term>      -> <term> * <fact> | <fact>
<fact>      -> ( <expr> ) | <id>

ASsociativity

결합법칙은 다음을 의미한다.

A + (B + C) = (A + B) + C

물론 문법은 이를 올바르게 표현할 수 있다. 단, 부동소수점의 계산을 진행할 때에는 주의해야 하는데, 반드시 결합적이지는 않기 때문.

가령 정밀도가 7자리인 부동소수점이 있다고 했을 때, 다음 계산은 방향에 따라 서로 다른 결과를 갖는다.

l

왼쪽, 오른쪽 방향으로 진행되는 결과는 다음과 같다.

  • l
  • l

왜 이런 결과가 발생되는 것인가. 이유는 간단하다.

l

첫 번째의 경우 한 번에 10이 더해지기에 잘리지 않는 것이고, 두 번째의 경우 1이 잘리는 위치인 8번째 위치에 계속 더해지기 때문이다.

연산자 결합규칙

어느 연산자가 우선순위를 갖는지를 명세하는 의미적인 규칙을 바로 '결합 규칙' 이라고 한다. 이런 규칙도 물론 BNF 문법으로 표현이 가능하며, 방향에 따라 left/right recursive라 불린다.

  • left recursive: RHS의 시작 위치에 LHS가 있는 경우. 얘는 좌결합 규칙을 명세한다.
  • right recursive: RHS가 끝나는 위치에 LHS가 있는 경우. 얘는 우결합 규칙을 명세한다.

가령 다음과 같다.

<assign>    -> <id> = <expr>
<id>        -> A | B | C
<expr>      -> <expr> + <term> | <term>     // left
<term>      -> <term> * <fact> | <fact>     // left
<fact>      -> <exp> ** <fact> | <exp>      // right
<exp>       -> ( <expr> ) | <id>

모호한 if-then-else

만약 if문이 다음과 같이 정의된 경우?

<stmt>      -> <if-stmt> | <non-if-stmt>
<if-stmt>   -> if <cond> then <stmt>
            | if <cond> then <stmt> else <stmt>

모호한 문법이 될 것이다. 예를 들어 다음의 문장을 만들어 보면...

if <cond> then if <cond> then <stmt> else <stmt>

두 개의 parse tree가 뜬다.

p

p

else가 어디에 걸리는지에 따라 parse tree가 달라지기에 모호해지는 것. 이는 다음과 같이 else를 가장 가까운 then과 연결되도록 문법을 구성하면 될 것이다.

<stmt>      -> <matched | <unmatched>
<matched>   -> if <cond> then <matched> else <matched>
            | <non-if-stmt>
<unmatched> -> if <cond> then <stmt>
            | if <cond> then <matched> else <unmatched>

else는 가장 가까운 then과 연결되어야 하기에 이 둘(else와 then 사이)의 중간에 들어가는 if는 반드시 else를 갖고 있어야 할 것이다. 이러한 상황을 다루어야 하기에, else가 없는 <unmatched> stmt를 분리해야 했고, 모든 then-else 사이에는 if-then-else 형태만을 갖는 <matched> stmt가 들어가는 것이다.

어렵게 생각하지 말자. 이 문법으로는 다음 parse tree 하나만 나온다.

p

EBNF

BNF의 readability, writability를 높이기 위해 사용하는 확장된(extended) BNF 문법이다.

  • [ ]: option
    • <selection> -> if ( <expr> ) <stmt> [else <stmt>]
  • { }: list (length >= 0)
    • <ident_list> -> ident {, ident}
  • ( ): multiple options
    • <term> -> <term> (* | /) <fact>
  • +: 한 번 이상 반복
    • <comp> -> begin {<stmt>}+ end
    • 일반적으로 bracket({})과 함께 사용한다.

물론 expression은 변함없기에 BNF에서 표현 불가능한 문장은 EBNF에서 가능하다거나 뭐 이런거 ㅇ벗다.

Associative in EBNF

EBNF에서의 결합규칙은 표현되지 않는다.

  • BNF: <expr> -> <expr> + <term>
  • EBNF: <expr> -> <term> {, <term>}

BNF에서는 left recursive가 보이지만, ENBF에서는 이러한 것이 표현되지 않기에 결하법칙을 표현할 수 없는 것. 다만, 문법에서나 표현되지 않는 것이지 실제 이러한 것들을 구현한 구문 분석기에서는 내부적으로 해결한다는 것을 알아두자.

e.g. EBNF

BNF를 EBNF로 바꾸는 몇 가지 예제를 보자

// BNF
<expr>  -> <expr> + <term>
        | <expr> - <term>
        | <term>
<term>  -> <term> * <fact>
        | <term> / <fact>
        | <fact>

// EBNF
<expr>  -> <term> {(+ | -) <term>}
<term>  -> <fact> {(* | /) <fact>}

조합으로 나오는 모든 경우에 대해 성립되도록 한다. 또, recursive 하지 않도록 구현 한다.

EBNF 최근 변동사항

  • 화살표 대신 콜론(:)이 사용되고, RHS는 다음 줄에 표기함
  • option을 나타낼 때, 대괄호 대신 아래첨자 opt 를 사용함
    • 아래첨자를 나타낼 수 없으니, 여기서는 _{opt} 를 사용하도록 하겠음
  • multiple options를 나타낼 때, 소괄호 대신 one of 키워드를 사용
    • e.g. <op> -> one of + * - / < >
  • OR 표기에 사용했던 terminal 기호 | 대신, 그냥 다음줄에 표기한다.

보면 다음과 같다.

statement:
    for ( expr1_{opt}; expr2_{opt}; expr3_{opt} ) <stmt>
    if ( <expression> ) <stmt>
    while ( <expression> ) <stmt>
    // ...

굳이 해석하지 않아도 얼추 이해가 갈 것이다. 정 모르겠다면, 평소 쓰던 문법을 생각해 보자.

Set

집합의 형태로도 표현이 가능하다. 다음 문법을 보자.

G:
    <S> -> <A> a <B> b
    <A> -> <A> b | b
    <B> -> a <B> | a

여기서 문법 G가 표현할 수 있는 문장들은 다음과 같다.

l

이렇게 표현하는 방법은 간단하다.

속성 문법

EBNF의 일종이다. 얘를 사용하면 type에 대한 호환성 이런것도 편리하게 기술할 수 있음.

정적 의미론 (static semantics)

BNF로는 한계가 있다. 대표젹으로 참조 전 선언되어야 한다는 것 이런거는 bnf로는 불가능하다.

정적 의미론은 보통 type의 제한사항 기술에 사용한다. 속성 문법은 이런 정적의미론의 규칙을 기술하고 올바름을 검사하는 형식적인 방법임.

다시말해, 속성 문법은 구문 뿐 아니라 의미론(정적 의미론) 모두를 기술할 수 있는 문법이라는 말. 이는 parse tree에 표기해 나타낸다.

속성 문법이란

속성 문법은 다음이 추가된 BNF다.

  • 속성 계산 함수 (attribute computation function) 가 각각의 규칙에 연관되어, 해당 규칙의 N의 속성들이 어떻게 계산되는지를 명세
    • 여기서 속성은 해당 요소가 가진 속성이라 생각하면 된다.
    • 이게 어떻게 계산되는지, 다시말해 자식에게서 올라오거나 뭐 어떻게 합성되거나를 명세하는 것.
  • 술어 함수(predicate function) 가 규칙에 연관되어 언어의 static semantics 규칙을 기술
    • 다시말해, 속성이 일치하는지를 검사한다는 말

한 마디로 N, T 같은 각각의 문법 기호에 대해 attribute 연관시키는 것이라 할 수 있다.

속성 문법 = BNF + 속성 + 속성 함수 + 술어 함수

이 때문에, Extended BNF라고도 할 수 있는 것이고, type을 기술할 수 있는 것이겠지.

Attribute set

위의 말은 좀 더 자세히 말하자면, 각각의 문법 기호(N, T) X에 속성들의 집합 A(X)가 연관(bind)되는 것이라고 할 수 있다. 여기서 A(X)는 다음 두 개의 분리된 집합으로 나타낼 수 있다.

  • S(X): parse tree의 윗 방향으로 의미 정보를 전달하는데 사용되는 합성 속성(synthesized attribute)
    • 여기서 계속 의미 정보속성 이라는 단어가 나오는데, 이들 모두 문법 기호가 어떤 의미(속성)인지를 나타내는 단어이다. 헛갈리지 말자.
    • X_0 -> X_1 ... X_n 일 때, S(X_0) = f(A(X_1) ... A(X_n))
      • 자신을 구성하는 자식 요소 X_1 ... X_n 에 의해서만 합성 속성이 결정된다 는 것 (dependency)
  • I(X): parse tree의 아래 방향과 횡 방향으로의 의미 정보를 전달하는데 사용되는 상속 속성(inherited attribute)
    • X_0 -> X_1 ... X_n 일 때, I(X_j) = f(A(X_0) ... A(X_n)), (1 <= j <= n)
      • ??? 책에 적혀있기로는 부모 요소와 형제 요소들에 의해서만 상속 속성이 결정된다고 한다.

그럼 parse tree 맨 아래 요소의 type은 어떻게 결정되냐? 얘는 Intrinsic attributes(내장 속성) 이라는 합성 속성이 bind 된다. parse tree 외부에서 가져옴.

뭐 아무튼 이러한 방식은 나중에 아래에서 예시를 보며 좀 더 이해해보고, parse tree의 모든 type이 계산되었으면 그 파스 트리는 '완전히 속성화되었다(fully attributed)' 라고 한다.

e.g. 1

만약 다음 문법에 대해 <proc_name>의 이름(name) 속성이 일치해야 하는 경우를 나타낸다면, 일반적인 BNF로는 불가능하다.

<proc_def>  ->  procedure <proc_name>
                    <proc_body>
                end <proc_name>

이는 속성 문법으로는 다음과 같이 나타낼 수 있다.

규칙:
<proc_def>  ->  procedure <proc_name>[1]
                    <proc_body>
                end <proc_name>[2]

술어 함수:
<proc_name>[1].string == <proc_name>[2].string

참고로 여기서 사용되는 대괄호([])는 언어의 일부가 아니며, 그냥 구분을 위한 인덱스이다. 이것만 주의하자.

e.g. 2

좀 더 큰 예제를 볼까. 다음 구문에 대해

<assign>    -> <var> = <expr>
<expr>      -> <var> + <var>
                | <var>
<var>       -> A | B | C

다음의 조건을 고려해야 한다면

  • 변수는 integer 또는 real 타입
  • 피연산자(<var>)의 타입은 서로 달라도 됨
    • 다를 경우 real
    • 같을 경우 해당 타입
  • 할당문에서는 두 타입이 반드시 같아야 함

이를 속성 문법으로 어떻게 나타낼 수 있을까.

먼저 각각의 타입을 나타내는 두 개의 속성을 정의해주도록 하자.

  • actual_type (실제 타입)
    • <var>, <expr>에 연관된 합성 속성
    • 변수나 식의 실제 타입인 int나 real을 저장하는 데 사용
      • 변수는 <var>의 타입
      • 식은 <expr>의 타입 (자식 노드들의 타입으로 결정되겠지)
  • expected_type (예상 타입)
    • <expr>에 연관된 상속 속성

왜 두 가지가 필요하냐고? 배정문에서 <var><expr>의 타입을 예측할 것이고(expected_type), 이 타입과 실제로 계산되는 <expr> 타입(actual_type)이 서로 동일한지 술어 함수로 계산할 것이니까 두 개가 필요하다.

이를 속성 문법으로 나타내주면 다음과 같다.

1.
구문 규칙:
    <assign> -> <var> = <expr>
의미론 규칙:
    <expr>.expected_type <- <var>.actual_type
    // <expr>의 expected type이 <var>의 actual type으로 결정됨
    // 다시말해, <expr>의 타입이 <var>의 type이라고 예상한다는 의미

2.
구문 규칙:
    <expr> -> <var>[1] + <var>[2]
의미론 규칙:
    <expr>.actual_type <-
        if (<var>[1].actual_type = int) and (<var>[2].actual_type = int)
            then int
            else real
        end if
        // <expr>의 actual type이 이러한 방식으로 결정됨을 나타냄
술어 함수:
    <expr>.actual_type == <expr>.expected_type
    // 술어 함수는 속성의 규칙을 기술한다고 했음
    // 다시말해, <expr>의 actual type과 expected type이 서로 일치해야 함을 기술

3.
구문 규칙:
    <expr> -> <var>
의미론 규칙:
    <expr>.actual_type <- <var>.actual_type
    // 따로 더해주는 것 없으면 <expr>의 actual type은 <var>의 actual type이 되겠지
술어 함수:
    <expr>.actual_type == <expr>.expected_type
    // 마찬가지로 <expr>의 actual type과 expected type은 서로 일치해야 함을 의미함

4.
구문 규칙:
    <var> -> A | B | C
의미론 규칙:
    <var>.actual_type <- look-up (<var>.string)
    // look-up 함수는 주어진 변수 이름을 이용해 심볼 테이블로부터 변수의 타입을 반환함

이렇게 나타낼 수가 있다. 여기서 구문 규칙은 나올 수 있는 문법에 대해 모두 나타낸 것임을 주의하자.

또, 다음을 잊어먹지 말자.

  • 의미론 규칙: 속성을 계산
  • 술어 함수: 규칙을 기술

이를 parse tree로 나타내보면 다음과 같다. 먼저 속성을 명시하지 않았을 때는 다음과 같고,

i

속성을 기술했을 때는 다음과 같다. 이 때, parse tree를 decorate 한다고 함.

i

<var>[2]var[1], <var>[3]<var>[2]로 생각하면 된다.

흐름(과정)은 다음과 같다.

  1. <var>.actual_type <- look-up (A): 규칙 4
  2. <expr>.expected_type <- <var>.actual_type: 규칙 1
  3. <var>[1].actual_type <- look-up (A): 규칙 4
  4. <var>[2].actual_type <- look-up (B): 규칙 4
  5. <expr>.actual_type <- int 또는 real: 규칙 2
  6. <expr>.expected_type == <expr>.actual_type: 규칙 2
    • true 또는 false

이를 완전히(실제로) 나타낸 것은 다음과 같다.

i

  1. 만약 A의 타입이 real이면,
  2. <expr>의 expected type은 real이 되고,
  3. B의 타입이 int인 경우
  4. <expr>의 actual type은 real이 된다.
  5. <epxr>의 exptected type과 actual type 둘 다 real이니 결과는 true.
    • 즉, 성립하는 문장 이라고 할 수 있다.

연습 문제


어휘/구문 분석

언어를 구현하는 세 가지 방법인 컴파일러, 인터프리터, 하이브리드 셋 모두 어휘 분석과 구문 분석 과정을 거친다.

어휘 분석

Regular expression에 기반하는 유한 오토마타. 다시말해 문자열에 대해 패턴을 유추해 어휘 항목들을 식별하고 이를 token에 bind한다.

다음과 같이 구성됨. 패턴인식과정임. 유한 오토마타.

i

여기서는 다음을 볼 수 있다.

  • 들어온 첫 번째 문자가 letter([a-z][A-Z])면 변수명이라고 생각할 수 있겠다.

    • 두 번째부터는 digit([0-9])이 나올 수도 있을 것이다.
    • 때문에, digit 또는 letter가 나올 때 까지 문자열을 받는다.
    • 문자열 받는게 끝나면 lookup table을 통해 해당 문자열이 예약어인지를 판단한다.
  • 들어온 문자열이 digit이면, 계속 digit이 나올 때 까지 문자열을 받는다.

    • 문자열 받는게 끝나면 integer를 반환한다.
    • 얘는 lookup table을 참조하지 않는데, 당연히 digit으로만 구성되는 예약어는 없기 때문.
  • 들어온 문자에 대해 패턴을 유추할 수 없으면 lookup table에서 해당 문자열을 찾아 반환한다.

뭐 이런걸 볼 수가 있다. 이를 구현한걸 보면 좀 더 이해가 될 것.

/* a lexical analyzer system for simple arithmetic expressions */
#include <stdio.h>
#include <ctype.h>

/* global variables */
int charClass;
int lexLen;
int token;
int nextToken;

char lexeme[100];
char nextChar;

FILE *in_fp, *fopen();

/* function declarations(prototype) */
void addChar();
void getChar();
void getNonBlank();
int lex(); // return token

/* character classes */
#define LETTER 0
#define DIGIT 1
#define UNKNOWN 99

/* TOKEN */
#define INT_LIT 10
#define IDENT 11
#define ASSIGN_OP 20
#define ADD_OP 21
#define SUB_OP 22
#define MULT_OP 23
#define DIV_OP 24
#define LEFT_PAREN 25
#define RIGHT_PAREN 26

// main
int main (void) {
    // open the input data file and process its contents
    if ((in_fp = fopen("front.in", "r")) == NULL) {
        printf("ERROR - cannot open file\n");
    } else {
        getChar();
        
        do {
            lex();
        } while (nextToken != EOF);
    }
}

// a function to lookup operators and parentheses and return the token
int lookup (char c) {
    addChar();

    switch (c) {
        case '(':
            nextToken = LEFT_PAREN;
            break;
        case ')':
            nextToken = RIGHT_PAREN;
            break;
        case '+':
            nextToken = ADD_OP;
            break;
        case '-':
            nextToken = SUB_OP;
            break;
        case '*':
            nextToken = MULT_OP;
            break;
        case '/':
            nextToken = DIV_OP;
            break;
        default:
            nextToken = EOF;
            break;
    }

    return nextToken;
}

// a function to add nextChar to lexeme
void addChar () {
    if (lexLen <= 98) {
        lexeme[lexLen++] = nextChar;
        lexeme[lexLen] = 0;
    } else {
        printf("Error - lexeme is too long\n");
    }
}

// a function to get the next character of input and determine its character class
void getChar () {
    if ((nextChar = getc(in_fp)) != EOF) {
        if (isalpha(nextChar)) { // is alphabet?
            charClass = LETTER;
        } else if (isdigit(nextChar)) {
            charClass = DIGIT;
        } else {
            charClass = UNKNOWN;
        }
    } else {
        charClass = EOF;
    }
}

// a function to call getChar() until it return a non-whitespace character
void getNonBlank() {
    while (isspace(nextChar)) {
        getChar();
    }
}

// a simple lexical analyzer for arithmetic expressions
int lex () {
    lexLen = 0;
    getNonBlank();

    switch (charClass) {
        case LETTER:
            addChar();
            getchar();

            while (charClass == LETTER || charClass == DIGIT) {
                addChar();
                getChar();
            }

            nextToken = IDENT;
            break;
        
        case DIGIT:
            addChar();
            getChar();

            while (charClass == DIGIT) {
                addChar();
                getChar();
            }

            nextToken = INT_LIT;
            break;
        
        case UNKNOWN:
            lookup(nextChar);
            getChar();
            break;

        case EOF:
            nextToken = EOF;
            lexeme[0] = 'E';
            lexeme[1] = 'O';
            lexeme[2] = 'F';
            lexeme[3] = 0; // null
            break;
    }

    printf("Next token is: %d, Next lexeme is %s\n", nextToken, lexeme);

    return nextToken;
}

/** INPUT

(sum + 47) / total

*/

/** OUTPUT

Next token is: 25, Next lexeme is (
Next token is: 11, Next lexeme is sum
Next token is: 21, Next lexeme is +
Next token is: 10, Next lexeme is 47
Next token is: 26, Next lexeme is )
Next token is: 24, Next lexeme is /
Next token is: 11, Next lexeme is total
Next token is: -1, Next lexeme is EOF

*/

이해가 안되면 직접 한 번 적어보자. 어렵지 않다.

Parsing problem

구문 분석 과정을 바로 parsing 이라고 한다. 상향식(bottom-up)과 하향식(top-down) 방식이 있으며, 둘 다 본다고 함.

  • 하향식(top-down): root 노드로부터 leaf 노드로까지 아랫방향으로 생성
  • 상향식(bottom-up): leaf 노드로부터 root 노드로까지 윗방향으로 생성

근데 뭐 다 건너뛰고, 바로 재귀-하강 파싱(recursive-descent parsing)으로 넘어간다. 필요없나?

Recursive-descent parsing

EBNF 기반 입력된 문자열에 대한 derivate를 top-down(하향식)으로 진행하는 것이라고 함. 재귀적으로.

이를 우리는 구현해야만 한다. 각각의 N에 대해 하나의 procedure를 작성하는 것 으로 구현이 진행된다. 뭔 말인지는 구현 진행해보면 알 것.

  • 프로시저의 호출: 해당 생성규칙(LHS)을 적용
  • 프로시저의 동작: 해당 생성규칙의 RHS를 수행

구현해보도록 하겠다. RHS의 각 T에 대해 nextToken과 비교하며 진행하는 것이다. matched 되면 parse 계속 진행하고, 실패하면 ERROR. 말했듯이 N은 해당 규칙을 구현한 프로시저를 호출하는 것이다.

참고로 lex() 함수는 위 구현에서와 같이 nextToken에 다음 토큰을 저장한다.

/*
* <expr> -> <term> {(+ | -) <term>}
*/

void expr () {
    // parse the first N
    term();

    while (nextToken == ADD_OP || nextToken == SUB_OP) {
        lex(); // set nextToken
        term(); // parse the N
    }
}

무슨 말인지 알겠는가? <expr> -> <term> {(+ | -) <term>} 이거를 그대로 구현한 것이다. 이해가 되는가?

또 하나 보자.

/*
* <expr> -> <factor> {(* | /) <factor>}
*/

void expr () {
    factor();

    while (nextToken == MULT_OP || nextToken == DIV_OP) {
        lex();
        factor();
    }
}

솔직히 뭐 어려울 것은 없다고 본다. 그럼 다음은 어떨까.

/*
* <factor> -> id | ( <expr> )
*/

void factor () {
    // 어느 RHS를 파싱할 것인지 판단
    if (nextToken == ID_CODE) { // ID_CODE := id
        lex();
    } else if(nextToken == LP_CODE) { // LP_CODE := (
        lex();
        expr(); // parse N

        if (nextToken == RP_CODE) { // RP_CODE := )
            lex();
        } else {
            // 문법 규칙 상 반드시 '('로 시작했으면 ')'로 끝나야하지
            error();
        }
    } else {
        // 아예 없는건 또 안되겠지
        error();
    }
}

약간 복잡해지긴 했으나, 핵심은 다르지 않다. N은 해당 프로시저를 실행하면 되고, 그 외의 것들(token)은 상황에 맞춰 행동하면 된다. EBNF만 구현하면 된다.


이름, 바인딩, 영역

알고있는 명령형 언어는 폰 노이만 컴퓨터 구조의 추상화... 구현한 것이지.

여기서 나타나는 변수는 다음의 특징을 갖는다.

  • type
  • scope, lifetime
  • name, address, value

참고로 여기서 나오는 언어들은 Fortran, C, C기반 언어 기준이다.

Name

설계 시 다음이 고려되어야 할 것이다.

  • 대소문자 구분되는가?
  • 특수어가 예약어 또는 키워드인가?

참고로 C기반 언어에서는 웬만하면 대소문자 구분되고 특수어 선언 불가능하지만, FF의 경우 대소문자 구분없고 int 이런걸로도 변수선언이 가능했다고 함.

Length

너무 짧으면 의미를 담기 힘들겠지. 언어에서 길이는 각각 다음과 같다.

  • FF95: 31
  • C99: 제한없으나 처음 63개만 의미가 있음
  • C#, Ada, Java: 제한없음. 모두 의미 있고
  • C++: 제한 없다고는 하나, 구현자(컴파일러?)에 따라 다르다.

Special characters

  • PHP: 모든 변수 이름이 $로 시작됨
  • Perl: 변수 이름에 특수문자 사용해 type 명세

Case sensitivity (대소문자 구분)

FF, Ada 이런애들 빼고 구분은 해주나, 가독성 떨어지게 사용될 수가 있음.

가령 Rose, ROSE, ROse 이런애들의 경우 좀 읽기 힘들겠지.

Special awords (특수어)

구문 구별 시 사용하지. 특수어는 예약어 또는 키워드 이다.

  • Keywords: 어떤 문맥 안에서만 의미를 가짐. if~else 뭐 이런거.
  • Reserved words: int 뭐 이런애들. 참고로 COBOL은 이게 개많아서 쓰기 힘들었댄다.

변수

알겠지만, 변수는 memory cell의 추상화이다. 직접 접근하는게 아니라 얘를 통해 접근하니까 맞말이지.

다음 6개 속성들로 특정된다. 이거 하나 나올듯

  • name
  • address
  • value
  • type
  • lifetime
  • scope

Name

모든 변수가 이름을 갖는가? 이름 안가지는애도 있다고는 하나 여기서는 논외.

Address

변수에 연결된 주소를 말하는 것이겠지. 참고로 종종 l-value 라고 불린댄다. LHS, RHS 이 때의 Left를 말하는것임. 왼 쪽에 있으니까. 물론 r-value 도 존재한다. 얘는 아래에서 'value' 로 나온다.

그럼 변수는 실행 중 다른 시기에 다른 주소를 가질 수 있을까? 물론 가능하다. 잘 알아두자.

int main (void) {
    func();
    func();
}

void func () {
    int a;
    printf("%p", &a);
}

여기서 a는 같은 변수이지만, 실행 시마다 다른 주소값을 가질 것이다. 이걸 말하는 것.

물론 동일한 메모리를 여러 변수가 참조할 수도 있다. 이 때문에 side-effects가 발생되는 것이겠지. 참고로 저 위에서도 다뤘는데, 이를 alias 라고 했지. 판독성을 저해하고 프로그램의 검증을 어렵게 하고... pointer를 생각해보자.

Type

해당 변수가 가질 수 있는 값의 범위와 가능한 연산의 집합을 의미한다.

Value

변수에 연결된 메모리 위치에 저장된 내용을 의미하겠지. 말했듯이 얘는 r-value 로도 불린다.

Binding

변수와 변수의 type 혹은 value 사이. 또는 연산과 기호 사이와 같은 하나의 속성과 하나의 개체 간의 연관(association) 을 의미한다.

Binding time

이 때, binding이 일어나는 시기를 binding time 이라고 함. 당연한 것이긴 한데...

binding time을 쪼개보면 여러개로 나뉜다. 각각 다음과 같다.

  • 언어 설계 시간: 연산자 기호와 연산간의 바인딩
  • 언어 구현 시간: 실수 타입과 표현간의 바인딩
  • 컴파일 시간: 변수와 타입간의 바인딩
  • 링킹 시간: 라이브러리 함수와 코드와의 바인딩
  • 로드 시간: static 변수와 메모리 사이의 바인딩
  • 실행 시간(런타임): non-static 변수와 메모리 사이의 바인딩

예제를 하나 볼까. 다음 C 코드를 보자.

int count;
count = count + 5;
  • count의 type은 컴파일 시간 에 바인딩된다.
  • count의 가능한 값들의 집합은 언어 구현 시간 에 바인딩된다.
  • +언어 설계 시간 에 바인딩된다.
  • literal 5의 의미는 언어 구현 시간 에 바인딩된다.
  • count의 값은 이 배정문이 실행되는 실행 시간 에 바인딩된다.

이렇게 바인딩되는 시점을 나눌 수 있다. 참고로 이러한 것은 언어 설계의 아주 기본이라고 할 수 있는데, 언제 어떻게 바인딩되는지를 알아야 설계를 제대로 할 수 있기 떄문.

Static and Dynamic binding

이런 바인딩은 또 static과 dynamic 바인딩으로 나눌 수가 있는데, 각각 다음과 같다.

  • static: 바인딩이 실행 전에 일어나고, 실행 중 변경되지 않으면
  • dynamic: 바인딩이 실행 중에 일어나고, 실행 중 변경 가능성이 있으면

뭐 이렇다.

Type binding

변수는 당연히 참조 전에 어떤 타입에 바인딩되어야 할 것이다. 여기서 2가지 고려사항이 있는데, 각각 다음과 같다.

  • 타입이 어떻게 명세되는가?
    • implicit인지, explicit인지
  • 바인딩이 언제 일어나는가?
    • static인지, dynamic인지

Static binding

바인딩이 실행 전에 일어나고 변경되지 않는 static binding의 경우, 명시적(explicit) 또는 묵시적(implicit)으로 선언(명세)할 수 있다. 의미는 각각 다음과 같음.

  • explicit declaration: 변수 타입을 직접 선언함
  • implicit declaration: 변수 타입을 유추하도록 선언함

차이는 C#을 보면 되겠다.

var a = 10; // integer (implicit)
int b = 10; // integer (explicit)

묵시적인 경우 타입 추론(type inference) 을 통해 타입을 유추해낸다.

참고로 cs에서 묵시적 바인딩을 사용하기 위해서는 반드시 초기값을 주어야만 한댄다. (static binding)

Dynamic binding

변수의 타입이 배정문을 통해 명세되는 것이다. 다시말해 RHS 타입으로 LHS의 타입이 바인딩된다는 말.

또한 dynamic binding이니 실행 중에 언제고 타입을 변경할수도 잇다.

// js: 타입 추론(type inference)을 통해 타입 유추

let a = 0; // integer
a = 'b'; // string
a = [1, 2, 3]; // integer array

뭐 이런건 개유연하지만 오류가 떠도 그냥 통과시켜버리니 신뢰성이 많이 떨어지겠지.

그리고 실행 중에 타입이 변경되니 보통 dynamic binding은 interpreter로 구현하곤 한다. 때문에 실행시간도 늘어남.

기억공간 바인딩과 존속기간

일단 변수는 다음의 과정으로 할당(allocation)과 회수(deallocation)를 한다.

  • 기억장소 할당: 변수에 바인딩되는 메모리 셀은 사용 가능한(가뇽) 메모리 pool에서 가져옴
  • 기억장소 회수: 변수로부터 바인딩이 해제된 메모리 셀을 다시 메모리 pool로 반환하는 것

이 때, 할당되어있는 기간. 즉, 변수가 특정 메모리 셀에 바인딩되어있는 기간을 lifetime(존속기간) 이라고 하는 것이다.

lifetime에 따라 변수에는 4가지 유형이 있다. 각각 다음과 같음.

  • static
  • stack-dynamic
  • explicit heap-dynamic
  • implicit heap-dynamic

뭐하는 애들인지 각각 보도록 하자.

static variable

프로그램 실행이 시작되기 전에 메모리에 바인딩되며, 이게 프로그램 종료될 때 까지 동일 위치에 유지되는 변수.

가령 C의 global 변수(static)를 예로 들 수 있겠다.

  • 장점

    • 직접 주소를 지정하기에 효율적임
    • 이전 값에 따라 현재의 값이 달라지는 과거 민감(history-sensitive) 부프로그램 이 가능
  • 단점

    • 유연성이 떨어지겠지. 가령 재귀가 불가능하다거나.
    • 다른 곳에서는 해당 공간을 사용할 수 없겠지. 그 변수가 차지했으니.

stack-dynamic variable

locally한 변수들을 생각해보면 되겠다. 타입은 static binding이지만, 실제 runtime에서 메모리에 바인딩되는 애들 말이다.

여기서 elaboration(세련화) 라는 용어가 나온다. 이건 그냥 선언문에서 지시한 기억장소 할당과 바인딩되는 과정이라 생각하면 된다.

  • 장점

    • 재귀가능
    • 다른 곳에서도 해당 메모리 공간 사용 가능
  • 단점

    • history-sentive한 부프로그램 작성은 불가하겠지
    • 할당과 회수에 따른 추가적인 시간이 소요되겠지. 당연한 것이긴 한데..
    • 간접 주소로 지정해서 느리댄다.

explicit heap-dynamic variable

명시적으로 메모리 공간을 할당받아 바인딩하는 변수. c의 malloc()을 생각해보자.

참고로 타입 자체는 static binding이다. 메모리 바인딩이 dynamic하게 진행되는거고. 알지?

  • 장점

    • 동적으로 자료구조 관리가 가능하지. c에서 stack 구현했던거 생각해봐라.
  • 단점

    • 비효율적이고
    • 비신뢰적이지. 왜냐고? 예측할 수가 없으니까. 올바르게 쓰기도 힘들고. 관리하기도 복잡하고.

implicit heap-dynamic variable

JS에서 봤던것. 값 할당 시마다 메모리 바인딩되는 변수이다.

var list = [10, 12, 4];
  • 장점

    • 진짜 유연하다. 타입이 뭐든 신경쓸 필요가 없지.
  • 단점

    • 당연히 실행이 느리고
    • 오류떠도 그냥 통과되니 비신뢰적이지

e.g.

다음 코드에서 나오는 모든 변수들을 나열해보고, 이를 lifetime에 따라 분류해보자.

int a = 0;

int* f (void) {
    int *p;
    static int y = 1;

    p = (int *) malloc(sizeof(int));
    *p = a + y;
    y++;

    return p;
}

변수는 총 4개이며, 각각 다음과 같다.

  • a: static variable
  • p: stack-dynamic variable
  • y: static variable
  • anonymous: explicit heap-dynamic variable
    • malloc()으로 생성된 변수 말하는거다.

이름없는 변수를 주의하자. 왜 저렇게 분류되는지는 모르면 처음부터 다시 읽고.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment