본문 바로가기
C++/뇌를 자극하는 C++

[C++] 챕터 11 - 포인터 : 정보에 대한 정보

by Minkyu Lee 2023. 4. 27.

// 11-1 : 변수의 주소

#include <iostream>
using namespace std;

int main()
{
    // 변수 정의
    char c = 'B';
    int i = 19;
    float f = 4.5f;

    // 주소 출력
    cout << (int*)&c << "\n"; // int*으로 형변환. 다음장에서 설명한다.
    cout << &i << "\n";
    cout << &f << "\n";

    return 0;
}

/*
--- 결과 ---
0000000FF4EFFAC4
0000000FF4EFFAE4
0000000FF4EFFB04

---

변수의 위치, 즉 주소를 구하는 방법은 이렇다.
변수의 이름 앞에 &(ampersand)를 붙여주는 것이다.

*/

 

// 11-2 : 변수 a를 가리키는 포인터 p

#include <iostream>
using namespace std;

int main()
{
    int a = 123; // 일반 변수 정의
    int* p; // 포인터 변수 정의
    p = &a;

    // 출력
    cout << &a << "\n";
    cout << p << "\n";
    cout << &p << "\n"; // 포인터 변수의 주소

    return 0;
}

/*
--- 결과 ---
00000077342FFC34
00000077342FFC34
00000077342FFC58

---
포인트 변수의 정의 예제이다.

int*는 포인터 타입을 의미한다
그러나 위처럼 정의시, int 타입의 변수만 가리킬 수 있다.

char*라고 정의하면 char를 가리킬 수 있다.
*/

 

// 11-3 : 여러가지 타입의 포인터

#include <iostream>
using namespace std;

int main()
{
    int i = 300;
    int* pi = &i;

    char c = 'C';
    char* pc = &c;

    float f = 700.5f;
    float* pf = &f;

    bool b = true;
    bool* pb = &b;

    short int s = 456;
    short int* ps = &s;

    return 0;
}

/*
왜 하나의 타입밖에 가리킬 수 없을까?
이유는 메모리에 값만 저장하고 타입은 저장하지 않기 때문이다.
어떤 타입인지 지정해주어야 메모리의 어디서부터 어디까지 값을 가져올지 알 수 있다.

모든 포인터 변수의 크기는 동일하다.
담기는 값이 항상 주소값이기 때문이다.
*/

 

// 11-4 : void 포인터의 사용

int main()
{
    int i = 400;
    void* pv = &i; // void는 아무 타입이나 가리킬 수 있다. 형변환이 필요없다.

    int* pi = (int*)pv; // 직접 형변환해야 안전하다.

    return 0;
}

/*
형변환은 int 타입의 값이 있다라는 정보를 추가해주는 셈이다.

void포인터는 주소를 저장하는 용도로만 사용한다.
때가 되면 형변환해서 사용한다.
형변환하지 않고는 사용할 수 없다.
*/

 

// 11-5 : 정보에 접근하는 방법

#include <iostream>
using namespace std;

int main()
{
    int a = 123;
    int* p = &a;

    cout << *p << "\n"; // *p는 p가 가리키는 변수라는 의미다. 즉, p의 값을 구할 수 있다.
    *p = 789; // p가 가리키는 변수의 값을 변경한다.

    cout << a << "\n";
    cout << *p << "\n";

    return 0;
}

/*
--- 결과 ---
123
789
789

---
비유하자면,
p는 과자의 위치이다. (주소)
*p는 과자 그 자체이다.

*p에서 값을 읽으면 a의 값을 반환한다.
*p에 값을 쓰면 a의 값이 바뀐다.

*/

 

// 11-6 : 포인터의 동작 방식

#include <iostream>
using namespace std;

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

    // pc가 가리키는 값
    cout << hex; // 정수를 16진수로 출력
    cout << (int)*pc << "\n"; // 형변환. 숫자로 출력하게 만들기.

    return 0;
}

/*
--- 결과 ---
78

---


*/

 

// 11-7, 8 : 일반적인 포인터 사용법, NULL의 사용

// 11-8
#include <iostream>
using namespace std;

int main()
{
    // 포인터 변수 정의, 초기화
    int* p = NULL;
    if (NULL != p) *p = 30; // 실행 안된다.

    int a = 100;
    p = &a;

    if (p) *p = 30; // 실행 된다.

    return 0;
}

// 11-7
int main()
{
    // 포인터 변수 정의, 초기화
    int* p = 0;
    if (0 != p) *p = 30; // 실행 안된다.

    int a = 100;
    p = &a;

    if (p) *p = 30; // 실행 된다.

    return 0;
}

/*
--- 결과 ---

---
p를 초기화하지 않은 상태로 사용하면 안된다.
오동작된다. 안전을 확신할 수 없는 포인터는 사용하기 전 확인해야한다.

개발자는 다음과 같은 습관을 가져야 한다.
1. 포인터 변수는 항상 0으로 초기화한다.
2. 사용하기 전에는 0이 아닌지 비교한다.

주소 0번지에 위치할 수 있는 변수는 없다.
그래서 아무것도 가리키지 않음의 의미로 사용한다.

매번 비교하는 것이 일반적이다.
0대신 NULL 사용이 많다.
차이가 없으나 사람이 보기에 눈에 잘띄어서 그렇다.
*/

 

// 11-9 : 기본 타입과 const

#include <iostream>
using namespace std;

int main()
{
    const int a = 123;
    a = 456;

    return 0;
}

/*
--- 결과 ---
오류가 발생한다.

---
const는 변수의 값을 변경할 수 없게 만든다.
상수 같은 변수가 된다.
const는 constant로, 상수 라는 뜻의 약자다.

const는 정의할 때 반드시 초기화한다.

const 사용은 두가지 경우가 있다.
1. 처음 정의할 때 부터 const인 경우.
2. const가 아니지만, 다른 곳에 넘겨줄 때만 잠깐 const인 척 하는 경우.

*/

 

// 11-10 : 상수 대신에 const 변수 사용

#include <iostream>
using namespace std;

int main()
{
    // 배열의 크기를 const 변수에 보관한다.
    const unsigned int arraySize = 100;

    char characters[arraySize] = { 0 };

    for (int i = 0; i < arraySize; ++i) characters[i] = i + 1;

    return 0;
}

/*
--- 결과 ---


---
상수 대신 const 변수 사용예시이다.

위처럼 하면 배열의 개수를 변경해야할 때,
중복 코드를 없애고, 실수를 줄일 수 있다.


*/

 

// 11-11, 12, 13, 14 : 이하 코드 주석 참고

#include <iostream>
using namespace std;

// 11-14 : 두 군데 모두 const인 경우
int main()
{
    int i1 = 10;
    int i2 = 20;
    const int* const p = &i1;

    p = &12; // X
    *p = 30; // X

    return 0;
}

// 11-13 : 포인터 자체가 const인 경우
int main()
{
    int i1 = 10;
    int i2 = 20;
    int* const p = &i1; //int타입을 가리키는 p는 const 속성을 가진다 라는 뜻이다.

    p = &12; // X
    *p = 30; // O

    return 0;
}

// 11-12 : 가리키는 변수가 const인 경우
int main()
{
    int i1 = 10;
    int i2 = 20;
    const int* p = &i1; // p가 가리키는 변수는 const int 타입이다. 라는 뜻이다. 실제 그러한지는 중요치 않다.

    p = &i2; // O
    *p = 30; // X

    return 0;
}

// 11-11 : const 사용하지 않은 경우
int main()
{
    int i1 = 10;
    int i2 = 20;
    int* p = &i1;

    p = &i2;
    *p = 30;

    return 0;
}

/*
--- 결과 ---


---
const의 의미 외우는 법

const 다음에 오는 것을 상수화시킨다.라고 생각하자.

// const int* p
const 다음에 int가 오므로. int 타입의 값.
즉, 포인터가 가리키는 값이 const이다.

// int* const p
const 다음에 p가 오므로, p 자체가 const이다.

*/

댓글