C++ 이해의 시작 [1]

포인터(Pointer)

포인터는 놀랍게도 변수다. 다른 변수의 위치를 가리키는 변수.

  • 메모리와 변수
    메모리는 정보를 저장하기 위한 하드웨어(공간). 공간 하나 당 1Byte.
    변수는 메모리에 위치.
    여기서 메모리의 각 공간은 한 바이트를 기준으로 공간마다 주소가 부여되어 있다.

#include <iostream>
using namespace std;

int main() {
    char c = 'c';
    int i = 25;
    float f = 2.5f;

    cout << "c의 주소 = " << (int*)&c << "\n";
    cout << "i의 주소 = " << &i << "\n";
    cout << "f의 주소 = " << &f << "\n";

    return 0;
};
**출력 결과 **

c의 주소 = 0x16d5373cb
i의 주소 = 0x16d5373c4
f의 주소 = 0x16d5373c0

여기서 재미있는 점을 하나 발견할 수 있는데
4바이트 크기의 int타입과
4바이트 크기의 float타입의 주소가 정확히 4만큼 차이나는 부분이다.

거의 동시에 정의한 변수들이므로 비슷한 위치에 자리잡은 것이고,
여러 공간을 걸쳐서 자리잡은 정보의 경우
첫번째 공간의 주소가 그 위치를 나타내게 된다는 것을 알 수 있다.


  • 포인터 변수
    그렇다면 포인터 변수는 어떻게 저장할까...?

    그건 쉽다!!!!!!!!!!!!!! 각 타입 뒤에 * 을 붙여주면 된다

#include <iostream>
using namespace std;

int main() {
    // int 타입
    int i = 25;
    int* pi = &i;

    // char 타입
    char c = 'C';
    char* pc = &c;

    // float 타입
    float f = 2.5f;
    float* pf = &f;

    // bool 타입
    bool b = true;
    bool* pb = &b;

    // short int 타입
    short int si = 2;
    short int* psi = &si;

    return 0;
};

처음 포인터를 공부 할 때 머리가 받아들이지 못한 부분이다.
걍 멍청해서 그런것 같긴 한데,,,
i 에는 25라는 값이 저장될 뿐이고,
pi에는 25라는 값이 저장되어 있는 i의 주소가 저장되는 것이다.


  • void 포인터

    모든 타입을 가리킬 수 있는 포인터.
    주소를 저장하는 용도로만 사용하고, 보관된 주소를 사용해야 할 때 타입이 있는 포인터로 형변환 시켜야 한다.


  • 포인터의 동작 방식
#include <iostream>
using namespace std;

int main() {
    int i = 0x12345678;
    int* pi = &i;
    char* pc = (char*) &i;

    cout << hex;
    cout << "pc = " << (int*)pc << "\n";
    cout << "*pc = " << (int) *pc << "\n";
    cout << "pi = " << pi << "\n";
    cout << "*pi = " << *pi << "\n";
    
    return 0;
};
**출력 결과**

pc = 0x16f31b3d8
*pc = 78
pi = 0x16f31b3d8
*pi = 12345678

바이트 단위로 쉽게 구분 할 수 있도록 변수 i에 16진수 형식으로 저장했다.
4개의 바이트는 각각 0x12, 0x34, 0x56, 0x78 로 채워진다.

출력 결과를 보자.
변수 i의 주소를 담고 있는 pi와 pc는 당연히 같은 주소값을 갖는다.
int 타입 포인터 변수 pi는 주소 4바이트의 내용을 전부 가져오는 반면
char 타입 포인터 변수 pc는 주소 1바이트의 내용만 가져오게 된다.

  • 리틀 엔디안 (Little-Endian)
    2바이트 이상의 크기를 가진 변수가 메모리에 자리잡는 방식에 따라 구분되는데,
    위의 출력 결과를 보면 int 타입 변수가 메모리에 자리 잡을 때 0x78, 0x56, 0x34, 0x12 와 같이
    거꾸로 자리잡고 있음
    을 알 수 있다.
    이러한 방식이 바로 리틀 엔디안이라 한다.
    (그냥 참고 정도...)

  • NULL을 사용한 초기화
#include <iostream>
using namespace std;

int main() {
    int* p = NULL;
    int a = 100;
    p = &a;

    if (NULL != p) {
        *p = 30;
    }

    return 0;
};
이렇게까지 하는 이유가 뭘까...?

p를 초기화하지 않은 상태에서는 쓰레기 값을 가지고 있다.
이 쓰레기 값은 실제로 어떤 값이 될지 아무도 알 수 없고, 특히 포인터 변수의 경우
어디를 가리키고 있는지 알 수가 없다. 따라서 우리는 어딘지 알 수 없는 곳의 정보를 바꿔버리는
참사를 대비하고자 다음의 방식을 사용한다.

  • 포인터 변수는 항상 0(null)으로 초기화한다.
  • 포인터 변수를 사용하기 전에는 0(null)이 아닌지 비교한다.

  • 배열과 포인터
** 1. 배열의 원소를 가리키는 포인터 **

#include <iostream>
using namespace std;

int main() {
    int nArray[10];
    int* p = &nArray[0];

    if (nArray == p) {
        cout << "nArray == &nArray[0]\n" << "배열의 이름 nArray는 첫번째 원소의 주소를 의미한다" << "\n";
    }

    for (int i = 0; i < 10; ++i) {
        *(p + i) = i; // i개 만큼 뒤에 있는 원소의 주소에 i 대입
        p[i] = i; //포인터 변수를 사용할 때도 배열의 원소에 접근하는 법과 동일
        *(nArray + i) = i; // 배열의 이름은 항상 첫번째 원소의 주소만을 의미하는 상수다.

        cout << *(p+i) << "\n";
    }
    return 0;
};

** 2. 배열을 가리키는 포인터 **

#include <iostream>
using namespace std;

int main() {
    long lArray[20];
    long (*p)[20] = &lArray;

    (*p)[3] = 300;
 
    cout <<"lArray[3] = " << lArray[3] << "\n";

    return 0;
};

** 3. 포인터의 배열 **

#include <iostream>
using namespace std;

int main() {
    double a,b,c;

    double* pArray[3];

    pArray[0] = &a;
    pArray[1] = &b;
    pArray[2] = &c;

    cout << "pArray[0] = " << pArray[0] << "\n";
    cout << "pArray[1] = " << pArray[1] << "\n";
    cout << "pArray[2] = " << pArray[2] << "\n";

    return 0;
};


* 출력 결과 *

pArray[0] = 0x16cedb3a0
pArray[1] = 0x16cedb398
pArray[2] = 0x16cedb390

  • 포인터와 구조체
** 1. 구조체를 가리키는 포인터 **

#include <iostream>
using namespace std;

struct Rectangle { // 사각형 정보를 갖는 구조체
    int x, y;
    int width, height;
};

int main() {
    Rectangle rc = { 100, 100, 50, 50 };
    Rectangle* pointer = &rc;

    (*pointer).x = 200; // *pointer는 pointer가 가리키는 것. 즉, 구조체 rc
    pointer->y = 250;

    cout << "rc = ( " << rc.x << ", " << rc.y << ", ";
    cout << rc.width << ", " << rc.height << ")\n"; 

    return 0;
};


* 출력 결과 *

rc = ( 200, 250, 50, 50)

** 2. 포인터를 포함하는 구조체 (서로를 가리키는 구조체 변수) **

#include <iostream>
using namespace std;

struct Dizzy { 
    int id;
    Dizzy* pointer = NULL;
};

int main() {
    Dizzy obj1, obj2, obj3;

    obj1.id = 1;
    obj1.pointer = &obj2;

    obj2.id = 2;
    obj2.pointer = &obj3;

    obj3.id = 3;
    obj3.pointer = &obj1;

    cout << "obj1.id = " << obj1.id << "\n";
    cout << "obj2.id = " << obj1.pointer -> id << "\n";
    cout << "obj3.id = " << obj1.pointer -> pointer -> id << "\n";
    cout << "obj1.id = " << obj1.pointer -> pointer -> pointer -> id << "(again)\n";

    return 0;
};


* 출력 결과 *

obj1.id = 1
obj2.id = 2
obj3.id = 3
obj1.id = 1(again)

헷갈린다. 하지만 쫄지 말자.
직관적으로 풀어보자.
"obj1.pointer->id"obj1의 멤버인 pointer가 가리키는 구조체의 멤버 id가 된다.

가장 복잡해 보이는 마지막 친구는 어떨까?
obj1.pointer는 obj2의 주소를 담고 있으므로, obj1.pointer는 obj2의 주소이다.
obj1.pointer->pointer는 obj2.pointer가 담고 있는 주소, 즉 obj3의 주소값이다.
obj1.pointer->pointer->pointer는 다시 obj3.pointer가 담고있는 주소이므로 obj1의 주소가 되고
결국 obj1의 멤버 id인 1이 출력된다.

링크드 리스트에 대한 내용이지만 지금 다루지 않겠다...


추가 ) 포인터를 가리키는 포인터

#include <iostream>
using namespace std;

int main() {
    char c = 'C';
    char* pointer = &c;
    char** doublePointer = &pointer;


    if (*doublePointer == pointer) {
        cout << "*doublePointer는 doublePointer가 가리키는 변수\n";
        cout << "따라서 pointer에 담긴 값인 c의 주소값이다\n";
        cout << "*doublePointer = " << (int*) *doublePointer << "\n\n";
    }
    if (**doublePointer == c) {
        cout << "**doublePointer는 doublePointer가 가리키는 변수가 가리키는 변수\n";
        cout << "따라서 pointer가 가리키는 주소에 담긴 값인 c의 값이다\n";
        cout << "**doublePointer = " << **doublePointer << "\n";
    }

    return 0;
};


* 출력 결과 *

*doublePointer는 doublePointer가 가리키는 변수
따라서 pointer에 담긴 값인 c의 주소값이다
*doublePointer = 0x16cfa33cb

**doublePointer는 doublePointer가 가리키는 변수가 가리키는 변수
따라서 pointer가 가리키는 주소에 담긴 값인 c의 값이다
**doublePointer = C

좋은 웹페이지 즐겨찾기