여러 소스를 보았지만 가장 완벽한 소스는 아래와 같습니다.


출처 : http://borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tip&no=880 + 알파

#include <stdio.h>

#include <math.h>


#define round(x)        ((x) >= 0 ? (long)((x) + 0.5) : (long)((x) - 0.5))


int getInternalAngle(int ox, int oy, int px, int py, int qx, int qy)

{

    float oq = sqrt(pow(pointO.x - pointQ.x, 2) + pow(pointO.y - pointQ.y, 2));

    float op = sqrt(pow(pointO.x - pointP.x, 2) + pow(pointO.y - pointP.y, 2));

    float pq = sqrt(pow(pointP.x - pointQ.x, 2) + pow(pointP.y - pointQ.y, 2));


    if (op < 1 || pq < 1) {

        return 0;

    }


    float temp = (pow(op,2) + pow(pq,2) - pow(oq,2)) / (2*op*pq);


    float angle = acos(temp);

    angle = angle * (180 / M_PI);


    if (pointO.x != pointP.x) {

        float a = (pointP.y - pointO.y) / (pointP.x - pointO.x);

        float b = (pointP.x * pointO.y - pointO.x*pointP.y) / (pointP.x - pointO.x);

        float y = a * pointQ.x + b;

        if (a > 0) {

            if (pointQ.y > y) {

                angle = 360.0f - angle;

            }

        } else {

            if (pointQ.y < y) {

                angle = 360.0f - angle;

            }

        }

    } else {

        if (pointQ.x < pointP.x) {

            angle = 360.0f - angle;

        }

    }


    return round(angle);

}


int main()

{

    int ax,ay,bx,by,cx,cy;

    printf("점 A의 좌표를 입력하세요 : \n");

    scanf("%d%d",&ax,&ay);

    printf("점 B의 좌표를 입력하세요 : \n");

    scanf("%d%d",&bx,&by);

    printf("점 C의 좌표를 입력하세요 : \n");

    scanf("%d%d",&cx,&cy);

    printf("ABC의 각도는 %d도 입니다.\n",getInternalAngle(ax,ay,bx,by,cx,cy));


    return 0;

}



별거 없는데... 생각하는데 시간이 너무 들었네요.. orz

게임서버와 웹, 빌링쪽에 연동하려할때 상대쪽에선 자바나 펄위주라 md5에 대한 암호 방식을 선호하던군요.

문제는 이걸 게임서버 (C++)에서 이용하려 할때 마땅한 정보가 없더군요.. ㅠㅠ

그러더중 msdn을 훝어보던중 적당한 예제소스를 발견했습니다.

 

참고 : MD5 ? http://ko.wikipedia.org/wiki/MD5

그리고 전체 알고리즘 : http://www.zedwood.com/article/121/cpp-md5-function

출처 : http://msdn.microsoft.com/ko-kr/library/windows/desktop/aa382380(v=vs.85).aspx

 

.. 좀더 핵심만, MD5 압축데이터를 얻고 오고 싶을때는...

#include <iostream>

#include <windows.h>

#include <wincrypt.h>

 

/*!==========================================================================================

 * @brief     MD5 해쉬 데이터를 얻어오기

 * @return    bool                                           성공/실패

 * @param     char * buffer                                  입력 버퍼

 * @param     DWORD dwBufferSize                             입력 문자열 사이즈 "\0"제외

 * @param     BYTE * byteFinalHash                           결과 해쉬 버퍼

 * @param     DWORD * dwFinalHashSize                        결과 해쉬 버퍼의 크기

 * @author    Kim Dong-Sung (galaxy_wiz@naver.com)

 * @date      2012-11-30

 ==========================================================================================*/

bool GetMD5Hash(char *buffer, DWORD dwBufferSize, BYTE *byteFinalHash, DWORD *dwFinalHashSize)

{

        DWORD          dwStatus       = 0;

        BOOL           bResult        = FALSE;

        HCRYPTPROV     hProv          = 0;

        HCRYPTHASH     hHash          = 0;

        DWORD          cbHashSize     = 0;

 

        try{

               //프로바이더 압축핸들 얻어오기

               if(0 == CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))

                       throw "CryptAcquireContext";

 

               //압축알고리즘 선택, 여기서는 MD5

               if(0 == CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))

                       throw "CryptCreateHash";

 

               //입력된 버퍼를 기반으로 헤쉬 데이터 생성

               if(0 == CryptHashData(hHash, (const BYTE*) buffer, dwBufferSize, 0))

                       throw "CryptHashData";

 

               // 해쉬 사이즈얻어오기

               DWORD dwCount = sizeof(DWORD);

               if(0 == CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&cbHashSize, &dwCount, 0))

                       throw "CryptGetHashParam";

 

               //출력버퍼에 헤쉬데이터를 충분히 담을 있는지 확인

               if(*dwFinalHashSize < cbHashSize){

                       printf("\n Output buffer %d, is not sufficient, Required size = %d", *dwFinalHashSize, cbHashSize);

                       bResult = TRUE;

                       throw "Memory problem";

               }

 

               //이제 해쉬를 계산해서 얻기

               if(CryptGetHashParam(hHash, HP_HASHVAL, byteFinalHash, dwFinalHashSize, 0)){

                       bResult = TRUE;

               }

               else{

                       throw "CryptGetHashParam";

               }

              

        }

        catch(char *errMsg){

               printf("%s failed, Error=0x%.8x\n", errMsg, GetLastError());

        }

 

        if(hHash)      CryptDestroyHash(hHash);

        if(hProv)      CryptReleaseContext(hProv, 0);

 

        return bResult;

}

 

int main(int argc,char *argv[])

{ 

        BYTE byteHashbuffer[256];

        DWORD dwFinalHashSize= 256;

        CHAR str[] = "password";

 

        //_count_of(str)-1을 해서 맨 뒤의 '\0'을 뺀 버퍼 사이즈를 넣음.배열검사 생략했음

        GetMD5Hash(str, _countof(str)-1, byteHashbuffer, &dwFinalHashSize);

 

        printf("%s\n", byteHashbuffer);

        return 0;

} 

 

대충 감을 잡으실수 있을꺼 같습니다.

위의 예제 단어 "password"의 경우 메모리 디버거에서는

 

 

 

같은 결과 값이 나왔습니다.


그다지 책에는 설명이 안되어있는 virtual 키워드...
솔직히 일본에서 일할때 그쪽에선 특별히 뭔가 상속하면서 클래스를 만드는걸 꺼려서해 그런지...
virtual 키워드를 왠만하면 쓰지 않았기 때문에 프로그래밍으로 밥먹고 사는 저도 가물가물한 놈입니다.

잡설은 그만하고
virtual 과 override 차이만 얘기 합니다.
만약

/************************************************************************/
/*상위클래스                                                            */
/************************************************************************/
class upClass
{
public:
  void print(){
  cout<<"up class"<<endl;
 }
};
/************************************************************************/
/* 상속 A                                                               */
/************************************************************************/
class aClass : public  upClass
{
public:
 void print(){
  cout << "aClass"<<endl;
 }
};
/************************************************************************/
/* 상속 B                                                               */
/************************************************************************/
class bClass : public upClass
{
public:
 void print(){
  cout <<"bClass"<< endl;
 }
};

이렇게 해놓았다면
AClass 클래스를 만들어 print()를 호출할때 상속 받은 UpClass의 print함수가 아닌
AClass의 print 함수를 불러오는건 프로그래머로썬 상식입니다.

그리고 이걸 AClass함수가 원래 UpClass를 상속해 존재한 같은 이름의 print 함수를 덮어 씌었기 때문에 override라고 합니다.

보통 프로그래밍 할땐 문제가 없지만
만약 경우에 따라 A,B클래스를 따로 부르고 싶을땐 문제가 발생합니다.

예를들어
//메인 함수
void main()
{
 upClass *ptr;                       //aClass를 부를지, bClass를 부를지 모르니까 둘의 공통 분모 클래스의 포인터 생성
 int num = 100;                     //적당한 조건문
//조건에 따라서
 if(num < 10){
  ptr = new aClass();           //aClass를 생성
 }else{
  ptr = new bClass();           //bClass를 생성
 }
 ptr->print();                       //위 조건에 따라 분기된 클래스의 print()함수를 실행
 delete ptr;
}

이렇게 하면


이런 결과가 나옵니다.
분명히 조건에 따라 bClass를 만들었는데 bClass의 print에서 정의한 cout << "bClass" << endl; 의 결과가 나오지 않습니다.

이런 문제를 해결하기 위해 쓰는것이 virtual 키워드 입니다.
컴파일러에게, 만약 이녀석 함수가 불러질때, 상속된 녀석이 이 함수를 오버라이딩 했을 가능성이 있으니 상속된 함수라면
정말 오버라이딩 했는지 확인한뒤 처리 하란 뜻입니다.

즉, 위의 상위 클래스에서 virtual를 추가하고 다시 컴파일 해보면

/************************************************************************/
/*상위클래스                                                            */
/************************************************************************/
class upClass
{
public:
  virtual void print(){
  cout<<"up class"<<endl;
 }
};

 


의도한 대로 bClass에서 생성된 print 가 실행 되었습니다.

추가로, 가끔 소멸자에 virtual 키워드를 붙이라고 하는걸 들어본 적이 있을껍니다.
그 이유는 위와 같이, 소멸자에 virtual 를 붙이지 않고 단순히

upClass *a = new bClass 로 할때

a가 소멸할시 소멸자는 upClass의 소멸자를 호출하는거지 , new 로 선언한 bClass 의 소멸자가 불러오지 않기 때문이죠.
virtual 를 소멸자에 선언하면, 당연히 bClass 의 소멸자가 실행되고 upClass의 소멸자가 실행됩니다.

이게 중요한 이유는 의도치 않는 메모리 누수가 발생하기 떄문이죠.
특히 서버 프로그래밍경우 메모리 관리가 엄청 중요합니다. (이것때문에 서버가 크리티컬로 다운되면 게임 전체가 stop이죠... ㅠㅠ)

'C/C++언어' 카테고리의 다른 글

MSXML 파싱  (0) 2012.07.03
프로그램 시간 측정  (0) 2012.03.21
Predefined Macros  (0) 2012.01.05
[C/C++] Debug Printf  (0) 2012.01.03
어셈블리 코드도 #if같은 메크로 분기시켜서 소스 관리하기.  (0) 2011.05.27

설명 #

"함수포인터"는 C언어에서 자주 쓰이는 기본 용법중 하나입니다. 정의는 다음과 같습니다.
선언된 함수가 저장된 메모리 번지값을 담고 있는 포인터 변수
하지만 이것을 왜 보통의 포인터와는 달리 특별취급하냐면 다음과 같은 이유 때문입니다.
  1. 일반 포인터 변수와는 조금 다르게 보이는 문법으로 선언됩니다. 예를 들어 함수 포인터 변수는 다음과 같이 선언합니다. (functionPtr이 포인터 변수명이 됩니다)
    void (*functionPtr)(const char *s_)
    
  2. 함수포인터는 실행부분을 가리키는 포인터 변수인탓에 () 연산자를 사용할 수 있습니다. 즉, 위의 functionPtr은 아래와 같이 실행가능합니다.
    functionPtr("바보나라~")
    

주로 함수포인터는 콜백(callback)을 구현하는데 사용됩니다. 어떤 라이브러리를 제작할 때 개발자가 어떤 "경우"에 대한 반응을 함수로 작성하고 이를 다시 라이브러리에서 호출하도록 작성하고자 할 때, 개발자가 작성한 함수의 포인터를 넘겨받아 실행하도록 하면 아주 유용할 때가 많습니다. (대표적인 예가 win32 api에서 윈도우 프로시져를 WNDCLASS의 lpfnWndProc맴버에 등록하는 부분을 들 수 있습니다) 즉, 함수를 마치 변수와 같이 취급하는 것이 가능해집니다.

예제 #

아래 예제는 함수포인터를 활용한 간단한 예제입니다.
#include <stdio.h>

void testout1(const char *s) {
  printf("시험출력1:%s\n", s);
}

void testout2(const char *s) {
  printf("시험출력2:%s\n", s);
}

// 함수포인터 변수
void (*funcPtr)(const char *s);

int main() {
  // 함수포인터를 testout1, testout2로 각각 대입해보고 실행해본다.
  funcPtr = testout1;
  funcPtr("테스트");
  funcPtr = testout2;
  funcPtr("테스트");
}

실행결과는 아래와 같습니다.
시험출력1:테스트 시험출력2:테스트
main()안에서는 funcPtr만 실행했지만 마치 testout1, testout2를 각각 실행한 것과 같은 효과가 있음을 알 수 있습니다.

typedef 거는 법 #

함수포인터를 어떤 포인터 변수로서 활용하고 싶을 때 typedef를 활용하면 매우 편리합니다. 실제로 win32 API에서는 윈도우 프로시저 함수포인터형으로 WNDPROC라는 타입이 있습니다. (이부분을 보면 어느정도 사용법에 대한 느낌이 올겁니다) winuser.h 헤더화일에서 검색해보면 다음과 같이 선언되어있습니다.
typedef LRESULT(CALLBACK *WNDPROC)(HWND,UINT,WPARAM,LPARAM);
실제로 사용될 때에는 다음과 같이 사용됩니다.
LRESULT CALLBACK winproc1(HWND,UINT,WPARAM,LPARAM) { }; ... WNDPROC winprocPtr = winproc1; ...
꽤 유용한 문법이니 알아두시면 좋습니다.

주의사항 #

함수포인터는 알아두면 상당히 유용한 기법이지만 사용시에는 다음과 같은 부분을 주의해야합니다.
  1. 대입되지 않은 (NULL값상태의) 함수포인터를 실행하지 않도록 주의해야합니다. 초기화를 항상 NULL로 해두고 실행전에 검사하는 것도 좋은 방법입니다.
  2. "같은 함수 포인터 타입"이라는 것을 증명하려면, 반환값, 매개변수의 타입 및 개수가 모두 일치해야합니다. 보통 하나라도 다른 함수포인터값을 대입하면 컴파일시 오류가 나게 됩니다.


출처 : http://www.redwiki.net/wiki/wiki.php/%C7%D4%BC%F6%C6%F7%C0%CE%C5%CD

지금까지는 하나의 클래스에 대해 이야기를 했습니다. 지금부터는 하나 이상의 클래스를 이야기할까 합니다. 즉, 클래스 간의 관계에 대한 이야기입니다.

 

클래스의 관계는 여러 가지가 있지만 재사용 측면에서 크게 두 가지 관계가 존재합니다.( 우리는 복잡한 것을 생각하지 않으므로 간단하게 접근합니다.)

  • 첫째, '-이다'의 관계( is_a 관계)

    • 상속(inheritance)
  • 둘째, '-가지다'의 관계( has_a 관계)

    • 포함(composition)

 

첫째, is_a 관계는 Person 클래스와 Student 클래스의 관계입니다.

  • Student(학생)은 Person(사람) 이다. 관계이기 때문에..
  • 그림으로는 아래와 같이 표현할 수 있습니다.

상속Person.png 

이때 Person 클래스를 부모(Parent) 클래스라 하고 Student, Professor 클래스를 자식(child) 클래스라 합니다.

 

 

둘째, has_a 관계는 Car 클래스와 Radio 클래스의 관계입니다.(이것은 다음에 진행합니다. --;)

  • Car(자동차)는 Radio(라디오)를 가진다. 관계이기 때문에..
  • 그림으로는 아래와 같이 표현할 수 있습니다.

포함car.png 

 

 1, 상속

상속은 부모 클래스의 모든 내용(속성과 행동)을 자식 클래스가 물려 받는 것입니다.

위 그림에서 Person의 속성(멤버 변수)와 행동(멤버 함수)을 Student가 물려 받습니다. 그리고 자신만의 상태와 행동을 추가하여 내용을 확장합니다.

그래서 상속을 이용하면 코드를 재상용하여 효율적으로 클래스를 생성할 수 있습니다.

 

Student 클래스를 만들기 위한 간단한 예제코드를 볼까요?

첫째, 상속을 사용하지 않는 Student 클래스 정의

class Student
{
    char name[20];
    int age;
    int grade;
public:
    void Eat( ) {   }
    void Study( ) { }
};

void main( )
{
    Student std1;
}

 학생은 사람이므로 사람으로서 필요한 모든 내용(속성과 행동)을 Student 클래스가 정의하고 있습니다. 이렇게 되면 Professor 클래스를 정의할 때도 사람에 필요한 모든 내용을 정의해야 합니다. 그러면 코드 중복이 발생하여 여러 가지 불이익을 받게 됩니다.

 

Student 객체의 그림입니다.

상속없는_student_객체.png 

 

 

둘째, 상속을 사용한 Student 클래스 정의

class Person
{
    char name[20];
    int age;
public:
    void Eat( ) {   }
};
class Student : public Person
{
    int grade;
public:
    void Study( ) { }
};

void main( )
{

    Person person1;

 

    Student student1;

}

여기서 Person 클래스가 이미 존재한다고 가정한다면 Student 클래스를 정의하는 것은 위쪽 예제보다 훨씬 간단합니다. 속성 grade와 행동 Sudent()만 정의할 뿐이니까요. 그리고 부가적으로 따라오는 이득도 만만치 않습니다. 문법적으로 상속은 " : public Person"와 같이 합니다.(빨간색 부분)

 

Person 객체와 Student 객체의 그림입니다.

당연한 이야기지만 Student 객체는 위쪽 상속을 사용하지 않는 객체의 속성, 행동과 같은 객체가 만들어 집니다. 상속을 사용해서 Student 클래스를 정의한 것뿐이니까요.

상속없는_student_객체(2).png 

 

 

2, 함수 재정의(function overriding)

 함수 재정의란 부모 클래스에 정의된 멤버 함수를 자식 클래스에서 다시 정의하는 것을 말합니다.

 

예로 아래와 같은 Person 클래스가 있다고 가정합니다.

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
void main( )
{
    Person person1("김영수", 20);

    person1.Print( );
}
  1. name : 김영수, age : 20

 person1의 Print() 멤버 함수는 이름과 나이를 출력합니다.

 

이때 학생을 정의하기 위한 Student 클래스가 필요합니다.

이미 Person 클래스가 존재하므로 Student 클래스는 Person을 상속받아 만들기로 결정합니다.

아래와 같이..

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g)     {
    }
    void Study( ) { }
};
void main( )
{
    Student student1("김학생", 20, 1);

    student1.Print( );
}
  1. name : 김학생, age : 20

 그리고 학생 객체 student1을 만들어 학생의 정보를 출력(Print() 호출)하지만 Print() 함수는 grade의 정보를 출력하지 못합니다.

Print()함수는 Person의 메소드로 grade의 어떠한 내용도 알지 못하기 때문입니다.

한마디로 Person 클래스보다 Student 클래스가 더 구체화된 클래스이므로 Print() 함수의 기능도 Student에 맞게 더 구체화 시켜야 합니다.

그래서 부모 클래스(Person)의 함수(Print())를 자식 클래스(Student)에서 재정의하는 것입니다.

아래 예제와 같이...

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g)     {
    }
    void Study( ) { }
    void Print( ) // 함수 재정의
    {
        Person::Print(); // 이름과 나이를 출력하고
        cout << "grade : " << grade << endl; // 학년도 출력합니다.
    }
};
void main( )
{
    Student student1("김학생", 20, 1);

    student1.Print( );
}
  1. name : 김학생, age : 20
    grade : 1

 Student 클래스에서 Print()함수를 재정의하여 이름과 나이도 출력하고(Person::Print()) 학년도 출력합니다. 재정의 함수에서 부모의 함수를 호출하려면 접근 연산자(::)를 사용해야 합니다. ( Person::Print() <=  이렇게... )

아니면 재귀함수 호출이 되겠죠?

 
출처 : http://coolprogramming.springnote.com/pages/3422007

+ Recent posts