C++에서 #변수명 사용법

C++에서 가끔 #을 붙여쓰는 변수를 봤는데 오늘 용도를 알았다.

#include <iostream>

#define Test(a) { printf("%s", #a); }

int main(void)
{
int ahnjungwoong = 1000;
Test(ahnjungwoong);

return 0;
}

이렇게 코드를 입력하고 실행하면 Test() 안의 #a는  ahnjungwoong의 값인 1000이 찍히는게 아니라 Test(a)의 인자로 들어온 변수명인 ahnjungwoong의 값이 찍힌다.

LOG4CXX를 사용할 때 클래스의 이름을 태그로 출력하거나 할 때 자주사용되므로 기억해놓을 것.

C#에서 레지스트리 읽고 쓰는 방법

Registry 클래스를 이용한다.

일단 소스 상단에 using Microsoft.Win32를 선언해준다.

RegistryKey reg;
reg = Registry.LocalMachine.CreateSubKey("Software").CreateSubKey("RegistryKeyTest");
SetValue()를 통해 값을 설정하고 GetValue()를 통해 값을 읽어올 수 있다.

reg.SetValue("Text", "글을 입력하겠소"); // 값을 저장한다.
reg.GetValue("Text", "없음")     // text라는 이름을 가진 값을 가져온다.
// 이때 값이 없다면 "없음" 이라고 값을 얻어온다.
reg.GetValue("Text") // text라는 이름을 가진 값을 가져온다.
레지스트리에 값을 삭제할 때는 DeleteSubkey()를 쓴다.

reg.DeleteSubKey("Text", false); // 값을 삭제한다.
Registry.LocalMachine.DeleteSubKey("Software\RegistryKeyTest"); // 레지스트리키를 삭제한다.

출처 : http://happyguy81.blog.me/10148698440

 

2013년 5월 13일 추가.

이 기능을 사용하려면 프로그램을 관리자 권한으로 실행해야 정상작동한다. 관리자 권한으로 실행하지 않았을시에는 오류가 발생할 수 있다. 때문에 내 경우에는 힘들여만들어놓은 이 기능을 안 쓰고 파일을 이용하도록 변경해 나가는 중.

C#에서 Byte[]와 String간 변환하기

서버에서 받은 데이터를 byte[]에 문자열을 저장하고 Message.Show()로 보여주려고 했더니 System.Byte[]만 계속 찍혔다.

뭐가 문제인가 네이버에서 찾아봤더니 좋은 글 발견.

String을 byte[]로 변환하려면,
byte[] ba = System.Text.Encoding.Default.GetBytes(str);

byte[]를 String으로 변환하려면,
String str = System.Text.Encoding.Default.GetString(ba);

출처 : http://blog.naver.com/hursh1225?Redirect=Log&logNo=40120911491

auto_ptr, shared_ptr, scoped_ptr

스마트포인터

메모리를 사용하는 과정에서 할당이후 해제가 되지 않으면 해당 메모리 공간에는 다른 데이터를 넣을 수 없게 되어 사용가능한 메모리공간이 점점 줄어드는 메모리누수(leak)현상이 발생한다. 또한 해당 메모리 공간에 접근하거나 하여 쓰레기값이 나오거나 혹은 프로그램이 오류를 일으킬 수도 있다. 따라서 할당(malloc, new 등) 이후에는 반드시 해제(free, delete 등)을 해줘야하는데 이 과정의 복잡함과 프로그래밍에서의 실수를 방지하기 위해 자동으로 메모리를 해제해주는 포인터가 바로 스마트포인터이다.

1. auto_ptr

C++ STL에 있는 기본 스마트포인터. 객체가 특정 스코프(…{….}…)가 종료 될 때 소멸자가 호출된다는 원리를 이용한다.

기본적인 사용법

void main()
{
        auto_ptr<int> pInt(new int);
        *pInt = 10;
        cout<<*pInt<<endl;
}

auto_ptr의 단점

  1. 스코프 종료 시점에 메모리를 해제하기 때문에 메모리 할당을 배열 단위로 받을 때에는 제대로 메모리를 해제하지 못할 수 있다.
  2. auto_ptr로 할당한 포인터 변수를 서로 대입할 시 (ex : a = b) a와 b가 같은 곳을 가리키는 것이 아니라 a는 b가 가리키고 있던 주소가 들어가고 b는 NULL 상태가 된다. 이렇게 하는 이유는 a와 b가 같은 메모리를 가리키고 있다면 auto_ptr에 의하여 같은 메모리를 두번 해제하려 할 수 있기 때문이다. 따라서, auto_ptr은 같은 메모리 영역을 가리키는 auto_ptr을 2개 이상 생성할 수 없다.
  3. 위와 같은 문제점 때문에 STL의 컨테이너들에는 사용할 수 없다.

그래서 auto_ptr은 C++ 11 에서 deprecated 되었고 앞으로를 위해서라도 이것을 사용하면 안된다.

2. shared_ptr

auto_ptr의 문제점 때문에 boost에서는 shared_ptr이 생겨나게 되었다. 메모리 포인터가 가리키고 있는 객체의 수를 ‘레퍼런스 카운터(참조횟수)’라는 것을 이용하여 0에서부터 1씩 증가시키고,  메모리 해제가 될때 1씩 감소시키다가 0이 되면 메모리를 해제한다. 따라서 같은 메모리 영역을 가리키지 못한다는 단점을 극복하게 된다.

기본적인 사용법

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

class ClassA
{
public:
	ClassA(){cout << "ClassA()" << endl;}
	~ClassA(){cout << "~ClassA()" << endl;}
	void hello(){cout << "# hello()" << endl;}
};

vector< shared_ptr<ClassA> > vec;

int main () {
	shared_ptr<ClassA> obj(new ClassA());
	cout << "* use_count (new): " << obj.use_count() << endl;

	vec.push_back(obj);
	cout << "* use_count (push): " << obj.use_count() << endl;

	obj->hello();

	vec.pop_back();
	cout << "* use_count (pop): " << obj.use_count() << endl;

	obj.reset(new ClassA());
	cout << "* use_count (reset): " << obj.use_count() << endl;

  return 0;
}

shared_ptr로 할당된 객체는 use_count()라는 함수를 통해 레퍼런스 카운터의 상태를 알아낼 수 있다.

 shared_ptr의 단점

  1. 레퍼런스 카운터를 이용하기 때문에 환형 링크드리스트처럼 head->next->…->next->tail->head 식으로 계속 다음을 가리키는 경우에는 레퍼런스 카운터가 0이 되지 않아 메모리가 해제되지 않을 수 있다.

3. scoped_ptr

동적으로 할당되는 객체에 대한 포인터를 가진다. 자신이 삭제되면 가리키고 있는 객체도 자동으로 삭제시킨다. 이 포인터는 복사가 불가능하게 되어있으므로 소유권이나 참조카운트 등의 문제가 발생하지 않는다.

기본적인 사용법

// ScopedPtrTest.h
#include <iostream>
#include <memory>
#include <boost/scoped_ptr.hpp>

using std::cout;
using std::endl;

class Sample
{
   public:
       Sample() { cout << "생성" << endl; }
       ~Sample() { cout << "소멸" << endl; }
       void print() { cout << "print" << endl; }
};

// ScopedPtrTest.cpp
#include "ScopedPtrTest.h"

typedef boost::scoped_ptr<Sample> scopePtr;

void printStr(scopePtr* Ptr); // 값 복사가 불가능하므로 포인터 형태로 넘겨야 한다.

int main(void)
{
    scopePtr p1(new Sample());
    scopePtr p2(new Sample());

    p1->print();
    printStr(&p1); // 스마트 포인터 scoped_ptr(scopePtr)의 포인터를 넘긴다.

   /*
       // scoped_ptr - 자신이 가지는 포인터를 다른 인스턴스로 할당하거나 넘겨줄 수 없다.
       //                    한번 가리키는 객체에 대한 삭제의 책임을 전적으로 진다.
       //                    (복사가 빈번한 STL에서 사용할 수 없다.)

    p2 = p1             // 에러! 할당 불가!
    scopePtr p3(p1) // 에러!
   */

   return 0;
}

void printStr(scopePtr* Ptr) // 값 복사가 불가능하므로 call by reference(address)로 인자를 넘긴다.
{
    (*Ptr)->print();
}

scoped_ptr의 단점

  1. 포인터를 다른 변수에 복사하거나 할당할 수 없다.
  2. STL의 컨테이너에 넣을 수 없다.
  3. 동적으로 생성한 객체들의 배열을 만들고 싶을 때에는 scoped_array를 이용한다.

출처&참고URL

  • http://psychoria.blog.me/40155382971
  • http://sweeper.egloos.com/2826435
  • http://bangkert89.blog.me/90161420244
  • http://blog.naver.com/ddongtong18/120161513218
  • http://breaklee.blog.me/60137887986
  • http://sanaigon.tistory.com/72
  • http://codecooking.tistory.com/21
  • http://blog.daum.net/creazier/15309389
  • http://silped.tistory.com/3
  • http://blog.naver.com/jjmyann123?Redirect=Log&logNo=120168606246
  • http://blog.naver.com/xelona?Redirect=Log&logNo=70041650058

IOCP에 대한 좋은 설명 문서

IOCP에 대해 검색하다가 발견하게 된 좋은 문서. 데브피아의 이기탁님이 쓰신 문서라고 한다. IOCP에 대하여 자세하게 잘 설명하고 있는듯하다. 나중에 다시 생각날 때마다 보기 위해 여기에 글을 쓰고 해당문서를 저장해놓는다.

이렇게 퍼와도 되는지 모르겠다. 내가 가져온 곳은 http://sweeper.egloos.com/2811340 (당연히 저작권은 이기탁님에게 있다.)

쓰레드 처리를 위한 Auto Lock/Unlock 클래스

오늘 공부의 목표는 Lock/Unlock의 자동화였다.

CRITICAL_SECTION을 사용할 때 이것을 초기화, 삭제해주는 것뿐만 아니라 EnterCriticalSection/LeaveCriticalSection을 일일히 수동으로 해준다는 것은 귀찮을 뿐만 아니라 사람의 실수에 의하여 문제가 생길 소지가 있는 부분이기 때문. 만약 언락을 안하면 데드락 상황에 빠질 수 있기 때문에 반드시 언락을 해줘야하는데 어떤 프로그램 로직상 혹은 작성자의 실수로 이런 경우가 생길 수 있다. 그래서 그걸 아예 자동화해주는게 Auto Lock/Unlock 클래스.

두개의 헤더파일로 구성된다.

lock.h 파일

#include <iostream>
#include <Windows.h>
#include <process.h>

class Lock
{
public :
	Lock()
	{
		// std::cout << "INIT" << std::endl;
		InitializeCriticalSection(&cs_);
	}

	~Lock()
	{
		// std::cout << "DELETE" << std::endl;
		DeleteCriticalSection(&cs_);
	}

	void lock()
	{
		// std::cout << "LOCK" << std::endl;
		EnterCriticalSection(&cs_);
	}

	void unlock()
	{
		// std::cout << "UNLOCK" << std::endl;
		LeaveCriticalSection(&cs_);
	}
private :
	CRITICAL_SECTION cs_;
};

크리티컬 섹션을 생성하고 초기화와 삭제, 락과 언락을 해주는 메서드로 이루어져있다.

그다음은 AutoLockUnlock.h 파일

#include <iostream>

#include "lock.h"

class AutoLockUnlock
{
private :
	static Lock lock_;
public :
	AutoLockUnlock()
	{
		// std::cout << "AutoLockUnlock() 생성자" << std::endl;
		lock_.lock();
	}

	~AutoLockUnlock()
	{
		lock_.unlock();
		//std::cout << "AutoLockUnlock() 소멸자" << std::endl;
	}
protected :
private :
};

Lock AutoLockUnlock::lock_;

실제사용은 autolockunlock.h 파일을 인클루드하고 스코프를 만들어주기 위해 락이 필요한 부분을 {, }로 감싼 다음 해당 블록 안 제일 첫줄에 AutoLockUnlock autolockunlock; 처럼 하나 선언해준다. 블록이 시작되면 AutoLockUnlock의 생성자에 의해 크리티컬섹션의 초기화와 락이 시작되고 코드 블록이 종료되면 AutoLockUnlock의 소멸자에 의하여 언락이 되고 크리티컬섹션 삭제까지 된다.

아래와 같은 쓰레드 함수가 있다면 맨처음에 AutoLockUnlock 타입을 같은 변수를 생성함으로써 초기화/락/언락/삭제를 한번에 할 수 있다.

unsigned int WINAPI ThreadFunc(void * arg)
{
	AutoLockUnlock autolock; // 여기서 오토락을 건다. 생성만 해주면 알아서 만들어지고 알아서 삭제된다.
	std::cout << "쓰레드 시작 :: " << target << std::endl;

	for (int i = 0; i < 50000; i++)
	{
		target = target + 1;
	}

	for (int i = 50000; i < 100000; i++)
	{
		target = target - 1;
	}
	std::cout << "쓰레드 종료 :: " << target << std::endl;
	return 0;
}

중요한건 {, }에 의한 코드블록에 의해서 AutoLockUnlock 클래스의 생성자와 소멸자가 호출된다는 점. 이것을 이용하여 자동화된 Lock/Unlock을 구현한다.

쓰레드를 쓸 때는 항상 이러한 자동화된 락과 언락을 사용함으로써 데드락에 빠지는 사태를 막을 수 있다. 쓰레드를 쓸 때는 무조건 사용할 것.

cin을 쓸 때 입력을 한번 지나가는 현상을 해결하는 방법

오늘 공부하던 중 발견한 부분.

cin을 통해 문자 하나를 입력 받은 후 getline으로 다른 문자열을 입력 받으려고 할 때 문제가 생긴다. 입력을 한번 건너뛰는 현상.

cin으로 입력 받은 다음 다른 이벤트(?)가 더 오는 것 때문이라고 한다. 이 문제를 해결하기 위해서 std::fflush(stdin);을 이용하면 이 현상이 사라진다. cin을 이용할 때는 반드시 기억해놔야할듯.

std::string temp;
std::cin >> temp;
std::cout << "start" << std::endl;
int rtn = std::fflush(stdin);

std::string msg;
std::getline(std::cin, msg);
std::cout << "end" << std::endl;

C++로 만든 계산기

회사 스터디 커리큘럼 중 만들어 본 C++로 구현된 사칙연산 계산기이다.

사실은 TCP 서버/클라이언트를 공부하는 와중에 책에 나온 예제가 너무 쉽다하여 계산기 기능을 가진 서버와 클라이언트로 바꾸기로 해서 만들게 되었다. 책에는 클라이언트에서 입력값 3개를 받도록 한다. 첫번째로는 상수, 두번째는 연산자, 세번째로는 상수를 받아서 서버로 보내면 서버는 두 상수를 연산자에 따라 계산하고 클라이언트로 다시 전송하는 방식이다. 이거야 뭐….;;

이 방법도 너무 쉽기 때문에 아예 수식으로 입력 받도록 만들었다.

최소한 인터넷에 떠도는 대부분의 사칙연산 계산기보다는 내가 만든게 훨씬 더 정교하고 정확하다고 말할 수 있을 것 같다.

만드는 과정에서 어려웠던 점과 해결하는 알고리즘 몇가지에 대해 기억해두려고 여기에 적어둔다. 혹시 누가 이 글을 보게 된다면, 내가 생각했던 부분이나 내 코드에 문제가 있다면 지적해주면 좋겠다.

 

일단 수식을 입력 받은 후 제일 처음 할일은 수식의 빈칸을 모두 제거하는 일이다. 수식의 처음부터 끝까지 검색하며 빈칸을 모두 제거하는 함수를 하나 만들어 적용한다.

1. 일단 괄호 문제

사칙연산에서 괄호가 들어가면 계산의 우선순위가 바뀐다는 것이 문제인데 이에 대한 해결책은 후위 연산과 스택을 응용하는 방법이다. 여기에 대해서는 인터넷에 이미 많은 글들이 있어서 따로 여기에 적을 필요는 없는 것 같다. 내 경우에는 vector와 stack을 둘다 응용하며 썼다.

보통 인터넷에서 찾은 사칙연산 계산기는 딱 여기까지만 해법이 나와있는 경우가 대부분이다. 난 여기에 보완하여 아래의 문제사항들을 대응할 수 있도록 바꾼다.

2. 음수 연산자 문제

위에 괄호 문제를 해결하고 나니 음수가 문제가 되었다. 예를 들면, -2+3 같은 식은 2 앞에 붙은 -를 연산자가 아니라 해당 상수와 같이 묶어서 봐야한다는 것. 그리고 여기에는 괄호에 마이너스가 붙는 경우까지 생겨난다. 예를 들면, -(2+3) 같은 수식이다. 위에 1번에서 괄호문제를 풀기 위해 후위 연산으로 변경하고 상수 스택과 연산자 스택으로 만들었다면 – 기호를 하나의 연산자로 취급하기 때문에 같이 계산할 대상 상수가 없어서 오류가 나게 된다. 이 문제를 해결하기 위해서는 음수를 0-x와 같은 식으로 바꿔줘야한다. 예를 들면 -2+3은 0-2+3으로, -(2+3)과 같은 경우는 0-(2+3)과 같이 바꿔줘야한다. 이렇게 하면 후위연산으로 전환 후에 스택으로 계산할 때 같이 비교할 대상이 있기 때문에 정상적으로 계산이 된다.

하나 주의할 것은, 만약 음수 기호 앞에 0이 아닌 다른 상수가 왔다면 이 경우에는 정상적으로 계산이 되므로 더이상 어떤 처리를 하면 안된다. 아래에 나오는 이중연산자 문제를 미리 처리하고 이 음수 연산자 문제를 처리하도록 만들었기 때문에 아래와 같은 정리할 수 있다.

  1. 괄호 앞에 -가 있는 경우 [ex : -(3+4) 라면]
  2. 0 – x로 변경한다. [ex : 0-(3+4)]
  3. 하지만 해당 부분 앞에 0이 아닌 상수가 있는 경우에는 [ex : …3-(3+4)] 변경하지 않는다.
  4. 그러므로 n번째에 ‘(‘가 오고 n-1번째에 -가 왔다면 n-2번째에는 반드시 0이 아닌 상수이어야하며 그렇지 않은 경우에는 0-x로 변경한다.
  5. 그런데 이미 이중연산자는 이전에 걸러져서 올 것이므로 위와 같은 상황이 오는 경우는 n-2번째가 ‘)’이거나 ‘(‘인 경우 밖에 없다.
  6. 따라서 이러한 경우는 해당 부분이 ‘)-(‘이거나 혹은 ‘(-(‘인 경우이므로, 이런 경우에는 각각 ‘)+0-(‘와 ‘(0-(‘으로 변경한다.

위의 6가지 규칙을 차례대로 따라 수식을 처리하면 된다. string의 find와 replace 메서드를 가지고 위 처리를 할 수 있다.

정말 사람 애먹인 작업이었다. 인터넷으로 찾아봐도 이 문제를 해결하는 방법에 대해서는 아무 도움을 받을 수 없어서 혼자 생각했다. 한참 골치아팠던 문제.

3. 소수점 문제

위에 문제들을 해결하고나니 이번엔 정수형 상수만 처리가 되었다. 소수점 처리도 하기 위해 후위연산 변환 후에는 double 형으로 처리할 수 있도록 변경한다. double 형으로 하면 소수점 이하 6자리까지는 나온다. 만약 이보다 더 강력한 정밀도를 원한다면 char 배열을 응용하여 계산하면 가능하다. (…지만 너무 귀찮아서 내가 해보진 않았다.) 소수점이 들어가게 되면 처리하고 가공할 때 숫자, 사칙연산자 뿐만 아니라 . 까지도 처리한다는 것에 유의해야한다.

4-1. 잘못된 수식에 대한 문제 – 수식의 맨마지막에 연산자가 오는 경우

수식계산 중 처음부터 아예 잘못된 수식이 입력될 가능성이 있다. 예를 들면 수식의 맨마지막에 연산자가 붙는 경우, 예를 들면, ‘2+3-‘와 같은 경우. 이 역시도 후위연산 전환후 계산과정에서 계산할 대상이 없어지게 되므로 프로그램이 죽는 문제가 발생한다. 수식의 맨마지막에 숫자가 아닌 다른 기호가 왔는지 찾아서 처리한다.

4-2. 잘못된 수식에 대한 문제 – 수식의 괄호의 갯수가 맞지 않거나 짝이 맞지 않는 경우

정상적인 수식이라면 여는 괄호와 닫는 괄호의 갯수가 맞아야하며 서로 짝이 맞아야한다. 이 상황을 만족시키기 위해서 난 다음과 같이 생각했다.

  1. 수식에 맨처음부터 검색했을 시 나오는 괄호가 닫는 괄호이거나, 수식의 맨뒤에서부터 검색했을 시 나오는 괄호가 여는 괄호라면 이 수식은 잘못되었다. 따라서 find나 find_first_of 등의 메서드를 이용해서 여는 괄호와 닫는 괄호가 처음 나오는 인덱스를 구한 다음 두 인덱스를 검사해보면 된다.
  2. 수식의 괄호의 갯수를 맞춰보기 위해서는 flag를 하나 설정하고 초기값을 0으로 둔다. 앞에서부터 검색해가며 여는 괄호 ‘(‘가 나왔을 시 flag를 1 더하고, 닫는 괄호가 ‘)’가 나왔을 시 flag를 1 뺀다. 수식의 끝까지 다 돌았을 때 여는 괄호와 닫는 괄호의 갯수가 같았다면 flag는 0이 되어야한다. 만약 0보다 크다면 여는 괄호가 더 많은 것이고 0보다 작다면 닫는 괄호가 더 많은 수식이다.

4-3. 잘못된 수식에 대한 문제 – 수식에 문자가 들어온 경우

간단하게 하나씩 검색해가며 문자가 들어있는지 판단한다. 문자열 검색은 너무 간단해서 적어둘 필요가 없을 것 같다. C/C++을 해본 사람이라면 누구나 다 간단하게 할 수 있다.

4-4. 잘못된 수식에 대한 문제 – 이중연산자가 있는 경우

한줄로 수식을 입력받는 경우 연산자가 이중으로 입력될 수 없다. 예를 들면 3+-2, 3*-2 등의 경우에는 처리할 수 없다. 한줄로 입력받는 사칙연산의 경우 연산자가 겹칠 경우에는 항상 괄호로 묶어야하며 그렇지 않고 연산자가 이중으로 온 경우에는 계산할 수 없다. 수식이 입력되었을 때 현재 글자와 이전 글자를 비교함으로써 이중연산자가 있는지 검색하고 있다면 오류로 판단한다.

 

위 프로그램을 클라이언트와 서버로 나누었다. TCP로 통신하게 되며 여기에 스레드를 통한 코드는 없다. 그냥 한번 1:1 대응 통신이다.

서버 프로그램 다운로드 :: CalcServer

클라이언트 프로그램 다운로드 :: CalcClient

여기까지 하면 대부분의 계산을 다 처리할 수 있다. 남은건 서버에서 계산한 결과를 클라이언트로 보내는 것. 나같은 경우는 클라이언트로 패킷을 보낼 때 처음 한바이트를 에러코드로 넣었다. 때문에 클라이언트에서 잘못된 수식을 입력하면 서버가 무슨 이유로 계산이 안됐는지 알려주고 클라이언트는 그 이유를 화면에 보여준다.

여기까지 하고 테스트 해보니 한줄로 입력가능한 사칙연산은 대부분 다 처리할 수 있었다. 거의 한 98%? 후…

간단한 사칙연산 계산기 하나 만드는게 얼마나 어려운지 몸소 체험했다. 이 문제의 핵심은 첫째로 중위연산을 후위연산으로 전환하고 스택을 이용하여 계산해가는 것. 그리고 둘째로 후위연산으로 전환할 때 나오는 문제점을 해결해나가는 것이다. 후위연산과 스택을 응용한 계산을 처리하지 못한다면 아무 진행도 할 수가 없다. 이 원리를 꼭 이해해야만 다음 과정들을 해결해나갈 수 있다.

네트워크 공부하다가 어쩌다보니 문자열과 수식연산에 대해 많이 공부하게 된것 같다.

 

위에 98%는 해결했다고 한 이유… 그 2%. 아직 해결하지 못한 문제가 남아있어서 이것도 적어둔다.

1. 수식에 0/x가 입력되거나 x/0이 입력되는 경우 혹은 계산과정 중 이런 경우가 나오는 경우

일단 수학에서는 0 나누기 x는 0이고 x 나누기 0은 무한대다. 컴퓨터에서는 0 나누기 x를 0, x 나누기 0은 오류로 판단한다.

입력된 수식 중 0 / x가 있는지 검색하거나 x / 0이 있는지 검색하는건 그리 어렵지 않다. 문자열에서 해당하는 부분을 검색하면 되니까.

문제는 입력된 수식을 계산하는 과정중 이러한 부분이 나오면 문제가 된다. 예를 들면, 100+3/(3-3)*(3-3) 이런 수식의 경우에는 처음부터 0 / x나 x / 0이라는 부분은 없기 때문에 단순히 문자열검색으로는 문제가 없다. 하지만 계산하는 과정 중에 이런 부분이 나오기 때문에 계산 과정 중 오류가 나고 프로그램이 죽게 된다. 이 문제를 해결하려면 후위연산으로 전환한 후에 스택을 처리하며 연산자 스택에 나누기가 있을 때 계산 스택에서 0이 있는지 확인해보는 수 밖에 없다.

이 문제는 스택 구조만 좀더 생각해보면 충분히 해결할 수 있을 것으로 보이는데 현재는 지쳐서 중지.

2. 괄호 앞에 상수가 나오는 경우

이건 만들다가 깜빡 잊고 안 넣은건데 나중에서야 생각났다. 수학의 규칙에서는 괄호 앞에 상수와 상수끼리는 연산자를 생략할 수 없다. 예를 들면, 2*3을 23이라고 쓸 수 없는 것과 마찬가지다. 연산자를 생략가능한 경우는 문자 즉, 변수와 같이 쓸 경우네는 2*x를 2x라고 쓸 수 있다. 지금 내가 만든 것은 상수항만 입력 받은 수식이므로 당연히 연산자는 생략될 수 없다.

중요한건 괄호가 있는 경우에도 연산자는 생략할 수 없다. 따라서 2(3+4)라는 수식은 실제로는 이렇게 써서는 안되는 수식이다.

이 문제에 대해서는 예전에 이게 되는거다 안되는거다 한번 인터넷에서 논란이 되었었는데, 난 이 경우에 대해서는 상수끼리는 연산자를 생략할 수 없다.라고 생각하고 그렇게 규정하기 때문에 잘못된 수식으로 간주한다. 그리고 C++ 컴파일러도 이런식의 수식은 잘못되었다고 판단하기 때문이다. 2(3+4)가 2*(3+4)라는 것은 인간이 쓰기 편하게 하려고 그렇게 약속한 것일뿐 엄밀히 기계적으로 따졌을 때는 틀렸다고 생각한다.

어찌되었던 괄호앞에 상수가 나오는 경우에는 잘못된 수식으로 봐야하는데 이 경우에 대한 처리를 넣지 못했다. 이것도 스택 구조만 조금 생각하면 충분히 해결할 수 있을 것 같다.

3. 불완전한 소수로 입력된 경우

소수를 입력할 때 0.1, 3.0처럼 입력하지 않고 .1, 3. 처럼 불완전하게 입력하는 경우에 대한 문제다. 테스트 해본 결과 double 형으로 변경하는 과정에서 이러한 숫자들은 전부 완전한 소수형태로 자동전환이 되었다. 하지만 수학에서 이렇게 쓰는 경우는 없으므로 체크하는게 좋을 것 같다. 이건 구현하기 쉬운 부분이라 일단 패쓰.

string 타입에 빈칸을 포함하는 문자열을 입력 받는 방법

string 타입의 변수 str에 문자열을 넣을 때

cin >> str;

처럼 하면 문자열은 들어가는데 문자열에 빈칸이 들어가면 입력이 안되었다. 정확히는 안된다기 보다는 되는 것처럼 보이고 다음 cin으로 입력을 받을 때 그냥 무시처리 된다는 것이지만. 문자열 중간의 공백 때문에 생기는 것인데 이것을 해결하기 위해 인터넷을 찾아본 결과 다음처럼 하니까 해결되었다.

getline(cin, str, 'n');

getline 함수가 아닌 cin만 가지고는 공백이 들어간 문자열은 받을 수 없다고 한다.

자바나 액션스크립트에서는 이런 고민할 필요가 없었는데 C++에 오니 참 이런 것 하나 처리하기에도 민감할 필요성이 있는 것 같다.