2025. 4. 8. 18:40ㆍProgramming Language/C++
1. 포인터
포인터는 다른 변수나 메모리의 주소를 저장하는 변수로 일반 변수의 경우는 값을 저장하나 포인터는 어디에 값이 있는지(메모리 주소)를 저장한다.
C++ 프로그램이 실행되면 변수들이 메모리(RAM)에 저장된다.
각 변수는 이 메모리 안에서 고유한 주소를 갖게 되는데
int a = 450;
이렇게 선언된 변수는 RAM의 어딘가에 저장이 되고 그 어딘가를 특정하기 위한 주소값이 생성된다.
포인터는 이렇게 선언된 변수의 위치를 저장한 주소값을 담을 수 있는 변수인 것이다.
2. 포인터의 선언
포인터의 기본적인 선언 방식은
타입 * 포인터변수명;
과 같이 선언하며 실 사용의 예시를 보자면
int * ptr;
으로 이 포인터의 경우는 int 타입의 변수 혹은 상수의 주소값을 저장할 수 있는 포인터 변수를 선언한 것이다.
이렇게 작성된 포인터는 ptr은 int를 가리키는 포인터라고 말할 수 있다.
그리고 이렇게 선언된 변수에 값을 초기화 하기 위해서는 주소값을 전달해야한다.
그때 사용하는 연산자는 &로 &가 붙은 변수는 주소값을 반환한다.
이걸 포인터에 할당해주면 된다.
&는 단항연산자로 사용되었을 때만 주소값을 반환하는 연산자로 기능한다.
(이항 연산자의 경우, 연산자의 좌 우에 값이 존재하는 경우, 비트 연산자로써 기능함)
이렇게 할당한 포인터를 통해서 값을 가져오는 방법(주소값 말고 주소값을 전달한 변수안에 있는 값을 가져오는 방법)은 * 연산자를 사용해서 가져올 수 있다.
이는 D-Reference라고 해서 역참조를 하는 연산자, 역참조 연산자라고 불린다
사용 방법은 포인터의 이름 앞에 역참조 연산자를 붙여줌으로써 사용이 가능하다.
그리고 역참조 연산자를 사용해서 값을 넣어줄 수 도 있다.
이건 ptr이 가리키는 주소에 존재하는 값 자체를 바꾸는 것이기에 포인터 ptr이 가리키던 변수 a의 값의 변화를 만든다.
만약
이렇게 넣어줬다면 ptr1을 바꾸면
결국 동일하게 변경이 된다.
보통은 주소값을 갖고 있는 포인터는 A가 B를 가리킨다 라고 말하는데
int a = 450;
int* ptr = &a; // ptr -> a = ptr이 a를 가리킨다
int* ptr1 = ptr; // ptr1 -> ptr = ptr1이 ptr을 가리킨다
// ptr1 => ptr => a = ptr1이 ptr을 가리키고 ptr은 a를 가리킨다
// 고로 ptr1 => a 라고 할 수 도 있다.
와 같다.
그래서 주소값도
이렇게 같은걸 바라보는것을 볼 수 있다.
만약 이 포인터에 상수를 직접 넣게 된다면
이렇게 주소값으로 벹어 내는데 위에서 할당한 0은 포인터가 아무것도 가르키지 않음, null을 의미하는 특수한 값으로 사용된다.
사실 그냥 상수를 등록하려고 한다면
이렇게 등록할 수 없다고 나온다.
그렇기에 null을 가리키는 포인터는 이를 역참조하려고 한다면 에러를 발생시킨다.
3. 포인터의 타입 지정
포인터는 주소값을 저장하는 변수인데 왜 궂이 타입정보를 적어서 선언해야할까?
이는 포인터가 담고 있는 주소값을 어떤 타입으로 다뤄야 할지에 대한 정보를 작성해주는 것이다.
공용체를 예시로 들어보자.
이런 공용체가 있다고 보자
각각의 멤버는 같은 주소값을 공유하고 있게 된다.
이때 만약에
적절한 방식은 아니지만 각각의 공유체의 멤버를 (타입*)으로 포인터형으로 전환해서 주소값을 저장한다고 생각해보자.
int* iptr = &pu.a 가 아니라 int* iptr = (int*)&pu.a인 이유
공용체의 경우는 같은 주소를 공유하기 때문에 하나의 멤버만 활성화 할 수 있고 이렇게 활성화(직접 할당)된 멤버에 접근해야만 안전하게 사용이 가능하다.
그렇기에
int* iptr = &pu.a
이런 식으로 사용하면 간혹 컴파일러에서 정의되지 않은 동작(undefined behavior)로 접근을 막기도 한다.
그렇기에 컴파일러에게 나 일부러 이렇게 쓰는거야 라고 알리기 위해서
int* iptr = (int*)&pu.a
이렇게 캐스팅해주면 컴파일러가 막지 않는다.
그렇기에 int* iptr = &pu.a 대신에 int* iptr = (int *)&pu.a을 사용한 것이다.
그리고 (int * )&pu.a 의 의미는 pu.a의 주소를 int* 타입으로 강제로 해석하겠다로 위에서 말했던것 처럼 해당 주소는 int형 포인터에 넣어질 주소값이야 라고 명시적으로 알려주는 것이라고 보면 될 듯 하다
이때 만약에 int 형타입의 공용체의 멤버에만 값을 넣어준다고 한다면
출력의 값이
int형 포인터를 제외한 다른 값들은 이상한 값이 나오게 된다.
공용체는 한번에 값의 타입을 다르게 보여줄 수 있어서 사용한 것이지만 어찌됐든 결국에 해당 타입을 * 역참조 할때 이 값을 int값으로 볼지 float값으로 볼지 double값으로 볼지에 대해서 컴파일러에게 알려줄 필요가 있다는 것이다.
4. 포인터의 상수화
일반적으로 상수를 선언한다면
선언 이후에 이 값을 바꾸지 못한다.
그런데 만약 포인터에 const 선언을 하게 되면 어떻게 될까?
이렇게 상수로 선언된 포인터 ptr의 경우는
이렇게 값을 바꿀 수 있게 된다.
상수는 못바꾼다고 했는데 왜 바꿀 수 있는 것일까?
이건 const 가 int * ptr 에 붙어 ptr을 상수로 만드는 것이 아니기에 ptr의 값 자체는 바꿀 수 있으나 const int 타입을 바라보는 포인터이기 때문에 ptr을 역참조했을때 그 값을 바꿀 수는 없게 된다.
이는 위에서 말했던것 처럼 해당 주소에 있는 값을 어떤 타입으로 다뤄야 할지에 대한 타입인 것이라서 해당 주소값이 가리키는 값은 상수로써 바라보고 있기 때문에 변경할 수 없다라고 알리는 것이다.
#const int * ptr 과 int const * ptr은 동일한 기능을 한다 아마 int const * ptr은 안쓸것 같은데 우선 된다는 것은 알리기 위해 작성했다
그러면 ptr 자체를 상수로 만들려면 어떻게 해야할까 ?
그때는 * 와 ptr의 사이에 const를 넣어주면 된다.
보이다 싶이 const가 타입* 과 포인터명 사이에 들어가면 해당 포인터 자체가 상수가 되어 포인터에 할당된 값을 변경시킬 수 없게 된다.
이때는 물론 바라보고 있는 주소 값은 int형으로 보기 때문에 상수가 아니라서 역참조해서 값을 변경하는 것은 가능하다.
여기서
이렇게 const를 앞 뒤로 붙여줄 수 있는데 이렇게 하면
주소값을 바꾸는것도 역참조해서 그 값을 바꾸는것도 불가능하게 된다는 점을 알고있자.
'Programming Language > C++' 카테고리의 다른 글
Ch07. 포인터 - 03.문자열과의 관계 (0) | 2025.04.09 |
---|---|
Ch07. 포인터 - 02. 배열과의 관계 (0) | 2025.04.09 |
Ch06. 복합데이터 - 09. Range-based for (0) | 2025.04.08 |
Ch06. 복합데이터 - 08. std::array (0) | 2025.04.08 |
Ch06. 복합데이터 - 07. 열거형(enum) (0) | 2025.04.08 |