JSCC 준비

버퍼 기본 메서드: getc, peekc, ungetc, add, is_empty

5.3.2) 버퍼 기본 메서드: getc, peekc, ungetc, add, is_empty

다음은 StringBuffer의 확장 메서드를 구현하기 위해 필요한 getc 메서드를 구현한 것이다.

StringBuffer.js (getc)

/**

버퍼로부터 문자를 하나 읽습니다포인터가 이동합니다.

@return {string}

*/

StringBuffer.prototype.getc = function() {

// 필드에 접근하기 위해 this 키워드를 반드시 붙여야 합니다.

if (this.idx >= this.str.length)

 

// throw와 StringBufferException 사이에 new가 붙었습니다.

throw new StringBufferException('Buffer is empty', this);

 

// 사실 StringBufferException의 두 번째 인자로 this도 넘겼습니다.

return this.str[this.idx++];

}

이전 예제와 비교하여 세 가지가 달라졌다첫 번째는 필드에 접근하기 위해 반드시 this를 붙여야 한다는 것이다.왜 그런지 살펴보자.

 

5.3.2.1) 왜 this가 붙는가?

this는 현재 함수가 호출된 위치에서 가장 가까운 객체에 접근하는 키워드다만약 this가 붙지 않는다면 idx와 str같은 키워드를 가장 가까운 객체가 아닌 전역에서 찾는다. this는 이를 위한 것이다.

 

5.3.2.2) 왜 throw와 Exception 사이에 new가 붙는가?

다음으로 throw와 Exception 사이에 new가 붙는 것을 생각해보자이전 C++의 예제에서는 new를 붙여서 예외를throw하지 않았다. C++의 new는 힙 메모리 영역에 동적으로 공간을 할당한 다음할당된 메모리의 주소를 반환하는 연산자다따라서 new 연산자로 할당한 메모리는 언제나 해제해주어야 하는데 이는 번거롭다그래서 이전 예제에서는 new 연산자를 이용하는 대신 이렇게 해결했었다.

TryCatch2.cpp

#include <iostream>

 

// 일반 예외 형식 Exception을 정의합니다.

class Exception {

std::string _msg;

public:

Exception(const std::string &msg) : _msg(msg) {}

const char *toString() const { return _msg.c_str(); }

};

 

int main(void) {

try {

// 1. Exception("...") 구문이 임시 예외 객체를 생성합니다.

// 2. 생성된 임시 예외 객체를 throw 합니다.

throw Exception("예외가 발생했습니다.");

}

// 3. ex는 throw된 임시 예외 객체를 참조합니다.

catch (Exception &ex) {

// 4. 예외를 사용합니다.

std::cout << "메시지: " << ex.toString() << std::endl;

// 5. catch 블록을 벗어나면서 임시 객체에 대한 참조가 사라지고

// 임시 예외 객체가 자동으로 소멸됩니다.

}

return 0;

}

주석에 나와 있듯먼저 임시 객체가 생성된 다음 이 예외 객체가 throw되고, catch 영역에서 이 예외를 사용한 다음 블록을 벗어나면 예외가 소멸되는 구조다이때 catch 영역에 참조를 뜻하는 &가 붙지 않으면 catch 영역에서 임시 객체를 catch할 때 ex가 throw된 임시 객체를 복사하게 된다그래서 성능에 저하가 발생하기 때문에 임시 예외 객체를 catch할 때는 &를 붙여 참조한다.

JavaScript에는 포인터라는 개념이 없다객체에 대한 모든 변수는 객체에 대한 참조다. C++만 배웠다면 이 말을 이해하기 쉽지 않을 테니 예를 들어보자. C++에서는 하나의 객체를 참조하기 위해서는 참조 또는 포인터를 사용한다.

RefObject.cpp

#include <iostream>

 

// Object 클래스를 정의합니다.

struct Object { int value; };

 

// 포인터를 이용하여 Object 객체의 값을 변경합니다.

void ChangeValue(Object *pObject, int value);

// 참조를 이용하여 Object 객체의 값을 변경합니다.

void ChangeValue(Object &rObject, int value);

// 인자는 사본이므로 이렇게는 값을 변경할 수 없습니다.

void ChangeValueWrong(Object o, int value);

 

int main(void) {

Object object;

 

// 객체가 선언된 지역이므로 값이 잘 변경됩니다.

object.value = 10;

std::cout << object.value << std::endl;

 

// object의 사본이 생성되므로 값이 변경되지 않습니다.

ChangeValueWrong(object, 20);

std::cout << object.value << std::endl;

 

// 포인터나 참조를 이용하면 값이 잘 변경됩니다.

ChangeValue(&object, 30); // 포인터를 이용한 변경

std::cout << object.value << std::endl;

ChangeValue(object, 40); // 참조를 이용한 변경

std::cout << object.value << std::endl;

 

return 0;

}

 

// 포인터를 이용하여 Object 객체의 값을 변경합니다.

void ChangeValue(Object *pObject, int value) {

pObject->value = value;

}

// 참조를 이용하여 Object 객체의 값을 변경합니다.

void ChangeValue(Object &rObject, int value) {

rObject.value = value;

}

// 인자는 사본이므로 이렇게는 값을 변경할 수 없습니다.

void ChangeValueWrong(Object o, int value) {

o.value = value;

}

실행 결과

10

10

30

40

반면 JS에서는 다음과 같이 코드를 작성하여 객체에 접근한다.

RefObjectJS.htm

// MyObject 형식을 정의합니다.

function MyObject(value) { this.value = value; }

 

// 값을 변경하는 함수를 정의합니다.

function changeValue(o, value) {

// o는 object의 사본이 아닌 object에 대한 참조처럼 행동합니다.

o.value = value;

}

 

function main() {

var object = new MyObject();

 

// object를 초기화하고 값을 출력합니다.

object.value = 10;

log(object.value);

 

// 값을 변경하고 출력합니다.

changeValue(object, 20);

log(object.value);

}

각각의 changeValue 메서드를 자세히 보라. C++에서 함수의 인자는 객체에 대한 사본이고, JS에서는 객체에 대한 참조다. C++에서의 점 연산자는 현재 객체의 멤버에 접근하는 연산자고, JS에서의 점 연산자는 현재 변수가 가리키는 객체의 멤버에 접근하는 연산자로두 언어의 점 연산자가 역할이 다른 것이다정리하면 JS에는 포인터가 없고 변수는 객체에 대해 참조 정보를 갖는다.

결론은 throw와 Exception 사이에 new가 붙어 새로운 객체를 생성한 후 이 객체를 throw하면, catch 영역에서 이를 받는 ex는 throw된 Exception 객체에 대한 참조라는 것이다말이 아주 혼란스러우므로 한 번만 더 정리하자.

1. new Exception("...")을 통해 새로운 객체를 생성한다.

2. throw를 통해 생성한 Exception 객체를 throw한다이 객체를 thrownEx라고 하자.

3. catch 영역에서 throw된 Exception 객체를 받는다이는 ex = thrownEx이 실행된 것과 같다.

throw와 Exception 사이에 new가 들어가는 이유는 이 정도로 정리할 수 있다.

 

5.3.2.3) 왜 Exception의 인자로 this를 넘겼는가?

단순하다. this는 예외가 발생한 StringBuffer 객체를 말하는데이전에 StringBufferException에 예외에 대한 정보도 전달하겠다고 했다그럼 예외에 대한 정보는 당연히 StringBuffer 객체가 가지고 있을 것이다그래서StringBufferException 객체에 예외에 대한 정보를 가지고 있는 StringBuffer 객체를 넘겼다이 정도면 설명할 수 있는 가장 친절한 선에서 설명한 것이라고 생각한다.

나머지는 peekc, ungetc와 같은 기본 메서드에 대한 내용인데 C++의 예제와 차이가 없으니 코드를 복사하는 것으로 마무리하겠다.

StringBuffer.js (peekc, ungetc, add, is_empty)

/**

버퍼의 포인터가 가리키는 문자를 가져옵니다포인터는 이동하지 않습니다.

@return {string}

*/

StringBuffer.prototype.peekc = function() {

if (this.idx >= this.str.length)

throw new StringBufferException('Buffer is empty', this);

return this.str[this.idx];

}

/**

버퍼에서 읽었던 값을 되돌립니다되돌릴 수 없으면 false를 반환합니다.

@return {boolean}

*/

StringBuffer.prototype.ungetc = function() {

if (this.idx > 0) {

--this.idx;

return true;

}

return false;

}

 

/**

버퍼의 끝에 문자열을 추가합니다.

@param {string} s

*/

StringBuffer.prototype.add = function(s) {

this.str += s;

}

 

/**

버퍼가 비어있다면 true, 값을 더 읽을 수 있다면 false를 반환합니다.

@return {boolean}

*/

StringBuffer.prototype.is_empty = function() {

return (this.idx >= this.str.length);

}

 

5.3.3) 버퍼 확장 메서드: get_number, get_identifier, get_operator, get_token

사실 우리는 이전 절에서 기본 메서드를 다루면서 JS에서의 특이 사항을 모두 학습했고, C++에서 작성했던 확장 메서드의 논리가 변하는 것도 아니기 때문에 변하는 부분을 눈으로만 확인하면 된다.

StringBuffer.js (get_number, get_identifier, get_operator)

/**

버퍼로부터 정수를 획득합니다.

@return {string}

*/

StringBuffer.prototype.get_number = function() {

this.trim(); // 공백 제거

if (this.is_empty()) // 버퍼에 남은 문자가 없다면 예외

throw new StringBufferException("Buffer is empty", this);

else if (is_digit(this.str[this.idx]) == false) // 첫 문자가 숫자가 아니면 예외

throw new StringBufferException("invalid number", this);

var value = '';

while (this.is_empty() == false) {

if (is_digit(this.str[this.idx]) == false)

break;

value += this.str[this.idx];

++this.idx;

}

return value;

}

 

/**

버퍼로부터 식별자를 획득합니다.

@return {string}

*/

StringBuffer.prototype.get_identifier = function() {

this.trim(); // 공백 제거

if (this.is_empty()) // 버퍼에 남은 문자가 없다면 예외

throw new StringBufferException("Buffer is empty", this);

else if (is_fnamch(this.str[this.idx]) == false)

throw new StringBufferException("invalid identifier", this);

var identifier = '';

while (this.is_empty() == false) {

if (is_namch(this.str[this.idx]) == false) // 식별자 문자가 아니라면 탈출

break;

identifier += this.str[this.idx];

++this.idx;

}

return identifier;

}

 

/**

버퍼로부터 연산자를 획득합니다.

@return {string}

*/

StringBuffer.prototype.get_operator = function() {

this.trim();

if (this.is_empty())

throw new StringBufferException("Buffer is empty", this);

var ch = this.str[this.idx++]; // 현재 문자를 획득하고 포인터를 이동한다

var op = '';

switch (ch) {

case '+': op = ch; break;

case '-': op = ch; break;

case '*': op = ch; break;

case '/': op = ch; break;

default: throw new StringBufferException("invalid operator", this);

}

return op;

}

 

/**

공백이 아닌 문자가 나올 때까지 포인터를 옮깁니다.

*/

StringBuffer.prototype.trim = function() {

while (this.is_empty() == false) { // 버퍼에 문자가 남아있는 동안

if (is_space(this.str[this.idx]) == false) // 공백이 아닌 문자를 발견하면

break; // 반복문을 탈출한다

++this.idx; // 공백이면 다음 문자로 포인터를 넘긴다

}

}

그리고 이 예제에서 get_token 메서드에 try-catch 구문이 추가된다.

댓글

댓글 본문