No. 28 : C언어 포인터 배열(+2차원 배열 문자열 출력) (2024)

book_project

No. 28 : C언어 포인터 배열(+2차원 배열 문자열 출력)

2023. 2. 7. 14:36

URL 복사 이웃추가

본문 기타 기능

신고하기

이번 글에서부터는 포인터에 대한 심화부분에 대해서 다룹니다. 기본적인 이론은 앞의 포인터(1) ~ 포인터(5)에서 다루었으므로, 해당 글에서는 기초적인 이론에 대한 설명은 없을 예정이니 앞글 참고하여 봐주시면 감사하겠습니다.

지금까지 본 필자는 포인터에 대해 포인터 변수라는 말을 꾸준히 사용해왔으며, 포인터가 변수라는 것을 재차 강조하였습니다. 여타 다른 변수와 같은 점을 가지고 있는 포인터 변수는, 포인터 배열이라는 것이 존재합니다. 다른 배열 변수와 마찬가지로, 포인터 배열은 주소를 저장할 수 있는 포인터 변수들이 나란히 이어진 형태로 메모리 방이 할당되어 저장되게 됩니다. 선언하는 방식도 일반 배열 변수와 유사합니다. 우리가 지금까지 포인터 변수를 선언할 때 사용했던 것처럼, *을 붙여주기만 하면 됩니다.

정수형 포인터 배열을 한 번 선언해보도록 하겠습니다.

int *a[3];

해당 포인터 배열은 정수형 자료를 가리키는 포인터 변수 3개를 배열 요소로 가지는 포인터 배열입니다.

포인터 배열과 역참조를 활용하여 정수형 변수 c의 값을 변화시켜 보도록 하겠습니다.

#include <stdio.h>main() {int a = 10;int b = 20;int c;int* point[3];point[0] = &a;point[1] = &b;point[2] = &c;*point[2] = *point[0] + *point[1];printf("C : %d\n", c);}

결과화면

위의 코드를 자세히 살펴보도록 하겠습니다.

  1. 정수형 변수 a, b를 선언한 후, 각각 10과 20으로 초기화하였습니다.

  2. 정수형 변수 c를 선언하였습니다.

  3. 포인터 배열 point[3]을 선언하였습니다.

  4. 포인터 배열 point[0], point[1], point[2]에 각각 a, b, c의 주소를 넣어 각 변수를 가리키도록 하였습니다.

  5. 포인터 배열 point의 배열 요소를 역참조하여 c = a + b를 하였습니다.

  6. 결과적으로, 10 + 20 = 30으로 c의 값 30이 printf()함수로 출력되었습니다.

2차원 배열과 주소의 관계

이전에 No. 19에서 2차원 배열에 대하여 설명한 적이 있습니다. 2차원 배열에 대해 조금 심화한 개념을 설명할 예정인데, 아래의 포인터 배열 부분에서 필요한 부분이라 결국 이번 글에 같이 붙이기로 결정하였습니다. 아무래도 이전에 설명했던 개념의 확장편과 유사하니 기억이 잘 나지 않으신다면 No. 19와 No. 22를 한 번 훑어보신 후 읽어보신다면 도움이 됩니다.

이전 글 중 포인터를 설명할 때 & 이 녀석에 대해 설명한 적이 있습니다. 이녀석은 주소 연산자로써, 임퍼샌드라 불린다고 설명한 적이 있습니다. 이전의 글에서도 몇 번 이 녀석을 활용하여 각 배열요소의 주소나 변수의 주소를 활용한 적이 몇번 있기도 합니다. 그리고 배열 변수의 경우, num[5]라는 배열 변수가 있다고 가정했을 때, num[0]의 주소는&num[0]이나 num으로 표현할 수 있다고 하였습니다.

그렇다면, 2차원 배열에서는 어떨까요? 일단 예시를 들어 일반화를 하는 것이니만큼, 배열의 크기를 조금 줄여서 예시를 하나 들어보도록 하겠습니다. num[2][3]이라는 배열 요소가 있다고 가정해보겠습니다. 이 배열은 총 6개의 방이 있는 배열 변수입니다. 하지만 여기서, 이 배열은 조금 더 자세히 살펴보도록 하겠습니다.

num[0][0], num[0][1], num[0][2], num[1][0], num[1][1], num[1][2]

이 배열 요소들을 자세히 살펴보면, 이 배열 요소는 배열을 배열 요소로 가지고 있다고 말할 수도 있습니다. 즉, 배열 요소 3개로 이루어진 배열변수 2개(num[0][0]~num[0][2]와 num[1][0]~num[1][2])가 배열요소인 배열이라말할 수 있습니다. 즉, 두개의 배열이 합쳐져 하나의 배열이 된 셈이지요. num[0]과 num[1]라는 배열이 합쳐져서 말이죠.

그러니까, 즉 num[0]과 num[1] 자체가 각각 num[0][0]~num[0][2], num[1][0]~[1][2]의 이름이 된다는 뜻입니다. 이해가 되셨나요?

2차원 배열 num은 num[0]과 num[1]으로 이루어진 배열이며, num[0]은 num[0][0], num[0][1], num[0][2]로 이루어진 또 다른 배열이고, num[1]은 num[1][0], num[1][1], num[1][2]로 이루어진 또 다른 배열 이라는 뜻이죠.

그리고 No. 22에서도 한 말이지만, 배열의 이름은 배열의 첫번째 요소의 주소와 같습니다. (매우 중요합니다. 잘 기억해두세요.)

즉, num[0] = &num[0][0]이 되는 것이고

num[1] = &num[1][0]이 됩니다.

이를 확인하는 간단한 코드를 작성해보도록 하겠습니다.

#include <stdio.h>main() {int num[2][3] = { 1, 2, 3, 4, 5, 6 };char Englishword[4][7] = {"Alppe", "onion", "banana", "melon"};int i;printf("num : %u\n", num);printf("num[0] : %u\n", num[0]);printf("&num[0][0] : %u\n", &num[0][0]);printf("\n------------\n\n");printf("num[1] : %u\n", num[1]);printf("&num[1][0] : %u\n", &num[1][0]);printf("\n------------\n\n");printf("num_size : %d byte\n", sizeof(num));printf("num[0]_size : %d byte\n", sizeof(num[0]));printf("num[1]_size : %d byte\n", sizeof(num[1]));printf("\n------------\n\n");for (i = 0; i < 4; i++)printf("Englishword[%d] : %s\n", i, Englishword[i]);}

결과화면

뺀 아래의 문자열 출력(단어 출력)에 관해서는 아래의 포인터 배열에서 이어서 설명할 예정이니 여기서는 그저 아, 이런게 있구나 정도로만 넘겨주시면 될 것 같습니다.

문자열과 포인터 배열

포인터 배열은 주로 문자열을 처리할 때 자주 사용됩니다. 이전에 No. 22에서, 문자열을 저장하는 방법에 대해 잠깐 언급한 적이 있습니다. No. 22에서는 문자형 배열을 활용하여 저장하는 방법에 대해서만 언급하고, 포인터를 활용하는 방법도 있으나 아직 이 블로그에서 포인터를 다룬 적이 없으므로 포인터를 활용하는 방법에 대해서는 추후에 다시 다루어본다 말한 적이 있습니다.

그리고 이제는 포인터를 배웠으니, 한 번 포인터를 이용하여 문자열을 저장하는 방법에 대하여 설명하도록 하겠습니다.

일단 결론적으로 말해서, 두 방법 모두 문자열을 저장할 수 는 있습니다만 저장되는 방식이 다릅니다. 각 방법에 따라 메모리를 많이, 또는 적게 사용하며 메모리 사용 효율에도 차이가 납니다.

우선 기억이 안나실 분들을 위해 우선 2차원 배열을 활용하여 영어단어들을 저장해보도록 하겠습니다.

char Englishword[4][7] = {"Alppe", "onion", "banana", "melon"};

"A l p p e \0"

"o n i o n \0"

"b a n a n a \0"

"m e l o n \0"

A

l

p

p

e

\0

o

n

i

o

n

\0

b

a

n

a

n

a

\0

m

e

l

o

n

\0

의 형태로, 이때 저장하려는 이름의 길이가 배열의 크기보다 작아야 하므로 학생들 이름을 저장하는 데 사용한 총 메모리 공간은 4 X 7 = 28byte 입니다. 하지만 이중에서 남는 공간이 생겨나며, 3byte는 사용하지 않게 됩니다.

이번에는 포인터 배열을 이용해 다시 한 번 작성해보도록 하겠습니다.

char *Englishword[4] = {"Alppe", "onion", "banana", "melon"};

char *Englishword[4] 는 문자형자료의 주소를 저장하는 포인터 변수의 배열 중 크기가 4인 배열입니다. 이 포인터 배열은 각각 4개의 방이 할당되고, 각각의 메모리 방에는 각 문자열의 첫번째 글자의 주소가 저장됩니다.

조금 더 풀어서 말해볼까요? 이렇게 포인터 배열을 선언하고 문자열을 초기화하게 된다면 해당 문자열들은 각각 임의의 메모리에 자동적으로 저장 됩니다. 그리고 저장된 문자열 메모리의 첫 번째 요소(첫 번째 글자)의 주소 값을 차례대로 포인터 배열 요소에 저장합니다. 그리고 포인터 배열의 경우, 첫 번째 글자의 주소 값을 이용하면 해당 문자열을 전부 다 출력할 수 있습니다. 왜냐하면 어디까지가 문자열의 끝인지 표시하는 \0가 모든 문자열에 포함되어 저장되어 있으니까요.(No. 22 참고) 즉, 해당 주소에서 시작하는 문자열로부터 NULL(\0) 이전까지의 문자를 출력합니다. 우리가 원하는 문자열이 출력되는 것이죠.

예를 들어, Alppe로 따지자면 해당 문자열이 저장된 주소가 Englisjword[0] 값에 저장되었다는 뜻입니다. 즉, English[0]를 통해 "Alppe"에 접근할 수 있게 된겁니다.

문자열을 저장하는 일을 컴퓨터에게 맡기게 되며, 컴퓨터는 2차원 배열을 저장하는 것과 달리 낭비하는 메모리 없이 우리가 저장하려는 문자열을 저장하였습니다.

#include <stdio.h>main() {int i;char* Englishword[4] = { "Alppe", "onion", "banana", "melon" };for (i = 0; i < 4; i++)printf("Englishword[%d] : %s\n", i, Englishword[i]);}

결과화면

자, 여기까지 이해가 잘 되셨나요? 주소라는 개념을 활용해 문자열을 불러왔습니다. 하지만, 이 원리는 포인터 배열 뿐만 아니라 2차원 배열에서도 활용이 가능합니다. 바로 위의 '2차원 배열과 주소의 관계' 문단의 마지막에 적혀있던 코드입니다. 한 번 아까 위에서 사용했던 코드 중 우리가 지금 필요한 코드만을 가져와보도록 하겠습니다.

#include <stdio.h>main() {int i;char Englishword[4][7] = { "Alppe", "onion", "banana", "melon" };for (i = 0; i < 4; i++)printf("Englishword[%d] : %s\n", i, Englishword[i]);}

아마 위에서 이걸 처음 보시고 당황했던 분들도 있을 겁니다. 하지만 지금은 포인터 배열에 대해 이미 한 번 설명한 적이 있으니, 포인터 배열과 구조가 유사하다는 것 또한 눈치채실 수 있을 겁니다. 이전에 했던 말이 기억나나요? No. 27에서 배열의 첫번째 요소의 주소는 배열의 이름과 같다고 말한 적이 있습니다. 배열의 이름을 출력하게 되면, 배열의 첫번째 요소의 주소가 출력되었던 것을 No. 27에서 다루어본 적이 있습니다. (다소 간단한 개념이니, 기억이 나지 않으신다면 한 번 글을 읽어보시는 것을 추천드립니다.)

여기서 Englishword[0]는 Englishword[0][0]부터 Englishword[0][7]까지의 배열의 이름입니다.

즉, Englishword[0]은 Englishword[0][0]의 주소를 의미합니다. 그리고, 해당 문자열의 주소를 입력하면 NULL(\0)가 나올 때까지 모든 문자열을 출력합니다. 우리가 원하는 단어 Alppe이 모두 출력되는 것이죠.

원리는 포인터 배열과 유사하고, 실제로 활용하는 것 역시 주소이니 만큼 포인터 배열의 일부라고 볼 수 있으나 배열 자체는 2차원 배열인만큼 헷갈려 하시는 분들이 있으으실까봐 문단의 마지막에 같이 추가해보았습니다.

포인터 배열 출력하기

이제 충분히 포인터 배열에 관하여 이해하셨나요? 포인터 배열의 마지막(와! 드디어!)으로 포인터 배열을 출력하는 방법에 대해 알려드리도록 하겠습니다.

사실, 이미 위에서 한 번 출력한 적이 있기 때문에 바로 이해하셨다면 굳이 이 부분까진 보지 않으시고 바로 활용하셔도 괜찮습니다만은, 그래도 한 번은 정리하는 것이 좋을 것 같아 이 문단도 추가로 작성하게 되었습니다.

포인터 배열을 출력하기 위해서는 우선 포인터 배열이 필요하겠죠? 위에서 사용했던 포인터 배열을 다시 한 번 가져와보도록 하겠습니다.

char* Englishword[4] = { "Alppe", "onion", "banana", "melon" };

포인터 배열에 대한 설명이 기억나시나요? 각 배열 요소는 해당 문자열의 첫번째 글자의 주소를 가진다고 말하였습니다. 그리고 해당 주소를 출력하게 되면 저장된 메모리가 자동적으로 \0까지의 글자를 출력하며 원하는 문자열이 출력되는 것이죠.

출력하는 방법은 다음과 같습니다.

printf("%d", Englishword[n]);

n에 어떤 숫자가 들어갈 지는, 어떤 문자열을 출력하느냐에 따라 다를 겁니다. 예를 들어 onion을 출력하는 것이라면, 우리는 Englishword[1]을 입력하면 되는 겁니다.

공감이 글에 공감한 블로거 열고 닫기

댓글쓰기 이 글에 댓글 단 블로거 열고 닫기

인쇄

No. 28 : C언어 포인터 배열(+2차원 배열 문자열 출력) (2024)

References

Top Articles
Latest Posts
Article information

Author: Fr. Dewey Fisher

Last Updated:

Views: 6328

Rating: 4.1 / 5 (62 voted)

Reviews: 93% of readers found this page helpful

Author information

Name: Fr. Dewey Fisher

Birthday: 1993-03-26

Address: 917 Hyun Views, Rogahnmouth, KY 91013-8827

Phone: +5938540192553

Job: Administration Developer

Hobby: Embroidery, Horseback riding, Juggling, Urban exploration, Skiing, Cycling, Handball

Introduction: My name is Fr. Dewey Fisher, I am a powerful, open, faithful, combative, spotless, faithful, fair person who loves writing and wants to share my knowledge and understanding with you.