Programming/Programming2018. 12. 23. 17:44

1.     컴파일 환경


OS Windows 7, Visual Studio 2015 (_MSC_VER==1900)에서 코딩 및 컴파일했다.

 

2.     연산자 설정


통상적인 계산이 아닌 @, #, &이라는 특수한 연산 기호를 사용해 a, b, c를 처리하는 것이므로 연산 자체를 어떻게 처리할 것인지부터 생각해야 했다. 그래서 내린 결론은 전역으로 각각 operationAt, operationSharp, operationAnd라는 char형 배열을 정의해 연산 결과를 매칭시킨다였다. 이를 그림으로 표현하면 아래와 같다.



이렇게 구현한다면 연산하는 과정 없이 그대로 매칭만 시켜주면 된다. 그래서 전역으로 각각 연산자마다의 연산 결과를 담은 배열을 정의했다.



계산기가 동작하기 전에, 연산자들(@, #, &)의 연산 결과를 operations.txt에서 받아와서 등록하는 과정이 필요하다. 그래서 ifstream을 활용해 파일을 읽어내 각각 배열에 대입해주었다.



위와 같이 정의하고 계산 기능을 하는 코드가 실행되기 전에 setOperation 함수를 호출한 후, 전역에 정의된 배열의 상태를 보면 operations.txt data와 일치하게 설정된 것을 볼 수 있다.



이것으로 연산자의 연산 처리에 대한 설정은 끝마쳤다.

 


3.     계산


이제 입력 받은 수식을 계산해야 한다. 입력은 cin으로 받아 string vector eq에 넣고, “EOI”를 입력 받으면 입력을 종료한다.



그리고 eq에 있는 요소를 하나씩, 계산하는 함수 Calculate에 넘겨준다.


 

Calculate 함수는 다음과 같이 이루어져있다.



isValidEq 함수에서 true를 받으면 수식이 유효하다는 의미이다. 유효할 경우는 postfix의 길이와 같은 크기로 공간을 동적으로 할당받아 그 공간에 postfix로 변환된 수식을 저장하고, 변환된 수식을 가지고 실제 계산을 진행한다.

 


A.     수식의 유효성 검사


Calculate 함수는 입력 받은 수식의 연산 결과를 출력한다. 하지만 연산을 시행해 결과를 내기 전에, 입력 받은 수식이 유효한지부터 검사해야 한다. 잘못된 수식을 즉시 연산하다가는 오류가 발생할 수 있기 때문이다.

수식의 유효성을 검사하기 위해서 isValidEq 함수를 정의했다.



유효성 검사 코드는 수식 자체가 유효한지 더 자세히 검사한다. , 괄호를 제대로 썼는지, 연산자를 연속해서 사용하지는 않았는지, 연산자로 수식이 시작하거나 끝나지는 않았는지, 그리고 연산자에 피연산자를 제대로 작성했는지이다.



좌측 괄호가 들어오면 스택에 push해놓고, 우측 괄호가 들어오면 괄호의 유효성을 검사한다. 만약 스택이 비어있으면 당연히 괄호의 쌍이 맞지 않는다는 것이므로 false return한다. 그렇지 않을 경우에는 우리가 비교하고자 하는 것은 스택에서 가장 위에 있는 좌측 괄호이다. 스택의 맨 위에 있는 괄호가 알맞은 짝인지 판단해 다르면 false return하고, 올바르면 스택에서 빼낸 후에 검사를 계속 진행한다.



연산자에 대한 처리는 먼저 연산자가 수식의 맨 앞이나 뒤에 왔는지 검사한다. 좌측 피연산자나 우측 피연산자가 존재하지 않는 수식이라는 의미이기 때문에 반드시 올바른 수식이 아니기 때문에 false return한다. 그렇지 않으면 수식의 현재 검사하고 있는 문자의 앞과 뒤의 문자로 괄호의 시작과 끝과 맞닿아 있을 경우에 피연산자가 빠지지는 않았는지, 연산자가 연속해서 오지는 않았는지 검사한다. 이 검사를 모두 통과하면 수식 검사를 계속한다.



수식 검사가 1차적으로 끝난 후엔 스택이 비어있는지 확인한다. ‘(a#a)&(a#a‘와 같은 수식은 이상의 테스트를 통과하지만, 쌍이 맞지 않는 불완전한 수식이기 때문이다.

주석 처리된 부분은 시행 착오를 여러 번 거치다 필요 없는 구문으로 판단되어 지워진 코드들이다. 먼저, a부터 c까지의 operands들이 들어오면 원래는 numCount라는 변수로 피연산자들이 몇 개나 되는지 카운트했었다. ‘a#’과 같이 1개 이하의 피연산자가 있다는 것은 구문에 오류가 있다는 의미이기 때문이다. 하지만 이는 연산자의 위치가 수식의 제일 앞이거나 뒤인지 판단하는 구문에서 이미 걸러지기 때문에 필요하지 않은 코드가 되었다. 또한, 원래는 연산자를 확인한 후에는 스택에 넣었었다. 처음에는 올바르게 사용된 연산자를 스택에 넣어놓고 나중에 다른 어떤 부분에서 확인하는데 사용할 수 있을 것이라 생각했다. 하지만 기능을 모두 구현한 다음에 살펴보니 연산자를 스택에 넣는 행위 자체가 의미 없는 코드가 되었다. 괄호를 검사할 때도 이 때 넣어놓은 연산자 때문에 따로 연산자를 모두 꺼내야 되는 코드가 추가적으로 필요했고, 연산자를 잘못 사용한 경우는 이미 함수가 false return되기 때문이었다. 그래서 연산자를 스택에 넣어두는 코드 자체를 아예 없애 불필요한 코드를 줄였다.

 


B.      Postfix로 변환


검사를 마친 수식은 postfix로 바뀌어도 문제가 없다. 스택 계산기를 구현하려면 수식을 postfix 형태로 만들어내는 것이 계산 과정에서 훨씬 편리하다. makePostfix 함수는 바꿀 수식 string을 전달받아 bufpostfix로 바뀐 수식을 저장해준다.



피연산자가 들어오면 작성하고 있는 buf의 현재 위치에 그대로 넣어준다. 연산자가 들어오면 먼저 스택이 비어있는지, 혹은 스택의 맨 위에 좌측 괄호가 있는지 확인해 그렇다면 스택에 바로 넣어준다. 그렇지 않다면 & #, @로 나누어 처리해준다. & # @보다 높은 연산 우선순위를 가지고 있다. 스택의 맨 위에 있는 연산자보다 연산 우선순위가 같거나 높은 연산자를 만나면 스택에서 맨 위에 있는 연산자를 꺼내 buf에 넣어줘야 한다. 그러므로, # @는 가장 낮은 연산 우선순위를 가지므로 같거나 높은 연산자를 만날 수 밖에 없으므로, 연산자를 만날 때 마다 스택 맨 위의 연산자를 buf에 넣고 pop한다. 그리고 &은 같은 &을 만났을 때는 @ #과 마찬가지로 바로 buf에 넣고 pop, &이 아닌 다른 연산자를 만났을 때는 항상 더 높은 연산순위를 가지므로 스택에 넣어준다.



좌측 괄호를 만나면 스택에 넣어준다. 우측 괄호를 만나면 적절한 좌측 괄호를 만날 때까지 연산자를 꺼내 buf에 넣어준다.

위의 for문이 끝났을 때 스택이 비어있으면 그대로 return, 아직 남아있으면 들어있는 연산자를 모두 꺼내 buf에 넣어주고 return한다. 이렇게 수식을 Postfix로 만들어내는 과정이 끝났다.

 


C.      Postfix문 계산


이제 스택 계산기로 계산하기 상대적으로 쉬운 postfix문까지 만들어냈으니, 계산만 해서 그 값을 출력하면 된다. Postfix문을 계산해 출력하는 함수 calcPostfix는 다음과 같다.



피연산자(a, b, c)를 만나면 스택에 넣고, 연산자를 만나면 newCalc 함수를 호출한다. newCalc 함수는 스택 맨 위의 두 피연산자를 꺼내 cursor에 해당하는 연산자로 연산 결과 값을 구해 스택에 넣어주는 함수이다.



상술했듯이, 미리 설정해놓은 배열들에서 대응하는 값을 스택에 넣어주는 것으로 연산을 진행한다.

이렇게 연산이 모두 끝나고 나면 스택의 맨 위에는 연산 결과만이 남아있을 것이다. 이 결과를 출력해주고 calcPostfix 함수를 마친다. 우리는 입력 받은 수식을 계산해 출력하는 과정까지 끝냈다.

 


4.     다른 예시 입력


-       a#(a@b&c)

-       a&b&c@(a&b&c)

-       a&&b

-       a#b#(a&c#)



모두 정상적으로 출력되는 것을 확인할 수 있었다.



GitHub : https://github.com/BinZlP/StackCalculator

Posted by BinZIP
Programming/Programming2018. 12. 23. 17:31

1.     컴파일 환경

Visual C++ 2015 (_MSC_VER==1900) 환경에서 디버그 및 컴파일했다.

 

2.     클래스 설계

MyIntVector.h 헤더 파일의 소스코드를 일부씩 가지고 와서 설명하겠다. 크게 멤버변수, 생성자, 멤버함수로 나누어 설명한다. 생성자와 멤버함수의 precondition postcondition을 헤더 파일에 작성하였으나, precondition을 만족시키지 못할 경우의 예외처리를 최대한 하는 방향으로 코드를 작성했다.

 

A.     멤버변수



멤버 변수는 데이터를 담는 int*형 변수 data data의 최대 수용할 수 있는 원소의 개수를 값으로 가지는 capacity, 그리고 저장된 원소의 개수를 값으로 갖는 size로 구성했다.

 

B.      생성자 및 소멸자



생성자는 총 2개로, 첫 번째 생성자는 정수를 전달받아 그 정수를 capacity의 값으로 초기화한다. 이 생성자는 기본값으로 DEFAULT_CAPACITY를 가지므로 아무것도 전달되지 않았을 때는 매크로 상수로 정의된 DEFAULT_CAPACITY의 값으로 capacity 값을 초기화 하게 된다. 현재 설정해놓은 DEFAULT_CAPACITY 32이다.



나머지 한 생성자는 copy constructor이다. 인자로 MyIntVector의 참조자를 받아 그 참조하는 MyIntVector와 완전히 동일한 새로운 객체를 만든다. , 생성되는 객체의 data에 참조한 data deep copy하고 capacity size를 그대로 복사한다.

마지막으로 소멸자는 동적 할당된 data delete해준다.

 

C.      연산자


연산자는 =,+=, [], +, -, *, -(Unary), ==, ()를 재정의했다.


= 연산자이다. 좌항에 있는 MyIntVector 객체에 우항의 MyIntVector deep copy한다. 좌항과 우항에 같은 MyIntVector이면 불필요한 복사 작업을 건너뛰기 위해 분기를 넣어주고, capacity가 다를 때만 data를 다시 할당 받도록 했다.

return 타입을 MyIntVector& 타입으로 하는 이유는 a=b=c와 같이 연쇄적으로 사용할 수 있도록 하기 위해서뿐만이 아니다. 만약 단순히 참조자 타입이 아니라 value return하면, = 연산자가 return될 때 복사 생성자로 결과 객체와 동일한 객체를 생성해낸다. , 호출될 때마다 생성자와 소멸자가 호출된다는 것이다. 지금 우리가 구현하는 간단한 클래스에서는 value return하더라도 당장 큰 문제는 없겠지만, 다른 복잡한 클래스를 구현했을 때 불필요한 오버헤드가 많이 들 수 있다.



+= 연산자는 좌항의 MyIntVector에 우항의 MyIntVector element를 이어 붙인다. 만약 우항의 객체가 비어있다면 return하고, 이어 붙일 공간이 부족할 경우 추후 설명할 reserve 함수를 호출해 capacity를 확장했다. 처음에 설계했을 때는 공간을 2배로 확장하고 이어 붙이는 작업을 진행하도록 했는데, 좌항 객체의 capacity 2배를 해도 우항 객체의 데이터를 모두 담아내지 못할 정도로 우항 객체에 데이터가 많을 때 오류가 발생했다. 그래서 충분히 확보될 때까지 2배씩 확장하는 작업을 반복하도록 코드를 구성했다.



+, -, * 연산자는 두 MyIntVector의 스칼라 합, , 곱 연산을 한다. 이 때, MyIntVector는 가지고 있는 element의 개수가 동일해야 연산이 가능하므로, 다를 때에는 오류로 판단하도록 코드를 작성하였다. 연산 결과를 tmp에 저장해 return한다.

처음에 함수를 작성할 때는 해당 연산자를 사용할 때마다 a=a+b와 같은 형식으로 사용할 것으로 생각했다. 그래서 return tmp가 대입되어 들어가게 될 경우, a data memory leak을 유발할 가능성이 있다고 생각해 a의 소멸자를 호출해줘야 한다고 생각했다. 하지만 애초에 =연산자는 좌항의 객체에 deep copy하는 것이라 아무 상관 없었다. 이런 생각이 든 이후엔 a+b의 결과로 return되는 tmp도 소멸자를 호출하지 않으면 memory leak을 유발하지 않을까 하는 생각이 들었는데, tmp는 대입 연산이 끝난 후에 함수의 지역 변수이므로 free될 것이므로 그럴 필요 없다는 결론을 내려 위와 같이 코드를 구성했다.



(Unary)- 연산자는 MyIntVector 객체의 data 안의 모든 element 값의 부호를 바꾼다. 비어있을 경우는 할게 없으므로 그대로 return하고, 그 외의 경우는 모든 원소에 -1을 곱한 값을 가지는 tmp return한다.



== 연산자는 두 객체가 완전히 동일한 데이터를 가지는지 확인한다. 두 객체의 size가 다르면 당연히 다른 객체이므로 false return한다. Size가 같으면 데이터를 하나하나 대조해서 서로 다른 값이 발견되면 false return하고, 모든 element가 같으면 true return한다.



() 연산자는 호출한 객체의 모든 element 값을 전달받은 x의 값으로 바꾼다. for문으로 객체의 모든 저장된 data의 값에 x를 대입해 return한다.



마지막으로 [] 연산자는 x번째 인덱스에 있는 element 값을 반환한다. 만약 x값이 음수이거나 element의 인덱스 범위를 넘어갈 경우는 오류이므로 프로그램을 종료하도록 했다.

 

D.     멤버함수

멤버함수는 pop_back, push_back, capacity, size, reserve, isEmpty, clear 함수를 만들었다.



pop_back 함수는 MyIntVector 객체 데이터의 맨 뒤에 있는 element를 제거하는 함수이다. 만약 데이터가 비어있으면 아무것도 하지 않고, 그 외의 경우는 size의 값을 1 줄이는 것으로 맨 뒤의 element 제거를 구현했다.



push_back 함수는 MyIntVector 객체에 데이터를 하나 삽입한다. 전달받은 x를 데이터의 맨 뒤에 추가하는 함수이다. 만약 객체에 데이터를 담을 공간이 부족하면 reserve 함수를 이용해 공간을 확보한 뒤 데이터를 삽입한다.



isEmpty 함수는 데이터가 없으면 true, 하나라도 존재하면 false를 반환한다.



멤버함수 중에서 가장 중요하다고 할 수 있는 reserve 함수는 MyIntVector capacity를 확장해주는 함수이다. 먼저 reserve 하려는 크기가 지금 저장되어 있는 element의 개수보다 작을 경우는 데이터를 잘라내려는 의도보다 잘못된 값을 입력했을 가능성이 더 높다고 판단해 그대로 return했다.

새로운 n 크기의 int형 배열을 동적으로 할당해, 새로운 배열에 기존 데이터를 copy한다. 마지막으로 새로운 배열의 주소를 data에 저장하고 기존 data delete 해주는 것으로 확장 작업을 마친다.



clear 함수는 MyIntVector 객체의 모든 데이터를 삭제한다. size 0으로 만들어주면 pop_back으로 모든 element를 제거한 것과 같은 것이므로 size 0으로 설정해줬다.



Capacity, Size 함수는 객체의 capacity 값 혹은 size 값을 반환하는 함수이다.

 


추가적으로, 이후 테스트를 진행할 때 데이터의 상태를 쉽게 확인하기 위해 데이터를 한 개씩 출력해주는 멤버함수 printElements를 만들었다.

 

3.     Main 함수


작성한 모든 연산자 및 함수가 정상적으로 동작하는지 테스트하는 코드를 작성했다.



설명하기 앞서 테스트하는 도중에 MyIntVector의 상태를 쉽게 볼 수 있도록 만든 printVectorInfo 함수를 정의해 사용했다.



먼저 MyIntVector타입 객체 vector1을 정의해 vector1의 상태를 살펴보았다.



아무것도 들어있지 않고 capacity DEFAULT_CAPACITY(==32) MyIntVector가 생성되었다.




그 후 vector1 0부터 4까지 값을 차례로 가지는 elementpush_back으로 삽입했다. vector1의 상태를 출력한 후 data 0번째 인덱스 값, 1번째 element를 출력하게 했다.



0부터 4까지 차례로 삽입되었고, 0번째 인덱스 값은 0이 맞으므로 push_back []연산자가 정상적으로 동작함을 알 수 있다.




vector2 vector1과 동일하게 생성했다.



vector2 vector1과 완전히 동일하게 생성되었음을 확인했다.




vector2 vector1을 이어 붙이는 += 연산자를 사용하고 vector2의 상태를 확인했다.



0부터 4까지의 원소가 2번 반복되는 것을 보아 정상적으로 이어 붙여졌으므로, +=연산자가 잘 동작함을 확인할 수 있다.




vector3을 생성해, 대입 연산자를 연쇄적으로 사용해보았다. 그 후, vector3 vector1 ==연산자로 비교했다.



대입 연산자를 연쇄적으로 사용해도 정상적으로 동작하며, 비교 연산자인 == 연산자도 정상적으로 동작함을 알 수 있다.




vector327개의 0을 삽입해 size 32로 만들고, 0을 하나 더 삽입하는 코드를 작성해 vector3의 상태를 확인했다.



size 32일 때 0을 하나 더 삽입했더니 capacity 2배인 64로 늘어났다. push_back을 사용했을 때 자동으로 reserve됨을 볼 수 있었다.




vector3의 모든 element 10으로 만들고, vector4 vector3과 똑같게 생성한 후 모든 원소를 1로 바꾸었다. 다음, vector3에서 vector4를 스칼라 뺄셈을 진행하고 vector3의 상태를 확인했다.



10-1 9 33개의 element가 모두 설정되어 있는 것을 보아, ()연산자와 연산자가 잘 동작함을 확인할 수 있다.




마찬가지로 + * 연산자도 테스트 해보았다.



1+1 2이고, 2*9 18이므로 모두 잘 동작함을 확인할 수 있다.




(Unary)- 연산자도 잘 동작하는지 테스트 해보았다.



18에서 -18로 바뀌어서 잘 출력되므로 (Unary)- 연산자가 잘 동작함을 확인했다.




vector3에서 한 elementpop_back으로 제거하고 vector3의 상태를 확인한 후, reserve 함수로 capacity 32로 바꾼 후 vector3의 상태를 확인한다.



Capacity 32로 줄어들었다. pop_back reserve 함수가 잘 동작하는 것을 볼 수 있다.




마지막으로 clear함수를 사용해 데이터를 모두 없애고, isEmpty로 데이터가 없는지 확인한다.



vector3에 있는 element들이 모두 제거되었다. 그러므로 clear 함수와 isEmpty 함수가 잘 동작함을 확인했다.



GitHub: https://github.com/BinZlP/MyIntVector



'Programming > Programming' 카테고리의 다른 글

[C++] 스택을 활용한 수식 계산기  (0) 2018.12.23
[C++] 함수 및 다중 정의 (Overload)  (1) 2018.01.21
[C++] Try 구역과 예외 처리  (0) 2018.01.15
[C++] 문자열 & 벡터  (0) 2018.01.13
[C++] 변수와 기본 타입  (0) 2018.01.11
Posted by BinZIP
Programming/Programming2018. 1. 21. 03:06

1.     다중 정의 (Overload)



이름은 같지만 매개변수가 다르며, 같은 유효 범위 내에 있는 함수를 다중정의(overload)했다고 함.


void print(const char* cp);

void print(int* arr, int size); // (1)

void print(const int* arr, const int size); // (1)을 재선언

 

다중 정의한 함수는 반드시 매개변수 수나 타입이 달라야 함. 또한, 두 함수에서 반환 타입만 다르다면 오류임.

매개변수에 const만 새로 선언되고 나머지는 이전에 정의된 함수의 매개변수와 같으면 함수를 재선언함. 상위 const는 함수에 전달할 수 있는 객체에 아무런 영향을 미치지 않기 때문. , 매개변수에 const가 선언되어 있는데 새로 선언할 때 const가 없는 경우는 오류.


 다중 정의를 사용해 공통 연산에 대한 이름을 고안하는 것을 피할 수 있을지라도, 실제로 비슷한 일을 하는 연산에 대해서만 다중 정의해야함. 실제로 프로그래밍을 하다보면 overloading 하는 것 보다 다른 이름으로 정의해 함수를 사용하는 것이 가독성 면에서 좋은 경우가 꽤 있음.

 




2.     함수의 전문적 용도를 위한 기능



기본 인자


공통 값을 함수에 대한 기본 인자로 선언할 수 있음. 기본 인자를 사용하는 함수는 그 인자를 사용하거나 사용하지 않고 호출할 수 있음.

호출에서 인자는 위치로 해석함. 후행 인자만 생략할 수 있음.


void printArr(int* arr, int size = 10, int start = 0); // int형 배열을 출력하는 함수

printArr(intArr); // ==printArr(intArr,10,0);

printArr(intArr,20); // ==printArr(intArr,20,0);

printArr(intArr, ,5); // 오류: 후행 인자만 생략 가능

 

기본 값을 사용하지 않을 것 같은 매개변수는 매개변수 목록 앞쪽에, 기본 값을 사용할 것 같은 것은 뒤에 두도록 순서를 지정하는 것이 기본 인자를 사용하는 함수 설계의 일부분임.

이미 선언한 기본 값은 재정의로 바꿀 수 없음. 하지만 기본 인자가 적용되지 않은 매개변수에 대한 추가는 가능함.

 


인라인(inline) constexpr 함수


함수를 사용하면 동일한 행동을 보장하고, 유지보수적인 측면이나 코드의 가독성 면에서 좋다는 장점이 있으나, 함수 호출은 동일한 표현식을 평가하는 것 보다 느리다는 단점이 있음. 대부분의 시스템에서 함수를 호출 할 때 레지스터를 저장하고 반환한 후 복원하며, 인자를 복사하고 프로그램을 새로운 위치로 분기하기 때문임.

 


inline 함수


inline으로 정의한 함수는 호출한 각 위치에 함수의 내용을 정렬해 확장함.

일반적으로 inline 메커니즘에서 의도한 것은 자주 호출되는 작고 직선적인 함수에 대한 최적화.


inline void printArr( … );

 

inline 지정은 컴파일러에 대한 요청이지, 컴파일러에서 무조건 따르지는 않음. 일반적으로 재귀함수는 인라인하지 않으며 몇 십줄 짜리 코드를 가진 함수도 거의 확실히 인라인하지 않음.


 __inline과 inline은 같으나, __forceinline을 통해 inline을 컴파일러에게 강제할 수 있음. 하지만 이 때는 프로그래머의 판단에 의존하게 되어 잘못 사용했을 때 성능을 오히려 저하시키거나 큰 효과를 보지 못할 수 있으니 주의해서 사용해야 함.



constexpr 함수


상수 표현식에 사용할 수 있는 함수.

return 타입과 각 매개변수 타입이 상수 타입이어야 하며 함수 본체에는 return 문이 정확히 하나만 있어야 함.

컴파일러에서는 constexpr 함수에 대한 호출을 해당 호출 결과값으로 바꿀 수 있을 때 그렇게 하며, 또한 그 함수를 즉시 확장할 수 있도록 constexpr 함수는 암시적으로 inline 함수임.

일반적으로 inline, constexpr 함수는 헤더에 정의함. inline constexpr 함수는 프로그램에서 여러 번 정의할 수 있으므로 컴파일러에서는 선언이 아닌 정의가 필요하고, 해당 inline constexpr 정의는 모두 정확히 일치해야 하기 때문.

 



3.     오류 수정 지원 도구



응용 프로그램 개발을 완료하고 출시할 준비가 되면 오류 수정용 코드를 해제해 작동하지 않도록 하는데, assert NDEBUG를 사용

 

Assert 전처리기 매크로


assert는 전처리기 매크로. 조건으로 사용하는 표현식을 하나 취함. 흔히 일어날 수 없는 조건을 확인하는데 사용.

표현식을 평가해 표현식이 거짓이면 메시지를 출력하고 프로그램을 종료.

assert 매크로는 cassert 헤더에서 정의함. 그러므로 사용하려면 cassert 헤더를 포함해야 함.

 


NDEBUG 전처리기 변수


assert NDEBUG라는 전처리기 변수 상태에 따라 행동이 달라짐. NDEBUG를 정의하면 assert에서는 아무것도 하지 않음.

#define으로 정의해 사용


#include <iostream>

 

#define NDEBUG

#include <cassert>

 

using namespace std;

 

int main(void) {

           assert(0);

 

           cout << "PUBLIC MODE" << endl;

           return 0;

}




[NDEBUG 매크로 상수를 정의했을 때]



[NDEBUG 매크로 상수를 정의하지 않았을 때]

Posted by BinZIP
Programming/Programming2018. 1. 15. 03:32

예외는 데이터베이스 연결이 끊어지거나 예상하지 못한 입력을 만나는 등 정상적인 프로그램 기능에서 벗어난, 실행 중 발생하는 이상 현상

일반적으로 예외 처리는 프로그램 한 부분에서 자체적으로 해결할 수 없는 문제를 감지하고, 그 문제가 감지부에서 계속해서 프로그램을 실행할 수 없는 것일 때 사용

C++에서 예외 처리는 다음을 포함: throw 표현식, try 구역, exception 클래스



1.     Throw 표현식


감지부에서 처리할 수 없는 무언가를 만났음을 나타내는 데 사용. 이 때 throw에서 예외를 일으킨다고 함.


int num1=0;

cout << "Enter a number (100 or less integer) :";

cin >> num1;

 

if (num1 > 100)

           throw runtime_error("You must enter 100 or less integer.");

cout << num1 << endl;



100을 초과하는 입력을 넣으면 런타임 오류가 나는 것을 확인할 수 있음

위 코드에서는 100을 초과하는 입력을 받으면 runtime_error 타입 객체인 표현식을 던짐.

예외를 발생시키면 현재 함수를 마치고 이 오류를 처리할 수 있는 처리자로 제어를 옮김.

 

 


2.     Try 구역


처리부에서 예외를 처리하는 데 사용.

try 구역은 try 키워드로 시작하고 하나 이상인 catch 절로 마침.

try 구역 안에서 실행한 코드에서 발생한 예외는 일반적으로 여러 catch 절 중 하나에서 처리.

 

try 구역의 일반적인 형식은 다음과 같음


try{

           (logic 1)

}catch (exception-declaration) {

           (logic 2)

} …

 


try 구역은 try 키워드로 시작.

try 구역 다음에는 하나 이상인 catch 절 목록이 옴.

catch catch 키워드, 괄호 안에 있는 객체의 선언, 구역 이 세 가지로 구성.



int num1=0;

 

while (1) {

           try {

                     cout << "Enter a number (100 or less integer) :";

                     cin >> num1;

 

                     if (num1 > 100)

                                throw runtime_error("You must enter 100 or less integer.");

 

                     cout << num1 << endl;

                     break;

           }

           catch (runtime_error err) {

                     cout << err.what() << endl;

                     cout << "If you wanna try again, enter y:";

                     char opt;

                     cin >> opt;

                     if (opt != 'y')

                                break;

           }

}

 

return 0;

 


throw에서 설명한 바와 같이, throw가 선언되는 순간 그 에러를 처리할 처리자로 이동하여 코드를 실행함. 이는 위 예제 코드를 실행하는 것으로 알 수 있음.

여기서 중요한 것은 처리자를 찾는 동안 실행중이었던 함수를 빠져나간다는 것임.

처리자 검색은 연속적인 함수 호출과 반대임. 예외가 발생하면 그 예외를 발생한 함수를 가장 먼저 확인하고, 그 함수에서 적당한 처리자가 없으면 그 함수를 호출했던 함수를 찾아감. 이 과정을 적당한 처리자가 나올 때까지 반복함.

적합한 처리자를 찾지 못하면 terminate 라이브러리 함수로 실행을 옮기는데, 이 함수는 시스템에 따라 다르지만 프로그램을 더 이상 실행하지 않고 중단함을 보장함.

try 구역이 없으면 처리자도 있을 수 없으므로, try 구역이 없는 프로그램에서는 예외가 발생하면 terminate를 호출해 프로그램을 종료.

 

예외는 프로그램의 정상적인 흐름을 가로챔. 예외가 발생한 지점에서 보면 호출한 쪽에서 요청한 계산 중 일부만 처리하고 나머지는 처리하지 않은 채 남아 있을 수 있음. 일반적으로 프로그램에서 어떤 부분을 우회하게 되면 객체가 유효하지 않거나 불완전한 상태로 남아있으며, 또는 자원이 해제되지 않는 등의 일이 생길 수 있음. 예외를 처리하는 동안 적절히 정리하는 프로그램을 예외에 안전하다고 함. 예외에 안전한 코드를 만드는 것은 굉장히 어려운 일.

프로그램에서 예외를 처리하고 계속해서 진행하려면 일반적으로 예외가 발생할 수 있는지, 객체 유효성을 보장하고 자원이 누출되지 않으며, 적절한 상태로 프로그램을 되돌리기 위해 반드시 해야 하는 것이 무엇인지 항상 알아야 함. 




3.     표준 예외 및 Exception 클래스


C++ 라이브러리에서는 표준 라이브러리 함수에서 문제가 생겼을 때 이를 보고하는데 사용하는 여러 클래스를 정의.

이 클래스는 다음 네 가지 헤더에서 정의함.

exception: 가장 일반적인 예외 클래스인 exception을 정의. 이는 예외가 발생했다는 것만 알리고 다른 추가 정보는 알리지 않음.

stdexcept: 몇 가지 일반적인 목적의 예외 클래스를 정의함.

new: bad_alloc 예외 타입을 정의함. 자세히는 추후 설명

type_info: bad_cast 예외 타입을 정의. 이 또한 자세히는 추후 설명

 

Exception 클래스는 발생한 것에 대한 정보를 throw와 연관된 catch 사이에 전달하는 데 사용.

exception, bad_alloc, bad_cast 객체에는 기본 초기화만 할 수 있으며 이 예외 타입 객체에 초기 값을 지정할 수 있음.

다른 예외 타입은 이와 반대로 string이나 C 형식 문자열로 초기화 할 수 있으나 기본 초기화가 불가능. 그러므로 반드시 초기 값을 지정해야 하며, 이 초기 값은 발생한 오류에 대한 추가 정보를 제공하는 데 사용

 


stdexcept에서 정의하는 표준 예외 클래스

  • exception: 가장 일반적인 유형의 문제
  • runtime_error: 실행 중에만 발견할 수 있는 문제
  • range_error: 실행 중 오류 의미 있는 값 범위를 벗어나 생성된 결과
  • overflow_error: 실행 중 오류 오버플로한 계산
  • underflow_error: 실행 중 오류 언더플로한 계산
  • logic_error: 프로그램의 논리적 오류
  • domain_error: 논리 오류 결과가 존재하지 않는 인자 (ex. 루트 안에 음수 값이 들어간 경우)
  • invalid_argument: 논리 오류 적절하지 않은 인자
  • length_error: 논리 오류 해당 타입의 최대 크기보다 더 큰 객체를 생성하려 함
  • out_of_range: 논리 오류 유효한 범위를 넘어선 값 사용


Posted by BinZIP
Programming/Programming2018. 1. 13. 07:08

1.     String Library


String은 가변길이 문자열.

String을 사용하려면 string 헤더를 include 시켜야 함.

 

직접 초기화와 복사 초기화


string s1; // 빈 문자열로 초기화

string s2 = s1; // 빈 문자열 s1을 복사해 초기화

string s3 = “Hi”; // “Hi” 문자열을 복사 초기화

string s4(10, ‘c’); // ‘c’ 10개 복사본으로 초기화

string s5(“Hi”); // s3과 같이 초기화되나 직접 초기화

 

string 연산

getline(is, str); // is에서 한 줄 읽어 s에 넣고 is 반환

str.empty(); // str이 비어있으면 true, 아니면 false 반환

str.size(); // str의 문자 수를 반환

비교 연산은 대소문자를 구별하며 사전 순으로 함.

 

str의 모든 char에 대해 연산하고 싶으면 다음과 같이 사용

for(auto &c : str) // 요소에 직접 접근해 변경할 때

for(auto c : str) // 출력 등을 할 때

 

 


2.     Vector Library


Vector는 타입이 모두 같은 객체의 모음

Vector는 그 안에 다른 객체를 담기 때문에 흔히 컨테이너라고 함

Vector를 사용하려면 vector 헤더를 include 시켜야 함

 

Vector는 클래스 템플릿(Class template) – 템플릿 자체는 함수도 템플릿도 아님. 템플릿을 사용해 클래스나 함수를 만드는 과정을 인스턴스화(Instantiation)라고 함.

Vector에서 추가 정보는 담을 객체 타입.


vector<int> iv; // iv에 담을 객체는 int

vector<vector<string>> page; // page에 담을 객체는 string을 가지는 vector

 

정의 및 초기화


vector<int> v1; // int vector 하나 정의

vector<string> page{“a”, “an”, “the”}; // 목록 직접 초기화

vector<string> page = {“a”, “an”, “the”}; // 목록 복사 초기화

vector<int> ivec(10, -1); // int 요소가 10개인 ivec에 각각 -1로 직접 초기화

vector<int> ivec2(10); // int 요소가 10개이며 각각 0으로 초기화됨

vector<int> ivec3{10}; // 값이 10인 요소 1

 

Vector는 배열과 다르게 실행 중에 요소를 제한 없이 추가할 수 있음. Vector의 각 요소가 모두 같은 값이 아니라면 개수를 정해놓는 것이 오히려 성능에 나쁜 영향을 미침.

 

Vector 연산


v.empty()

v.size()

v.push_back(i);

 

String 연산과 마찬가지로 vec의 모든 요소에 대해 연산하고 싶으면 다음과 같이 사용

for ( auto &i : vec )

for( auto i : vec )

 

Size_type 타입

String Vector에서 size를 사용하면 반환되는 타입.

크기를 나타내는 부호 없는 타입. 정확히 알 필요는 없음.

 

 


3.     Iterator


반복자(Iterator)를 사용하면 객체에 간접적으로 접근할 수 있음

포인터와는 다르게 반복자는 주소 연산자를 사용해 얻지 않음 이름이 begin end인 멤버를 통해

end에서 반환한 반복자는 연관된 컨테이너의 마지막 요소 바로 다음에 위치한 반복자임 흔히 off-the-end iterator(끝 지난 반복자) 혹은 end 반복자라고 말함

auto b = v.begin(), e = v.end();

 

Iterator 연산

++, -- : iterator가 가리키고 있는 컨테이너 요소의 전, 후를 지시하도록 할 수 있음

-> : iterator를 역참조해 대상 요소에서 멤버를 가져올 수 있음.

* : iterator가 나타내는 요소에 대한 참조자를 가져올 수 있음.

 

Iterator 타입

size_type과 비슷하게 정확하게 알 필요 없음. 하지만 반복자가 있는 라이브러리 타입에서는 실제 반복자 타입을 나타내는 iterator, const_iterator라는 타입 정의

const_iterator const 포인터처럼 행동함

 

역참조와 멤버 접근 결함

반복자를 역참조하면 그 반복자가 나타내는 객체를 얻음. 만약 객체가 클래스 타입이면 그 객체의 멤버에 접근할 수 있음.

*it.empty() // 오류. it iterator이기 때문에 empty 멤버 없음.

위와 같은 표현식을 간단히 하기 위해 ->연산자를 정의.

it->empty()

 

일부 vector 연산은 반복자를 무효화함. vector는 동적으로 커질 수 있기 때문에 크기가 변하면 해당 vector에 속한 모든 반복자가 무효화될 수 있음. 자세히는 추후에 설명

 

반복자 산술 연산

string vector에 대한 반복자에서는 한 번에 여러 요소를 이동하는 연산 가능

두 반복자의 빼기 연산은 오른쪽 반복자에 더해 왼쪽 반복자가 되는 수를 반환함. 그래서 중앙점에 가장 가까운 요소를 나타내는 iterator를 계산하는 식은 다음과 같음

auto mid = v.begin() + v.end() / 2

 

Posted by BinZIP
Programming/Programming2018. 1. 11. 04:00

1.   복합 타입


복합 타입(Compound Type)은 다른 타입을 사용해 정의한 타입. C++에서는 대표적으로 참조자와 포인터가 있다.


-       참조자

참조자(Reference)는 객체에 별칭을 정의함.

참조자로 참조하는다른 타입이 곧 참조자의 타입.

참조자 타입을 정의할 때는 선언자를 &d 형식으로 쓰는데, 여기서 d는 선언할 이름.


int num = 10;

int &refNum = num;

refNum += 2; // num 2를 더하는 것과 같다.

int &refNum2; // 오류. 참조자는 초기화해야 한다.

int ii = refNum; // ii = num과 같다.


변수를 초기화할 때는 초기 값을 생성하는 객체에 복사해 넣지만, 참조자를 정의할 때는 초기 값을 복사하는 것이 아닌 참조자를 초기화식에 결합함. 일단 초기화 한 후에는 참조자는 자신의 초기 객체와 결합한 채로 남기 때문에 반드시 초기화 해야 함.

참조자는 별칭. 객체가 아니며, 이미 존재하고 있는 객체에 대한 다른 이름이라고 보아야 함.

 

-       포인터

포인터는 다른 타입을 가리키는 복합 타입.

참조자처럼 다른 타입에 직접적으로 접근하는데 사용됨. 하지만 참조자와는 다르게 포인터는 그 자체로 객체임. , 초기화 하지 않고 정의 가능.

포인터는 대입하거나 복사할 수 있으며, 포인터 하나가 생명 주기 동안 여러 다른 객체를 가리킬 수 있음.


void* 포인터

void* 타입은 모든 객체의 주소를 담을 수 있는 특별한 포인터 타입. 가리키는 객체의 타입은 알 수 없음.

할 수 있는 일이 제한적. 가리키는 객체에 연산을 할 수 없음.

일반적으로 메모리를 메모리로 다루기 위해서 사용함.

 

참조자는 객체가 아니기 때문에 참조자에 대한 포인터는 존재하지 않지만, 포인터는 그 자체로 객체이기 때문에 포인터에 대한 참조자는 존재할 수 있음.

 



2.   Const 한정자


변수의 값을 바꿀 수 없도록 정의하고 싶을 때 사용.


상위와 하위

포인터 그 자체가 const일 경우 top-level(상위) const, 가리키는 대상 객체가 const일 경우는 low-level(하위) const라고 함.


int i=10;

const int ci = 100;

int *const p1 = &i; // 상위 const

const int *p2 = &ci; // 하위 const

 

객체를 복사할 때 상위 const는 무시하지만, 하위 const는 절대 무시하지 않음. 객체를 복사할 때, 두 객체 모두에 하위 const 한정 표시가 있거나 두 객체 타입을 서로 변환할 수 있어야 함.

일반적으로 const가 아닌 객체는 const 객체로 변환할 수 있지만, 그 반대는 아님.

 

constexpr과 상수 표현식

상수 표현식(constant expression)은 컴파일 중에 값을 평가할 수 있으며, 그 값을 바꿀 수 없는 표현식.

객체나 표현식이 상수 표현식인지 여부는 타입과 초기 값에 따름.


const int a = 20; // O

const int b = a+1; // O

int c = 27; // X, 값이 바뀔 수 있음

const int d = getScale(); // X, 초기 값을 알 수 없음

 

constexpr 변수

변수가 상수 표현식인지 컴파일러에서 확인하게 할 수 있음.

constexpr로 선언한 변수는 암시적으로 const이므로 상수 표현식으로 초기화해야 함.


constexpr int a = 20; // O

constexpr int b = a+1; // O

constexpr int c = getScale(); // getScale constexpr 함수 일 때만 가능

 

상수 표현식은 컴파일 중에 평가할 수 있으므로 constexpr 선언에 쓸 수 있는 타입은 제한적.

constexpr을 사용할 수 있는 타입을 상수타입(literal type)이라고 함.

 

constexpr 선언에서 포인터를 정의하면 constexpr는 포인터가 가리키는 타입이 아닌 포인터 자체에 적용됨.


const int* p = i;

constexpr int* q = i;

 

p const int형에 대한 포인터, q int형에 대한 const 포인터가 됨.

 



3.   그 외


-       타입 별칭(Type alias)

typedef 혹은 using을 사용해 복잡한 타입 정의를 간단하게 할 수 있음.


typedef unsigned int UINT;

using UINT = unsigned int;


using을 사용해 타입 별칭을 정의하는 것을 별칭 선언(alias declaration)이라 함.

 

-       Auto 타입 지정자

auto 타입 지정자는 int, double과 같은 특정 타입을 명명하는 타입 지정자와 달리 컴파일러에서 초기 값을 통해 타입을 추론하도록 함. 그러므로, auto를 타입 지정자로 사용하는 변수에는 반드시 초기 값이 있어야 함.

auto는 일반적으로 상위 const를 무시하므로, 추론한 타입에 상위 const가 있으려면 명시적으로 지정해야 함.


int i=0;

const int ci = i;

auto a = &ci; // aconst int*.

const auto b = &ci; // bconst int* const

 


-       decltype 타입 지정자

변수를 초기화 하는 데 표현식을 사용하는 것이 아니라 컴파일러에서 표현식으로부터 추론한 타입으로 변수를 정의하고 싶을 때 사용.

컴파일러에서는 타입을 결정하기 위해 표현식을 분석할 뿐, 평가하지는 않음.

decltype(func()) sum = a; // sum의 타입은 func가 반환하는 타입

decltype을 적용하는 타입이 변수이면 상위 const와 참조자를 포함해 대상 변수의 타입을 반환함.

적용하는 타입이 변수가 아닌 표현식이면 해당 표현식에서 반환하는 타입을 얻음.


int r=0, *pr=&r, &i = r;

decltype(r) a=0;

decltype(r+1) c = 1;

decltype(i) d = c;

decltype(*pr) b = a;


dint& 타입, b 또한 int& 타입이므로 초기화는 필수적임.

 

 

Posted by BinZIP

카카오톡 플러스친구 API를 활용해 자동응답 봇을 만들어보았다. 또한, 우리 학과 홈페이지의 공지사항 중 14일 이내에 게시된 글의 주소를 가져와 응답할 수 있도록 했다. 데이터 파싱에는 PHP Snoopy Class를 활용했다.


웹 어플리케이션 구현에 PHP를 사용했다.

웹서버 설정에서 .php 없이 실행하도록 etc/apache2/apache2.conf(Ubuntu server 기준)의 설정을 다음과 같이 변경해준다.




 

소스코드: https://github.com/BinZlP/KakaoPlusBot

 


#Keyboard.php


<?php

echo <<< EOD

{

    "type" : "buttons",

    "buttons" : ["공지사항 확인", "도움말", "직접 소통하기"]

}

EOD;

?>

 

keyboard.php는 채팅방에 사용자가 처음 들어왔을 때 보여질 입력 옵션을 json data로 반환해야 한다. 간단하게 echo로 위와 같이 구현할 수 있다. , EOD 전후에 공백을 포함한 어떤 문자도 들어가서는 안된다.

 


#Message.php


<?php

include_once 'Snoopy.class.php';

 

function rtn_message(){

           $snoopy = new snoopy;

           $snoopy->fetch("http://cs.kw.ac.kr/department_office/lecture.php");

           $txt = $snoopy->results;

 

           $cs_link = "http://cs.kw.ac.kr";

 

           $rex = "/^site_type=3\"\>.+\<\/a\>$/i";

 

           $rex_a = "|<a[^>]+>(.*)</a>|U";

           $rex_sbj = "/\<td class=\"subject\"\>(.*)\<\/td\>/i";

           $rex_tr = "/\<tr\>(.*)\<\/tr\>/i";

 

           $rex_date = "/[0-9]{4}\-[0-9]{2}\-[0-9]{2}/";

 

           $exp_tr = explode("<tr>",$txt);

 

           $now_date = date("Y-m-d",strtotime("+9 hours"));

           $n_date = date_create(date("Y-m-d"));

 

           $message = "";

 

           for($i = 5;$i<11;$i++){

                     $date = "";

                     $url_a_tag = explode("\"",$exp_tr[$i]);

                     $whole_url = $cs_link . $url_a_tag[5];

                     $slice_a = $url_a_tag[6];

                     $title_str = substr($url_a_tag[6],3,strlen($url_a_tag[6])-19);

 

                     preg_match_all($rex_date,$exp_tr[$i],$date);

                     $t_date = date_create($date[0][0]);

                     $interval = date_diff($n_date,$t_date);

 

                     if($interval->days<=14){

                                // print($date[0][0]);

                               

                                $message = $message."게시일: ".$date[0][0]."\\n";

                                $message = $message.$whole_url."\\n";

                                $message = $message."\\n";

                     }

           }

           if($message==""){

                     $message="최근 14일간 올라온 공지사항이 없습니다.";

           }

           return $message;

}

 

$data = json_decode(file_get_contents('php://input'));

 

if($data->content == "공지사항 확인"){

           $message = rtn_message();

 

           echo <<< EOD

{

           "message":{

                     "text": "$message"

           },

           "keyboard":{

                     "type": "buttons",

                     "buttons" : ["알림 시작하기", "도움말", "직접 소통하기"]

           }

}

EOD;

 

}

if($data->content == "도움말"){

           echo <<< EOD

{

           "message":{

                     "text": "소프트웨어학부 제2대 학생회 [프리]입니다. \\n프리의 플러스친구는 소프트웨어학부의 최근 14일간 공지사항을 버튼 하나로 자동으로 알려주는 기능을 하고 있습니다. \\n또한, 학생회에 질문하거나 전달하고 싶으신 말씀이 있으면 여기에 남겨주세요.",

                     "photo":{

                                "url": "http://server_url/kakao_auto/pree1.jpg",

                                "width": 600,

                                "height": 600

                     },

                      "message_button":{

                                "label": "소프트 학생회 페이스북",

                                "url": "http://www.facebook.com/softwarekw/"

                     }

           },

           "keyboard":{

                     "type": "buttons",

                     "buttons" : ["알림 시작하기", "도움말", "직접 소통하기"]

           }

}

EOD;

}

 

if($data->content == "직접 소통하기"){

           echo <<< EOD

{

           "message":{

                     "text": "소프트웨어학부 제2대 학생회 [프리]입니다. \\n전달하고자 하시는 말씀이 있으시면 여기에 메세지를 남겨주세요. 최대한 빠르게 확인하여 답변 드리도록 하겠습니다."

           },

           "keyboard":{

                     "type": "text"

           }

}

EOD;

}

?>

 

message.php는 특정 입력이 들어왔을 때 응답할 내용과 응답 후 사용자의 키보드를 어떻게 출력할 것인지에 대한 코드가 들어가야 한다. 파싱해온 내용을 message.php에 담아서 보내는 코드이다. , 메시지의 내용은 1000자 이하가 되어야 하고, 1000자 이상이 되었을 때는 결과가 사용자에게 출력되지 않으니 주의해야 한다.

 

 사용자에게 한 번의 요청이 왔으면 응답은 단 한 번만 할 수 있다. 적어도 플러스친구 기본 제공 API에서는 그런 것 같다.

 사용자에게 요청이 오지 않은 상태에서 사용자에게 메시지를 보내기 위해서는 카카오톡의 프로토콜을 모방해야 할 필요가 있어 보인다. 옛날에 LOCO 프로토콜을 사용해 통신할 때는 ID, PW, Device UUID, X-VC Value만 있으면 로그인 패킷, 메시지 전송 패킷을 흉내내어 개인적으로 메시지를 보내는 봇을 구현할 수 있었는데, 2016년쯤에 한 번 프로토콜을 엎었던 걸로 기억한다


'Programming > Web Programming' 카테고리의 다른 글

JavaScript Basic #7  (0) 2017.07.22
JavaScript Basic #6  (0) 2017.07.21
JavaScript Basic #5  (0) 2017.07.18
JavaScript Basic #4  (0) 2017.07.18
JavaScript Basic #3  (0) 2017.07.16
Posted by BinZIP
Programming/Programming2017. 12. 28. 23:02

작품명: I Wanna Be the Kwangwoon Man


소개: IWBTB(I Wanna Be The Boshy)시리즈 게임을 보고 영감을 받아 제작하게 된 탄막 슈팅 게임이다. 쓸데없이 어려운 조작과 극악 난이도의 보스 몬스터, 지나치게 비싼 업그레이드 비용을 내세워 게이머의 집중력과 순발력을 극단적으로 요구한다.


제작기간: 약 1달, 실제 투자시간은 약 120시간 정도. 너무 오래걸렸다..




실행파일 다운로드: IWBTKM.zip


C언어와 SDL(Simple Directmedia Layer) 라이브러리를 사용해 게임 전체적인 기능을 구현했다. 메모리 누수 현상이 심했는데 gameManage.c의 drawText함수에서 Surface 하나를 free 해주지 않아서 생기는 문제였다. 

스테이지마다 다른 몬스터 이미지를 사용하고, 맵에서 게임 전체적인 진행과 관련된 부분 중 반복되는 부분은 함수를 사용해 코드를 줄이려 노력했으나, 너무 귀찮고(?) 어차피 몇 만줄 짜리 코드가 아니라 그냥 썼다.

마찬가지로 세이브데이터 암호화도 어차피 플레이해볼 사람이 없을 것으로(혹은 맛보기로 해보거나) 판단되어 하지 않았다.


혹시라도 이 게임을 플레이하는 유저 중, 세이브 데이터를 수정하지 않고 이 게임을 클리어한다면 당신은 웬만한 탄막 게임은 다 정복할 수 있을 것이다.

Posted by BinZIP
Programming/Web Programming2017. 7. 22. 14:31

JavaScript #7 – Object-Oriented (객체지향)

Prototype

객체의 원형. 객체는 property를 가질 수 있는 것은 그 동안 계속 봐왔던 사실임. 그 중 prototype이라는 property는 용도가 약속되어 있음.

Prototype에 저장된 속성들은 생성자를 통해서 객체가 만들어질 때 그 객체에 연결됨.


function Ultra(){}

Ultra.prototype.ultraProp = true;

 

function Super(){}

Super.prototype = new Ultra();

 

function Sub(){}

Sub.prototype = new Super();

 

var o = new Sub();

console.log(o.ultraProp);



생성자 Sub를 통해 만들어진 객체 o에서 ultraProp을 찾을 때 실제로는 o, Sub.prototype, Super.prototype, Ultra.prototype에서 ultraProp을 차례로 찾게 됨. , prototype은 객체와 객체를 연결하는 연결고리 역할을 함. 그래서 이러한 관계를 ‘Prototype Chain’ 이라고 함.

 

Standard Built-in Object (표준 내장 객체)

JavaScript가 기본적으로 가지고 있는 객체. 프로그래밍을 하는데 기본적으로 필요한 도구들.

내장 객체에는 object, function, array, string, Boolean, number, math, date, RegExp가 있음.


var arr = new Array('seoul','new york','ladarkh','pusan', 'Tsukuba');

function getRandomValueFromArray(haystack){

    var index = Math.floor(haystack.length*Math.random());

    return haystack[index];

}

alert(getRandomValueFromArray(arr));

 

위 코드는 배열에서 랜덤한 요소 하나를 가지고 오는 코드임. 그런데 저 코드를 array라는 표준내장 객체에 포함시키면, 모든 array가 마치 원래 array 객체의 메소드인 것처럼 위의 기능을 사용할 수 있음.


Array.prototype.rand = function(){

    var index = Math.floor(this.length*Math.random());

    return this[index];

}

var arr = new Array('seoul','new york','ladarkh','pusan', 'Tsukuba');

alert(arr.rand());

 

Object

Object 객체는 객체의 가장 기본적인 형태를 가지고 있는 객체. 아무것도 상속받지 않는 객체임.

JavaScript의 모든 객체는 Object 객체를 상속받기 때문에 모든 객체는 Object 객체의 Property를 가지고 있음. 또한 Object 객체를 확장해 모든 객체가 접근할 수 있는 API를 만들 수 있음.

 


Object.prototype.contain = function(neddle) {

    for(var name in this){

        if(this[name] === neddle){

            return true;

        }

    }

    return false;

}

var o = {'name':'BinZIP', 'city':'seoul'}

alert(o.contain('BinZIP'));

var a = ['BinZIP','supernice','bin'];

alert(a.contain('supernice'));

 


두 번의 alert 모두 True라고 출력되는 것을 확인할 수 있음.

하지만 Object 객체는 확장하지 않는 것이 좋음. 모든 객체에 영향을 주기 때문에 객체가 기본적으로 가지고 있을 것으로 예상하고 있는 객체 외에 다른 객체를 가지고 있는 것은 혼란스러울 수 있기 때문.


for(var name in o){

    console.log(name); 

}

 


이런 문제를 회피하기 위해서는 해당 객체의 소속인지 확인할 수 있는 hasOwnProperty를 사용할 수 있음.


for(var name in o){

    if(o.hasOwnProperty(name))

        document.write(name+'<br/>'); 

}



 

Data Type

데이터 타입이란 데이터의 형태를 의미함. 데이터 타입은 크게 객체와 객체가 아닌 것, 이 두 가지로 구분할 수 있음.

객체가 아닌 것에는 숫자, 문자열, Boolean(True/False), null, undefined가 있다. 이런 객체가 아닌 데이터 타입을 Primitive Type(원시 데이터 타입)이라고 함. 그 외 모든 데이터 타입들은 객체임.

그런데 아래 코드를 보자.


var str='hello';

document.write(str.length);

document.write('<br/>');

document.write(str.charAt(0));




문자열에 property와 메소드가 있음. 그렇다면 객체라는 의미인데, 문자열은 객체가 아니라고 했음. 이는 내부적으로는 문자열이 Primitive Type이고 문자열 관련 어떤 작업을 하려고 할 때 JavaScript가 임시로 문자열 객체를 만들어 사용하고, 사용이 끝나면 제거하기 때문임.


문자열이 Primitive Type이지만 이와 관련해 필요한 기능성을 객체지향적으로 제공해야 하는 필요 또한 있기 때문에, Primitive Type을 객체처럼 다룰 수 있도록 제공하는 객체가 있음. 이를 Wrapper Object(레퍼객체)라고 부름. Wrapper Object에는 String, Number, Boolean이 있음.

 

참조

다음 코드를 보자.


var a = {'id':1};

var b = a;

alert(b.id);



1이라는 것을 당연히 예상할 수 있음. 그렇다면 다음 코드의 결과를 예측해보자.


var a = {'id':1};

var b = a;

b.id=2;

alert(a.id);




결과는 2. B id의 값을 2로 변경했는데 aid의 값도 2가 되었음. 이는 변수 b a에 담긴 객체가 서로 같음을 의미함.

이는 b에 새로운 객체가 복제되어 생성되는 것이 아니라, a의 객체를 참조하고 있다는 의미임.

그런데, 아래 코드를 보자.


var a = 1;

var b = a;

b = 2;

alert(a+' '+b);




여기서는 a의 값이 바뀌지 않았음. , Primitive Type은 복제되어 들어가고, 객체는 참조함을 확인할 수 있음.


그렇다면 함수에 참조 데이터 타입을 넘겨주면 어떻게 될까? 다음 코드를 보자.


var a = {'id':1};

function func(b){

    b = {'id':2};

}

func(a);

alert(a.id);




함수 func의 파라미터 b로 전달된 값은 객체 a. B를 새로운 객체로 대체하는 것은 b가 가지고 있는 객체를 변형하는 것이기 때문에 객체 a에 영향을 주지 않음. 하지만 아래는 다름.


var a = {'id':1};

function func(b){

    b.id = 2;

}

func(a);

alert(a.id);



이는 b에 새로운 객체를 만들어 적용하는 것이 아닌 참조된 a에 있는 reference의 속성을 바꾸고 있으므로, 그 속성이 소속된 객체를 대상으로 수정한 것임. 그렇기 때문에, aid의 값이 변경됨.

'Programming > Web Programming' 카테고리의 다른 글

[PHP] 카카오톡 자동 응답 봇 구현  (0) 2018.01.07
JavaScript Basic #6  (0) 2017.07.21
JavaScript Basic #5  (0) 2017.07.18
JavaScript Basic #4  (0) 2017.07.18
JavaScript Basic #3  (0) 2017.07.16
Posted by BinZIP
Programming/Web Programming2017. 7. 21. 16:40

JavaScript #6 – Object-Oriented (객체지향)

 

Object-Oriented Programming

객체지향프로그래밍(OOP): 로직을 상태와 행동(변수와 메소드)로 나누고 연관된 것들끼리 그룹핑한 것을 객체라 하고 이를 조립해 프로그래밍을 하는 것.

객체지향의 여러 가지 특성

1.     부품화: 프로그램의 로직들을 기능별로 나눠 부품화하는 것

2.     은닉화, 캡슐화: 로직을 온전히 부품화하기 위해 내부동작법은 숨기고 사용법만 노출하는 것

3.     인터페이스: 부품들간의 접점에서의 규칙, 약속

4.     객체지향은 코드의 재활용성을 높임.

 

객체

객체: 서로 연관된 변수와 함수를 묶은 것

메소드: 객체를 구성하는 함수

생성자: 객체를 만드는 역할을 하는 함수. JavaScript에서 함수는 재사용 가능한 로직의 묶음이 아니라 객체를 만드는 창조자임.


function Person(name){

    this.name = name;

    this.introduce = function(){

        return 'My name is '+this.name;

    }  

}

var p1 = new Person('BinZIP');

document.write(p1.introduce()+"<br />");

 

var p2 = new Person('supernice');

document.write(p2.introduce());



생성자 내에서 객체의 Properties를 정의하는 작업을 초기화라고 함.

생성자 함수는 일반 함수와 구분하기 위해 첫 글자를 대문자로 표시.

 

전역객체

모든 객체는 전역객체의 Property. 그리고 모든 전역변수와 함수는 window 객체의 Properties. 객체를 명시하지 않으면 임시적으로 window property로 간주됨.


var o = {'func':function(){

    alert('Hello?');

}}

o.func();

window.o.func();



Hello?가 두 번 alert 되는 것을 확인할 수 있음. 이에 따라 모든 객체가 기본적으로 전역객체의 property임을 알 수 있음.

 

This

This는 함수 내에서 Context of Calling Function(함수 호출 맥락)을 의미함. 맥락이라는 의미에 따라서, 함수를 어떻게 호출하느냐에 따라 this가 가리키는 대상이 달라짐.

함수를 호출했을 때 this는 전역객체인 window와 같음.


function func(){

    if(window === this){

        document.write("window === this");

    }

}

func();




참고로, 웹브라우저 JavaScript의 전역객체는 window이지만, node.js의 전역객체는 global. 이들의 구성 메소드는 차이가 있기 때문에 알고 있어야 함.

 

객체 소속의 메소드의 this는 그 객체를 가리킴.

 

var o = {

    func : function(){

        if(o === this){

            document.write("o === this");

        }

    }

}

o.func();



 

아래 코드는 함수를 호출했을 때와 new를 이용해서 생성자를 호출했을 때의 차이임


var funcThis = null;

 

function Func(){

    funcThis = this;

}

var o1 = Func();

if(funcThis === window){

    document.write('window <br />');

}

 

var o2 = new Func();

if(funcThis === o2){

    document.write('o2 <br />');

}



생성자는 빈 객체를 만듬. 그리고 이 객체 안에서 this는 만들어진 객체를 가리킴.

생성자가 실행되기 전까지는 객체는 변수에도 할당될 수 없기 때문에 this가 아니면 객체에 대한 어떠한 작업을 할 수 없음.

Apply, call 함수 메소드를 사용하면 this의 값을 제어할 수 있음


var o = {}

var p = {}

function func(){

    switch(this){

        case o:

            document.write('o<br />');

            break;

        case p:

            document.write('p<br />');

            break;

        case window:

            document.write('window<br />');

            break;         

    }

}

func();

func.apply(o);

func.apply(p);



 

상속

객체의 로직을 그대로 물려 받는 또 다른 객체를 만들 수 있는 기능. 하지만 단순히 물려받는 것이라면 의미가 없음. 기존의 로직을 수정하고 변경해서 파생된 새로운 객체를 만들 수 있게 해줌.


function Person(name){

    this.name = name;

    this.introduce = function(){

        return 'My name is '+this.name;

    }  

}

var p1 = new Person('BinZIP');

document.write(p1.introduce()+"<br />");

 


이 코드는 위에서 다뤘던 코드임. 이 코드를 다음과 같이 수정함.


function Person(name){

    this.name = name;

}

Person.prototype.name=null;

Person.prototype.introduce = function(){

    return 'My name is '+this.name;

}

var p1 = new Person('egoing');

document.write(p1.introduce()+"<br />");

 

수정한 코드에 새로운 Programmer라는 생성자를 만들고 이 생성자의 prototype Person 객체를 연결해봄.


function Person(name){

    this.name = name;

}

Person.prototype.name=null;

Person.prototype.introduce = function(){

    return 'My name is '+this.name;

}

 

function Programmer(name){

    this.name = name;

}

Programmer.prototype = new Person();

 

var p1 = new Programmer('BinZIP');

document.write(p1.introduce()+"<br />");



Person 객체의 메소드 introduce Programmer 객체도 사용할 수 있음을 확인함. Programmer Person의 기능을 상속하고 있는 것. 하지만 단순히 똑같은 기능을 갖게 되는 것이 아니라, 부모의 기승을 계승 발전할 수 있는 것이 상속의 가치임.

 

function Person(name){

    this.name = name;

}

Person.prototype.name=null;

Person.prototype.introduce = function(){

    return 'My name is '+this.name;

}

 

function Programmer(name){

    this.name = name;

}

Programmer.prototype = new Person();

Programmer.prototype.job = function(){

    return "Job : Programmer";

}

 

var p1 = new Programmer('BinZIP');

document.write(p1.introduce()+"<br />");

document.write(p1.job()+"<br />");



ProgrammerPerson의 기능을 가지면서 job이란 메소드를 부가적으로 가지고 있음을 확인할 수 있음.

 

 

'Programming > Web Programming' 카테고리의 다른 글

[PHP] 카카오톡 자동 응답 봇 구현  (0) 2018.01.07
JavaScript Basic #7  (0) 2017.07.22
JavaScript Basic #5  (0) 2017.07.18
JavaScript Basic #4  (0) 2017.07.18
JavaScript Basic #3  (0) 2017.07.16
Posted by BinZIP