포인터라는 개념은 C의 대표적인 특징 중의 하나로, 포인터를 쓰면 여러 가지 형의 데이터를 마음대로 참조할 수 있다. 그러나 사용 방법이 틀리면 프로그램이 폭주하거나 이해하기 어려운 프로그램이 만들어질 위험성이 있다. C를 처음 공부하는 대부분이 최초로 만나는 벽이 포인터라고도 한다. 이번 주 강의에서는 포인터의 개념과 사용법에 관해서 설명하고, 포인터와 배열, 포인터와 문자열의 관계에 대해서도 설명한다.
1. 포인터란?
포인터(pointer)는 C의 대표적인 특징이다. 포인터를 사용함에 따라서 메모리상의 데이터를 쉽게 사용할 수 있다. 잘못 이해하면 수정이 곤란한 프로그램을 작성할 가능성이 있지만, 포인터 사용법에 익숙해지면 명쾌하고 훌륭한 프로그램을 작성할 수 있다.
(1) 포인터의 선언
포인터 변수는 포인터(주소값)를 저장하는 변수로 다음과 같이 *을 붙여 선언한다.
int *pa;
이것은 변수 pa가 int형 데이터를 참조하기 위한 포인터 변수인 것을 의미한다. 즉 pa는 (int *)형의 변수를 선언하고 있는 것이다. 여기서 주의할 사항은 포인터 변수명은 pa이며 *pa는 아니라는 것과 포인터 변수 pa의 형이 int형이 아니고 pa에서 참조할 수 있는 데이터형이 int형이라는 것이다. 따라서 char형 데이터를 지정하는 포인터 pb는 다음과 같이 선언된다.
char *pb;
그러면 (int *)형의 포인터 변수 pa와 (char *)형의 포인터 변수 pb가 참조할 수 있는 데이터가 각각 int형과 char형이라는 것이며 포인터 변수의 크기는 모두 주소를 넣어두는데 필요한 크기로 대개 int형과 같은 크기이다.
(2) 포인터 연산자
포인터 연산을 위해 다음 두 가지 연산자가 필요하다. 첫 번째로 포인터가 지정하는 내용을 갖는 내용(content) 연산자 *와 두 번째로 변수가 할당되어 있는 메모리상의 주소값을 갖는 주소(address) 연산자 &이다. 예를 들어, (int *)형의 포인터 변수 pa에 대하여 (위에서 선언된 포인터 변수를 가정하자)
pa = &a;
라고 하면, 변수 a의 메모리상의 주소가 pa에 대입된다. 이렇게 처리한 후, *pa라 하면 pa가 지정하는 메모리의 데이터를 얻을 수 있다. 따라서
b = *pa;
은 변수 a 의 내용이 변수 b에 대입된다.
2. 포인터와 문자열
C로 문자열을 취급할 경우에는 포인터를 이용하면 편리하다. 문자열을 Hyolee로 정의하고자 하면,
char *str;
str = "Hyolee";
라고 표시한다. C 컴파일러는 Hyolee라는 문자열을 메모리에 저장하고 마지막에 '\0'을 첨가시킨다. 그리고 str="Hyolee"에 의하여 "Hyolee"를 지정할 문자열이 저장된 메모리의 시작 주소를 포인터 str에 전달한다. 즉, "Hyolee"라는 문자열 상수는 그 문자열이 저장되어 있는 시작 주소를 식의 값으로서 보유하고 있는 것이지 문자열의 실체가 아니라는 것에 주의해야 한다.
그러면 포인터 str은 문자열 "Hyolee"의 처음 주소(시작 주소, 문자열의 첫 번째 문자의 주소)를 지정하고 있으므로 *str이라고 하면 문자 'H'를 취하게 된다. 여기서 str++ 로 하면 포인터 변수 str의 값이 +1씩 증가되므로 이 때의 *str은 다음 문자(y)를 가리키게 된다. 따라서 str로 지정하는 문자열을 차례대로 취해 가려면 다음과 같이 된다. (문자열을 메모리에 저장하고 마지막에 '\0'을 첨가시킨다는 것을 상기하자)
while(*str != ' \0'){
str++;
}
예제 1. 문자열의 문자를 1문자씩 취해서 표시하여라.
● 프로그램
#include <stdio.h>
#include <string.h>
main()
{
char *str;
str = "Hyolee"; /* ① */
while (*str != '\0') { /* ② */
printf("%c\n", *str);
str++; /* ③ */
}
}
● 해설
① 포인터 str 에는 문자열 "Hyolee"의 처음 주소(첫번째 문자의 주소)가 대입된다.
② *str 에 의하여 str로 표시되는 주소의 문자를 취한다.
③ 포인터를 다음 문자로 진행시킨다. 즉 str에 저장되어 있는 주소값이 +1씩 증가된다.
● 결과
H
y
o
l
l
e
<참고> 문자열이란?
문자열(character string)이란 하나 이상의 문자들의 연속이다.
"I love you."
I |
|
l |
o |
v |
e |
|
y |
o |
u |
. |
\0 |
1. 문자열은 문자들의 배열로 저장된다.
예) char name[40];
name[0] = 'I';
name[1] = ' ';
name[2] = 'l';
name[3] = 'o';
....
name[10] = '.';
name[11] = '\0';
2. 문자열의 끝은 null 문자('\0')로 표시한다.
3. 문자 'X'와 문자열 "X"는 다르다.
문자 'X'의 메모리 상태
X |
문자열 "X"의 메모리 상태
X |
\0 |
예제 2: 문자열을 사용한 프로그램 예
#include <stdio.h>
#define MESSAGE "Welcome to the C programming world !!!"
main() /* Example of a character string */
{
char name[30];
printf("Input your name : ");
scanf("%s", name);
printf("Hello %s, %s\n", name, MESSAGE);
}
● 결과
Input your name: Hyolee (사용자가 입력한 자신의 이름)
Hello Hyolee, Welcome to the C programming world!!!
3. 포인터와 배열
(1) 포인터와 1차원 배열
C에서는 포인터를 사용해서 배열의 요소를 참조할 수 있다. 따라서 포인터와 배열은 밀접한 관계가 있다. 예를 들어
int a[10];
라는 배열이 있을 때
int *pa;
라는 포인터 변수를 선언하고
pa = a;
라고 하면 포인터 pa를 사용해서 배열 a[0]∼a[9]를 참조할 수 있다. 배열명 a는 배열의 시작 주소(첫번째 요소의 주소)를 지정하는 포인터 상수라고 생각할 수 있다. 따라서 pa=a;에 의하여 배열의 시작 주소가 pa에 전달되기 때문에 *pa라고 하는 것에 따라 a[0]의 내용을 참조할 수 있게 된다. *(pa+i)라고 하면 i+1번째의 요소 a[i]가 참조된다. 다시 포인터 변수 pa를 사용해서 pa[0], pa[1]… pa[i]라는 표시가 가능하다. [ ]는 주소 계산을 하는 연산자로 생각할 수 있다.
여기서 주의할 사항은 pa[0]∼pa[9]라는 영역이 새롭게 확보되는 것이 아니라 기존에 존재하는 배열 a를 포인터 변수 pa를 사용해서 a[0] ∼a[9]를 참조한다는 것이다.
(2) 포인터와 2차원 배열
2차원 배열과 포인터의 관계를 살펴보자. 가령
int a[4][3];
이라고 2차원 배열을 선언했을 때 메모리상에는 a[0][0]∼a[3][2]까지의 각 요소들이 일렬로 확보된다.
a[0][0] |
a[0][1] |
a[0][2] |
a[1][0] |
a[1][1] |
a[1][2] |
a[2][0] |
a[2][1] |
a[2][2] |
a[3][0] |
a[3][1] |
a[3][2] |
지금
int *pa;
라는 포인터를 선언하고,
pa = a;
라 하면, 포인터 변수 pa에 2차원 배열의 시작 주소를 전달할 수 있다. 이렇게 한 후 *pa라고 하면, a[0][0]의 내용을 취하고,*(pa+3)라고 하면 a[1][0] 의 내용을 얻을 수 있다. 이와 같이 포인터를 사용해서 2차원 배열을 직접 참조할 수 있다. C에 있어서 2차원 배열 a[i][j]의 a[i],a[i][j]는 각각 서로 다른 의미를 갖고 있다. 배열명 a는 2차원 배열의 선두 번지를 표시하는 상수이고 a[i]는 i+1번째 행(a[i][0], a[i][1])의 선두 번지를 표시하는 상수이며, a[i][j]는 i+1번째 행 j+1번째 열의 요소를 가리키는 변수이다. 따라서
pa = a[1];
라 하고, *pa 로 하면 이것은 a[1][0]을 참조하는 것이 된다. 이와 같이 C에서는 배열의 참조를 융통성 있게 실행하도록 a(i,j)라는 표현은 사용하지 않고 a[i][j]라는 분리 가능한 표현을 한다. 더욱이 a나 a[i]는 상수이므로 이것들에 대한 대입은 실행이 안 되기 때문에 주의해야 한다.
확인문제 1
/* 배열의 각 원소의 주소를 출력하는 프로그램 */
#include <stdio.h>
#define SIZE 4
void main(void)
{
int array[SIZE] = {5, 10, 15, 20}, index;
double real[SIZE] = {5.5, 10.10, 20.20, 30.30};
for(index = 0; index < SIZE; index++)
printf("Address of array[%d] is %u === Adress of real[%d] is %u\n",
index, array + index, index, real + index);
}
이 프로그램의 의도는 comment에 있는 것처럼 배열의 각 원소의 주소를 출력하는 프로그램입니다. 즉, 실행 결과에서 보듯이 배열의 시작 주소인 배열명(포인터 상수)에 1을 더하면, 각 원소의 자료형의 크기인 바이트 수만큼 주소가 증가한다는 것을 알 수 있지요. int 형은 (터보 씨의 경우) 기억 장소를 2바이트 사용하여 표현되고, double 형은 8바이트를 사용하여 표현되므로 포인터에 1을 더한다고 할 때 int 형의 바이트 크기인 2만큼 증가되고 double 형의 경우에는 8만큼 증가됩니다. 다시 말하면 포인터에 1을 더한다는 말은 배열의 한 원소의 크기만큼 증가한다는 것입니다. 배열의 시작 주소를 나타내는 배열명(이 프로그램에서는 array)에 index만큼을 증가시킨다는 것은 다음과 같은 의미를 가집니다.
array + index <=> &array[0] + index *sizeof(배열의 자료형)
따라서 이 프로그램의 실행 결과에서 보듯이 int형 포인터가 1만큼 증가되면 int 형의 바이트 크기인 2 바이트만큼 주소가 증가되고, double 형 포인터가 1만큼 증가되면 double 형의 바이트 크기인 8만큼 증가됩니다.(기억 장소의 주소는 바이트 단위로 할당되는 것 아시죠?)
기억 장소에 할당된 배열의 각 원소에 저장된 데이터를 참조할 수 있는 방법은 3가지 방법이 있습니다.
(1) 참조하고자 하는 배열 원소의 첨자를 사용한다. --- array[i]
(2) 배열명과 간접 연산자를 사용한다. --- *(array + i)
(3) 포인터 변수에 배열을 할당하고 간접 연산자를 사용한다. --- *(pt + i)
즉, 위의 3가지 참조 방법으로부터, 배열의 i번째 원소에 저장된 값을 얻고자 한다면 다음과 같이 할 수 있습니다.
array[i] <=> *(array + i) <=> *(pt + i)
유사하게 컴퓨터 내부의 기억 장소에 할당된 배열의 각 원소에 대한 주소는 다음과 같이 3가지 방법에 의해서 얻을 수 있습니다.
(1) 배열 원소에 주소 연산자('&')를 사용한다. --- &array[i]
(2) 배열명을 사용하여 특정 원소(i)의 주소를 구한다. --- array + i
(3) 포인터 변수에 배열을 대입한 다음 배열의 특정 원소의 주소를 포인터 변수를 사용하여 계산할 수 있다. --- pt + i
이상의 결과로 부터 배열의 i번째 주소를 얻고자 한다면 다음과 같이 할 수 있습니다.
&array[i] <=> array + i <=> pt + i
확인문제 2
/* 1차원 배열과 포인터의 관계 예 */
#include <stdio.h>
#define SIZE 4
void main(void)
{
int array[SIZE] = {5, 10, 15, 20}, i, *pt;
/* Data print */
pt = array;
for(i = 0; i < SIZE; i++)
printf("array[%d] = %2d, *(array + %d) = %2d, *(pt + %d) = %2d\n",
i, array[i], i, *(array + i), i, *(pt + i));
putchar('\n');
/* Address print */
for(i = 0; i < SIZE; i++)
printf("&array[%d] = %u, array + %d = %u, pt + %d = %u\n",
i, &array[i], i, array + i, i, pt + i);
}
(3) 문자열 복사
문자열을 복사하는 경우를 생각해 보자. char *str; 라고 선언된 포인터에 str="Hyolee"; 라고 하는 경우 문자열의 실체를 복사하는 것이 아니고 단순히 문자열의 처음 주소(첫번째 문자의 주소)를 대입하고 있다는 것이다. 문자열의 실체를 복사 이동하는 곳은 포인터가 아닌 배열이어야 한다. 여기서 간단히
char s[80];
로 선언하고
s = "Lee Hyolee";
라고 지정하는 것은 잘못된 것이다. 배열명 s는 상수이므로 이러한 대입은 안된다. 따라서 문자열을 배열 s[ ]에 복사하는데는 다음과 같이 1문자 단위로 복사해야만 한다.
i = 0;
while(*str != '\0') {
s[i] = *str;
i++;
str++;
}
s[i] = '\0';
문자가 '\0'일 때는 s[i]에 대입하지 않고 루프를 빠져 나오므로 루프 밖에서 '\0'을 첨가시켜야 한다. 위의 프로그램은 다시 다음과 같이 쓸 수 있다.
i = 0;
while(*str != '\0'){
s[i++] = *str++
}
s[i] = '\0';
또 다시 더욱 간단하게
i = 0;
while((s[i++] = *str++) != '\0')
;
라고 쓸 수 있다.
예제 3. 포인터를 이용해서 문자열을 배열로 복사하여라.
● 프로그램
#include <stdio.h>
main()
{
char *str, *ds, s[80];
str = "Hyolee zzang!";
ds = s;
while ((*ds = *str) != '\0') {
ds++;
str++;
}
printf("%s\n",s);
}
● 결과
Hyolee zzang!
4. 포인터의 배열
C에서는 포인터 변수의 배열도 가능하고 이것은 여러 개의 문자열 데이터를 참조하는 경우에 많이 사용된다. 포인터 배열은
char *name[3];
으로 선언한다. 이것에 의하여 name[0], name[1], name[2]라는 3개의 char형 데이터를 참조하는 포인터 배열이 준비된다.
포인터 배열을 사용해서 문자 데이터를 초기화하려면
static char *name[ ]={
"Hyolee",
"Joohyun",
"Yoori"
};
라고 표시한다.
포인터 배열을 사용해서 문자열의 각 문자를 참조하고자 하는 경우에는 name[0][2] 등과 같이 표시한다. 이것으로 문자열 "Hyolee"의 세 번째 문자인 'o'가 참조가 된다.
예제 4. 포인터 배열을 사용해서 저장된 문자열 데이터 중 각 데이터의 첫번째 문자가 H인 것만을 표시하여라.
● 프로그램
#include <stdio.h>
#define N 4
main()
{
char *name[]={"Hyolee","Joohyun","Hyori","Yoori"};
int i;
for (i=0; i<N; i++)
{
if (name[i][0]=='H')
printf("%s\n",name[i]);
}
}
● 해설
선두 문자는 name[i][0]이고 문자열의 선두 주소는 name[i]이다.
●결과
Hyolee
Hyori
5. 포인터의 주소 계산
지금까지는 pa++나 pb++에 의하여 다음 데이터를 참조할 수 있다고 했다. 그러나 int형 데이터가 2바이트 또는 4바이트라는 것을 생각하면 pa++에 의해 pa가 +1씩 증가된 것으로 다음의 데이터를 참조할 수는 없다. 따라서 pa++이 pa의 산술적인 1의 증가를 의미하는 것은 아니다. 실은 C에 있어서 포인터 변수의 연산(pa++나 pa=pa+1)에는 포인터형에 의해서 정해진 α가 더해진다.
pa = pa + α
α는 일반적으로 char형이면 1, short int형이면 2, long int형이면 4, float형이면 4, double형이면 8이 된다.
확인문제 3
/* 포인터에 적용되는 6가지 기본 연산의 예 */
#include <stdio.h>
#define SIZE 4
void main(void)
{
int array[SIZE] = {15, 20, 25, 30}, *pt1, *pt2;
pt1 = array;
pt2 = &array[2];
printf("&array[0] = %u, pt1 = %u, *pt1 = %d, &pt1 = %u\n",
array, pt1, *pt1, &pt1);
pt1++;
printf("&array[1] = %u, pt1 = %u, *pt1 = %d, &pt1 = %u\n",
&array[1], pt1, *pt1, &pt1);
printf("&array[2] = %u, pt2 = %u, *pt2 = %d, &pt2 = %u\n",
&array[2], pt2, *pt2, &pt2);
pt2++;
pt1--;
printf("&array[3] = %u, pt2 = %u, *pt2 = %d, &pt2 = %u\n",
&array[3], pt2, *pt2, &pt2);
printf("Relative position: pt2 - pt1 = %u elements\n",
pt2 - pt1);
printf("Relative address: pt2 - pt1 = %u\n",
(pt2 - pt1) * sizeof(*pt1));
pt1 = pt2;
printf("pt1 = %u, pt2 = %u, *pt1 = %d, *pt2 = %d\n",
pt1, pt2, *pt1, *pt2);
}
포인터간의 뺄셈 연산을 수행하려면 반드시 두 개의 포인터가 동일한 자료형의 대상체를 포인트하고 있어야 합니다. 즉, 두 개의 포인터가 동일한 배열의 서로 다른 원소를 가리키고 있을 때 가능합니다. 만약 double형 포인터에서 int형 포인터의 뺄셈을 수행하면 컴파일러는 "Suspicious pointer conversion"이라는 오류 메시지를 출력할 것입니다.
한 배열의 서로 다른 원소를 가리키는 두 개의 포인터간에 뺄셈을 수행하면 두 개의 포인터가 가리키는 원소의 위치차를 계산하게 됩니다. "6-5 포인터의 주소 계산"에서 설명한 것처럼 이는 마치 포인터에 1을 증가시키면 다음 원소로 포인터가 증가하는 것과 동일합니다. 확인 문제 6-3에서 포인터의 상대적 위치(주소)를 계산하는 것을 보았습니다.
printf("Relative position: pt2 - pt1 = %u elements\n", pt2 - pt1);
즉, 위의 실행문은 포인터 변수 p2가 가리키는 배열의 원소의 위치로부터 포인터 변수 p1이 가리키는 배열의 원소의 의치를 감산하는데, 이 때 결과는 배열의 원소간의 상대적 위치의 차가 됩니다. 따라서 컴푸터 내부에서 배열 원소간의 위치의 차를 계산하기 위해 실제 연산의 수행은 다음과 같이 수행한다고 생각하면 됩니
다.
(pt2 - pt1) / sizeof(배열의 자료형) <=> (8902 - 8896) / 2
따라서, 연산의 실행 결과는 3이 됩니다. 즉, 실제 두 포인터에 저장된 주소의 차는 6이 되지만 배열의 자료형이 int형이므로 배열 원소간의 상대적 위치의 차를 계산하기 위해서 컴파일러는 int형의 바이트 크기로 나누게 되는 것입니다.
확인문제 4
/* 두 포인터간의 상대적 위치를 이용하여 배열 중앙의 상대적 주소를 계산하는 프로그램 */
#include <stdio.h>
#define SIZE 7
void main(void)
{
int array[SIZE] = {15, 20, 25, 30, 35, 40, 45};
int *low, *high, *mid, i;
low = array;
high = &array[SIZE-1];
for(i = 0; i < SIZE; i++)
printf("&array[%d] = %u, array[%d] = %d\n",
i, &array[i], i, array[i]);
mid = low + ((high - low) / 2);
printf("low = %u, high = %u, high - low = %u\n",
low, high, high - low);
printf("mid = %u, array[%d] = %d\n", mid, (high - low )/ 2, *mid);
}
확인문제 5
/* &array[i], array + 1, pt + i, &pt[i]를 이용한 배열 원소의 주소 참조
방법과 array[i], *(array + 1), *(pt + i), pt[i]의 배열 원소의 참조 방법*/
#include <stdio.h>
#define SIZE 4
void main(void)
{
int array[SIZE] = {15, 20, 25, 30}, i, *pt;
pt = array;
printf("array[i] *(array + 1) pt[i] *(pt + 1) \n");
printf("----------------------------------------------\n");
for(i = 0; i < SIZE; i++)
printf("%5d %12d %11d %9d\n",
array[i], *(array + i), pt[i], *(pt + i));
putchar('\n');
printf("&array[i] array + 1 &pt[i] pt + 1 \n");
printf("-------------------------------------------\n");
for(i = 0; i < SIZE; i++)
printf("%7u %10u %11u %9u\n",
&array[i], array + i, &pt[i], pt + i);
}
확인문제 6
/* *pt++, (*pt)++, *++pt, ++*pt의 연산을 확인하는 예제 프로그램 */
#include <stdio.h>
#define SIZE 5
void main(void)
{
int array[SIZE] = {10, 15, 20, 25, 30}, data, *pt;
pt = array;
printf("--- data = *pt++ Operation ---\n");
printf("Before => pt = %5u, *pt = %3d\n", pt, *pt);
data = *pt++;
printf("After => pt = %5u, *pt = %3d, data = %3d\n\n", pt, *pt, data);
printf("--- data = (*pt)++ Operation ---\n");
printf("Before => pt = %5u, *pt = %3d\n", pt, *pt);
data = (*pt)++;
printf("After => pt = %5u, *pt = %3d, data = %3d\n\n", pt, *pt, data);
printf("--- data = *++pt Operation ---\n");
printf("Before => pt = %5u, *pt = %3d\n", pt, *pt);
data = *++pt;
printf("After => pt = %5u, *pt = %3d, data = %3d\n\n", pt, *pt, data);
printf("--- data = ++*pt Operation ---\n");
printf("Before => pt = %5u, *pt = %3d\n", pt, *pt);
data = ++*pt;
printf("After => pt = %5u, *pt = %3d, data = %3d\n\n", pt, *pt, data);
}
확인문제 7
/* 2차원 배열 array[3][2]의 주소를 확인하는 프로그램 */
#include <stdio.h>
void main(void)
{
int array[3][2];
printf(" array = %u, array[0] = %u, &array[0][0] = %u\n",
array, array[0], &array[0][0]);
printf(" *array = %u, &array[0] = %u, &array[0][0] = %u\n",
array, &array[0], &array[0][0]);
printf("(array + 1) = %u, array[1] = %u, &array[1][0] = %u\n\n",
array + 1, &array[1], &array[1][0]);
printf("(array + 1) = %u, array[0] + 1 = %u, &array[0][0] + 1 = %u\n",
array + 1, array[0] + 1, &array[0][0] + 1);
printf("(array + 1) = %u, &array[0] + 1 = %u, &array[1][0] + 1 = %u\n",
array + 1, &array[0] + 1, &array[1][0] + 1);
putchar('\n');
printf(" sizeof(array) = %d\n", sizeof(array));
printf(" sizeof(array[0]) = %d\n", sizeof(array[0]));
printf("sizeof(array[0][0]) = %d", sizeof(array[0][0]));
}
터보 씨에서는 각각 12, 4, 2입니다. 프로그램 전체를 설명하지요. 확인 문제 6-7의 첫 번째 실행 결과는 다음과 같다고 가정합시다. 항상 일정하지는 않겠지요?
array = 65480, array[0] = 65480, &array[0][0] = 65480
위의 결과에서 보듯이 array, array[0], &array[0][0]은 모두 배열 array의 시작 주소인 65480을 갖는다는 것을 알 수 있습니다. 즉, 배열명 array는 부분 배열의 시작 주소인 &array[0]을 갖게 되며, 부분 배열 array[0]은 자신의 첫 번째 원소의 시작 주소 &array[0][0]을 갖게 되므로 모두 동일하다는 것입니다. 두 번째 출력도 유사하게 *array는 &array[0]의 주소를 갖게 되므로 역시 65480 번지를 갖게 됩니다.
(array+1) = 65480, array[1] = 65480, &array[1][0] = 65480
위의 세 번째 출력에서 array + 1은 배열의 시작 주소(&array[0])에 1을 더하게 되므로 siaeof(array[0])의 크기만큼 증가하게 됩니다. 따라서 4만큼 증가한 65484 번지가 됨을 알 수 있습니다.
(array+1) = 65484, array[0]+1 = 65482, &array[0][0]+1 = 65482
(array+1) = 65484, &array[0]+1 = 65484, &array[0][0]+1 = 65486
위의 네 번째 출력과 다섯 번째 출력에서 array[0] + 1은 65482 번지를 가리키고, &array[0] + 1이 65484 번지를 가리킨다는 것을 알 수 있습니다. 먼저 네 번째 출력의 array[0] + 1을 살펴보면 array[0]은 &array[0][0]과 동일합니다. 따라서 array[0] + 1은 &array[0][0] + 1과 동일하므로 &array[0][1]이 됩니다. 그러나 다섯 번째 출력에서 &array[0]은 부분 배열 array[0]의 시작 주소이므로 array와 동일하기 때문에 &array[0] + 1은 array + 1과 동일하므로 65484 번지가 됩니다.
결론적으로 array와 &array[0]는 서로 동일하며, 1만큼 증가시켰을 때(array + 1, &array[0] + 1) 부분 배열의 크기 만큼 번지가 증가하게 되고, *array, array[0], &array[0][0]은 서로 동일하며 1만큼 증가시켰을 때(*array + 1, array[0] + 1, &array[0][0] + 1) 최하위 배열의 원소의 크기인 int 형 크기만큼 주소를 증가시킵니다. 이를 확인하는 출력이 다음의 마지막 세 줄로 출력되는 sizeof 연산자입니다.
sizeof(array) = 12
sizeof(array[0]) = 4
sizeof(array[0][0]) = 2
위의 결과에서 보는 것 처럼 배열 array는 전체 배열을 구성하고 있는 원소의 바이트 수인 12(터보 씨에서)를 출력하게 되며, 부분 배열 array[0]는 부분 배열에 포함된 원소들의 바이트 수인 4(터보 씨에서)를 출력하게 됩니다. 그리고 마지막으로 array[0][0]는 int 형 원소를 표현하기 위한 바이트 수인 2(터보 씨에서)가 됩니다.
확인문제 8
/* 포인터와 간접 연산자, 배열의 첨자를 이용하여 배열의 특정 원소를 접근하는 프로그램 */
#include <stdio.h>
void main(void)
{
int array[3][2] = {{10, 20}, {100, 200},{1000, 2000}};
printf("array[0][0] = %d, *(array[0]) = %d, *(*(array)) = %d\n",
array[0][0], *(array[0]), *(*(array + 0)));
printf("array[0][1] = %d, *(array[0]+1) = %d, *(*(array)+1) = %d\n\n",
array[0][1], *(array[0] + 1), *(*(array + 0) + 1));
printf("array[1][0] = %d, *(array[1]) = %d, *(*(array+1)) = %d\n",
array[1][0], *(array[1]), *(*(array + 1)));
printf("array[1][1] = %d, *(array[1]+1) = %d, *(*(array+1)+1) = %d\n\n",
array[1][1], *(array[1] + 1), *(*(array + 1) + 1));
printf("array[2][0] = %d, *(array[2]) = %d, *(*(array+2)) = %d\n",
array[2][0], *(array[2]), *(*(array + 2)));
printf("array[2][1] = %d, *(array[2]+1) = %d, *(*(array+2)+1) = %d\n",
array[2][1], *(array[2]+1), *(*(array+2)+1));
}
확인문제 9
/* *(*(array + i) + j)의 수식을 확인하기 위한 프로그램 */
#include <stdio.h>
void main(void)
{
int array[3][2] = {{10, 20}, {100, 200},{1000, 2000}};
int i, j;
i = 2, j = 1;
printf(" array + i = %u, &array[i] = %u\n", array + i, &array[i]);
printf(" *(array + i) = %u, &array[i][0] = %u, array[i] = %u\n",
*(array + i), &array[i][0], array[i]);
printf(" *(array+i) + j = %u, &array[i][0] + j = %u, &array[i][j] = %u\n",
*(array + i) + j, &array[i][0] + j, &array[i][j]);
printf("*(*(array + i) + j) = %d, array[i][j] = %d\n",
*(*(array + i) + j), array[i][j]);
}
확인문제 10
/* 정방형 2차원 배열의 원소에 저장된 데이타의 이동 */
#include <stdio.h>
#define SIZE 2
void main(void)
{
int A[5][5];
int B[5][5], i, j, m, n;
int p, q, x, z, value;
x = n = z = value = 1;
m = SIZE;
p = 1;
q = 0;
/* 2차원 배열 A에 초기값 할당 */
for( i = 0; i <= SIZE; i++)
for( j = 0; j <= SIZE; j++)
*(*(A + i) +j) = value++;
for(i = 0; i <= SIZE; i++)
{
for(j = 0; j <= SIZE && p >= 0 && n <= SIZE; j++)
{
if( x <= SIZE ) /* 배열의 상위 부분 처리 */
{
if(i == 0 && j == 0)
{
B[i][j] = A[i][j];
continue;
}
*(B[p] + q) = *(A[i] +j);
p--;
q++;
if( p < 0 && j <= SIZE)
{
p = ++x;
q = 0;
}
}
else /* 배열의 하위 부분 처리 */
{
B[m][n] = A[i][j];
n++;
m--;
if( n > SIZE )
{
n = ++z;
m = SIZE;
}
}
}
if( i == SIZE && j == SIZE) (*(B + i))[j] = (*(A + i))[j];
}
/* 이동 결과의 출력 */
for(p = 0; p <= SIZE; p++)
{
for(q = 0; q <= SIZE; q++)
printf("%-5d", B[p][q]);
putchar('\n');
putchar('\n');
}
}
확인문제 11
/* array, &array[0]와 array[0], &array[0][0]의 차이 */
#include <stdio.h>
void main(void)
{
int array[3][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}};
clrscr( );
printf("array = %u, &array[0] = %u, array[0] = %u, &array[0][0] = %u\n",
array, &array[0], array[0], &array[0][0]);
printf("array + 1 = %u, &array[0] + 1 = %u, array[1] = %u\n",
array + 1, &array[0] + 1, array[1]);
printf("array[0] + 1 = %u, &array[0][0] + 1 = %u, &array[0][1] = %u\n",
array[0] + 1, &array[0][0] + 1, &array[0][1]);
printf("sizeof(array[0]) = %d bytes.\n", sizeof(array[0]));
printf("sizeof(array[0][0]) = %d bytes.\n", sizeof(array[0][0]));
}
확인문제 12
/* 2차원 배열 포인터 pt의 연산을 확인하는 예제 */
#include <stdio.h>
void main(void)
{
double table[3][2];
double (*pt)[2];
pt = table;
printf(" pt = %u, *pt = %u, pt[0] = %u, &table[0][0] = %u\n",
pt, *pt, pt[0], &table[0][0]);
printf("pt + 1 = %u, *(pt + 1) = %u, pt[1] = %u, &table[1][0] = %u\n",
pt + 1, *(pt + 1), pt[1], &table[1][0]);
}
예제 5. 포인터 배열로 표시되는 문자 패턴으로 반복되는 모양을 작성하여라.
● 프로그램
#include <stdio.h>
#include <string.h>
main( )
{
char *finkl[ ]={"[ ]",
"[ ]",
"[ Hyolee !! ]",
"[ ]",
"[ ]" };
int j, k;
for (j=0; j<=14; j++){
for (k=1; k<=3; k++){
printf("%s", finkl[j%5]);
}
printf("\n") ;
}
}
● 해설
j가 0~14까지 변화하는 동안 j%5는 0, 1, 2, 3, 4, 0, 1, 2...로 반복하여 값을 표시한다.
● 결과
[ ][ ][ ]
[ ][ ][ ]
[ Hyolee !! ][ Hyolee !! ][ Hyolee !! ]
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
[ Hyolee !! ][ Hyolee !! ][ Hyolee !! ]
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
[ Hyolee !! ][ Hyolee !! ][ Hyolee !! ]
[ ][ ][ ]
[ ][ ][ ]
댓글