포인터 : 포인터란 메모리 주소를 가리킬 수 있는 변수
포인터를 이해하기 위해서는 여러분은 컴퓨터의 메모리에 대해 조금 알아야 합니다. 컴퓨터의 메모리는 그 위치를 가리키는 연속적인 번호가 붙여진
부분으로 나누어질 수 있습니다. 각 변수는 메모리의 각 유일한(주소라고 함)을 가리키고 있습니다.
#include <iostream>
using namespace std;
int main()
{
unsigned short shortVar = 5;
unsigned long longVar = 65535;
long sVar = -65535;
cout << "shortVar:" << shortVar << "\t";
cout << "Address of shortVar:";
cout << &shortVar << "\n";
cout << "longVar:" << longVar << "\t";
cout << "Address of shortVar:";
cout << &longVar << "\n";
cout << "sVar:";
cout << sVar << "\t Address of sVar:";
cout << &sVar << "\n";
return 0;
}
여러분이 각 변수의 실제의 주소를 숫자로 알아야 할 이유는 없습니다. 중요한 것은 각 변수는 자신의 주소를 갖고 또 그 변수의 형만큼의 길이를
갖는다는 것입니다. 여러분은 변수형을 선언함으로써 컴파일러에게 그 변수형에 해당하는 길이 만큼의 메모리를 잡아 놓을 것을 말해줍니다.
컴파일러는 자동적으로 그 변수의 주소를 할당합니다. 예를 들어, long 정수형은 4바이트이고 이는 메모리에서 4바이트를 차지한다는 의미입니다.
포인터에 주소를 저장
모든 변수는 주소를 갖고 있습니다. 주어진 변수를 몰라도 여러분은 주소를 포인터에 저장할 수가 있습니다. 예를 들어, howOld가 정수형이라고 합
시다. howOld의 주소를 저장할 포인터를 pAge라 하면 여러분은 다음과 같이 쓸 수 있습니다.
Int *pAge = 0;
이 선언은 pAge가 정수형의 변수 주소를 가리키는(정수형의 포인터라고 한다.) 변수라는 뜻입니다. 다시 말해 pAge는 정수형의 주소를 가지도록
선언되었습니다.
pAge는 변수 입니다. 여러분은 정수형 변수를 선언하면 그 변수에 저장되는 내용은 정수로 해석됩니다. 여러분이 pAge를 포인터로 선언했으면
pAge에 저장되는 내용은 주소로 해석됩니다. pAge는 변수는 변수이되 그 내용이 약간 다를 뿐입니다.
여기의 예에서 pAge는 0으로 초기화되어 있습니다. 그 값이 0인 포인터를 널 포인터라고 합니다. 모든 포인터는 그들이 생겨날 때 어떤 값으로 초기
화가 됩니다. 포인터에 어떤 값을 초기화시켜야 할지 모르겠거든 0으로 초기화합시다. 초기화 되지 않은 포인터를 와일드(wild) 포인터라고 합니다.
♨ 주의 포인터는 항상 초기화 합시다.
여러분이 포인터를 0으로 초기화했다면 언젠가는 howOld의 주소를 pAge에 저장해야 합니다. 다음과 같이 저장합니다.
Unsigned short int howOld = 50; // 변수를 만듦
Unsigned short int *pAge = 0; // 포인터 만듦
pAge = &howOld; // howOld의 주소를 pAge에 저장
줄여서 unsigned short int *pAge = &howOld; // 포인터르 만듦
이제 pAge는 howOld의 주소를 저장하는 포인터입니다. pAge를 사용하여 여러분은 실제적으로 howOld의 값을 알 수 있습니다.
포인터 pAge를 사용하여 howOld에 접근하는 것을 간접 지정(indirection)이라 합니다. 왜냐하면 pAge를 경유하여 howOld로 접근하기 때문입니다.
♨ 간접지정 : 포인터에 지정된 주소값을 통해 원래의 변수에 접근하는 방식을 의미합니다. 포인터는 그 안에 지정된 주소값을 통해 그 주소에 저장된
값을 알 수 있는 간접적인 방식을 제공합니다.
포인터 이름
포인터는 일반 C++에서 가지는 변수명을 가질 수 있습니다. 이 책에서는 일단 포인터는 p로 시작하는 작명방식을 쓰고자 합니다.
간접 지정 연산자
간접 지정(indirection) 연산자(*)는 조회 연산자(dereference)라고도 부릅니다. 포인터가 조회되었을 때 그 포인터에 저장된 주소에 있는 값이 검색됩
니다. 일반적인 변수들은 그 값에 직접 접근 방식을 제공합니다. 가령 여러분이 yourAge라 불리는 unsigned short 정수형을 선언했다면, 여러분은
howOld의 값을 그 새로운 변수에 저장할 수 있습니다.
Unsigned short int yourAge;
yourAge = howOld;
포인터에는 원하는 값의 주소가 저장되어 있으므로 간접 지정 방식을 통해 접근합니다. 포인터 pAge를 통해 yourAge에 howOld 값을 저장하려면 다
음과 같이 합니다.
Unsigned short int yourAge;
yourAge = *pAge;
pAge 앞에 붙어있는 간접 지정 연산자(*)는 '이 주소에 저장된 값'을 의미합니다. 따라서 둘째 줄은 다음과 같이 해석됩니다. 'pAge에 저장된 값(이는
주소를 가리킴)을 가져와, 이 주소가 가리키는 곳으로 찾아가 저장된 값을 찾아 yourAge에 저장합니다.'
*pAge = 5; // 값 5를 pAge 안에 지정된 주소 위치에 저장하라.
포인터, 주소, 변수
포인터라는 변수
그 포인터 변수에 저장된 주소값
그 포인터 변수에 저장된 주소값이 가리키는 메모리를 찾아가면 저장되어 있는 실제값
Int theVariable = 5;
Int *pPointer = &theVariable;
theVariable은 5로 초기화된 정수형 값입니다.
pPointer는 정수형 값이 저장되는 주소를 저장하는 포인터입니다.
포인터를 사용한 자료 연산
포인터가 변수의 주소값을 할당받았다면, 그 변수의 자료에 접근하기 위해 여러분은 포인터를 쓸 수가 있습니다.
#include <iostream>
using namespace std;
typedef unsigned short int USHORT;
int main()
{
USHORT myAge; // 변수
USHORT *pAge = 0; // 포인터
myAge = 5;
cout << "myAge:" << myAge << "\n";
pAge = &myAge;
cout << "*pAge:" << *pAge << "\n\n";
cout << "*pAge = 7 \n";
*pAge = 7; // sets myAge to 7
cout << "*pAge:" << *pAge << "\n";
cout << "myAge:" << myAge << "\n\n";
cout << "myAge = 9\n";
myAge = 9;
cout << "myAge:" << myAge << "\n";
cout << "*pAge:" << *pAge << "\n\n";
return 0;
}
주소 관찰
포인터는 여러분이 주소를 실제 그 주소를 정확히 알지 못하더라도 연산할 수 있게 해줍니다. 오늘 이후로 포인터에 변수의 주소를 대입하면 그 주소
가 대입이 되고 그 포인터는 그 변수의 주소를 가지고 있다는 것을 자신 있게 알게 될 것입니다.
♨ 포인터가 가리키는 주소에 저장된 값에 접근하려면 간접 지정 연산자(*)를 씁시다.
모든 포인터를 유효한 주소값이나 널(0)로 초기화합시다.
포인터에 저장된 주소값과 그 주소의 메모리에 저장된 값을 구별할 줄 압시다.
포인터를 선언하려면 저장되는 주소가 가리키는 값의 변수형 또는 객체형을 쓰고, 포인터 연산자를 쓰며(*) 그 포인터의 이름을 씁시다.
Unsigned short int *pPointer = 0;
포인터에 값을 저장하거나 초기화할 때 저장하고자하는 주소의 변수명 앞에 주소 연산자(&)를 붙여 대입합니다.
Unsigned short int *pPointer = &theVariable;
포인터를 소환하려면 소환연산자(*)를 포인터 앞에 붙입니다.
Unsigned short int theVariable = *pPointer;
도대체 왜 포인터를 쓰는가?
여태껏 여러분은 변수의 주소를 가리키는 포인터에 대해 자세히 배웠습니다. 그런데 여러분은 이렇게 복잡한 방식이 맘에 안들 것입니다. 왜 값에
직접 접근할 수 있는 변수가 있는데 포인터를 쓰는 것일까요?
포인터 용도
- 자유 기억 장소의 자료를 다룰 때
- 클래스 멤버 자료와 클래스 멤버 함수에 접근할 때
- 함수에 주소 전달을 할 때
스택과 자유 기억 공간(Free Stor)
- 전역 공간
- 자유 기억 공간
- 레지스터
- 코드 영역
- 스택
지역 변수는 함수의 매개 변수와 함께 스택에 저장됩니다. 코드는 코드 영역에 저장되고 전역 변수는 전역 공간에 저장됩니다. 레지스터는 여러 잡다
한 일들을 합니다.(예를 들어, 스택의 꼭대기와 명령 포인터를 계속 추적하는) 그리고 나머지의 메모리는 자유 기억 공간(free store)로 분류되는데 힙
(heap)이라고 불립니다.
지역 변수의 문제점은 영원히 지속되지 않는다는 것입니다. 함수가 반환되면 지역 변수들은 버려집니다. 또한 전역 변수들은 그 문제를 해결했지만
프로그램의 모든 부분에서 제한 없이 접근할 수 있다는 대가를 치러야 합니다.
전화기에는 메모리 기능이 있습니다. 김군 집의 전화번호는 1번 단추에, 이군 집의 전화번호는 2번 단추에 기억을 시켜 넣는 것입니다. 이렇게 하면
여러분은 친구 집에 전화를 걸려고 일일이 그 번호를 다 입력을 하지 않아도 됩니다. 그 전화번호는 어디 구석에 넣어둡니다. 여러분이 번호 하나만
누르면 전화가 알아서 찾아가 줍니다. 그 자세한 전화번호는 무엇인지 모릅니다. 하지만 그 집에 통화는 할 수 있습니다.
여러분은 컴퓨터의 메모리에 주소를 통해 접근할 수 있습니다. 이는 실제 전화 번호 입니다. 하지만 여러분은 실제 메모리의 주소를 몰라도 됩니다.
단지 포인터에 잡아넣으면 됩니다. 특정 번호만 누르면 됩니다. 포인터는 여러분이 여러분의 자료에 자세한 주소를 몰라도 접근할 수 있게 해줍니다.
포인터는 간접 접근 방식으로 자료에 접근할 때 강력한 도구를 제공합니다. 모든 변수는 주소를 가지고 있는데 이는 주소 연산자 &를 통해 얻을 수
있습니다. 이 주소가 포인터에 저장됩니다.
포인터는 그 포인터가 가리키는 객체의 형을 쓰고 간접 연산자(*)를 붙인 뒤 포인터의 이름을 써서 완성시킵니다.
포인터는 객체, 또는 널을 가리키게 하여 초기화시킵니다.
간접 연산자(*)를 사용하여 포인터에 저장된 주소값을 접근합니다.
상수형 포인터를 선언하여 그 포인터에 다른 객체의 주소를 대입할 수 없게 하고 또 상수형 객체의 포인터를 선언하여 포인터가 가리키는 값을 바꿀
수 없게 할 수 있습니다. 힙에 new 객체를 만들어 포인터에 그 값을 대입할 수 있습니다. Delete 예약어를 호출하여 메모리를 풀어놓을 수 있습니다.
Delte는 메모리를 풀어놓지만 그 포인터를 파괴하지는 않습니다. 따라서 다른 주소값을 그 포인터에 저장할 수 있습니다.
간만에 포인터에 대해서 함 공부하면서 정리한 내용들은데 역시 포인터는 잘쓰면 해가 되고 잘못사용 독이 된다는 사실을 알았습니다…