2016. 11. 28. 17:45

안녕하세요. 게임 개발자 놀이터입니다. 정말 오랜만에 포스팅하는데요..

갑자기 제가 float를 C++로 소수점을 출력할일이 생겼는데.. 순간.. 기억이..! 나질않아서..!!! 이렇게 정리하려고합니다.


앞으로도 이런 이슈가 생기면 대부분 정리해두려고합니다.


먼저 cout이 가지고있는 presision을 사용하면 됩니다.


먼저 일반적인 코드와 실행결과입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
 
using namespace std;
 
int main()
{
    float f = 3.141592f;
 
    cout<<f;
 
    return 0;
}


위의 코드를 실행하게 되면 출력결과는 다음과 같습니다.

꼭 직접 해보시기 바랍니다.



그럼 cout이 가지고있는 presision을 사용해보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
 
using namespace std;
 
int main()
{
    cout.precision(7);
 
    float f = 3.141592f;
 
    cout<<f;
 
    return 0;
}
cs

위처럼 입력 하고 출력하면.


정상적으로 나오는걸 확인 하실수있습니다!



+ 숫자를 크게하면 소숫점은 다르수가 나오게 됩니다.


왜 그럴까요..?




Posted by 시리시안
2016. 7. 28. 10:11

const ( 상수 )


많이 들어봤으나, 사용하는 사람은 자주 사용하고, 사용하지 않는 사람들은 자주 사용하지 않을것입니다.


상수 (constant)는 값을 '절대로' 바꿀 수 없다. 라는 특징을 가지고 있습니다.

또한 정의시 무조껀 값을 지정해주어야 합니다.


const ( 자료 타입 ) ( 상수 명 ) = ( 상수 값 );


1
2
3
4
5
6
#include <iostream>
 
int main()
{
    const int c = 10
}
cs


이런식으로 말이죠.


하지만 const의 위치는 여러군데에 붙을 수 있답니다.


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int main()
{
    int a = 10;
    
    const int * c1 = &a;     
    int * const c2 = &a; 
 
 
}
cs


위와 같은 코드를 보시면, c1과 c2의 차이점을 아시겠나요?


간단하게 생각하면 어떤게 상수화가 되는지를 생각하면 됩니다.


c1의 경우 정수형 포인터가 됩니다. 즉 c1의 메모리 주소를 변경이 가능하지만, 메모리에 있는 값을 변경하지 못하게 합니다.


하지만 c2는 상수형 포인터가 됩니다. c2의 메모리주소는 상수화 되어서 변경이 불가능하지만, 메모리에 있는 값은 변경이 가능하게 됩니다.


즉 이런 대입이 가능합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
int main()
{
    int a = 10;
    
    const int * c1 = &a;     
    int * const c2 = &a; 
    
    int b = 20;
    
    c1 = &b;
    c2 = 100;
 
}
cs


이 의외에 대입을 하면 에러가 나니, 직접 해보시기 바랍니다.



Complite const 와 Runtime const


C++에서 const에는 2가지의 종류가 있습니다.


바로 컴파일 상수와 런타임 상수 입니다.


어떤 차이 냐면 아래 소스를 보시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
 
int main()
{
    int m_1 = 10;
    int array01[m_1];  // error.
 
    const int m_2 = 10;
    int array02[m_2];  // ok..
 
    const int m_3 = s1;
    int array03[m_3];   // error.
}
cs


위 소스 에서 가운데 있는 array02를 제외하곤 전부 에러가 발생합니다.


이유가 뭘까요?


먼저 array01은 배열의 크기가 변수이기때문에 에러가납니다.


반대로 array02는 상수이기때문에 가능하지요. 하지만,


array03은 상수를 넣었는데 왜 에러가 날까요?


이는 array02에 쓰이는 m_2는 컴파일 상수이며, array03에 쓰이는 m_3는 런타임 상수이기 때문입니다.


실제로 코드를 보시면 m_3는 프로그램이 실행되어 배열 생성까지 도달할때 m_3에 대입되는 s1의 값을 모릅니다.


위 코드는 간단해서 생각만으로도 값을 알아낼 수 있지만, 좀더 복잡한 코드가 된다면 눈으로도 알아낼 수 없을것입니다.


이런 컴파일 상수와 런타임 상수과 확실하게 나눠진다면 정말 편할텐데 말이죠.


(PS. C++11에서는 이를 해결 할 수 있는 constexpr 이 있습니다. 참고하세요.)



const 위치에 따른 쓰임 변화


const는 정말 여러 위치에 놓일 수 있습니다.


변수 선언 시 : [const] 자료형타입 [ const ] 포인터 [const] 변수명

함수 선언 시 : [const] 자료형타입 [ const ] 포인터 [const] 함수명(매개변수...) [const { }


함수 변수를 생각하면 총 7군데나 되네요.. 많아라..


겹치는곳을 생각하면 총 4가지입니다. 걱정마시죠!


그럼 먼저 다시 짚고 가야할 부분이 있습니다.


const(상수화)가 되면 값 변경이 불가능하다.


그럼 이제 찾아야 할 점을 '어디서' 또는 '어떤게' 값 변경이 안되는가 입니다.



그럼 몸풀기로 생각을 해봅시다.


const int * * a;

int *const * a;

int * * const a;


이렇게 3개의 변수가 각각 다른 위치에 const를 달고 있습니다. 이 3개를 구분지어 볼까요?


먼저 '어디서', '어떤게' 영향을 받았나가 중요합니다.


그럼 제일 크게 const를 기반으로 자료형과 변수명을 나눌수있습니다.


(const int) * * a;

(int *const) * a;

(int **) const a;


위 처럼 나눠서 보면 더 쉬울까요?


첫번째 (const int) * * a; 은 상수화된 int의 이중포인터 라는 것입니다.

즉 해석하면 가능과같습니다.

const int x =10;

const int * y = &x;

이때 &x 가 const int ** 라고 형입니다.

즉, (const int) * * a; 에선 알맹이 값인 a가 상수처리 되는것이죠.


다시 말하면

a (값변경가능 = 주솟값)

*a (값 변경가능 = 주솟값)

**a (값 병경 불가능 , 상수)

입니다.


2번쨰 (int *const) * a; 은 int*이 가지고 있는 주솟값 즉 *를 상수화 시킨겁니다.


가리키는 방향을 상수화 한거죠.


간단하죠?


3번쨰는 (int **) const a;

int **형 자체가 상수화가 된겁니다.


즉 처음으로 가리키는 주솟값이 상수화가 된것이지요.


함수도 비슷합니다. 하지만 마지막으로 함수 맨뒤에 붙는 const는 함수 내부 내용을 상수화 하겠다는 뜻입니다. 즉, 함수 내부에서는 값을 변화시키는 행위를 일절 하지 못하게 막는다는 소리입니다.


감사합니다



Posted by 시리시안
2016. 7. 27. 12:49



C++ 캐스팅(cast)에는 총 4가지 종류가 있습니다.


바로


static_cast

reinterpret_cast

const_cast

dynamic_cast 

입니다.


각각 무슨 기능을 하는지 알아 보자면


 

 static_cast


명시적 형 변환을 위한 캐스트 연산자


 reinterpret_cast


무조건 적으로 변환한다.(강제 변환)  ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.


 const_cast

 

const 즉 상수성을 제거 할때 사용됩니다.


 dynamic_cast 

 

RTTI( Run Time Type Information )를 위한 캐스팅 입니다.



하지만, 굳이 귀찮게 위의 방법을 써야하는 이유가 뭘까요?


저희가 C언어 쓸때는 그냥 (Type) 으로 캐스팅 연산을 했었습니다.


다음과 같은 경우를 봅시다.


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int main()
{
 
    int m_4byte = 0;
    double* m_pointer = (double*)&n;
    *m_pointer = 1.23;
 
    return -1;
}
cs


무엇이 문제 인지 보이시나요?


위와 같은 방법을 사용하면 어떻게 될까요?


에러가 납니다.  직접 VS를 열어서 빌드해보세요.


그럼 다음 경우를 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
 
int main()
{
 
    
    const int c_int = 50;
    int * p_int = (int*)&c_int;
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}
cs

다음과 같이 코드를 작성 한 후 실행하면 어떤 결과가 나올까요?
---------------------------
50
100
---------------------------
이 나오게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
 
int main()
{
 
    
    int value = 50;
    const int c_int = value;
    int * p_int = (int*)&c_int;
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}
cs


하지만 다음과 같이 코드를 작성 한 후 실행 하게되면

-------------------------
100
100
-------------------------


이 나오게 됩니다.


이는 const의 값이 결정되는 시점에 따라 달라집니다만, 그건 나중에 const에 관련 글을 적을때 적도록 하겠습니다.


이 2개의 코드로 알아야 하는것은 C에서 지원하는 cast 방식은, 불안전 하다는 것입니다.


2개의 코드 다 에러를 나타내지 않습니다. 하지만 비정상적인 결과를 출력하죠.


과연 이게 올바른 걸까요?


그럼 static_cast로 변경을 해봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
int main()
{
    int value = 50;
    const int c_int = value;
    int * p_int = static_cast<int*>(&c_int);
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}

cs


컴파일러는 const 또는 다른 형식 한정자로 캐스팅 할수없습니다. 라며 에러를 띄우고 빌드를 하지 않습니다.

불안전 하기 때문이죠.


만약 const값을 변하게 하려는 의도가 있다면, const_cast를 사용해야 할것입니다.


그렇기에 안정하고 정상적인 캐스팅을 위하면 C++에서는 기본적으로 명시적캐스팅인 static_cast를 사용하도록 합시다.


다음은 reinterpret_cast 입니다.


reinterpret_cast는 제가 위에 이렇게 적어 놨을겁니다.


무조건 적으로 변환한다.(강제 변환)  ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.


여기서 중요하게 보셔야 할 부분은 '메모리' 단위 입니다.


메모리 단위란 무엇일까요?


그럼 이 코드를 자세히 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    return -1;
}
cs


위의 코드에서 class C는 class A와 B를 다중 상속 받은 상태입니다. 그리고 A와 B는 각각 4byte짜리의 int를 가지고 있죠.

그럼 C를 선언 하는 순간 메모리는 어떻게 잡힐까요?


Class C

 Class A

 int a;

 4 Byte

 Class B

 int b;

 4 Byte

 C클래스 멤버

 int c;

 4 Byte


다음과 같이 구조를 가질껍니다.


그럼 위와 같은 메모리 구조를 가졌을때 아래와 같이 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    A* p_a = &c;
 
    cout << &<< end;    
    cout << p_a << end;
 
    return -1;
}
cs


코드를 작성 한후 실행하면 2개의 결과 값은 똑같이 나올겁니다.


하지만 이 코드를 어떨까요?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    B* p_b = &c;
 
    cout << &<< end;    
    cout << p_b << end;
 
    return -1;
}
cs


C클래스가 부모인 B클래스로 암시적 캐스팅이 된 형태 입니다.


실행 하면 결과는


------------------------------------

&c의 주소값

&c의 주소값 + 4byte 

------------------------------------


암시적 변환으로 메모리의 주소값이 바뀌었습니다.


물론 자식 -> 부모 변환이라 static_cast를 사용하셔도 됩니다.


하지만 reinterpret_cast를 사용하면 어떻게 될까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    B* p_b = reinterpret_cast<B*>(&c);
 
    cout << &<< end;    
    cout << p_b << end;
 
    return -1;
}
cs

놀랍게도 2개의 출력값이 같은 주솟값을 가지게 됩니다.


메모리 단위로 분해해서 재 생성 하기 때문이죠.


이와 같은 방법을 전 아직 어디서 잘 사용해야 할지 모르겠습니다. ( 나중에 알게되면 꼭 수정하겠습니다. ) 


하지만 저기서 p_b가 가진 멤버변수인 b를 수정하게 되면 A클래스가 가진 a가 수정이 된답니다.




Class C

 Class A

 int a;

 4 Byte

 Class B

 int b;

 4 Byte

 C클래스 멤버

 int c;

 4 Byte


위와 같은 구조가 

Class C

 Class B

 int b;

 4 Byte

 Class C

 int c;

 4 Byte


 ( )

 (4 Byte )


이렇게 조립 되었을뿐 메모리 주소를 변하지 않았기 때문이죠.


전 처음 이 사실을 알았을때는 정말 신기했습니다.  잘만 사용하면 메모리 단위로 잘 쪼개서 사용 할 수 있기 때문이죠.



마지막으로 dynamic_cast입니다.


dynamic_cast는 RTTI(Runtime Type Information)를 위한 캐스팅이라고 제가 위에 적었는데요.


런타임에 상속 계츨 관계를 가로지르거나 다운 캐스팅시 사용되는 캐스팅 연산자 라고 생각하시면 편합니다.


즉, 부모가 자식으로 캐스팅이 될 수 없는데, 런타임중 판단으로 가능하게 된다는거죠.


단 dynamic_cast는 다형성을 띄지 않은 객체간의 변환은 불가능하며, 컴파일이 에러를 낸답니다.


즉 다형성을 가지려면 클래스간 virtual 멤버 함수가 있어야 합니다.


코드로 표현하면 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;
 
class A {
public
    int a; 
 
    virtual void test(){}
};
 
class C : public A
{
    int c;
    virtual void test(){}
};
int main()
{
    C c;
    A* pA = &c; 
    C* c2 = dynamic_cast<C*>(pA);
    
    return -1;
}
cs


하지만 저 dynamic_cast가 올바르지 않았을경우는 null값을 반환하니 주의 하시기 바랍니다.


읽어주셔서 감사합니다

Posted by 시리시안
2016. 4. 20. 13:23


저희가 상속을 사용하여 클래스를 구성할때 최상위 부모 소멸자에는 Virtual을 넣어 주어야 합니다.

그 이유가 뭘까요?

이유를 알기위해선 상속을 받은 클래스의 생성과 소멸의 순서를 알아야합니다.

생성을 할경우 부모클래스의 생성자부터 호출되며 차례로 자식의 생성자를 호출하게 됩니다.

반대로 소멸자는 자식클래스의 소멸자를 먼저 호출하고 부모클래스의 소멸자가 호출됩니다.

이렇게 생각하면 아무런 문제없습니다. 

단, 우리가 비교적 자주 상속을 사용하여 선언할경우 부모클래스의 포인터로 자식클래스를 정의하여 사용하게 될것입니다.

이경우, 부모클래스로부터 자식 클래스를 호출할때 가상 함수로 정의되지 않은 자식 클래스의 오버라이딩된 함수를 호출하면 주체가 선언된 인자형이 부모이기 때문에 부모클래스의 멤버함수가 호출됩니다.

이렇게 생각했을때 소멸자 또한 오버라이딩된 멤버함수라 볼 수 있기 때문에, 만약에 부모 포인터로 객체를 삭제하면 부모클래스의 소멸자가 호출되며 정의된 자식의 나머지 공간의 메모리 만큼 누수되어 버립니다.

따라서 소멸자를 가상 함수로 선언하지 않으면 자식클래스의 소멸자가 결코 호출되지 않습니다. 

virtual을 사용하였다면 이것은 자식클래스에서 재정의 될 수 있음을 명시 하기 때문에 포인터의 종류와 상관없이 자식 클래스의 멤버함수가 호출되게 답니다. 즉, 자식 클래스의 소멸자가 호출되고 부모 클래스의 소멸자가 호출되게 됩니다.

따라서 상속관계를 이용하였고 소멸자에서 리소스를 해제해야 한다면 반드시 소멸자를 가상함수로 선언해주어야 합니다.

예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
 
class classA
{
    public:
      classA(){  cout << "A 생성" << endl;}
       virtual ~classA(){  cout << "~A 소멸" << endl;}
};
 
class classB : public classA
{
    public:
      classB(){cout << "B 생성"<< endl;}
      ~classB(){cout << "~B 소멸" << endl;}
};
 
 
int main()
{
  cout << "== 소멸자 테스트 시작 ==" << endl;
  classB *= new classB;
  classA *= B;
  delete A;
  return 0;
}
cs



실행 결과는 다음과 같습니다


ClassA에서 소멸자에 virtual을 사용하지 않았을 경우

===================================================================

START

A

B

~A

===================================================================


ClassA에서 소멸자에 virtual을 사용했을 경우

===================================================================

START

A

B

~B

~A

===================================================================


Posted by 시리시안
2016. 4. 14. 11:09

오버라이딩(Overriding)


※ 오버라이딩은 함수의 재정의를 말합니다.

※ 상속받은 자식 클래스에서 부모 클래스의 멤버 함수를 재정의 하는것을 말합니다.


오버로딩(Overloading)


※ 오버로딩은 함수의 중복 선언을 말합니다.

※ 같은 함수에서 인수만 다르면 얼마든지 정의할 수 있습니다.



===============================================

오버라이딩의 예


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
class _Parent{
public:
    void Something()
    {
        cout << "_Parent Shomthing Function" << endl;
    }
};
 
//자식클래스01
class _Children:public pitching
{
};
 
//자식클래스02
class _OtherChildren:public pitching
{
public:
    //함수 재정의(오버라이딩)
    void Something()
    {
        cout << "_OtherChildren Shomthing Function" << endl;
    }
};
 
void main()
{
    //_Children 객체생성 후 함수호출
    _Children *Children = NULL;
    Children->Something();
 
    //_OtherChildren 객체 생성 후 함수호출
    _OtherChildren *OtherChildren=NULL;
    OtherChildren->Something();
}
 
cs


위 함수의 결과가 어떻게 나올까요?


결과는

-----------------------------------------------

_Parent Shomthing Function

_OtherChildren Shomthing Function

-----------------------------------------------


이렇게 나온답니다.

이유는 Children객체는 상속받은 부모의 함수를 실행하지만, OtherChildren은 클래스내에서 재 정의한 함수를 호출하기 때문입니다.


이처럼 부모와 똑같은 함수명, 파라미터를 가지고 자식클래스에서 새롭게 정의하여 사용하는것을 오버라이딩이라고합니다.


===============================================


오버 로딩의 예


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
class Overload{
    public void Something()
    {
        cout << "Shomthing() Function" << endl;
    }
    public void Something(int _cnt,int _cnt)
    {
        cout << "Shomthing(int _cnt,int _cnt) Function" << endl;
    }
    public void Something(double _num)
    {
        cout << "Shomthing(double _num) Function" << endl;
    }
}
 
void main() {
        Overload ob = new Over();
        ob.Something(1,2);
        ob.Something(3);
}
cs


출력결과는

-----------------------------------------------

Shomthing() Function

Shomthing(int _cnt,int _cnt) Function

Shomthing(double _num) Function

-----------------------------------------------


입니다.

이유는 함수의 인자가 다르기 때문입니다. 각각의 맞는 인자로 알아서 호출된답니다.

이처럼 같은 함수내에서 인자로 구분되어 정의하는것을 오버로딩 이라고 합니다.



Posted by 시리시안
2016. 4. 14. 10:46
밑의 코드처럼 선언하시면 됩니다.
상속받아 사용할 때는 소멸자를 꼭 포함 시켜야 한다는점을 잊지마세요~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "stdafx.h"
#include <iostream>
 
class _IRef{
public:
    virtual ~_IRef(){}
    virtual void _Update() = 0;
};
 
class Node : public _IRef{
public:
    ~Node(){}
    void _Update(){ std::cout << "Node Update" << std::endl; }
};
 
class user{
public:
    void Something(_IRef* Ir){ Ir->_Update(); }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    Node* c = new Node;
    user u;
    u.Something(c);
 
    return 0;
}
 
 
 
cs



Posted by 시리시안
2016. 4. 14. 10:39

인터페이스(Interface)란 컴퓨터 용어에서 많이 등장합니다.

대표적으로 User Interface 입니다. 줄여서 UI라고 자주 부릅니다.

Interface를 어원을 찾아보면 Inter(중간) + face(맞대고 있다)입니다. 즉, 어느 중간 사이에서 맞대고 있는 부분을 의미를 가집니다.

User Interface, UI란 사용자 그러니까 플레이어와 컴퓨터 '중간' 사이에서 '맞대고 있어' 일어나는 상호작용을 매개하는 것을 말합니다.


지금부터 설명할 객체 지향 언어에서도 인터페이스란 객체와 객체 '중간' 사이에서 '맞대고 있어' 일어나는 사이에서 상호 작용의 매개로 쓰인답니다.


Java에서는 Interface라는 키워드를 통해서 Interface를 바로 생성할 수 있습니다.

interface로 할 수 있는 일이 무엇이냐 하면 클래스의 기본적인 틀을 제공하면서 다른 객체 사이에서의 '중간' 매개 역할도 담당한다는 것입니다.

그럼 자바를 이용해서 직접 인터페이스를 선언해 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
public interface Book{    //여기서 I는 인터페이스의 i입니다.
    private String _author;    //작가
    private String _title;    //제목
    private String _text;    //본문
    public void Show_Information(){
    }
}
public class Cartoon implements Book{
    //_author와 _title, _text에 접근 할 수 있음
    public void Show_Information(){
        // 꼭 구현해야함
    }
}
public class Novel implements Book{
    //_author와 _title, _text에 접근 할 수 있음
    public void Show_Information(){
        // 꼭 구현해야함
    }
}
cs


이처럼 인터페이스 선언하고 클래스를 선언할 수 있습니다.

이경우는 즉 Book 클래스가 인터페이스 클래스가 된것입니다.

즉 다음과 같이 사용 할 수 있습니다.


1
2
3
4
5
6
7
void main(){
    Book mCartoon = new Cartoon();
    mCartoon.Show_Information();//만화책 정보
    Book mNovel = new Novel();
    mNovel.Show_Information();//소설책 정보
}
 
cs


즉, 이렇게 Book이라는 클래스 하나로, Cartoon과 Novel을 선언하여 공통된 함수를 불러서 관리 할수있습니다.

손쉽게 표현하면 위처럼 사용하는게 아니라,


1
2
3
4
5
6
7
8
9
 
 
void main(){
    Book[] mBooks = new Book[2];
    mBooks[0= new Cartoon();
    mBooks[1= new Novel();
    for(int i=0; i<2++i)
        mBooks[i].Show_Information();//책정보 
}
cs


이처럼 한가지의 클래스로 묶어서 사용할 수 있습니다.


사용 용도 및 방법은 무궁무진합니다.


게임에서 적이 가진 모든 총알을 _IBullet 이라 선언하고 관리해도 되고, 모든 오브젝트를 _IObject 라고 하여 관리해도됩니다.

이 모든걸 다합쳐서 _IRef 로 인터페이스 선언하여 관리하면 정말 간단하겠죠?



C++에서 선언법은 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "stdafx.h"
#include <iostream>
 
class _IRef{
public:
    virtual ~_IRef(){}
    virtual void _Update() = 0;
};
 
class Node : public _IRef{
public:
    ~Node(){}
    void _Update(){ std::cout << "Node Update" << std::endl; }
};
 
class user{
public:
    void Something(_IRef* Ir){ Ir->_Update(); }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    Node* c = new Node;
    user u;
    u.Something(c);
 
    return 0;
}
 
 
 
cs






Posted by 시리시안
2016. 2. 25. 16:49

c언어에서의 파일입출력은

1. 파일을 연다

2. 파일에 입출력 작업을 한다.

3. 파일을 닫는다.

로 요약됩니다.


입출력 할 때 파일포인터가 필요하기 때문에

FILE *f; 와 같은식으로 파일 포인터를 선언해준다.


1. 파일 열기

파일을 열기 위해서는 fopen() 함수를 사용하게 됩니다.

따로 경로가 없다면 현재 소스파일이있는 그 경로를 나타냅니다.

파일명은 확장자 까지 포함한 풀 네임이며, 모드가 있는데,

w : 쓰기 모드, 만약 파일이 존재 하지 않는다면 파일을 새로 만든다.

r : 읽기 모드, 만약 파일이 존재 하지 않는다면 NULL을 리턴한다.

a : 추가 모드. 파일이 존재 하지 않는다면 파일을 새로 만든다.

w+ : 읽기/쓰기

r+ : 읽기/쓰기

a+ : 읽기/추가

b : binary


2. 입출력 작업

입출력을 하기 위해서는 파일입출력 함수들이 이용됩니다.

문자 : fgetc(), fputc();

문자열 : fgets(), fputs();

fscanf(), fprintf(); 


간단한 입출력 예제

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
    FILE *f;
    f=fopen("a.txt","w");
    fprintf(f,"Hello World");
    fclose(f);
}
cs

 

이렇게 입력후 실행하면!?

도스창엔 아무것도 안뜨게 됩니다.

왜냐하면 파일에 작업을 하고있기 때문이죠


소스가 있는 a.txt파일이 보일 것입니다.

텍스트 파일을 열어보면 이와같이 Hello World라는 문구가 출력되어 있습니다.



3. 파일 닫기

위 소스들을 잘 살펴보면 항상 

fclose(f); 라는 함수가 소스의 끝부분에 쓰여 있습니다.

바로 이 함수가 파일을 닫겠다는 의미입니다

Posted by 시리시안
2016. 2. 25. 13:10

기본적으로 많이 사용하는 형식의 데이터형만 기본 데이터로써 정의되고있고, 나머지는 프로그래머가 필요에따라 정의해서 사용할 수 있도록 만들수있습니다.


그래서 기본 데이터 형인 char, int, float, double등을 제공하고 나머지는 프로그래머가 배열, 포인터,구조체, 공용체, 혹은 typedef 같은 문법을 사용해 직접 정의 해서 사용합니다.


배열과 같은 무법으로 사용자 정의 데이터형을 만들게 되면 동일한 데이터 형만을 그룹지을 수 있습니다. 단순한 형태만 정의 가능하다는 단점이 있지만, 이를 보완하기 위해 만들어 진것이 구조체 입니다.


구조체는 서로 다른 데이터형을 하나의 데이터로 군집화 하여 사용 할 수 있도록 하는 사용자 정의 데이터 형입니다.


1. 구조체 정의하기

 

구조체를 정의 할 때에는 다음과 같이 사용합니다.


struct 구조체명 {

데이터형 변수명;

...

};

이렇게 선언된 구조체명이 새로 쓸수있는 데이터형의 이름입니다. 즉, int char처럼 사용가능합니다.


구조체는 struct 키워드를 앞에 명시한 다음에 사용할 구조체명을 적어서 정의합니다. 이 때 구조체

     내부를 구성하는 데이터는 미리 정의되어 있어야 하며, 각 데이터는 세미콜론(" ; ") 키워드로 

분리해야합니다. 

예를 들어 이름, 나이, 키, 몸무게와 같은 인적사항을 저장하는 People 이라는 구조체를 정의하면 다음과 같습니다.


1
2
3
4
5
6
7
 
    struct People {
        char name[20];
        int age;
        double height;
        double weight;
    };
cs

    위에서도 말했듯이 구조체 내부의 데이터는 미리 정의된 데이터형이여야하기 때문에 char, int, double

    등과 같은 기본 데이터형 외에 다른 구조체나 사용자가 따로 정의한 데이터형을 사용하고 싶으면

    이 구조체보다 앞서 정의되어 있어야합니다.

 

    일반적이지는 않지만 한번만 선언해서 사용하는 경우 아래와 같이 구조체명을 생략해서 사용하는 경우도 있습니다.

즉, 구조체를 정의함과 동시에 data 라는 변수를 선언하여 사용하기 때문에 이 데이터형을 다시 사용할 필요가 없다면 구조체명도 필요없기 때문에 생략 가능합니다.

 

 

1
2
3
4
5
6
    struct {
        char name[20];
        int age;
        double height;
        double weight;
    } data;
cs

 

 

2. 구조체 사용하기

 

    정의된 구조체는 구조체명을 이용하여 변수를 선언하듯이 선언해주면 되는데, C 언어에서는 구조체를

    이용하여 변수를 선언할 때에는 반드시 구조체명 앞에 "struct" 키워드를 붙여주어야합니다.

 

    // People 구조체를 사용하여 data 변수를 선언한다.

    struct People data;

 

    C++ 언어로 구조체를 선언할 때에는 struct 키워드를 생략하여 선언할 수 있지만 C 언어의 경우에는

    구조체를 선언할 때마다 struct 키워드를 명시해야 하기때문에 (굉장히 불편합니다.)

해결법으로는 아래와 같이 typedef 명령어를 이용하여 구조체를 또다른 데이터 타입으로 정의하여 사용하기도 합니다.

 

    typedef struct People PEOPLE;

 

    이렇게 선언하면 C 언어에서도  People 구조체를 사용하여 변수를 선언할때 struct 키워드를

    적지 않게되어 좀더 편리하게 사용할수 있습니다.

 

    PEOPLE data;  // struct People data;

 

    하지만, 구조체 정의 따로 typedef 정의 따로 사용하는 방식이 프로그램을 이해하기에 불편할수도

    있기 때문에 이것을 아래와 같이 한번에 사용하여 구조체를 정의하기도 합니다.

 

1
2
3
4
5
6
7
8
 
 // struct people { ... } 구조체를 PEOPLE 이라는 데이터형으로 정의한다.
    typedef struct People{
        char name[20];
        int age;
        double height;
        double weight;
    } PEOPLE;
cs

 

최근에는 C++ 를 사용하는 사람들이 많아짐에따라 struct 가 생략가능해지면서 typedef 을 사용하는 형식도 점차 줄어들고 있습니다.    (Class가 있으니..)

   

구조체로 선언한 데이터형도 기본데이터형(char, int, ...)과 동일한 데이터 형이기 때문에 변수 선언 배열, 포인터 문법을 아래와 같이 그대로 사용할수 있습니다.

 

PEOPLE data;      // 일반 변수

PEOPLE list[20];  // 배열

PEOPLE *p;         // 포인터

 

이렇게 선언된 구조체 변수는 아래의 코드처럼 구조체의 요소에 접근할 때에는 " . " 를 이용하여 구조체의 "변수명.요소명" 과 같은 형식을 사용합니다.

     

1
2
3
4
5
6
PEOPLE one;
 
    strcpy(one.name, "운영진");
    one.age = 23;
    one.height = 179.9;
    one.weight = 70;
cs

 

3. 구조체의 크기와 메모리 배열 ( struct member alignment )

 

구조체의 크기는 일반적으로 구조체를 구성하는 데이터형의 크기를 합산한 값입니다.


    Windows 32비트 운영체제는 레지스터의 크기, 데이터 버스의 크기, 데이터 처리 단위, 포인터의 크기

    등이 모두 4 바이트로 되어 있어서 해당 크기로 연산하고, 처리하는 것이 속도가 더 빠릅니다. 그래서 

    컴파일 옵션에 따라서 구조체를 해석할때 그 배열을 짝수 또는 4의 배수로 재구성하는 작업

    수행하기 때문에 실제로 구조체가 구성되는 것이 조금 다를 수 있습니다.

 

1
2
3
4
5
6
7
typedef struct Test {
        char a;       // 1 바이트의 데이터 크기를 가짐
        int b;          // 4 바이트의 데이터 크기를 가짐
        short int c;  // 2 바이트의 데이터 크기를 가짐
        int d;          // 4 바이트의 데이터 크기를 가짐
        char e;       // 1 바이트의 데이터 크기를 가짐
    } TEST;
cs

 

위와 같이 정의된 TEST 구조체를 선언하면 12 바이트의 메모리가 할당된다고 생각할 수도 있지만

구조체를 구성하고 저장하는 단위가 4 바이트이기 때문에 b 나 d 같은 4 바이트 이상의 크기를 가지는

변수가 4의 배수 주소 값에 위치하도록 아래와 같은 형태로 메모리가 사용되어 20 바이트의 메모리가 할당됩니다.

 

그렇기때문에 쓸데없는 메모리의 낭비를 막기 위해서는 아래와 같이 데이터를 구성하는 순서를변경하여 구조체를 정의하는 것이 좋습니다.

 

   

1
2
3
4
5
6
7
typedef struct Test {
        int b;          // 4 바이트의 데이터 크기를 가짐
        int d;          // 4 바이트의 데이터 크기를 가짐
        short int c;  // 2 바이트의 데이터 크기를 가짐
        char a;       // 1 바이트의 데이터 크기를 가짐
        char e;       // 1 바이트의 데이터 크기를 가짐
    } TEST;
cs

 

   

 

이처럼 구조체를 정의할 때 어떻게 배치하는가에 따라 구조체의 크기가 변경될 수 있기때문에 메모리를 낭비하지 않도록 주의해야합니다,

프로그래밍을 하다가 구조체의 크기를 명시해야하는 경우에는 수치 값이 아닌 sizeof 매크로를 사용하여 구조체의 크기를 반환받아 사용해야합니다. 

Posted by 시리시안
2016. 2. 23. 20:35


try, throw, catch 를 이용한 예외 처리 방법



예외 ( Exception ) 란? : 프로그램의 정상적인 실행을 방해는 조건이나 상태를 말합니다. ( 에러가 아닙니다.)


1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
 
 
void main()
{
    int a = 0;    
    int b =20;
    
    int c = b/a;
 
}
cs

위 상황을 봅시다. a의 값이 0이 할당된 상태에서 b를 a로 나누려고 합니다. 아직까진 오류가 아니지만, ( 물론 컴파일러에 따라서 오류로 판단할수 있습니다.) 

하지만, 오류가 아니라 판단되면, 다음식을 쓰레기값을 뱉을수 밖에없습니다. 그럴때 try catch문을 사용합니다.

try
1. 예외가 발생할만한 코드 블록을 지정합니다.
2. try{} 괄호 안에 예외 처리 대상 코드를 작성합니다. 
3. 이블록안에서 예외가 발생했을 때 throw 명령으로 예외를 던집니다.

throw
1. 프로그램이 정상적으로 실행될 수 없는 상활일 때 이 명령으로 예외를 던집니다.
2. thorw다음에 던지고자하는 예외객체를 넘김. 예외 던지면 catch문으로 이동(단: 예외 객체 타입이 맞을때만 적용됨 ), (오버로딩 가능)

catch
1. if-else문처럼 try-catch문으로 한쌍으로 쓰입니다.
2. try안에서 throw한 예외 객체에 대한 예외처리를 합니다
3. catch블록을 예외 핸들러라고 부릅니다..

사용법
try{
    if(예외조건)
       throw 예외객체;
} catch ( 타입 예외객체 ){
      예외처리;
}

사용 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
 
 
void main()
{
    int a = 0;    
    int b =20;
    
    try{
    if(a == 0)
        throw a;
    int c = b/a;
    }
    catch(int ex){
        std::cout<<"예외 처리 발생, a가 0입니다."<<std::endl;
    }
}
cs

그냥 if문으로 바로 처리하면 되는데, 왜 굳이 catch까지 쓰나요?

라고 물어볼수 있는데, 이는 Stack Unwinding(스택 풀기) 를 이해하면 쉽다.

다음 예제를 보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
 
 
int AddValue_abs (int a, int b)
{
    if(a<0)
        throw a;
    if(b<0)
        throw b;
 
 
    return a+b;
}
void main()
{
    int a,b;
    
    std::cin>>a>>b;
    
    try{
        //바로 보이는 이곳엔 throw가 없다.
        std::cout<<AddValue_abs(a,b)<<std::endl;
    }
    catch(int ex){
        //예외처리가 생기면 실행
        std::cout<<"예외 처리 발생"<<std::endl;
    }
}
cs


AddValue_abs라는 함수에서 try도 없는데, 그렇다고 catch도 없는데 뜬금없이 조건 검사후 throw를 던집니다.
여기서 Stack Winding(스택풀기)를 볼수있는데, throw가 던져진 시점에서 바로 뒤에 코드는 읽지 않습니다.
그리고 뒤로 스택을 풀어 나가며, throw로 던져진 변수 타입과 같은 인자를 지닌, catch를 찾습니다.

만약 예외 처리를 if문으로 검사하게되면, 여러 조건에 따라 함수 자체의 반환값에 예외를 하거나, 예외후 처음부터 다시해야하는 번거로움이 있는데, 이걸 사용할 경우 간편하게 할수가 있습니다.

또한 Throw로 클래스 자체를 넘겨버리면, Catch에서 calss를 인자로 받아올수 있다는점..!








 




'프로그래밍 > Language' 카테고리의 다른 글

파일 입출력에 대하여  (0) 2016.02.25
구조체에 대하여  (0) 2016.02.25
포인터에 대하여  (0) 2016.02.23
C언어 연산자에 대하여 - 비트연산  (0) 2016.02.23
C언어 연산자에 대하여  (0) 2016.02.23
Posted by 시리시안