반응형

#include <iostream>
using namespace std;

class A{
public :
 virtual void initialize(){
  cout<<"base initialize"<<endl;
 } 
 virtual float integrate(float dt){ return 0;}  
};

class B : public A{
public :
 void initialize(){
  
 }
 static float integrate(float dt){
  cout<<"derived initialize"<<endl;
 }
};


int main(){

 B derived;

 A* pBase= &derived;

 pBase->initialize();                             //A 클래스의 initialize() 가 호출된다

 return 0;
}

 

B 의  static float integrate(float dt)  ==>  float integrate(float dt) 로 고치면 당연히 B 의 initialize() 가 호출된다

 


반응형
반응형

잘 정리된 포스트

const_cast 는 포스트 하지 않았는데 간단하기 때문이다

상수성 const 를 제거한다


아래는 포스트 내용

 

 

http://prostars.tistory.com/55 

 

 

C++의 4가지 캐스트 연산자에 대한 이야기 중 두 번째다.
이번은 그중에서 dynamic_cast 에 대해서 이야기한다.

dynamic_cast 는 상속 관계 안에서 포인터나 참조자의 타입을 기본 클래스에서 파생 클래스로의 다운 캐스팅과 다중 상속에서 기본 클래스 간의 안전한 타입 캐스팅에 사용된다.
안전한 타입 캐스팅이란 런타임에 타입 검사를 한다는 것이며 아래에 조금 더 자세하게 나온다.
const_cast와 같이 다른 용도로는 사용하지 못하며 용도가 명확하다.
참고로 dynamic_cast 를 사용하려면 기본적으로 다형성은 이해를 하고 있어야 하며 RTTI도 이해하고 있다면 이 글을 볼 필요가 없을 것이다.


객체가 위치한 메모리의 시작부분을 찾는 데도 사용된다는데 사용해 본 적이 없다.
객체를 void* 로 dynamic_cast 하면 시작 주소가 나온다.
[예] void* p = dynamic_cast<void*>( ObjectPointer );

 

- dynamic_cast 사용
dynamic_cast 를 사용하기 전에 제약 사항을 확인하자.
나름 제약 사항이 많다.

•상속 관계 안에서만 사용할 수 있다.

•하나 이상의 가상함수를 가지고 있어야 한다.
•컴파일러의 RTTI 설정이 켜져 있어야 한다.

[예제 1]
class Base
{
public :
    virtual void Put( void ) { cout << "Base" << endl; }
};

class Derived : public Base
{
public :
    void Put( void ) { cout << "Derived" << endl; }
 };

int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Base;
    Base* pDerived1 = new Derived;
    Derived* pDerived2 = new Derived;
    Derived* pDerived = NULL;

    // 컴파일 오류 : 타입 변환을 할 수 없다.
    //pDerived = pBase;

    // 컴파일 성공 : 런타임에 타입 변환에 실패하며 널을 리턴한다.
    pDerived = dynamic_cast<Derived*>( pBase );        
    if ( pDerived == NULL )
        cout << "Runtime Error" << endl;

    // 컴파일 오류 : 타입 변환을 할 수 없다.
    //pDerived = pDerived1;

    // 컴파일 성공 : 런타임에 타입 변환에 성공하며 Derived 타입의 포인터를 리턴한다.
    pDerived = dynamic_cast<Derived*>( pDerived1 );
    if ( pDerived )
        pDerived->Put();

    // 컴파일 성공 : 이런 경우에는 캐스팅이 필요 없다.
    pDerived = pDerived2;
}


위의 [예제 1] 에서 dynamic_cast 의 기본 동작을 볼 수 있다.
[예제 1] 에서 중요한 내용은 'pDerived = dynamic_cast<Derived*>( pBase );' 에서 볼 수 있듯이 포인터가 실제로 가리키는 대상이 기본 클래스의 객체라면 변환은 실패한다는 것이다.
dynamic_cast 가 캐스팅해주는 것은 포인터나 참조자의 타입을 다운 캐스팅하는 것이지 객체의 타입을 캐스팅하지는 못한다.

dynamic_cast 는 캐스팅에 실패할 때 대상이 포인터라면 널을 리턴하고 참조자였다면 bad_cast 예외를 던진다.
이것이 위에서 언급한 '안전한 타입 캐스팅'의 의미다.
A에서 B로 포인터의 타입을 캐스팅하는 것이 문제없는지 런타임에 검사하여 처리할 수 있다.

다중 상속의 상황에서 기본 클래스 간의 타입 캐스팅을 보자.


[예제 2]
class BaseOne
{
public :
    virtual void Put( void ) { cout << "BaseOne" << endl; }
};

class BaseTwo
{
public :
    virtual void Put( void ) { cout << "BaseTwo" << endl; }
};

class Derived : public BaseOne, public BaseTwo
{
public :
    void Put( void ) { cout << "Derived" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    BaseOne* pBaseOne = NULL;
    BaseTwo* pBaseTwo = new Derived;

    // 컴파일 오류 : 타입 변환을 할 수 없다.
    //pBaseOne = pBaseTwo;

    // 컴파일 성공 : 런타임에 타입 변환에 성공하며 BaseOne 타입의 포인터를 리턴한다.
    pBaseOne = dynamic_cast<BaseOne*>( pBaseTwo );
    if ( pBaseOne )
        pBaseOne->Put();
    
    return 0;
}


[예제 2] 는 아래 [그림 1] 처럼 다중 상속 관계의 클래스 구성에서 기본 클래스 간의 타입 캐스팅을 보여준다.

 

 

사용자 삽입 이미지
[예제 2] 와 같은 캐스팅의 한 사용 예로는 각 기본 클래스 포인터(또는 참조자) 타입의 컨테이너 혹은 배열을 운용하면서 서로 간의 요소를 교환할 때 사용할 수 있다.

[예제 1] 과 같은 캐스팅을 다운 캐스팅이라고 하며 [예제 2] 와 같은 캐스팅을 크로스 캐스팅이라고 한다.
별도로 적지는 않았지만 파생 클래스에서 기본 클래스로의 캐스팅을 업 캐스팅이라고 한다.
업 캐스팅은 캐스팅 연산자가 있으나 없으나 안전하게 캐스팅된다.

- 참고 사항
다중 상속이 발생한다면 설계를 다시 검토하라는 말이 있듯이(있나..-_-?) 다중 상속은 그리 권장하지 않는 방식이다.

 


http://prostars.tistory.com/65  중에서..

 

 - reinterpret_cast 사용
reinterpret_cast 를 사용하기 전에 용도와 제약 사항을 확인하자.
전혀 관계없는 타입 간의 변환
상속관계가 없는 클래스 간의 변환 포함
const_cast 의 기능은 수행하지 못함

 

 


http://prostars.tistory.com/64

 

++의 4가지 캐스트 연산자에 대한 이야기 중 세 번째다.
이번은 그중에서 static_cast 에 대해서 이야기한다.

static_cast 는 기본적으로 C 스타일의 캐스팅과 가장 비슷한 기능을 한다.
물론 C 스타일의 캐스팅처럼 만능은 아니다. 4가지의 캐스트 연산자로 분리된 만큼 const_cast의 역할인 상수성을 날린다거나 하는 등의 다른 캐스트 연산자의 고유 기능은 수행하지 못한다.
다른 캐스트 연산자와 같이 static_cast 도 static_cast 만의 용도가 있다.

- static_cast 사용
static_cast 를 사용하기 전에 용도와 제약 사항을 확인하자.
실수형과 정수형, 정수형과 열거형등의 기본 데이터 타입 간의 변환
상속관계의 클래스 계층 간의 변환
런타임 타입 검사를 하지 않음
다형성이 없어도 변환 가능 (RTTI 옵션이 꺼져있어도 된다)
다중 상속에서 기본 클래스 간의 타입 변환은 못함
void 포인터를 다른 타입의 포인터로 변환
서로 다른 타입의 포인터 간의 타입 변환은 못함

서로 다른 타입의 포인터 간의 타입 변환에 reinterpret_cast 를 사용하기보다는 void 포인터를 경유하는 방식을 추천한다.

[예제 1]
class BaseOne
{
public :
    virtual void Put( void ) { cout << "BaseOne" << endl; }
};

class BaseTwo
{
public :
    virtual void Put( void ) { cout << "BaseTwo" << endl; }
};

class Derived : public BaseOne, public BaseTwo
{
public :
    void Put( void ) { cout << "Derived" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    int IntValue = 0;
    double DoubleValue = 100.0;

    // 암시적 캐스팅 : 경고 발생
    IntValue = DoubleValue;
    // 명시적 캐스팅 : 경고 없음
    IntValue = static_cast<int>( DoubleValue );

    // 컴파일 오류 : 변환할 수 없는 타입
    //char* pChar = static_cast<char*>( &IntValue );

    // 컴파일 성공 : void 포인터에서 변환은 가능
    void* pVoid = &IntValue;
    char* pChar = static_cast<char*>( pVoid );

    BaseOne* pBaseOne = NULL;
    BaseTwo* pBaseTwo = new Derived;
    Derived*    pDerived = NULL;

    // 컴파일 성공 : 계층간 타입 변환이 가능 (타입 안전 검사는 안함)
    pDerived = static_cast<Derived*>( pBaseTwo );

    // 컴파일 오류 : 변환할 수 없는 타입 (dynamic_cast 필요)
    //pBaseOne = static_cast<BaseOne*>( pBaseTwo );   

    return 0;
}
위의 [예제 1] 에서 static_cast 의 기본 동작을 볼 수 있다.
[예제 1] 의 'pDerived = static_cast<Derived*>( pBaseTwo );' 에서 타입 변환은 컴파일 타임에 끝난다.
pBaseTwo 에 NULL 이 들어가 있어도 컴파일은 성공한다.
즉, 변환하려는 포인터가 계층 관계에 있다면 어떤 값을 가지고 있던지 컴파일은 성공한다는 말이다.
문제는 런타임에 발생할 것이고 대부분 프로그램의 다운으로 이어질 것이다.
이런 문제점 때문에 클래스 계층 간의 타입 변환에 dynamic_cast 를 추천하는 것이다.

[예제 1] 에는 재미있는 내용이 하나 있다.
서로 다른 타입의 포인터 간의 변환은 금지지만 void 포인터를 경유한다면 가능하다.
'char* pChar = static_cast<char*>( &IntValue );' 이것은 컴파일이 안되지만,
'void* pVoid = &IntValue;
 char* pChar = static_cast<char*>( pVoid );' 이것은 컴파일이 된다. 물론 포인터 값도 정상적으로 넘어온다.

 

반응형
반응형

[1번]

 

float dd1=123.00993;
int ddd=*((int*)(&dd1));

float gg= *((float*)(&ddd));

 

이렇게 해야지만 float 값이 다시 gg 에 나타난다

 


 

 

[2번]

float dd1=123.00993;
int ddd=dd1;

float gg= ddd;

 

이렇게 하면 나타나는 값은 gg==123이 된다

 

즉 상황에 따라 부동소수점의 값을 유지하고 싶다면 [1번]을 간단히 정수부분만을 때어내고 싶다면 [2번] 을 사용하면 된다

반응형
반응형

아래글 원문의 주소는

http://cafe.naver.com/cplc/83117

이지만 글을 조금 수정합니다, 추가한 붉은 글씨가 수정한 부분입니다

 

 

32bit 부동소숫점 연산식을 알아야 합니다.

단순히 데이터 자릿수가 실수로 변환되어지는 것은 아니고요.. 실제론 꽤 복잡한 연산식으로 만들어진 것입니다.

 

32비트의 각 자릿수 마다 정해진 규정이 있고요 풀어보자면,,,,,

맨 끝 비트(32번째 비트)가 부호를 결정짓고요 간단히 부호(s)라고 정의합니다.

그다음 24번째 비트부터 31번째 비트까지의 8비트(1byte)가 지수입니다. 이를 e라고 정해두고,

그다음 맨처음 비트부터 23번째 비트까지가 가수입니다.  이를 m이라 정한다면

 

  부동소숫점 = (-1)^s * 2^(e-127) *1.m

 

 

지수부(2^(e-127))는 첫번째 비트부터 2의 승수를 곱해주는데 즉,2^0,2^1, 2^2, 2^3, 2^4, 2^5, 2^6, 2^7의 값을 첫번째 비트부터 순서대로 곱하도록 하고

 그냥 지수부를 비트열로 표현하면 2^e 로 표현되지만 현재는 DWORD 에서 float 로 변환하는 과정임으로 (2^(e-127)) 와 같은 식이 사용된다

 이렇게 변환하는 이유는  float 의 표현 범위를 늘리기 위함

 

가수부(1.m)는 마지막 비트(23번째=>이미지 숫자상으론 22) 부터 2의 마이너스 승수를 곱해야 합니다.

 

                   즉, 2^-1, 2^-2, 2^-3, 2^-4 .......... 2^-23의 값을 마지막 비트부터 순서대로 곱해야 합니다.

                    => 2^-2 = 1/(2^2) 즉 소수점 부분을 표현하기 위한 수치

 

 

 

 

32bit의 데이터값이 16#4015c28f 이란 것을 기준해서 풀어보도록 해볼까요?

먼저 이 데이터를 2진수로 변환해 봐야 해당된 비트의 상태를 알 수 있습니다.

변환해 보면..... 휘리릭...!!~~

 

(16진수)16#4015c28f  => (2진수) 2#0100 0000 0001 0101 1100 0010 1000 1111 로 되는 것을 알 수 있습니다..

앞의 16#, 2# 가 진수 표현을 나타냄

 

어떻게 변환됬냐구요?

그냥 16진수의 1자리를 4자리의 2진수로 바꾸면 됩니다.

 

    첫번째 비트는 2^0   ---  1

    두번째 비트는 2^1   ---  2

    세번째 비트는 2^2   ---  4

    네번째 비트는 2^3   ---  8

                    => 2^n ( 0<=n <= 8 )

 

  - 0000 = 16#0

  - 0001 = 16#1

  - 0010 = 16#2

  - 0011 = 16#3

  - 0100 = 16#4

  - 0101 = 16#5         : 대충 눈치 채셨죠?

  - 0110 = 16#6

  - 0111 = 16#7

  - 1000 = 16#8

  - 1001 = 16#9          : 아직 모르시겠나요?

  - 1010 = 10 = 16#a   : 16진수에선 0 ~ f 까지랍니다.

  - 1011 = 11 = 16#b

  - 1100 = 12 = 16#c   : 이젠 아셨기를...

  - 1101 = 13 = 16#d

  - 1110 = 14 = 16#e

  - 1111 = 15 = 16#f    : 다 해버렸네...

 

그럼 본론으로 들어가서...

정리해서 보면

 

(2진수) 2#0100 0000 0001 0101 1100 0010 1000 1111

 

[float 로 변환]

 

  [부호] = 2#0 = 0  

  [지수] = 2#0100 0000 0 = 1000 0000

              2#0 에서2# 다음에 오는 0 은 이미 부호비트로 취급했음으로 다음인 1 을 포함한 8(지수부)의 비트열을 가져옴 

            = (1*2^7) + (0*2^6) + (0*2^5) + (0*2^4) + (0*2^3) + (0*2^2) + (0*2^1) + (0*2^0)

            = (1*128) + (0*64) + (0*32) + (0*16) + (0*8) + (0*4) + (0*2) + (0*1) = 128+0+0+0+0+0+0+0 = 128

 

  [가수] = 001 0101 1100 0010 1000 1111 = (0*2^-1) + (0*2^-2) + (1*2^-3) + (0*2^-4) + (1*2^-5) + (0*2^-6) + (1*2^-7) +

              (1*2^-8) + (1*2^-9) + (0*2^-10) + (0*2^-11) + (0*2^-12) + (0*2^-13) + (1*2^-14) + (0*2^-15) + (1*2^-16) +

              (0*2^-17) + (0*2^-18) + (0*2^-19) + (1*2^-20) + (0*2^-21) + (0*2^-22) + (0*2^-23)

            = (0*0.5) + (0*0.25) + (1*0.125) + (0*0.625) + (1*0.3125) + (0*0.015625) + (1*0.0078125) + (1*0.00390625) +

              (1*0.001953125) + (0*0.0009765625) + (0*0.00048828125) + (0*0.000244140625) + (0*0.0001220703125) +

              (1*0.00006103515625) + (0*0.000030517578125) + (1*0.0000152587890625) + (0*0.00000762939453125) +

              (0*0.000003814697265625) + (0*0.0000019073486328125) + (1*0.00000095367431640625) +

              (1*0.000000476837158203125) + (1*0.000000238418579101562) + (1*0.000000119209289550781)

            = 0.169999957084656

 

결과를 계산식에 대입해 보면

    (-1)^0 * 1.169999957084656 * 2^(128-127)

  = 1 * 1.169999957084656 * 2

  = 2.33999991416931

  ≒ 2.34 

 

정리하느라 2시간 걸렸습니다.  -_-;;;

직접 계산하려고 하시지 말고 GM-WIN에서 [REAL to DWORD] 와 [DWORD to REAL] 펑션을 이용해서

시뮬레이션 해보시는게 건강에 이로울 듯 하네요...

 


[float 로 변환과 그 역]

 

 이러한 연산 가수부분의 변환 연산 과정이 있음으로 float 에서 DWORD 로 변환 할대는 가수부를 정수부형태 즉

16#...... 의 형태로 변환 해야 하기 때문에

float 값을 (DWORD) 로 그냥 캐스팅하면 정수부분만 나오게 되고 float 전체 값을 캐스팅 하기 위해선

 

float a;

 

*((DWORD*)&a)

 

의 방법으로 캐스팅하면 float 값을 DWORD 로 변환한 값을 얻을 수 있게 된다

 


[더하는 말로..]

 

directx 에서

float PointSize = 10.0f;
   _pD3dDevice->SetRenderState( D3DRS_POINTSIZE , *((DWORD*)&PointSize));

 

의 함수중 *((DWORD*)&PointSize 이것을 이렇게 변환 하는 이유를 추측해 보자면 이 함수안에서 사용 되는 값이

DWORD 값을 float 로 변환해 쓰이기 때문에 이전에 float 값을 DWORD 로 변환한 값 형태로 넘겨줘야 하는 것 같다

 

 

반응형
반응형


A 클래스  함수내에서 함수포인터로 넘어온 함수를 실행시키기 위해선

 

A 클래스의 this 포인터로 넘어온(또는 인자로 받은) 함수포인터 타입을 실행 시켜야 한다

 

fn 이 함수 포인터 타입이고 어떤 함수가 A의 소식이면서 fn 의 변수로 넘어왔을때

 

 

A에선

void method{

  (this->*fn)();

}

 

으로 호출하면 된다

 

괄호가 중요!


BLOG main image






http://blog.naver.com/sorkelf?Redirect=Log&logNo=40135728128



함수포인터이긴 하지만


클래스에 멤버함수를 함수포인터로 호출 하려면 어떻게 할까?


함수포인터도 어디까지 포인터이므로


멤버함수의 주소를 넘겨 주되 클래스에 소속되어 있으므로


클래스의 scope 연산자 (::)를 반드시 명시해야 한다


또한 어디까지나 멤버함수이므로 함수포인터로 호출시에도


반드시 해당하는 그의 객체가 존재해야 한다


백문이 불여일견.. 코드를 보는게 빠를 듯..









http://www.cyworld.com/blue_wons/7209444


[STL 응용] Map으로 문자열과 함수포인터 매핑하기.




@ 이것을 보게된 계기
 cmd창에 명령어를 if else 구문으로 처리하다가, 좀더 깔끔하면서도 관리되는 방식의 명령어 정리는 하지 못할까라는 생각을 했고
명령어가 많아질수록 제어구문의 길이도 길어지는것을 감안하여 검색하던중, 스택오버플로우에 정확한 구현내용을 포착
이에 한번 따라서 만들어봤습니다. 위 참고 출처에는 클래스 객체 자신 내부함수를 참조하는 것이 아니기때문에
비교하면서 봐야 합니다.

#include <map>
#include <iostream>
#include <string>

class Type
{
typedef void (Type::*FuncPointer)();
typedef map<std::string, FuncPointer > FuncMap;
typedef map<std::string, FuncPointer>::iterator FuncMapIter;

FuncMap _funcMap;
FuncMapIter _pos;
string buff;

public:
Type(){};
~Type(){};

LoadFunc()
{
//왼쪽 값이 key 이면서 first, 오른쪽 값이 value 이면서 second
_funcMap["Start"] = &Type::Start; //굳이 이런식으로 넣지 않아도 된다.
//_funcMap.Insert(make_pair("Start", &Type::Start)); //이렇게 쑤셔박아도 된다.
}

ActivateFunction()
{
cin>>buff;
_pos = funcMap.find(buff);
if(_pos != funcMap.end())
{
FuncPointer func = _pos->second;
(this->*func)(); //참조할때가 가장 중요하다.
//이것은 자기 자신 내부라서 (this->*func)() 이런식으로 참조를 했지만
//객체 포인터가 아닌경우 (name).*func() 이런식으로 참조가 가능하다.
//자칫 잘못하면, 컴파일러가 잘못된 참조 갯수*3배만큼 애러를 토한다.
}
}

private:
void Start(){  std::cout<<"Start function !"<<std::endl;
}


[출처] 멤버 함수 포인터|작성자 풍풍풍



반응형
반응형

http://blog.naver.com/qktxhtkdl/10110879615

 


UINT_PTR (32비트 및 64비트 호환 포인터연산)   

#include "stdafx.h"
#include <Windows.h>
UINT_PTR CalDistance(UINT_PTR a, UINT_PTR b)
{
 return a-b;
}
int _tmain(void)
{
 INT32 val1 = 10;
 INT32 val2 = 20;
 _tprintf(_T("distance : %d \n") , CalDistance((UINT_PTR)&val1, (UINT_PTR)&val2));
 return 0;
}
 


32비트 시스템과 64비트 시스템은 주소값의 범위가 다르기때문에
 
상호 호환이 가능한 코드를 사용하는 것이 좋다.
 
Polymorphic타입의 자료형을 사용해서 매크로정의에 따라 32비트 & 64비트 호환이 되는 코드는 위처럼 UINT_PTR을 사용한다.
 
PTR은 포인터 자체가 아닌, 포인터 연산을 위해 존재한다는 뜻으로 해석하면 된다.

 


 

 

http://vvalkyrie.tistory.com/705

 

INT_PTR 형 분류없음 2007/11/13 11:47
64비트 호환 프로그래밍을 위해 _PTR 접미사 (type for pointer precision의 뜻)가 붙는 형들이 새로 정의되었다는 것을 알았다. 포인터 연산을 위해 포인터 변수를 정수형 변수로 type casting 할 때 발생하는 문제를 해결하기 위해 도입된 듯. 이 중 UINT의 _PTR 형인 UINT_PTR 형은 다음과 같이 정의되어 있다.

[CODE]#if defined(_WIN64)
    typedef unsigned __int64 UINT_PTR;
#else
    typedef unsigned int UINT_PTR;
#endif[/CODE]
원래 이걸 찾게 된 이유는 SOCKET 형의 정체가 UINT_PTR 형이었기 때문이다.

winsock2.h:[CODE]typedef UINT_PTR SOCKET;[/CODE]

반응형
반응형

만약 4*4 행렬일 경우

 

<<2  ==  *4 와 같으며 row |  는 0~3 범위의 인덱스가 됨으로 이와 같은 비트 오퍼레이션 연산이 가능하다

 

T & element (int row, int col) {
        return _array[row | (col<<2)];
    }

 

 

 void set_row(int r, const vec4<T> & t) {
        for (int i = 0; i < 4; i++) element(r,i) = t[i];
    }

    void set_column(int c, const vec4<T> & t) {
        for (int i = 0; i < 4; i++) element(i,c) = t[i];
    }

반응형
반응형

class Matrix{

private :

union {
        struct {
            T _11, _12, _13, _14;   // standard names for components
            T _21, _22, _23, _24;   // standard names for components
            T _31, _32, _33, _34;   // standard names for components
            T _41, _42, _43, _44;   // standard names for components
        };
        T _array[16];   

    };

}



15개의 배열과 각 _숫자   는 순서적으로 이름은 다르지만 같은곳을 공유하게된다

반응형
반응형

furyheimdall.springnote.com


MFC 나 API 에서는 유저에게 숨겨진 실제 엔트리포인트가  CRTWinMain 으로 콘솔과는 전혀 다르게 사전에 초기화가 됩니다.

하지만 MFC 나 API 환경에서 간단한 수치를 디버깅 하기 위해선 익숙해지더라도 참 번거롭기 마련입니다.

이런 답답한 부분때문에 네이버에서 콘솔을 띄우는 방법에 대해 검색해보았는데 의외로 많더군요.

그중에 가장 좋다 싶은 방법을 선택하여 클래스로 만들어보았습니다만... 민망할 정도로 사용상 편의점이 아니고서는 썰렁하네요.


사용자 삽입 이미지

일단 기본적인 방법에 대해서 살펴보겠습니다.

  1. AllocConsole();   //콘솔을 할당    
  2. FreeConsole();   //콘솔을 해제   

  1. /* stdout 에 대한 설정 */  
  2. hCrt = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE),_O_TEXT);    
  3. hf = _fdopen(hCrt,"w");  
  4. *stdout = *hf;  
  5. setvbuf( stdout, NULL, _IONBF, 0 );   

기본적으로 위의 두가지 블럭안에 있는 코드만으로 콘솔을 띄우고 난 후 printf 나 cout 을 이용하여 출력, 콘솔을 닫는 작업들이 가능합니다.


그럼 완성된 클래스를 보지요.


  1. //[MFCConsole.h]    
  2. #pragma once  
  3. #include <io.h>  
  4. #include <fcntl.h>  
  5. #include <stdio.h>  
  6. #include <iostream>  
  7. using namespace std;    
  8. #define DEBUG_PRINT(x) printf("%s - Value : %ld[0x%lx], Address : 0x%lx\n",#x,x,x,&x)    
  9.   
  10. class CMFCConsole  
  11. {  
  12. public:  
  13.  CMFCConsole(void);  
  14.  ~CMFCConsole(void);  
  15.  void OpenConsole(void);  
  16.  void CloseConsole(void);   
  17. };  

  1. //[MFCConsole.cpp]    
  2. #include "StdAfx.h"  
  3. #include "MFCConsole.h"    
  4. CMFCConsole::CMFCConsole(void)  
  5. {  
  6.  //초기화 콘솔을 열고 stdout, stdin 설정후 콘솔을 닫음  
  7.  AllocConsole();  
  8.  int hCrtout = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE),_O_TEXT);  
  9.  FILE *hout = _fdopen(hCrtout,"w");  
  10.  *stdout = *hout;    
  11.  int hCrtin = _open_osfhandle((long)GetStdHandle(STD_INPUT_HANDLE),_O_TEXT);  
  12.  FILE *hin = _fdopen(hCrtin,"r");  
  13.  *stdin = *hin;  
  14.  setvbuf( stdin, NULL, _IONBF, 0 );  
  15.  FreeConsole();  
  16. }    
  17. CMFCConsole::~CMFCConsole(void)  
  18. {  
  19.  //해제  
  20.  _fcloseall();  
  21. }    
  22. void CMFCConsole::OpenConsole(void)  
  23. {  
  24.  //콘솔을 열고 타이틀을 셋팅, 열린 콘솔이 있을경우 무시됨  
  25.  if(AllocConsole()){  
  26.   SetConsoleTitle(_T("Furyheimdall's Console"));  
  27.   printf("-----------------------------------------------\n");  
  28.   printf("             Console Class for MFC             \n");  
  29.   printf("              Made by Furyheimdall             \n");  
  30.   printf("-----------------------------------------------\n\n");  
  31.  }  
  32. }    
  33. void CMFCConsole::CloseConsole(void)  
  34. {  
  35.  //콘솔을 닫음. 열린 콘솔이 없을경우 무시  
  36.  FreeConsole();  
  37. }   

반응형
반응형

http://boxbop.tistory.com/45

 

memmove : 어떠한 경우에도 사용할 수 있는 메모리 복사 함수
cf) void* memmove(void* dest, const void* src, sizt_t len);
->매개변수 src로 전달된 값을 시작주소로 하여 len바이트를 읽어 들여서, 매개변수 dest로 전달된 주소에 복사를한다.

 memcpy : 제한된 상황에서의 메모리 복사
cf) void* memcpy(void* restrict dest, const void* restrict src, size_t len);
->memmove와 기능상 차이는 없다. 그러나 dest와 src가 restrict으로 선언되었다. 따라서 함수가 호출되면서 dest와 src로 전달된 주소 값의 메모리는 각각 dest와 src로만 접근이 가능해야 한다.(원본대상과 복사본이 겹치는 경우 사용할 수 없다)

반응형
반응형


부동 소수점 기수법 
floating-point representation system, 浮動小數點記數法  [컴퓨터]


지수를 이용하는 기수 표기법. 매우 큰 수 및 매우 작은 수를 표현하는 데 사용된다. 부동 소수점 기수법에서 수는 부동 소수점 부분과 지수 부분(exponent)의 2부분으로 나뉘어 표현된다. 부동 소수점 부분을 가수부(mantissa)라고도 한다. 가수부는 그 수의 숫자 부분을 지정하고 지수부는 그 수의 크기(소수점의 위치)를 지정한다. 부동 소수점 기수법에서 어떤 수 (n)이 가수부 (a)와 지수부 (b)로 표현되면 n=a×rb가 된다(r는 기수). 예를 들면 10진수 314,600,000과 0.0000451은 각각 3,146E5와 451E-7로 표현되는데 3,146과 451은 가수부이고 E5와 E-7은 지수이다. 따라서 314,600,000=3,146×105가 되고, 0.0000451=451×10-7이 된다.

 

 

 

 

 

규칙들

1.가수는한자리,0이아닌정수로쓰여야만한다.

2.가수는소수부수들의고정된개수를가지고쓰여야만한다.(이것을M이라고정의)

3.지수는고정된개수의수들로쓰여야만한다.(이것을E라고정의)

4.가수와지수는각각개별적인부호를가진다

 

 

최대, 최소 지수 ±(10^E -1) = ±(10^2 -1) =±99

최대 기수 값 10.0 - {10^(-M) } = 10.0 -{10^(-3)} = 10.0 - 0.001 = 9.999

 

최대 표현 가능한 값 : 9.999 * 10^99

최소 표현 가능한 값 : -9.999 * 10^99

가장 작은 양의 값 : 1.000 * 10^99

 

반응형
반응형

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

using namespace std;

struct UObject{
 int aa;
 int bb;
 int cc;
};


enum EInternal {EC_Internal};


void* operator new(size_t t,EInternal* Mem){

 return malloc(t);
}

void* operator new(size_t t){

 return malloc(t);
}

int main(){

 

 int* pi=new int;

 char* pc=new char;

 void* X=new char;

 new( (EInternal*)X ) UObject;     // X 가 *Mem 으로 전달 되고 UObject 의 크기 12가 t 로 전달된다

 

// new( (EInternal*)X ) UObject( UObject 의 생성자 인수목록 ); 


 delete pi;
 delete pc;

 

 return 0;
}

반응형

'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

restrict 포인터 {void* restrict pt}  (0) 2012.11.01
부동 소수점( 기수법 )  (0) 2012.11.01
Protected 생성자  (0) 2012.11.01
win32 에서 출력창에 표시  (0) 2012.11.01
난수 발생 srand, rand  (0) 2012.11.01
반응형

http://blog.naver.com/kofpw?Redirect=Log&logNo=80142050098

 


복사http://blog.naver.com/kofpw/80142050098

 

 

MFC의 동적 객체 생성 방법을 공부하던 중, MFC의 메인이 되는 각 클래스, Doc, View, MainFrm은 App 클래스의 InitInstance() 함수를 통해 동적으로 생성되는데, 웃긴것은 각 클래스의 생성자가 protected로 지정되어있는데 버젓이 다른 클래스에서 접근하여 생성한다는 것이다.

 

이것에 대한 원리가 나와있길래 살펴봤다. 어려운 내용은 아니다.

 

우선 다음과 같은 코드가 있다고 하자.

 

#include <iostream>

using namespace std;

 

class Object

{

protected:

      Object() { cout << "생성자" << endl; }

 

public:

      ~Object() { cout << "소멸자" << endl; }

};

 

void main()

{

     Object obj;

}

 

실행해보면 당연히 에러가 난다. 생성자가 protected로 선언되어있어 외부에서 접근할 수 없기 때문이다.

그럼 어떻게 해야할까? 뭐 제일 간단한 방법은 public에 생성자를 선언하는 방법이지만, 그런 뻔한 방법 말고 여기서의 목적은 MFC에서 어떻게 protected 생성자가 버젓이 호출되는지 파헤치기 위함이니...

 

그럼 어떻게? 그럼 Object 객체를 생성시켜주는 public 함수를 하나 만들어 보는 것이다.

 

#include <iostream>

using namespace std;

 

class Object

{

protected:

      Object() { cout << "생성자" << endl; }

 

public:

      ~Object() { cout << "소멸자" << endl; }

     void CreateObject() { Object obj; }

};

 

void main()

{

     CreateObject();

}

 

또 당연히 에러가 난다. 메인함수에서 CreateObject()라고 쓰면 클래스의 멤버가 아닌 전역 함수를 찾기 때문이다. 하지만 그 어디를 봐도 CreateObject라는 이름을 가진 전역함수는 보이지 않는다. 따라서 CreateObject를 찾을 수 없다고 에러가 뜬다.

그렇다고 먼저처럼 Object 클래스를 생성하고 obj.CreateObject() 라고 할수도 없는 일이다. 생성자는 protected이니까..

 

여기서 떠오르는 것이 바로 static이다.

 

#include <iostream>

using namespace std;

 

class Object

{

protected:

      Object() { cout << "생성자" << endl; }

 

public:

      ~Object() { cout << "소멸자" << endl; }

     static void CreateObject() { Object obj; }

};

 

void main()

{

     Object::CreateObject();

}

 

클래스의 멤버 함수를 static으로 선언해버리면 클래스를 만들지 않고도 해당 함수를 호출 할 수 있게 된다. (static 함수는 메모리의 데이터 영역에 저장된다) 위 코드는 정상적으로 실행이 될 것이다. 그러나, static 함수에서 생성된 객체 obj는 지역 변수이므로, 함수가 종료되는 순간 소멸되어버릴 것이다. 함수가 무용지물이 되는 셈이다.

 

그럼 또 어떻게 해야할까? 뭐긴... 동적할당하면 되지~~

 

#include <iostream>

using namespace std;

 

class Object

{

protected:

      Object() { cout << "생성자" << endl; }

 

public:

      ~Object() { cout << "소멸자" << endl; }

     static Object* CreateObject() { return new Object; }

};

 

void main()

{

     Object* pObj = Object::CreateObject();

     delete pObj;

}

 

이렇게 하면 함수를 통해 생성한 객체를 받아와서 사용할 수 있게 된다.

 

결론은, MFC의 동적 객체 생성 방법도 이런 식으로 진행된다는 것이다. 다만, 그것이 표면적으로 드러나있지 않고 숨어있을뿐.....

[출처] Protected Constructor|작성자 쿨랜드


반응형
반응형


첨부파일 utillilty.cpp 

http://www.nicklib.com/bbs/board.php?bo_table=bbs_util&wr_id=12&sca=Visual+Studio

 

 


#ifdef WIN32
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <crtdbg.h>
#endif
void Trace(char* lpszFormat, ...)
{
#ifdef WIN32
 char szBuffer1[512];
 char* lpBuf;
 char* lpsz;
 char szBuffer[512];

 va_list args;
 va_start(args, lpszFormat);
 _vsntprintf(szBuffer1, sizeof(szBuffer1)/sizeof(szBuffer1[0]), lpszFormat, args);
 va_end(args);
 lpsz = szBuffer1;
 lpBuf = szBuffer;
 while (*lpsz != '\0')
 {
  if (lpBuf > szBuffer + sizeof(szBuffer)/sizeof(szBuffer[0]) - 3)
  {
   *lpBuf = '\0';
   if ((1 == _CrtDbgReport(_CRT_WARN, NULL, 0, NULL, "%s", szBuffer))) 
    _CrtDbgBreak(); 
   lpBuf = szBuffer;
  }
  if (*lpsz == '\n')
   *lpBuf++ = '\r';
  *lpBuf++ = *lpsz++;
 }
 *lpBuf = '\0';
 if ((1 == _CrtDbgReport(_CRT_WARN, NULL, 0, NULL, "%s", szBuffer))) 
  _CrtDbgBreak(); 
#else
 return;
#endif


반응형
반응형

http://pcsak3.com/431

 

 

난수 발생 함수

   

 

srand( (unsigned)time( NULL ) ); // 난수 발생초기화

m_iNowBlockShape = rand()%7; // 난수 발생

  

 

 

 

 

C에서 난수 발생

   

다른 언어와 비슷하겠지만 C언에서 여러 개의 난수표를 가지고 있습니다. 이 난수표에는 임의의 수가 나열되어 있으며 C에서 난수표의 값을 가져오는 방식입니다.

이 때 동일한 난수표와 동일한 위치에서 난수를 가져오게 된다면 어떻게 될까요?

항상 같은 값만 나올 것입니다.

그래서 사용할 난수표와 위치를 항상 변경해 줘야 하는데 srand() 함수로 변경할 수 있습니다.

srand 파라미터에 어떤 숫자를 넣어 주면 난수표와 위치가 변경됩니다.

그렇다면 항상 다른 값의 파라미터를 입력해야 하는데, 어떻게 해야 할까요?

또 난수를 발생시켜 넣어 줄까요? ;;

이 때는 시간을 파라미터로 넣어 주면 됩니다. 시간은 항상 변하니까요^^.

 

그래서 C 에서는 아래처럼 사용됩니다.

 

srand( (unsigned)time( NULL ) ); // 난수 발생초기화

m_iNowBlockShape = rand(); // 난수 발생

  

 

 

 

난수 발생하는 방법을 알았는데,

만약 0 ~ 6까지의 난수를 생성하려면 어떻게 해야 할까요?

생성된 난수를 7로 나눈 나머지를 사용하면 되겠죠^^

 

srand( (unsigned)time( NULL ) ); // 난수 발생초기화

m_iNowBlockShape = rand()%7; // 0 ~ 6까지의 난수 발생

  

 

 

 

그렇다면 난수의 최대값을 얼마일까요?

컴파일러 마다 다를 수 있는데 헤더 파일에 선언되어 있습니다.

 

printf("RAND_MAX: %d", RAND_MAX); // 생성할 수 있는 난수의 최대값

  

 

 

 

 

RAND_MAX 는 어디 있으며, srand(), rand() 함수는 어디에 있나요?

아래 헤더 파일을 추가해 줘야 합니다.

 

#include <stdlib.h> // RAND_MAX, srand(), rand() 함수

#include <time.h> // time() 함수

  

 

 

 

 

난수 초기화 함수를 여러 번 실행하면 난수 발생이 더 효과적인가요?

=> 그렇지 않습니다. 초기화를 한 번하던 열번 하던 효과는 같습니다.~~

 

 

 

 

난수 초기화를 하지 않으면 항상 같은 수가 나오나요?

=> 컴파일러마다 약간 다를 가능성도 있지만 이론상으로는 항상 같은 값이 나옵니다.

 

아래는 C에서 난수를 발생한 예입니다.

 

 

#include <stdio.h>

#include <stdlib.h> // RAND_MAX, srand(), rand() 함수

#include <time.h> // time() 함수

 

 

int main(int argccharargv[])

{

 

//    srand( (unsigned)time( NULL ) ); // 난수 발생초기화

 

    for (int i=0;i<10;i++)

    {

        printf("\n%d",rand());

    }

      

    

    return 0;

}

 

 

 

41

18467

6334

26500

19169

15724

11478

29358

26962

24464

반응형
반응형

 

0과 1이나 true/false같은 switch개념의 변수를 사용하려면 C언어에서 최소사양에 해당하는 char형을 생각해 볼 수 있습니다. 물론 int형이나 그 밖에 다른 형을 써도 상관은 없지만 1Bit만 있으면 되므로 굳이 많은 Data형을 할당하여 Memory를 낭비할 필요는 없습니다.

그런데 char형도 그리 효츌적이지는 않습니다. 왜냐하면 char는 1Byte단위(8bit)이므로 0과 1을 표시하는데 1Bit를 할당하고 나면 나머지 7Bit는 결국 낭비되기 때문입니다.

따라서 C에서는 Bit단위의 Data를 다룰때 해당 Bit영역을 최대한 활용하기 위해 Bit Field라는 것을 사용합니다.

Bit Field는 선언하고자 하는 Data형의 크기 만큼 Bit Field를 구조체 형식으로 선언하면 됩니다.

struct bitfield{
    unsigned char a : 1;
    unsigned char b : 1;
    unsigned char c : 1;
    unsigned char d : 1;
    unsigned char e : 1;
    unsigned char f : 1;
    unsigned char g : 1;
    unsigned char h : 1;
};

bitfield라는 이름으로 unsigned char형(8bit)의 a부터 h까지 Bit Field를 선언하였습니다.

unsigned char라고 한 이유는 char형의 전체 8Bit중 최상위 Bit(부호를 정하는 bit)까지 모두 사용하기 위해서 입니다.(만약 unsigned int라고 한다면 16Bit만큼을 의미하게 됩니다.)

unsigned char는 전체 8bit이므로 a부터 h까지 1비트씩 차지하여 결국 합이 1Byte의 Memory를 할당하게 됩니다. 이때 a부터 h까지 1Bit씩 차지한다는 것은 a : 1 등에 의해 정해지게 됩니다.

따라서 1Bit가 아닌 그 이상의 Bit를 할당하고자 할 경우에는 오른쪽의 숫자만 바꾸어 주면 될것입니다.

struct bitfield{
    unsigned char a : 2;
    unsigned char b : 3;
    unsigned char c : 1;
    unsigned char d : 1;
    unsigned char e : 1;
};

a는 2Bit, b는 3Bit.. 나머지는 모두 1Bit를 할당하였습니다. 따라서 a는 2Bit이므로 0~3까지 그리고 b는 3Bit이므로 최소 0~7까지의 Data를 담을 수 있습니다.

이때 만약 Data형의 범위를 벗어나도록 bit field를 지정하면 어떻게 될까요?

struct bitfield{
    unsigned char a : 2;
    unsigned char b : 3;
    unsigned char c : 1;
    unsigned char d : 1;
    unsigned char e : 1;
    unsigned char f : 1;
};

f 1Bit까지 전체 9Bit를 할당하였습니다.

unsigned char는 8bit이므로 f를 수용할 수 없습니다. 하지만 Compiler는 오류를 표시하지 않고 위에서 순서대로 각 Bit를 모두 담은 뒤 그 이상의 Bit가 존재하면 다음 영역을 또 다시 확보하고 해당 Bit를 담게 됩니다.

즉, 처음 a부터 e까지 8Bit(1Byte)에 담고 그 다음 f를 위해 다시 8Bit(char = 1Byte)영역을 확보한 후 처음 Bit부분에 f를 담게 되는 것입니다.(나머지 7bit는 낭비됩니다.)

#include <stdio.h>

main()
{
  struct bitfield{
    unsigned char a : 2;
    unsigned char b : 3;
    unsigned char c : 1;
    unsigned char d : 1;
    unsigned char e : 1;
  };
  
  struct bitfield bit;
  
  bit.a = 2;
  bit.b = 5;
  bit.c = 1;
  bit.d = 0;
  bit.e = 1;
  
  printf("%d\n", bit.a);
  printf("%d\n", bit.b);
  printf("%d\n", bit.c);
  printf("%d\n", bit.d);
  printf("%d\n", bit.e);
}


각 Bit에 값을 설정하고 해당 값을 확인합니다.


bit field라고 해서 특별히 정해진 구문이 있는것은 아닙니다. 단지 bit field를 선언하는데 구조체의 선언방법을 빌려 선언되는것 뿐이며 구문상의 차이도 몇 Bit의 영역을 차지하는가를 나타내는 : x 형식의 구현부분만 다를 뿐입니다.

때문에 실제 bit filed와 구조체사이에는 그리 큰 차이가 없습니다.

#include <stdio.h>
#include <string.h>

main()
{
  struct bitfield{
    unsigned char a : 2;
    unsigned char b : 3;
    unsigned char c : 1;
    unsigned char d : 1;
    unsigned char e : 1;
    
    char name[10];
    int age;
  };
  
  struct bitfield bit;
  
  bit.a = 2;
  bit.b = 5;
  bit.c = 1;
  bit.d = 0;
  bit.e = 1;
  
  strcpy(bit.name, "youngsoo");
  bit.age = 30;
  
  printf("%d\n", bit.a);
  printf("%d\n", bit.b);
  printf("%d\n", bit.c);
  printf("%d\n", bit.d);
  printf("%d\n", bit.e);
  
  printf("%s\n", bit.name);
  printf("%d\n", bit.age);
}


구조체와 bit field를 동시에 구현합니다.


bit field를 선언할때 몇 Bit를 할당할지에 대한 내용은 생략하고 Data형만 쓰는 경우가 있습니다. 이는 실제 Bit영역을 사용하지 않으면서 Program구조상에 구분영역을 만들어 주는 역활을 수행하기 위함입니다.

또한 Bit를 처음부터 채우지 않고 일정부분 비워둬야 하는 경우에도 자주 쓰이는 방법입니다.

#include <stdio.h>

main()
{
  struct bitfield{
    unsigned char a : 2;
    unsigned char   : 3;
    unsigned char c : 1;
    unsigned char   : 1;
    unsigned char e : 1;
  };
  
  struct bitfield bit;
  
  bit.a = 2;
  bit.c = 0;
  bit.e = 1;
  
  printf("%d\n", bit.a);
  printf("%d\n", bit.c);
  printf("%d\n", bit.e);
}


bit field의 3번째 부터 5번째(이전의 b영역)과 7번째(이전의 d영역)은 사용하지 않습니다. 또한 a와 c, e를 영역별로 구분하도록 합니다.


참고 :
각 Field의 구분은 Program이 아닌 개발자 입장에서 입니다.

반응형
반응형

strtok(_tcstok)는 문자열을 특정 기준에 따라 나누어 주는 함수이다.

 TCHAR.H routine _UNICODE & _MBCS not defined _MBCS defined _UNICODE defined
 _tcstok strtok _mbstok wcstok
 _tcstok strtok_l _mbstok_l wcstok_l


예를 들어

string[] = _T("My Friends are honest. \n But, JK is very stupid");

 라는 문장이 있다 이 문장에서 \n을 기준으로 두개의 문자열로 나누고 싶다면

다음과 같이 사용하면 된다.

특정 기준을 사용하기 위해 배열에 기준에 대한 항목을 넣어준다.

TCHAR seps[] = _T("\t\n");  //\t와 \n이 나오면 문자열을 나눈다.

아래는 위에서 설명한 내용에 따라 예제로 나타낸 것이다.
(MSDN 참고)
#include <string.h>
#include <stdio.h>
#include <tchar.h>
TCHAR string[] = _T("My Friends are honest. \nSo, I'm happy");
TCHAR seps[] = _T("\t\n");
TCHAR *token;

int _tmain(int argc, TCHAR argv[])
{
//문자열을 기준에 따라 token에 임시 저장한다.
token = _tcstok( string, seps );

while( token != NULL )
{
  //기준에 의해 나눈 문자열을 출력한다.
_tprintf(_T("%s\n"), token );

// 다음 문자열을 구한다.
token = _tcstok( NULL, seps ); // C4996
}
}

실행결과
My Friends are honest.
So.I'm happy

반응형
반응형

위의 표 중 Parameters in registers 에서 32bit 일 경우 _fastcall 은 파라미터 전달시 저장을 레지스터에 저장한다는 것 한개도 아닌 두개로

_thiscall 의 경우엔 한개로,  그런데 표준함수 호출인 _stdcall 은 레지스터를 사용하지 않는다 _cdecl 경우도 마찬가지

 

하지만 64 bit 컴퓨터에서는 레지스터 사용량이 대폭 증가한다,

 

속도면에서 상당히 우월한 레지스터이니 빨라질 수 밖에없다 

반응형

'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

비트필드(Bit Field)  (0) 2012.11.01
_tcstok 사용방법(문자열 분리)  (0) 2012.11.01
입,출력 스트림 - IO stream  (0) 2012.11.01
strcmp.c  (0) 2012.11.01
c++ 무한대 표기  (0) 2012.11.01
반응형


    1. 표준 입력와 출력

     

    모니터와 키보드를 콘솔(console)이라고 하는데, 이것들은 컴퓨터의 기본적인 입출력을 담당하는 표준 입출력(standard input and output) 장치입니다. 모니터로 데이터를 출력하는 방법과 키보드로 데이터를 입력받는 방법에 관하여 살펴봅시다.

     

    C++에서는 파일(장치도 파일로 간주)을 다루기 위해 스트림(stream)이라는 개념을 도입하였습니다. 스트림은 데이터가 이동하는 파이프와 같은 것으로 목적지까지 데이터를 순차적으로 전송하는 역할합니다. 또한 스트림은 데이터를 보내는 속도와 받아서 처리하는 속도의 차를 극복하기 위한 버퍼의 역할도 합니다. 스트림은 데이터의 전송 방향에 따라 출력 스트림과 입력 스트림으로 나누지고, 제작하고 있는 프로그램을 기준으로 밖으로 데이터를 보내면 출력, 외부에서 데이터를 가져오면 입력 스트림입니다. 이러한 스트림을 사용하기 위해서는 스트림을 만들고, << 와 >> 연산자를 사용하여 데이터를 전송하고, 사용 후에는 제거해 줘야 합니다. 표준 입출력을 위한 스트림은 만들고 제거하는 과정없이 항상 사용할 수 있는데, 다음과 같이 준비되어 있습니다.

     

    종류

     스트림

     연산자

    출력

     cout

     <<

    입력

     cin

     >>

     

    스트림을 사용하기 위해서는 먼저 해당 헤더 파일을 소스에 포함시켜야 하고, 이름 공간의 사용을 명시할 필요가 있습니다.

     
    #include <iostream>  // 헤더 파일
    using namespace std; // std 이름 공간 사용
     

    입력 스트림을 사용할 때는 입력된 데이터를 보관할 변수(확보된 메모리 공간)를 미리 준비해야 한다는 것입니다.
    예를 들어 친구의 이름과 나이를 입력 받아, 출력해 봅시다. 

     
    #include <iostream>
     
    using namespace std;
     
    int main()
    {
        char name[9]; // 이름을 입력 받기 위한 변수
        int  age;     // 나이를 입력 받기 위한 변수
     
        cout << "친구 이름 : ";
        cin >> name;
        cout << "친구 나이 : ";
        cin >> age;
     
        cout << "친구의 이름은 " << name << "이고, " 

             << "나이는 " << age << "입니다." << endl;
     
        return 0;
    }

     

    친구의 이름과 나이를 입력받으려면, 먼저 이를 위한 메모리 공간을 확보해야 합니다. 물론 가장 쉬운 방법이 변수를 만드는 것입니다. 먼저 이름을 저장할 변수를 만들어 봅시다. 이름은 문자열이므로 문자 배열이 필요합니다. 한글 4 자를 저장하려면 8 바이트가 필요하고 NULL문자를 위한 공간도 필요하므로 char[9]인 자료형이 필요합니다. 나이는 정수이므로 int 형 변수를 만들면 되겠습니다.

    반응형
    반응형

    /***
    *strcmp.c - routine to compare two strings (for equal, less, or greater)
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       Compares two string, determining their lexical order.
    *
    *******************************************************************************/

    #include <cruntime.h>
    #include <string.h>

    #ifdef _MSC_VER
    #pragma function(strcmp)
    #endif  /* _MSC_VER */

    /***
    *strcmp - compare two strings, returning less than, equal to, or greater than
    *
    *Purpose:
    *       STRCMP compares two strings and returns an integer
    *       to indicate whether the first is less than the second, the two are
    *       equal, or whether the first is greater than the second.
    *
    *       Comparison is done byte by byte on an UNSIGNED basis, which is to
    *       say that Null (0) is less than any other character (1-255).
    *
    *Entry:
    *       const char * src - string for left-hand side of comparison
    *       const char * dst - string for right-hand side of comparison
    *
    *Exit:
    *       returns -1 if src <  dst
    *       returns  0 if src == dst
    *       returns +1 if src >  dst
    *
    *Exceptions:
    *
    *******************************************************************************/

    int __cdecl strcmp (
            const char * src,
            const char * dst
            )
    {
            int ret = 0 ;

            while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
                    ++src, ++dst;

            if ( ret < 0 )
                    ret = -1 ;
            else if ( ret > 0 )
                    ret = 1 ;

            return( ret );
    }


    반응형
    반응형

    printf를 MSDN에서 찾아보면

     

    If the argument corresponding to a floating-point specifier is infinite, indefinite, or NaN, printf gives the following output.


    위의 내용을 해석하면...

    ( 만약 플로팅지정자로 일치하는 아규먼트가 무한대, 정의되지 않는 숫자(orNaN), 일경우

    printf 는 다음 출력구문을 보여준다  )



    ms 페이지에서 보면 해당 타입의 std::numeric_limits<T>::has_infinity


    의 has_infinity 가 true 일 경우 이러한 무한대 값을 표시해준다라고 나와 있다




    출처 : 지식인



    ValueOutput
    + infinity1.#INFrandom-digits
    – infinity–1.#INFrandom-digits
    Indefinite (same as quiet NaN)digit.#INDrandom-digits
    NAN

    digit.#NANrandom-digits

     

    이렇게 되어 있네요

     

    실수를 프린트에서 찍을때

    +무한은 1.#INF

    -무한은 -1.#INF

    정의되어 있지않은 숫자는  #IND

    NAN(Not a Number)는 #NAN이라고 표시된답니다.

     

    무한(Infinity)은 말 그대로 컴퓨터의 double로 표시할 수 있는 범위를 넘어간 큰수흘 의미 합니다.

    미정의 숫자(Indefinite)는 루트(-1)처럼 정의할 수 없는 숫자를 의미합니다.

    이런 에러가 발생했는 지를 찾기 위해서는 float.h에 있는 _isnan이나 _finite 함수를 이용할 수 있습니다. 다음 예를 참고하십시요

     

    #include < float.h>
    #include < math.h>
    #include < stdio.h>


    void testnumber(double val)
    {
        if(_isnan(val))
        {
            printf("%e은 숫자가 아닙니다.\n",val);
        }else if(!_finite(val))
        {
            printf("%e은 유한 범위의 숫자가 아닙니다.\n",val);
        }
        else
            printf("%e은 정상적인 숫자입니다.\n",val);


    }

    void main()
    {
        double val=1;

        // indefinite   
        testnumber(sqrt(-1));
       

        // infinity
        for(int i=0;i<10;i++)
        {
            val=val*1000000000000000000000000000000000.0;
            testnumber(val);
        }

    }

     

    반응형
    반응형

    대입연산자 오버로딩은 클래스 멤버로만 정의 가능하고 전역으로는 할 수 없다


    정적으로도 만들 수 없다.




    http://winapi.co.kr

    28-3-다.대입 연산자

    대입 연산자는 자신과 같은 타입의 다른 객체를 대입받을 때 사용하는 연산자이다. 객체 자체와 직접적인 연관이 있기 때문에 클래스의 멤버 함수로만 정의할 수 있으며 전역 함수로는 정의할 수 없다. 정적 함수로도 만들 수 없고 반드시 일반 멤버 함수로 만들어야 한다. 다음 예제는 앞장에서 만들었던 Person2예제에 디폴트 생성자를 추가하고 main 함수의 테스트 코드를 약간 수정한 것이다. Person 클래스는 생성자에서 동적으로 버퍼를 할당한다는 점에서 Time이나 Complex 클래스와는 다르며 이 버퍼를 주의깊게 다루어야 할 필요가 있다.

     

      : Person3

    #include <Turboc.h>

     

    class Person

    {

    private:

         char *Name;

         int Age;

     

    public:

         Person() {

              Name=new char[1];

              Name[0]=NULL;

              Age=0;

         }

         Person(const char *aName, int aAge) {

              Name=new char[strlen(aName)+1];

              strcpy(Name,aName);

              Age=aAge;

         }

         Person(const Person &Other) {

              Name=new char[strlen(Other.Name)+1];

              strcpy(Name,Other.Name);

              Age=Other.Age;

         }

         ~Person() {

              delete [] Name;

         }

         void OutPerson() {

              printf("이름 : %s 나이 : %d\n",Name,Age);

         }

    };

     

    void main()

    {

         Person Boy("강감찬",22);

         Person Young("을지문덕",25);

         Young=Boy;

         Young.OutPerson();

    }

     

    Person2 예제에서는 Young 객체를 선언할 때 Person Young=Boy; 형식으로 선언하면서 동시에 초기화를 했었다. 이때는 복사 생성자가 호출되는데 Person2예제에 복사 생성자가 작성되어 있으므로 이 코드는 이상없이 잘 동작한다. 그러나 일단 선언한 후 대입을 받게 되면 문제가 달라진다. 이 예제를 실행해 보면 프로그램이 종료될 때 다운되는 것을 확인할 수 있다.

    선언과 동시에 다른 객체로 초기화하면 이때 복사 생성자가 호출되고 복사 생성자는 새로 생성되는 객체를 위해 별도의 버퍼를 준비하므로 두 객체가 버퍼를 따로 가져 아무런 문제가 없다. 그러나 실행중에 이미 사용중인 객체를 다른 객체로 대입할 때는 초기화 단계가 아니므로 복사 생성자는 호출되지 않는다. 다음 두 경우를 잘 구분하자.

    대입은 ① 이미 생성된 객체에 적용된다. ② 실행중에 언제든지 여러 번 대입될 수 있다는 점에서 초기화와는 다르다. 실행중에 객체끼리 대입 연산을 하면 어떤 일이 벌어지는지 보자.

    깊은 복사를 하는 대입

    대입 연산자를 별도로 정의하지 않을 경우 컴파일러는 디폴트 대입 연산자를 만드는데 이 연산자는 디폴트 복사 생성자와 마찬가지로 단순한 멤버별 대입만 한다. 우변 객체의 모든 멤버 내용을 좌변 객체의 대응되는 멤버로 그대로 대입함으로써 얕은 복사만 하는 셈이다. 결국 Young의 Name 멤버는 Boy의 Name 멤버가 가리키는 버퍼의 주소를 그대로 가지게 될 것이다. 이때의 메모리 상황을 그림으로 그려 보자.

    두 객체 모두 "강감찬"을 가리키고 있으며 main 함수가 종료될 때 각각의 파괴자가 호출되는데 먼저 파괴되는 객체가 Name 버퍼를 정리할 것이고 나중에 파괴되는 객체가 이 버퍼를 이중으로 정리하려고 하므로 무효해진 메모리를 해제하는 오류를 범하는 것이다. 결국 이 문제는 복사 생성자를 정의하지 않았을 때의 문제와 동일하며 생성과 동시에 초기화할 때처럼 대입을 받을 때도 깊은 복사를 하도록 해야 한다.

    뿐만 아니라 생성할 때와는 달리 대입 연산은 실행중에 언제든지 여러 번 일어날 수 있기 때문에 객체가 사용중이던 메모리를 해제하지 않으면 다시는 이 메모리에 접근할 수 없는 문제도 있다. 위 그림에서 Young이 Boy를 대입받은 후 "을지문덕"은 더 이상 읽지도 쓰지도 못하며 해제할 방법조차 없다. 동적으로 할당한 메모리는 포인터가 진입점인데 이 진입점을 잃어버린 것이다. 이런 문제들을 해결하려면 = 연산자를 오버로딩하여 대입할 때도 깊은 복사를 하도록 해야 한다. Person 클래스에 다음 멤버 연산자 함수를 추가해 보자.

     

    class Person

    {

         ....

        Person &operator =(const Person &Other) {

            if (this != &Other) {

               delete [] Name;

               Name=new char[strlen(Other.Name)+1];

               strcpy(Name,Other.Name);

               Age=Other.Age;

            }

            return *this;

        }

    };

     

    복사 생성자의 코드와 유사한 코드가 반복되는데 대입되는 Other의 Name 길이+1만큼 버퍼를 새로 할당한 후 내용을 복사했다. Age는 단순한 정수형 변수이므로 그냥 대입하기만 하면 된다. 복사 생성자와 마찬가지 방법으로 깊은 복사를 하되 대입 동작은 실행중에 여러 번 그것도 임의의 순간에 발생할 수 있기 때문에 좀 더 신경써야 할 것들이 많다.

    우선 Name 멤버를 할당하기 전에 이전에 사용하던 메모리를 먼저 해제해야 한다. 복사 생성의 경우 Name은 새로 만들어지는 중이므로 할당되어 있지 않지만 대입은 사용중인 객체에 대해 일어나는 연산이므로 Name이 이미 할당되어 있을 것이다. 다른 객체를 대입받는다는 것은 이전의 내용을 버린다는 뜻이므로 이미 할당된 메모리를 해제할 필요가 있는데 이 처리를 하지 않으면 대입할 때마다 이전에 사용하던 메모리가 누수될 것이다. 그래서 new 연산자로 Name을 할당하는 코드 앞에 delete [] Name이 필요하다. 이때 Name이 이미 할당되어 있는지는 점검할 필요가 없는데 디폴트 생성자가 1바이트를 할당하고 있으므로 Name은 항상 동적으로 할당되어 있기 때문이다.

    그리고 대입 요청을 받았을 때 대입 대상이 자기 자신이 아닌지도 꼭 점검해야 하는데 A=A 같은 대입문도 일단은 가능해야 하기 때문이다. 이 문장은 자기가 자신의 값을 대입받는 사실상의 NULL문장이지만 고의든 실수든 아니면 코드의 일관성을 위해서건 틀린 문법은 아니므로 지원하는 것이 옳다. 자기 자신이 대입될 때는 아무 것도 하지 않고 자신을 리턴하기만 하면 된다. 만약 이 조건문을 빼 버리면 delete [] Name에 의해 자신의 버퍼를 먼저 정리해 버리고 정리된 버퍼의 내용을 다시 복사하려고 들기 때문에 객체의 내용이 제대로 유지되지 않을 것이다.

    대입 후 리턴되는 값

    대입 연산자의 리턴 타입이 Person &인 이유는 A=B=C식의 연쇄적 대입이 가능해야 하기 때문이다. 대입만이 목적이라면 void형으로 선언해도 상관없겠지만 기본 타입에서 가능한 모든 연산이 객체에서도 가능해야 하므로 가급적 똑같이 동작하도록 만들어야 한다. 대입 연산자가 대입된 결과값을 리턴하기 때문에 연쇄적인 대입이 가능하다.

    이때 리턴되는 객체가 상수일 필요는 없는데 대입 후 리턴되는 객체를 바로 사용할 수도 있고 변경할 수도 있다. (Young=Boy).OutPerson(); 식으로 대입받은 좌변 객체에 대해 멤버 함수를 호출할 수 있다. 설사 이 멤버 함수가 객체의 상태를 변경하는 비상수 함수라도 말이다. 기본 타입도 대입 연산자에 의해 리턴되는 것은 좌변값인데 다음 테스트 코드를 통해 확인해 보자.

     

         int i=1,j=2;

         (i=j)=3;

         printf("%d,%d\n",i,j);

     

    i=j 대입문에 의해 i에 2가 대입되고 i 자체가 리턴된다. 이때 리턴되는 레퍼런스는 좌변값이므로  바로 3을 대입할 수 있다. 출력되는 결과는 3,2가 된다. 실제로 이런 식은 잘 쓰이지도 않고 실용성도 없지만 어쨌든 클래스는 기본 타입과 같아야 하므로 기본 타입들이 하는 짓은 다 할 수 있어야 한다.

    올바른 디폴트 생성자

    Person3 예제는 디폴트 생성자를 정의하고 있으므로 Person Young; 선언문으로 일단 객체를 먼저 만들어 놓고 다른 객체의 값을 대입받아도 상관없다. 디폴트 생성자는 받아들이는 인수가 없으므로 멤버들을 NULL, 0, FALSE로 초기화하여 쓰레기를 치우는 것이 통상적인 임무이지만 동적 할당을 하는 클래스의 경우 포인터를 NULL로 초기화해서는 안된다. 왜 그런지 다음 테스트 코드를 실행해 보자.

     

         Person() { Name=NULL;Age=0; }

     

         Person Boy;

         Person Young=Boy;

     

    디폴트 생성자가 쓰레기를 치우고 있으므로 인수없이 객체를 생성할 수 있다. 그러나 이렇게 만들어진 객체를 사용할 때 여기저기서 문제가 생긴다. 위 테스트 코드는 복사 생성자를 호출하는데 복사 생성자의 본체에서 strlen 함수로 Other.Name의 길이를 구하고 있다. 0번지는 허가되지 않은 영역이므로 이 번지를 읽기만 해도 당장 다운되어 버린다. 복사 생성자가 쓰레기만 치운 객체를 전달받아도 죽지 않으려면 예외 처리 코드가 더 작성되어야 한다.

     

         Person(const Person &Other) {

              if (Other.Name == NULL) {

                  Name=NULL;

              } else {

                  Name=new char[strlen(Other.Name)+1];

                  strcpy(Name,Other.Name);

              }

              Age=Other.Age;

         }

     

    초기식의 객체가 NULL 포인터를 가리키면 새로 선언되는 객체도 같이 NULL포인터를 가지도록 해야 한다. 복사 생성자뿐만 아니라 대입 연산자, Name을 참조하는 모든 멤버 함수에서 Name이 NULL인 경우를 일일이 예외 처리해야 하는 것이다. 이렇게 하는 것이 귀찮고 비효율적이기 때문에 디폴트 생성자가 포인터를 초기화할 때는 비록 1바이트라도 할당하여 Name이 NULL이 되지 않도록 하는 것이 좋다. 비록 1바이트에 빈 문자열밖에 들어 있지 않지만 이 메모리도 동적으로 할당한 것이므로 읽을 수 있다.

    Person3의 디폴트 생성자가 할당하는 1바이트는 자리만 지키는 플레이스 홀더(PlaceHolder) 역할을 한다. 아무 짝에도 쓸모없는 것 같지만 Name이 반드시 동적 할당된 메모리임을 보장하여 이 버퍼를 참조하는 모든 코드를 정규화시키는 효과가 있다. 모든 멤버 함수는 Name의 길이가 얼마이든지 무조건 할당되어 있다는 가정하에 Name을 안심하고 액세스할 수 있다.

    동적 할당 클래스의 조건

    이 예제에서 보다시피 초기화와 대입은 여러 모로 다르다는 것을 알 수 있다. 초기화는 객체를 위한 메모리를 할당할 때 이 공간을 어떻게 채울 것인가를 지정하며 일회적인데 비해 대입은 실행중에 같은 타입인 다른 객체의 사본을 작성하며 회수에 제한이 없다. 대입이 초기화보다는 훨씬 더 복잡하고 비용도 많이 든다. 그래서 컴파일러는 복사 생성자와 대입 연산자를 구분해서 호출하며 따라서 우리는 둘 다 만들어야 한다. class A=B; 선언문을 디폴트 생성자로 A를 먼저 만든 후 B를 대입하는 것으로 처리할 경우 속도가 훨씬 더 늦어질 것이다. 실제로 구형 컴파일러는 이런 식으로 초기화를 구현했었다.

    Time이나 Complex 클래스는 복사 생성자가 없어도 선언할 때 다른 객체로 초기화할 수 있으며 대입 연산자를 굳이 정의하지 않아도 객체끼리 안심하고 대입할 수 있다. 왜냐하면 값만을 가지는 클래스는 컴파일러가 만들어 주는 디폴트 복사 생성자, 디폴트 대입 연산자만으로도 충분히 잘 동작하기 때문이다. 이에 비해 Person 클래스는 동적으로 할당하는 메모리가 있기 때문에 여러 모로 관리해야 할 것들이 많은데 최소한 다음과 같은 함수들이 있어야 한다.

     

    함수

    설명

    생성자

    생성될  메모리를 할당한다.

    파괴자

    사용하던 메모리를 반납한다.

    복사 생성자

    초기화될  별도의 메모리를 할당한다.

    대입 연산자

    사용하던 메모리를 해제하고 대입받는 객체에 맞게 다시 할당한다.

     

    이 중 하나라도 빠지거나 생략되면 Person 클래스는 제대로 동작하지 않는다. 생성자는 초기화라는 중요한 임무를 가지므로 꼭 동적 할당을 하지 않더라도 대부분의 클래스에 필수적이다. 나머지 셋은 생성자에서 동적 할당이나 그와 유사한 효과의 동작을 할 때 꼭 필요한데 셋 중 하나가 필요하다면 나머지 둘도 마찬가지로 필요하다. 그래서 이 셋은 같이 뭉쳐서 다니는 특징이 있으며 흔히 삼총사라고 부른다.

    Person3 예제의 Person 클래스는 비로소 완벽해졌으며 선언과 동시에 초기화, 실행중 대입 등이 가능해져 기본 타입과 동등한 자격을 가지게 되었다. 그러나 상속을 하지 않을 경우에만 완벽하며 상속할 경우 파괴자가 가상 함수여야 한다는 조건이 하나 더 추가된다. 이 예에서 동적으로 할당되는 메모리란 클래스 동작에 꼭 필요한 어떤 자원의 비유에 해당한다. 예를 들어 하드웨어 장치를 열어야 하거나 네트워크 접속, DB 연결, 권한 획득 등이 필요한 클래스는 모두 비슷한 법칙이 적용된다. 아무튼 멤버를 그대로 복사해서는 똑같은 객체를 만들 수 없는 모든 클래스에는 이런 함수들이 필요하다.

    복합 대입 연산자

    이번에는 대입 연산자와 유사한 복합 대입 연산자를 오버로딩해 보자. 복합 대입 연산자는 대입과 비슷한 동작을 하기는 하지만 아예 다른 연산자이므로 필요할 경우 따로 정의해야 한다. 예를 들어 Time 클래스에 operator + 연산자를 오버로딩했다고 해서 operator += 까지 같이 정의되는 것은 아니다. 다음은 += 복합 대입 연산자의 오버로딩 예이다.

     

      : OpPlusEqual

    #include <Turboc.h>

     

    class Time

    {

    private:

         int hour,min,sec;

     

    public:

         Time() { }

         Time(int h, int m, int s) { hour=h; min=m; sec=s; }

         void OutTime() {

              printf("%d:%d:%d\n",hour,min,sec);

         }

         Time &operator +=(int s) {

              sec += s;

              min += sec/60;

              sec %= 60;

              hour += min/60;

              min %= 60;

              return *this;

         }

    };

     

    void main()

    {

         Time A(1,1,1);

     

         A+=62;

         A.OutTime();

    }

     

    + 연산자와 다른 점은 호출한 객체를 직접 변경시키기 때문에 const가 아니라는 점, 그리고 자기 자신이 피연산자이므로 임시 객체를 필요로 하지 않는다는 점 정도이다. A+=62 연산문에 의해 A가 가진 시간에 62초를 더한 값이 A에 다시 대입된다. 사용자는 + 연산이 가능하면 +=연산도 가능하다고 기대하므로 가급적이면 두 연산자를 같이 제공하는 것이 좋다. 이 경우 +=을 먼저 정의해 놓고 + 연산자는 이 함수를 호출하는 것이 효율적이다.

     

    Time operator +(int s) {

         Time R=*this;

         R+=s;

         return R;

    }

     

    +=에 정수를 더하는 연산이 먼저 정의되어 있으므로 +는 임시 객체에 += 연산한 결과를 값으로 리턴하기만 하면 된다. 뿐만 아니라 덧셈의 규칙이 변경되더라도 +=의 코드만 수정하면 되므로 코드를 유지하기도 훨씬 더 쉽다.

    복사 생성 및 대입 금지

    클래스는 일종의 타입이므로 기본 타입과 완전히 동일해질 수 있는 모든 문법이 제공된다. 선언, 초기화, 복사, 대입, 연산 등등 int가 할 수 있는 모든 동작을 다 할 수 있다. 그러나 경우에 따라서는 이런 것이 어울리지 않거나 그래서는 안되는 클래스들도 있다. 예를 들자면 하드웨어를 직접적으로 제어하거나 유일한 자원을 관리하는 객체를 들 수 있는데 하나만 가지고도 충분히 원하는 동작을 모두 할 수 있으므로 굳이 둘을 만들 필요가 없다.

    이런 예는 멀리서 찾을 것도 없이 표준 입출력 스트림 객체인 cin, cout을 보면 된다. 이 객체 한쌍으로 화면에 원하는 모든 출력을 할 수 있고 키보드로 입력을 받을 수 있는데 cin, cout이 두 개씩 있을 필요가 없지 않은가? 어떤 경우에는 동일한 타입의 객체가 두 개 있을 경우 혼선이 빚어지기도 하고 서로 간섭하여 오동작하거나 데드락에 걸리는 부작용도 있다. 이런 클래스들은 허가되지 않는 연산을 적절히 막아야 하는데 금지할 필요가 있는 대표적인 연산이 복사 생성과 대입이다.

    복사 생성과 대입을 못하는 클래스를 만드는 방법은 생각보다 쉽다. 복사 생성자와 대입 연산자를 선언하되 둘 다 private 영역에 두는 것이다. 아예 정의하지 않으면 컴파일러가 디폴트를 만드므로 반드시 private영역에 직접 선언해야 한다. 어차피 호출되지 않을 함수들이므로 본체의 내용은 작성하지 않아도 상관없다. Person 클래스는 복사, 대입이 모두 가능한 경우이긴 하지만 금지해야 한다고 가정하고 위 예제를 대상으로 이 동작들을 금지시켜 보자.

     

    class Person

    {

    private:

         char *Name;

         int Age;

         Person(const Person &Other);

         Person &operator =(const Person &Other);

         ....

     

    이렇게 해 놓으면 Person Young=Boy; 같은 선언문이나 Girl=Boy; 같은 대입문이 실행될 때 컴파일러가 복사 생성자나 대입 연산자를 호출하려고 할 것이다. 객체를 선언하는 곳은 객체의 외부이므로 private 멤버를 호출할 수 없으며 컴파일 중에 이 동작이 허가되지 않는다는 것을 알 수 있다. 실행중에 문제를 일으키는 것보다 컴파일할 때 이 동작은 금지되었음을 확실히 알리는 것이 바람직하다. 좀 더 적극적으로 에러 내용을 상세하게 알리고 싶을 때는 이 둘을 public 영역에 두되 assert문을 작성해 놓는 방법을 쓸 수 있다.

    두 함수를 private 영역에 둘 때 본체 내용은 아예 작성하지 않는 것이 좋다. 왜냐하면 외부에서 이 함수를 호출하는 것은 컴파일러가 컴파일 중에 막아 주지만 클래스 내부의 멤버 함수나 프렌드 함수에서는 여전히 이 함수를 호출할 수 있기 때문이다. 함수를 선언만 해 놓고 본체를 정의하지 않더라도 이 함수가 호출되기 전에는 링커가 본체를 찾지 않으므로 아무 이상이 없다. 만약 정의되지도 않는 함수를 호출하려고 하면 컴파일은 무사히 되지만 링크할 때 에러로 처리되므로 이 동작이 불가능하다는 것을 알 수 있다. 외부에서 불가능한 동작을 시도하면 컴파일러가 막아주고 내부에서 엉뚱한 짓을 하려면 링커가 막아준다. C++의 객체는 이런 식으로 실수든 고의든 허가되지 않는 위험한 연산을 스스로 방어하도록 작성되어야 한다.

    복사 생성자, 대입 연산자 작성 규칙은 나름대로 복잡해서 이해는 되더라도 실무에서 직접 작성하기는 쉽지가 않다. 개념적인 이해는 꼭 해 두고 실제 코드를 작성할 때는 Person3 예제에서 코드를 복사한 후 원하는 부분만 수정하는 것이 편리하다. Person3 예제의 복사 생성자, 대입 연산자는 모든 상황에 대해 잘 작동하도록 만든 모범 답안이다.

     

    반응형
    반응형


    <헤더파일>

    ...

    class Nice

    {

    private:

       static int totalObject;

    // static int totalObject = 0;  (안 돼!)

    public:

        Nice();

        ~Nice();

        void ImAMethod();

    }

    ...

     

    <구현파일>

    ...

    int Nice::totalObject = 0; // static 멤버 변수의 초기화

     

    Nice::Nice()

    {

        // totalObject = 0;  (안 돼!)

    ...







    // 정적 변수

     

    Num은 클래스의 멤버이면서 클래스로부터 생성되는 모든 객체가 공유하는 변수여야 한다. 이것이 바로 정적 멤버 변수의 정의이며 이 문제를 풀 수 있는 유일한 해결책이다.

     

    //h. 파일

    class Count

    {

    private:

         int Value;

        static int Num;

    public:

         Count() { Num++; }

         ~Count() { Num--; }

         void OutNum() {

              printf("현재 객체 개수 = %d\n",Num);

         }

    };

     

    //cpp 파일

    int Count::Num=0;

    .....

     

    헤더 파일의 클래스 선언부에 정적 멤버 변수에 대한 내부 선언이 있고 구현 파일에 정적 멤버 변수에 대한 외부 정의 및 초기값 지정문이 온다. 이게 관례이다. 만약 헤더 파일에 외부 정의를 둔다면 헤더 파일이 두 번 인클루드될 때 이중 정의되므로 에러로 처리될 것이다.

     

    이렇게 선언하면 Num은 Count 클래스에 소속되며 외부 정의에서 지정한 초기값으로 딱 한 번만 초기화된다. Count형의 객체 A,B,C가 생성되었다면 각 객체는 자신의 고유한 멤버 Value를 개별적으로 가지며 정적 멤버 변수 Num은 모든 객체가 공유한다. 그래서 각 객체의 생성자에서 증가, 파괴자에서 감소하는 대상은 공유된 변수 Num이며 한 변수값을 모든 객체가 같이 관리하므로 Num은 생성된 객체의 정확한 개수를 유지할 수 있다.

     

    정적 멤버 변수는 객체와 논리적으로 연결되어 있지만 객체 내부에 있지는 않다. 정적 멤버 변수를 소유하는 주체는 객체가 아니라 클래스이다. 그래서 객체 크기에 정적 멤버의 크기는 포함되지 않으며 sizeof(C) = sizeof(Count)는 객체의 고유 멤버 Value의 크기값인 4가 된다.

     

    단 외부에서 정적 멤버 변수를 정의할 때는 예외적으로 액세스 속성에 상관없이 초기값을 줄 수 있다. 초기식은 대입과는 다르므로 액세스 속성의 영향을 받지 않는다. 정적 멤버 변수를 외부에서도 참조할 수 있도록 공개하려면 클래스 선언부의 public영역에 선언하면 된다. 외부에서 정적 멤버를 액세스할 때는 반드시 소속을 밝혀야 하는데 두 가지 방법으로 소속을 밝힐 수 있다.

     

    Count C;

    Count::Num=3;            // 클래스 소속

    C.Num++;                        // 객체 소속

     

    원한다면 C.Num처럼 객체.멤버 식으로 객체의 소속인 것처럼 표현할 수도 있다. 이 때 C객체의 이름은 별다른 의미는 없으며 C객체가 소속된 클래스를 밝히는 역할만 한다. 정적 멤버에 대해 객체의 소속으로 액세스하는 것은 일단 가능하지만 일반적이지 않으며 바람직하지도 않다. 정적 멤버는 논리적으로 클래스 소속이므로 가급적이면 클래스::멤버 식으로 액세스하는 것이 합당하다.

     

    //정적 함수

     

    정적 멤버 함수의 개념도 정적 멤버 변수의 경우와 비슷하다. 객체와 직접적으로 연관된다기보다는 클래스와 연관되며 생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다. 일반 멤버 함수는 객체를 먼저 생성한 후 obj.func() 형식으로 호출한 객체에 대해 어떤 작업을 한다. 이에 비해 정적 멤버 함수는 Class::func() 형식으로 호출하며 클래스 전체에 대한 전반적인 작업을 한다. 주로 정적 멤버 변수를 조작하거나 이 클래스에 속한 모든 객체를 위한 어떤 처리를 한다.

    정적 멤버 함수를 선언하는 방법은 정적 멤버 변수와 동일하다. 클래스 선언부의 함수 원형앞에 static이라는 키워드만 붙이면 된다. 정적 멤버 함수의 본체는 클래스 선언부에 인라인 형식으로 작성할 수도 있고 아니면 외부에 따로 정의할 수도 있는데 외부에 작성할 때 static 키워드는 생략한다.

     

    #include <Turboc.h>

     

    class Count

    {

    private:

         int Value;

         static int Num;

     

    public:

         Count() { Num++; }

         ~Count() { Num--; }

         static void InitNum() {

              Num=0;

         }

         static void OutNum() {

              printf("현재 객체 개수 = %d\n",Num);

         }

    };

    int Count::Num;

     

    void main()

    {

         Count::InitNum();

         Count::OutNum();

         Count C,*pC;

         C.OutNum();

         pC=new Count;

         pC->OutNum();

         delete pC;

         pC->OutNum();

         printf("%d",sizeof(C));

    }

     

    InitNum은 정적 멤버 함수이므로 Count 클래스의 객체가 전혀 없는 상태에서도 호출될 수 있다. main에서 Count::InitNum()을 먼저 호출하여 Num을 0으로 초기화하였다. 변수를 초기화하는 별도의 함수를 만들었으므로 원한다면 실행중에 언제든지 이 함수를 호출하여 Num을 0으로 리셋할 수도 있다.

     

    C 객체를 생성한 후 C.OutNum()을 호출하면 1이 출력되고 pC객체를 동적 생성한 후 pC->OutNum()을 호출하면 2가 출력된다. 이 두 호출의 예처럼 정적 멤버 함수를 객체의 이름으로 호출할 수도 있지만 이때 객체의 이름은 아무런 의미가 없으며 컴파일러는 객체가 소속된 클래스의 정보만 사용한다. C.OutNum(), pC->OutNum(); 이라는 표현을 허용할 뿐이지 이 호출은 실제로 Count::OutNum()으로 컴파일된다는 얘기다.

     

    그래서 delete pC;로 pC 객체를 해제한 후에도 pC->OutNum()이라는 호출이 정상적으로 동작한다. 컴파일러는 pC에 실제로 객체가 생성되어 있는지를 볼 필요도 없으며 pC가 Count *형이라는 것만 참조할 뿐이다. 심지어 main의 4번째 줄에 pC가 할당되기도 전인 C.OutNum()을 pC->OutNum()으로 바꿔도 잘 동작한다. 이걸 보면 컴파일러가 포인터의 타입만으로 호출할 함수를 결정한다는 것을 알 수 있다.

     

    주의)

    정적 멤버 함수는 특정한 객체에 의해 호출되는 것이 아니므로 숨겨진 인수 this가 전달되지 않는다. 클래스에 대한 작업을 하기 때문에 어떤 객체가 자신을 호출했는지 구분할 필요가 없으며 따라서 호출한 객체에 대한 정보도 필요가 없는 것이다. 그래서 정적 멤버 함수는 정적 멤버만 액세스할 수 있으며 일반 멤버(비정적 멤버)는 참조할 수 없다. 왜냐하면 일반 멤버 앞에는 암시적으로 this->가 붙는데 정적 멤버 함수는 this를 전달받지 않기 때문이다. 정적 멤버 함수인 InitNum에서 비정적 멤버인 Value를 참조하는 것은 불가능하다.

     

    static void InitNum() {

         Num=0;

         Value=5;

    }

     

    이 코드를 컴파일하면 정적 멤버 함수에서 Value를 불법으로 참조했다는 에러 메시지가 출력된다. InitNum의 본체에서 Value를 칭하면 누구의 Value인지를 판단할 수 없다. 또한 정적 멤버 함수는 생성된 객체가 전혀 없어도 호출할 수 있는데 이때 Value는 아예 존재하지도 않는다. 비정적 멤버 함수도 호출할 수 없으며 오로지 정적 멤버만 참조할 수 있다.

    [출처] Static 멤버 변수의 초기화|작성자 

    반응형
    반응형

    _MSC_VER 은 Visual C++ 의 컴파일러 버전을 나타내는 매크로 상수임

    1000 : Visual C++ 4.x
    1100 : Visual C++ 5
    1200 : Visual C++ 6
    1300 : Visual C++ .NET
    1310 : Visual C++ .NET 2003
    1400 : Visual C++ .NET 2005
    1500 : Visual C++ .NET 2008

    /* stdafx.h */

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    반응형
    반응형

    상수란 말씀하신 것처럼 값을 가지는 변수이나 그 값을 바꿀 수 없는 변수입니다.

    즉 한번 메모리에 변수를 지정하고 그 변수에 값을 초기화하고 난 그 이후에는 값을 바꿀 수 없는

    변수를 상수라고 일컫습니다. 다음과 같은 경우, PI가 상수가 되겠죠.

    const float PI=3.14f; // C

    public static final float PI=3.14f; // Java

    반면 리터럴은 이러한 변수 및 상수에 저장되는 값 자체를 일컫습니다.

    정수 리터럴, 실수 리터럴, 문자열 리터럴 이런 것들이 프로그래밍 언어의 한 요소로서

    리터럴이라고 불리는 겁니다.

    정수 리터럴 ---> 10, 1, 1000 등등
    실수 리터럴 ---> 10.1, 10e3 등등
    문자열 리터럴 ---> "System" "Exit" 등등

    쉽게 얘기하면 변수나 상수는 메모리에 할당된 공간(내지는 그 이름, 사실 이보다는 바인딩이라고 해서

    좀더 복잡한 얘기를 해야하지만 질문 내용과 무관하므로 생략하도록 하겠습니다.)이라면 리터럴은

    이 공간에 저장되는 값이라고 할 수 있습니다.

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

    바로 값이 되는 상수를 말하는 거죠.

    보통 프로그래밍을 할때 변수라는 걸 사용하는데.
    이 변수라는건 여러가지가 결합된 요소로 볼 수 있습니다.
    변수명, 주소, 공간, 형식이란 걸로 결합이 됩니다.
    변수명이란 식별자로 해당 메모리 주소를 참조해서 지정된 형식으로 데이타를 읽거나 쓸 수 있는것이죠.
    이에 반해서 상수란건 동일하게 구성이 되지만 쓰기연산이 금지된 겁니다.

    읽기만 가능하고 쓰기가 불가능해서 항상 같은 값을 유지하자는 거죠.

    리터럴은 다시 변수명이란게 사라진겁니다.

    이름같은 식별자를 통하지 않고. 바로 값이 되는 상수들을 특별히 리터럴이라고 합니다.

    숫자 상수 10이나 문자 상수 'A' 문자열 상수 'ABC' 이런것들이 리터럴이죠.

    C에서는 특별히 '\xx'같은 형태를 이스케이트 캐릭터, 확장열이라고 따로 문자상수를 정의해서 사용하기도 합니다

    반응형
    반응형

    http://www.winapi.co.kr/:

    아래 3개는 모두 동일

    const int Day=24;

    int const Day=24;

    const Day=24; //int 를 안쓰면 default 로 int 가 선언되어진다

    int 의 경우 const 순서 상관없이 변수값이 const 가 되지만 포인터의 경우에는 차이가 있다

    15-1-나.포인터와 const

    const를 포인터와 함께 사용하면 효과가 조금 달라진다. 다음 예제는 포인터와 const의 관계를 실험해 보기 위해 작성했는데 컴파일해 보면 몇 군데서 에러가 발생할 것이다.

    : ConstPointer

    #include <Turboc.h>

    void main()

    {

    int ar[5]={1,2,3,4,5};

    int *pi1=&ar[0];

    pi1++; // 포인터가 다른 대상체를 가리킬 수 있다.

    *pi1=0; // 대상체를 변경할 수 있다.

    const int *pi2=&ar[0];

    pi2++; // 포인터가 다른 대상체를 가리킬 수 있다.

    *pi2=0; // 에러 : 대상체가 상수이므로 변경할 수 없다.

    int * const pi3=&ar[0];

    pi3++; // 에러 : 포인터가 다른 대상체를 가리킬 수 없다.

    *pi3=0; // 대상체는 변경할 수 있다.

    const int * const pi4=&ar[0];

    pi4++; // 에러 : 포인터가 다른 대상체를 가리킬 수 없다.

    *pi4=0; // 에러 : 대상체가 상수이므로 변경할 수 없다.

    }

    반응형
    반응형

    http://www.gpgstudy.../viewtopic.php?t=566
    http://www.gpgstudy.../viewtopic.php?t=627

    http://www.gpgstudy.../viewtopic.php?t=860

    * get - 가장 흔히 쓰이며 의미하는 범위도 가장 넓다. GetDC(), getInstance(), getPosition() 등등. get은 아래에 나열된 다른 모든 돌려주는 동사들을 포괄한다. 그러나, get의 용도를 최대한 한정시킨다면 주로 ‘이미 계산되어 있는 객체의 속성 또는 시스템의 특정 상태를 돌려주며 상태, 시스템을 변경하거나 어떠한 복잡한 처리를 하지 않는’ 함수에 쓰는 것이 좋다고 본다.


    * retrieve - get에 비해 이것은 반환할 결과를 찾기 위한 처리(주로 검색)가 일어남을 암시한다. 필요하다면 이 동사는 search 같은 실제 수행 동사와 get으로 나눌 수 있다.

    예:

    aCheapestApple = anAppleBox.retrieveApple(CHEAPEST);

    =>

    anAppleBox.search(CHEAPEST);
    aCheapestApple = anAppleBox.getSearchResult();


    * acquire - 제한된 자원을 독점적으로 확보한다는 뜻이 있다. 예를 들어 주어진 윈도우 또는 시스템 전체에 DC가 하나이며 GetDC()로 얻은 DC를 ReleaseDC()로 풀기 전까지는 혼자만 사용해야 하는 상황이라고 하자. 그런 경우라면 GetDC() 보다는 AcquireDC()가 더 적합하다. 반환값은 확보한 객체 자체나 포인터, 핸들 등이다. 반대말은 release나 unacquire(get에 대해 release를 사용한다면 acquire에 대해서는 unacquire를 사용해서 구분해 주는 것이 좋을 것임).

    예:

    AudioManager AudioMgr = new DxAudioManagerImpl();
    ...
    AudioDevice* anAudioDevice = AudioMgr.acquireDevice();
    ...
    AudioMgr.unacquireDevice(anAudioDevice);

    * fetch - 돌려준 값을 담고 있는 컨테이너의 내부 상태가 변함을 의미한다. 주어진 컨테이너 안의 어떤 값을 가져오면 현재 값의 위치를 가리키는 포인터가 다음 값으로 이동하게 되는 경우에 fetch가 쓰인다. 데이터베이스 API에서 흔히 볼 수 있다. 스택의 pop과도 비슷하다. 또한 파일 시스템의 많은 함수들이 이러한 fetch 식으로 작동한다. 이는 moveNext와 get으로 분리될 수 있다.

    예:

    UnitList.reset();
    pUnit1 = UnitList.fetch();
    pUnit2 = UnitList.fetch();
    =>
    UnitList.moveFirst();
    pUnit1 = UnitList.getUnit();
    UnitList.moveNext();
    pUnit2 = UnitList.getUnit();

    * be 동사와 조동사 - 이들은 어떠한 상태 또는 여부를 뜻하는 부울 값을 돌려줄 때 주로 쓰인다.

    예:
    while ( isDone ) {
    if ( currentUnit.hasWeapon() ) ....
    ....

    }

    2. 객체의 상태를 변경하는 함수에 주로 쓰이는 동사들

    객체 또는 현재 범위의 어떤 값을 변경하는 함수들에 가장 흔히 쓰이는 동사는 set이다. 이런 동사들은 반환값이 없거나 또는 함수의 성공 여부를 알려주는 반환값을 가진다.

    * set - 가장 흔하게 쓰인다. 설명이 필요없겠지만 굳이 부연 설명을 하자면, get의 반대물이라는 차원에서, 상태의 변경에 복잡한 처리가 일어나지 않는 비교적 단순한 코드로 된 함수에 적합하다.

    그래픽 시스템

    display - 실제의 시각적 변화를 일으킨다는 의미가 있다(버퍼 스왑 등 실제 비디오 메모리 갱신의 차원에서). 또는 아래의 동사들을 포괄하는 고차원 함수에도 쓰인다(특정 그래픽 API에 국한되지 않는...)

    render - display와 비슷하나 내부 버퍼 메모리의 갱신만 의미할 수도 있다. 즉 render 후 상황에 따라 display가 수행되는 등. render부터는 특정 그래픽 API에 국한된 함수에 가까와진다. 목적어를 가질 수 있다(renderScene, renderBackground 등)

    draw - 개별 데이터나 개별 개체를 그린다는 의미로 많이 쓰인다. Model.draw()나 drawText() 등. render 안에서 일어나는 경우가 많다.

    put - 주로 2D에서 점이나 스프라이트를 ‘찍는다’는 의미로 쓰인다. 실제의 시각적 변화보다는 내부 메모리에서의 변화일 가능성이 크다. 3D에서는 별로 쓰이지 않음. 목적어를 가지는 경우가 많다(putPixel, putSprite 등)

    flush - 그래픽이나 장면 데이터를 스트림으로 간주할 때 쓰인다. 지금까지 명령 버퍼 또는 프레임 버퍼에 쌓여 있는 데이터를 실제 장치로 밀어 내보낸다는 의미를 가진다. 






    올리기동사에 추가해서 올려짐: 2002-09-06 09:49
    인용과 함께 답변 이 게시물을 del.icio.us에 추가

    가끔가다가..
    명사나 형용사로 함수명을 지어야 할때가 있습니다.
    무엇무엇과 같은것을...해라..
    음 단순한 동사로 했다가는 의미가 애매해지는 경우를 말하는것이죠..
    그럴때는 저의 경우는 Do를 앞에 써줍니다.
    행동을 해줘야 한다는걸 강조해 주는거죠..
    벽깨는 동작을 해라.. 라고 할때 BreakTheWall을 하면..
    벽을깨는거지만.
    DoMotionBreakingTheWall 이라고 하면 벽깨는 동작을 해라 라고 하게 됩니다.

    혹은.. bool값을 반환하는 단순상태의 경우는 Is나 Does를 앞에 써줍니다.
    이런 펑션을 If문 안에 써주면 주석을 쓰지않아도 의미가 명료해 집니다.
    GetBound?? 이런건 이름자체가 잘못된거지만.. bool을 이렇게 쓰면 웃길때가 있다고 치고
    IsInsideBound 는 명료하게 참거짓을 쿼리하는거라는 인식을 하게 하죠..
    음... 이건 제 습관이라서... 많은 공격 바랍니다.







    류광



    가입: 2001년 7월 25일
    올린 글: 3452
    소속: GPGstudy


    타 사이트 ID(?):
    docbook.kr::류광
     
    올려짐: 2002-09-06 17:18
    인용과 함께 답변 이 게시물을 del.icio.us에 추가


    아 좋은 지적입니다.. 동사들을 이야기하면서 뭔가 빠진 듯한 느낌이 들었는데 Do 류의 동사들이었네요. do, excute, run, process 등... 이들의 미묘한 차이에 대해서도 조만간 정리해야 겠습니다...

    한 가지.. Do의 경우 또 다른 용법은 Do 다음에 실제 동사가 오는 형태입니다. 이런 것은 처리의 위임이나 전처리 후 실제 처리 같은 경우에 많이 보이더군요. 예를 들어

    코드:
    // 다른 어떤 곳에서
    obj->OnDraw(); // 또는 그냥 obj->Draw()
    ...
    void SomeObject::OnDraw() // 역시 또는 그냥 Draw()
    {
    ...// 전처리
    // 실제 처리
    DoDraw();
    }


    이런 예에서 Do는 '이제 진짜로 하자~'라는 강조로 쓰이는 것 같습니다..






    반응형
    반응형


    보통은 자식 클래스가 자신의 부모 클래스의 생성자만 호출 가능하고, 할아버지 클래스의 생성자를

    호출 할 수 없지만, 가상 기반 클래스 에서는 예외가 있습니다...

    즉, 손자 클래스가 자신의 부모보다도 상위인 할아버지 클래스의 생성자를 호출 할 수

    있다는 점이죠...

    또한, 이 경우에 있어서는 중간 클래스를 의미하는 부모 클래스(class XY, class XZ) 에서는 비록...

    최상위 클래스의 생성자를 호출하는 코드인 CPointX(a) 가 XY, XZ 클래스의 생성자 초기화 리스트에

    있지만, 손자 클래스인 XYZ 클래스의 생성자 초기화 리스트 부분에서...단 "한 번만" 호출된다는

    점 입니다...

    브레이크 포인트를 걸어서 코드 흐름을 추적해 보면 알겠지만, 위 코드에선 CPointX(a) 부분이 한 번 밖에

    호출되지 않고, 이를 담당하는 것은 손자 클래스인 XYZ 클래스 입니다...

    정리하면,,,가상 기반 상속에서는 손자 클래스에서 할아버지 클래스의 생성자를 직접 호출한다는

    점이고, 중간 클래스인 부모 클래스에서는 비록, 그 상위 클래스의 생성자를 호출하는 코드가

    있다고 하더라도 호출되지 않는다는 점 입니다...

    또한...가상 상속을 사용하는 이유는 할아버지 클래스인 X 클래스를 상속받는 XY, XZ 클래스를 XYZ 라는

    클래스가 상속받기 때문에...XYZ 입장에서는 X 클래스의 멤버가 어디에서 온 멤버인지 모호하기 때문에

    이 가상상속을 사용하여, 모호함을 없애는 것입니다...

    X X

    | |

    XY XZ

    XY, XZ 를 상속받는 XYZ 클래스는 X의 멤버를 참조할 때 XY 에서 온 X인지, XZ 에서 온 X인지

    모호함으로 인해 판단하기 어렵다는 점입니다...

    그래서...이를 해결하고자 가상상속을 사용하는 것이고, 손자 클래스가 할아버지 클래스의 생성을

    직접 담당하는 것입니다...

    코드를 다음과 같이 수정 합니다...

    class CPointXY : virtual public CPointX{

    .....

    // CPointX(a) 생성자는 여기에서 호출되지 않는다.

    CPointXY(int a, int b) : CPointX(a), y(b)

    };

    class CPointXZ : virtual public CPointX{

    .....

    // CPointX(a) 생성자는 여기에서 호출되지 않는다.

    CPointXZ(int a, int c) : CPointX(a),z(c)

    };

    class CPointXYZ : public CPointXY, public CPointXZ{

    ...

    // 할아버지 클래스의 생성자를 직접 호출한다

    // 만약, CPointX(a) 를 명시적으로 호출하는 코드가 없다면 할아버지 클래스인

    // CPointX 클래스에 인자를 가지지 않는 디폴트 생성자가 있어야 합니다...

    CPointXYZ(int a, int b, int c) : CPointX(a), CPointXY(a,b), CPointXZ(a,c), xyz(0)

    ...

    };





    출처 : 

    http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=76771879&qb=7KaJLCDshpDsnpAg7YG0656Y7Iqk6rCAIOyekOyLoOydmCDrtoDrqqjrs7Tri6Trj4Qg7IOB7JyE7J24IO2VoOyVhOuyhOyngCDtgbTrnpjsiqTsnZgg7IOd7ISx7J6Q66W8IO2YuOy2nCDtlaAg7IiYIOyeiOuLpOuKlCDsoJDsnbTso6AuLi4=&enc=utf8&section=kin&rank=1&search_sort=0&spq=0&pid=R5vBMF5Y7uKsscvCnRhssc--453720&sid=UJDjOsm@kFAAAHDkOiE

    반응형
    반응형

    출처 : http://kin.naver.com/browse/db_detail.php?d1id=1&dir_id=10104&docid=285334

    void 형변환에 관하여 %p


    #include

    int multi[2][4];

    int main()
    {
    printf("\nmulti = %p",(void *)multi); /* ----------- 1
    printf("\nmulti[0] = %p",(void *)&multi[0]); /* ---------- 2
    printf("\nmulti[0] = %p",(void *)multi[0]); /* ---------- 3
    printf("\n&multi[0][0] = %p\n",(void *)&multi[0][0]); /* ---------- 4
    return 0;
    }

    요기에 보면.. void로 형변환 해주자나여.. 이게 맞는 거는 알겠는데, 왜
    void 형변환을 꼭 해줘야 하는지..

    형변환 안하고, 그냥 %u 쓰구 컴파일하면,

    warning: unsigned int format, pointer arg (arg 2) 이런 경고 메시지가
    뜨더라구여..

    void 포인터가 뭔지는 아는데요..

    가령 1번 줄에서, multi 를 (void *)multi 로 형변환해주면, multi가 가지고
    있는 bit(표현이 맞나?)에

    어떤 변화가 생기는건지 궁금하구, 또 왜 그렇게 형변환을 해줘야 하는지요..

    그것은 %p라는 것은 void* 형을 다룹니다.



    [질문자 평]

    사실, 제가 VC++로 컴파일로 하니.. 에러는 발견되지가 않더군요..

    아마 컴파일러랑 라이브러리의 영향을 받는 warning 증상 같습니다...^^;

    그런데, 조금 좋군요..ㅋㅋㅋ;

    %p는 (void*)를 취급하며, 그것의 주소를 화면의 출력을 하는 구조입니다.

    %u는 그것을 unsigned int 정수형으로 출력을 하라는 것이구요..

    실제 구현상에는 돌아갈때는 아무런 변화가 있을수도 있고, 없을 수도 있습니다.

    int 형은 32 bit인데 char형은 8 bit에다 넣을 려면은 어떻게 해야할까요?

    이중에서 해당하는 하위 8 bit를 넣어야 겠죠.. 이런식의 변화는 있을수 있습니다.

    또 실수형을 정수형으로 넣을경우는? 거기의 맞는 특별한 연산을 해줘야 겠죠?

    물론, (void*)는 영향이 없고, 단순히 시작주소만을 가집니다.. 즉, 모형를 모른다는 것입니다.

    모형의 요소 - 할당바이트, 데이터 저장 형식(실수, 정수, 문자..)

    다시 정리하면은 %p는 (void*)로 받도록 약속을 하였습니다. 그래서 형변환을 해줘야 warning을 뜨지않고, 프로그래머가 의도적으로 한것이구나를 컴파일러가 알아서 거기의 맞는 동작을 하겠죠? 그래서 형변환을 하였습니다.^^;


    그래서 어떠한 포인터를 삭제할때

    struct del{

    void operator()(void * ptr){

    delete ptr;

    }

    };

    이 가능하다

    반응형
    반응형

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <iostream>
    #include <Windows.h>
    #include <math.h>
    using namespace std;


    class c{
    public :
    c(){

    cout<<"c 생성자"<<endl;
    }

    ~c(){

    cout<<"c 소멸자"<<endl;
    }
    };


    class a : public c{
    public:

    a(){

    cout<<"a 생성자"<<endl;
    }
    ~a(){
    cout<<"a 소멸자"<<endl;
    }
    };

    class b{
    public:
    a ai;
    b(){
    cout<<"b 생성자"<<endl;
    }
    ~b(){
    cout<<"b 소멸자"<<endl;
    }
    };

    int main(void)
    {
    /*
    a ai; 의 경우
    생성자는 부모부터 실행되지만
    소멸자는 virtual 처럼 자식 부터 실행된다.
    */

    /*
    b bi;
    b 안에 클래스 멤버가 포함되어 있으면 포함된 생성자가 먼저 호출 된다
    */
    return 0 ;
    }

    반응형

    + Recent posts