Team Curio

비주얼 노벨 한국어 패치 팀 큐리오

프로그래밍

12.c 언어강좌 (9)

연이v 2009. 7. 10. 20:08
반응형
SMALL

=============================================================================
C 프로그래밍 9회
=============================================================================

이번 강좌 에서는 포인터에 대해서 알아보도록 하겠습니다.
포인터는 C언어에서 정말로 중요하죠.
C가 강력한 이유중 하나가 이 포인터 때문입니다.
대부분의 사람들이 C언어를 공부할때 바로 이 포인터를 가장 어렵게 생각합니다.
하지만 제가 생각하기엔 이 포인터는 그렇게 어려운 것이 아니라고 생각합니다.
어렵다는 생각을 갖지 마시고 포인터는 정말 쉬운 것이라는 생각을 갖고
이 강좌를 읽어 보시기 바랍니다.

1. 포인터가 도대체 무엇?
포인터의 정확한 이름은 '포인터형 변수' 입니다.
그냥 줄여서 포인터라고 하는 것이죠.
그럼 여기서 포인터는 변수라는걸 아셨겠죠?
변수에는 정수 형태, 장정수, 부동 소숫점 수, 문자 형태 등등이 있습니다.
그럼 포인터는 도대체 어떤 형태 일까요? 포인터는 바로 주소 형태 입니다.
그러니까 정수도 아니고, 문자도 아닌 메모리의 주소를 기억시키는 변수라는 것이죠.
포인터는 변수이고, 변수 중에서도 주소를 기억시키기 위한 변수라는 것을 꼭 기억해 두시면
다음은 잘 이해가 되실 겁니다.

2. 포인터는 어떻게? 선언을..
변수는 사용전에 선언을 해 주어야 합니다.
포인터 역시 변수 이므로 선언을 해 주어야 하겠죠?
포인터의 선언은 다음과 같이 합니다.
데이터형태* 포인터이름;
아까 포인터는 주소를 기억시키는 변수라고 했습니다.
그런데 주소에도 종류가 있죠? 정수 형태 변수의 주소, 문자 형태 변수의 주소 등등..
이런 어떤 형태의 주소인지를 지정해 주는 것이 데이터형태 입니다.
만약 주소의 형태가 특별히 정해져 있지 않았다면 void를 씀니다.
void를 써서 포인터를 선언하면 어떤 형태의 주소든지 넣을수 있죠.
그리고 *는 그 변수가 포인터임을 알리기 위해 써 주는 것이고, 포인터이름은 그 포인터를 상징하는
심볼입니다.
예를 들어 정수 형태의 변수의 주소를 넣는 포인터는
int* Pointer;
이렇게 선언합니다.
포인터 선언시 *의 위치는 데이터형태와 포인터이름 사이에라면 어느 곳에 와도 상관 없습니다.
예를 들어 아까 Pointer라는 변수는
int * Pointer;
이렇게 선언해도 되고,
int *Pointer;
이렇게 선언해도 상관 없습니다.

3. 포인터 연산자
C언어 에서는 포인터 사용시에 사용하는 연산자가 2가지가 있습니다.
그 두가지는 기존의 비트별 논리곱 연산자인 &와 곱셈 연산자인 *입니다.
포인터 연산자로 쓰일때 &는 주소 연산자라고 하고, *는 참조 연산자라고 합니다.

(1) 주소 연산자
주소 연산자란 어떤 변수의 주소를 얻는 연산자를 말합니다.
사용법은 주소를 얻고자 하는 변수의 앞에 &를 붙여 주기만 하면 됨니다.
그러니까
포인터 = &변수;
이렇게 하면 변수의 주소가 포인터에 들어가죠.
예를 들어
int Variable=10;
int *Pointer;
이렇게 두 변수를 선언했다고 할때,
Pointer = &Variable;
이렇게 해 주면 Variable라는 변수의 주소가 Pointer라는 변수로 들어갑니다.
이것을 실제 메모리 상의 구조를 통해 다시한번 살펴보죠.
처음에
int Variable=10;
int *Pointer;
이렇게 변수를 선언할 당시는
변수이름 | Variable Pointer 주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값          | 10 ? ? ?
다음과 같은 메모리 구조를 갖습니다.
여기서 Variable라는 변수는 실재 메모리 주소 1을 할당받았다고 하고, Pointer라는 변수는
실제 메모리 주소 3을 할당받았다고 합시다.
다음에
Pointer = &Variable;
이렇게 하면 Pointer라는 변수에 Variable의 주소인 1이 들어가서
변수이름 | Variable Pointer 주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값          | 10 ? 1 ?
이렇게 됨니다.

(2) 참조 연산자
포인터는 주소를 기억시키는 변수라는 것을 이미 배웠습니다.
그렇다면 그 기억시키고 있는 주소에 기억되어 있는 값을 사용하거나 다른 값으로 바꿀땐 어떻게 할까요?
그때 참조 연산자인 *를 사용 합니다.
사용법은
*포인터 = 값;
변수 = *포인터;
이런 식으로 사용하죠.
첫번째의 경우 포인터에 저장되어 있는 주소에 값을 넣어 줌니다.
두번째의 경우는 포인터에 저장되어 있는 주소에 기억되어 있는 값을 변수로 넣어 주게 되겠죠?
예를 들어..
int Var1=10, Var2;
int *Pointer;
이렇게 세개의 변수를 선언했고,
Pointer = &Var1;
이렇게 Pointer에 Var1의 주소를 넣었다고 합시다.
이때
*Pointer=5;
이렇게 해 주면 Pointer에 저장되어 있는 주소 즉 Var1이라는 변수의 주소에 5를 넣어 주므로
결국에는 Var1은 5가 됨니다.
그 다음으로
Var2=*Pointer;
이렇게 했다면 Pointer에 저장되어 있는 주소에 들어있는 값, 즉 Var1라는 변수의 주소에 들어있는 값인
5가 Var2에도 들어가 Var1, Var2가 모두 5가 되죠.
이걸 실재 메모리 구조를 보며 다시한번 살펴 볼까요?
처음에
int Var1=10, Var2;
int *Pointer;
이렇게 세개의 변수를 선언할 당시는
변수이름 | Var1 Var2 Pointer 주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값          | 10 ? ? ?
다음과 같은 메모리 구조를 갖는데, 여기서 Var1라는 변수는 실재 메모리 주소 1을 할당받았고,
Var2라는 변수는 실재 메모리 주소 2를, Pointer라는 변수는 메모리 주소 4을 할당받았다고 합시다.
이때
Pointer = &Var1;
이렇게 Pointer라는 변수에 Var1의 주소인 1을 넣어주면
변수이름 | Var1 Var2 Pointer 주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값          | 10 ? ? 1
이렇게 되겠죠?
그리고 다음으로
*Pointer=5;
이렇게 해 주면 Pointer에 기억되어 있는 주소 즉 1이라는 주소에 5를 넣어 주어
변수이름 | Var1 Var2 Pointer 주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값          | 5 ? ? 1
이렇게 됨니다.
그러므로 Var1의 값은 5로 바뀌게 됨니다.
마지막으로
Var2=*Pointer;
이렇게 하면 Pointer에 저장되어 있는 주소 즉 1이라는 주소에 들어있는 값인 5가 Var2에도 들어가
변수이름 | Var1 Var2 Pointer 주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값          | 5 5 ? 1
이렇게 Var1과 Var2에 모두 5가 들어가게 되죠.
4. 포인터에 초기값 주기. 변수는 선언시에 초기값을 줄수 있었죠?
그런데 포인터도 변수이므로 당연히 초기값을 줄수 있겠죠?
포인터에 초기값을 주는 방법은 일반 변수와 똑같습니다.
그러니까
int* Pointer=1;
이렇게 하면 1이라는 주소를 초기값으로 준 것이죠.
그런데 뭔가 이상하죠?
주소는 자기 마음데로 정하는게 아니거든요.
그래서 저렇게 선언하는 사람을 '바보'라고 하죠.
일반적으로 포인터에 초기값을 줄 때는
데이터형 변수;
데이터형* 포인터이름 = &변수;
이렇게 합니다.
우선 변수를 선언하고 그 변수의 주소를 초기값으로 주는 것이죠.
예를 들어
int Variable;
int* Pointer=&Variable;
이렇게 하면 Variable라는 변수의 주소가 Pointer라는 포인터에 초기값으로 들어갑니다.

5. 다시한번 포인터에 대해 정리.
포인터에 대해 다시한번 정리를 해 보죠.
우선 포인터는 변수이고, 변수 중에서도 주소를 기억시키는 변수 입니다.
포인터 선언은
데이터형태* 포인터명;
이렇게 해 주면 되고, 여기서 데이터형태는 그 포인터가 어떤 형태의 변수의 주소를 넣는 것인지를
지정하는 것이었죠?
다음으로 포인터 연산자에는 주소 연산자(&)와, 참조 연산자(*)가 있는데, 주소 연산자는
어떤 변수의 실제 주소를 얻고자 할때 사용하는 것으로 주소를 얻고자 하는 변수 앞에 붙여 주기만 하면 되고요.
참조 연산자는 포인터에 저장되어 있는 주소에 기억되어 있는 데이터를 변경시키거나 사용할때 쓰는 것으로, 포인터의 앞에 붙여 주기만 하면 됨니다.
예를 들어
int Variable;
int* Pointer;
이렇게 변수 두개가 있을때,
Pointer=&Variable;
이렇게 하면 Pointer라는 포인터에는 Variable라는 변수의 주소가 들어가고
*Pointer=10;
이렇게 하면 Pointer에 들어있는 주소 즉 Variable의 주소에 들어있는 값이 변경되므로
결국은 Variable가 변경되어 10이 들어가게 되죠.
마지막으로 포인터에 초기값을 줄때는 일반 변수와 똑같으나 주소는 마음데로 정하는 것이
아니므로 일반적으로 일단 변수를 선언하고 포인터에는 그 변수의 주소를 초기값으로 줍니다.
예를 들어
int Variable;
int* Pointer=&Variable;
이런 식으로.
그럼 예제를 하나 보죠.

/* 파일 이름 : C9-1.C 프로그램 내용 : 포인터 예제. */
#include void main()
{
int Var1, Var2;
int *pVar=&Var1;
*pVar=10;
printf("%d\n",Var1);
Var2=*pVar;
printf("%d\n",Var2);
pVar=&Var2;
*pVar=5;
printf("%d\n",Var2);
}

한번 잘 분석해 보세요. 어떤 결과가 나올까요?
결과는

C:\>C9-1.EXE
10
10
5
C:\>

이렇게 나옴니다.
이유는 잘 분석해 보시면 알게 됨니다.

6. 포인터의 배열
포인터를 배열로 만들수가 있을까요?
당연히 할수 있겠죠?
C로는 못하는게 없으니..
포인터의 배열을 만드는 것은 정말 쉽습니다.
일반 변수의 배열에 대해서는 배웠는데 그거랑 똑같이 하면 되지만
단지 데이터형태와 포인터배열명 사이에 *를 넣어 주면 되죠.
그러니까
데이터형태* 포인터배열명[크기];
이렇게 해 주면 됨니다.
예를 들어 정수 형태의 변수의 주소를 저장하는 10개의 원소를 가진 포인터배열은
int* PointerArray[10];
이렇게 선언하죠.
2차원 배열 역시 일반 배열과 똑같지만 단지 *만 넣어 주면 되죠.
그러니까
int* PointerArray[10][10];
이런 식으로 해 주면 되죠.
그리고 3차원 배열은
int* PointerArray[10][10][10];
이런 식으로 하고요.

7. 포인터를 어디에 써먹지?
지금까지 포인터에 대해 배웠습니다.
그런데 이 포인터를 과연 어디에 써먹을수 있을까요?
저번 강좌에서 일반 변수를 함수 호출시 인수로 전해주면 단지 변수에 들어있는 값을 복사해서
전해주는 Passing by value라는 호출 방법을 쓴다는 것을 배웠습니다.
그리고 배열의 경우 그 주소를 전해 주는 Passing by reference라는 방법을 쓴다는 것도 알았죠.
그럼 일반 변수를 Passing by reference로 전해 줄수는 없을까요?
포인터를 이용하면 이것이 가능합니다.
우선 방법을 알아보기 전에 예제 하나를 보죠.

/* 파일 이름 : C9-2.C 프로그램 내용 : 포인터 예제. */
#include void func(int data)
{
data=10;
}
void main()
{
int var=1;
func(var);
printf("%d\n",var);
}

이 소스는 Passing by value를 사용하는 함수 예제 입니다.
func함수에서는 data를 10으로 바꿨지만 main함수의 var은 바뀌지 않고 결국엔 1이 출력되죠.
그럼 이걸 Passing by reference로 바꿔 보죠.
꿀려면 과연 어떻게 해야 할까요?
우선 func함수 부터 고쳐야 겠죠?
Passing by reference는 주소를 전달 받는 것이니 인수를 포인터로 바꿔주어야 겠군요.

void func(int* data)
{
data=10;
}

이렇게 말이죠. 그런데 여기서 바꿀게 하나 더 남았죠?
data는 이제 포인터이므로 사용시에는 참조 연산자를 써서 사용해야 겠죠?


void func(int* data)
{
*data=10;
}

이렇게 바꿔야 하죠.
그럼 func함수는 다 고쳤네요.
그런데 main함수에서도 바꿀게 있죠?
func함수를 호출할때 그냥 변수를 보내면 않되고 주소를 보내야 하잖아요.
그러므로
func(&var);
이렇게 호출해주면 되겠죠?
이렇게 해서 바꾼 소스를 보도록 하죠.

/* 파일 이름 : C9-3.C 프로그램 내용 : 포인터 예제. */
#include void func(int* data)
{
*data=10;
}
void main()
{
int var=1;
func(&var);
printf("%d\n",var);
}

이렇게 되겠내요.
그럼 이젠 Passing by reference로 호출하게 됨니다.
그러므로 1이 아닌 10이 출력되게 되죠.
이렇게 해서 이번 강좌는 마치도록 하겠습니다.
다음 강좌에서는 포인터와 배열의 관계에 대해 알아보죠
반응형

'프로그래밍' 카테고리의 다른 글

14.c 언어강좌 (11)  (0) 2009.07.10
13.c 언어강좌 (10)  (0) 2009.07.10
11.c 언어강좌 (8)  (0) 2009.07.09
10.c 언어강좌 (7)  (0) 2009.07.09
9.c 언어강좌 (6)  (0) 2009.07.04