나의 IT일지
포인터 배열과 배열 포인터 본문
포인터는 변수여서 배열의 요소로 사용할 수 있고, 배열은 저장공간이기에 포인터에 저장할 수 있다. 즉, 포인터를 요소로하여 만든 배열을 포인터 배열, 배열의 주소를 저장하는 포인터를 배열 포인터라고 한다. 이 둘의 이름은 비슷하지만, 사용하는 방법은 다르다. 그래서 우리는 포인터 배열과 배열 포인터에 대해서 알아야 한다.
포인터 배열
우선 포인터 배열에 대해서 알아보자. 포인터 배열이란 포인터 변수들를 묶어놓은 묶음으로, 처리할 데이터의 주소를 모아 데이터를 쉽게 처리하기 위해서 사용이 된다.
포인터형 * 포인터배열명[요소개수]
위의 구조는 포인터 배열의 선언 구조로, 포인터에 저장할 주소를 가진 변수의 자료형을 포인터형칸에 적으며, 배열의 이름인 배열명, 배열에 몇 개의 포인터를 만들 것인지의 요소 개수로 구성되어 있다. 예시로 "int * arr[i]"을 선언했다고 하자. 그렇다면 다음과 같은 배열이 생성된다.
arr[0] | arr[1] | arr[2] | ... | arr[i-1] |
이때 생성된 요소들은 전부 정수형 포인터이며, 자료형에서 선언된 자료형을 가진 변수의 주소를 저장하는 공간이 된다. 그리고 요소의 호출번호를 보면 "i-1"인것을 볼 수 있는데, 이는 호출번호가 "0"부터 시작해서 나타나는 현상이다.
포인터 배열는 배열의 일종이기에, 변수를 요소로 하는 배열과 같은 초기화방법을 사용한다.
- 자료형* 포인터배열명[요소 개수] = {&변수1, &변수2...}
- 자료형* 포인터배열명[]= {&변수1, &변수2...}
1번 형식은 배열크기를 정하고 변수의 주소를 선언하는 형식으로, 주소의 갯수가 배열의 크기보다 작거나 같아야 한다. 만약 요소의 개수가 주소의 개수보다 클 경우, 남은 배열요소는 모두 0으로 채워진다.
그리고 2번 형식에는 배열크기를 정하지 않은 상태에서 선언하는 것을 볼 수 있다. 이렇게 선언을 하면, 주소의 갯수만큼 요소가 생성이 된다.
#include<stdio.h>
void main() {
int a = 10, b = 20, c = 30;
int* ar[3] = { &a,&b,&c };
for (int i = 0; i < 3; i++) {
printf("ar[%d]:%d\n", i, ar[i]);
}
printf("전 a=%d\n", a);
*ar[0] = 100;
printf("후 a=%d\n", a);
}
위의 코드는 포인터 배열에 3개의 변수의 주소를 초기화한 후에 요소의 값을 출력 코드로, 결과를 보면, 변수의 주소가 저장되어 있는 것을 확인 할 수 있다. 이때 포인터 배열의 요소의 값이 연속적이지 않은 것을 확인 할 수 있다. 즉, 각 요소에 저장되어 있는 값은 개별적인 변수의 주소값이기 때문에 연속적이지 않은 것이다. 추가적으로, 포인터배열의 요소를 통해 변수에 접근하려면, 포인터와 똑같이 간접참조연산자를 사용하면 된다.
포인터 배열은 첨자를 하나 사용하는 1차원 배열이다. 하지만 2차원 배열처럼 사용하는 방법이 있는데, 포인터 배열의 요소에 배열 주소를 저장하면 된다.
#include<stdio.h>
void main() {
int a[3] = { 10,20,30 };
int b[3] = { 40,50,60 };
int* ar[2] = { &a,&b};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++){
printf("ar[%d][%d]:%d\n", i,j, ar[i][j]);
}
printf("\n");
}
printf("전 a=%d\n", a[0]);
ar[0][0] = 100; //ar[0][0] == *(*(ar+0)+0)
printf("후 a=%d\n", a[0]);
}
위의 코드와 결과를 보면 1차원의 배열만 사용해서 2차원 배열처럼 사용하는 것을 확인 할 수 있다. 포인터 배열의 요소는 배열의 주소(ex) ar[0])를 가리킨다. 그리고 배열에도 요소가 있기에, 그 요소의 값을 부르기 위해선 요소 번호를 사용해야 한다. 즉, "포인터 배열명[포인터 배열 요소][배열 요소]"를 이용하여 요소의 값을 호출하는 것이다.
배열 포인터
그 다음에 배열 포인터에 대해서 알아보자. 배열 포인터는 1차원 배열을 가리키는 포인터로, 2차원 배열의 이름을 저장할 때 사용한다.
자료형 (*배열포인터명)[1차원 배열 1개의 요소개수];
위의 구조는 배열 포인터를 생성하는 구조로, 자료형은 배열포인터에 저장할 배열의 자료형이며, 대괄호 안에는 배열 1개의 요소개수를 적어야 한다. 예를 들면 "int (*pa)[4]"는 배열 1개의 요소개수가 4개인 정수형 배열의 주소를 저장하는 포인터를 생성하는 명령어이다.
배열 포인터를 생성하는 구조랑 포인터 배열를 생성하는 구조가 비슷하지만 큰 차이점이 존재한다. 배열 포인터는 포인터 기호와 이름을 중괄호로 묶음으로써 포인터라는 것을 알리는 반면, 포인터 배열은 포인터 기호와 이름을 따로따로 함으로써 배열이라는 것을 알린다.
2차원 배열을 사용하는 경우에 주로 사용되는데, 1차원 배열을 요소로 가지고 있기에 행에 따라 1차원 배열의 종류가 달라져서, 2차원 배열을 1차원 배열로 인식하는 기존의 포인터보단 배열 포인터를 사용한다. (ex: int(*pa)[4]=a)
#include<stdio.h>
void main() {
int i, j;
int arr[2][3] = {
{1, 2, 3},
{4, 5, 6}};
int(*parr)[3] = arr;
for (i = 0; i < 2; i++) {
for(j=0;j< 3;j++){ printf("parr[%d][%d]:%d\n",i,j, parr[i][j]); }
}
}
위의 코드를 보면 2차원 배열의 주소를 배열 포인터에 저장한 뒤, 반복문을 통해 배열 포인터의 요소를 출력한다. 이때, 2차원 배열의 값을 호출하듯이 "포인터명[행요소][열요소]"로 해당 주소에 있는 요소의 값을 호출하는 것을 볼 수 있다.
이는 "포인터명[행요소][열요소]"는 "*(*(포인터명+행요소)+열요소)"이며, "*(포인터명+행요소)"부분은 1차원 배열의 주소를 구하는 부분이고, "*(*()+열요소)"는 1차원 배열의 요소의 주소를 구하는 부분이여서 나타나는 현상이다. 그래서 2차원 배열 호출하듯이 호출할 수 있는 것이다.
#include<stdio.h>
void main() {
int i, j;
int arr[2][3];
int(*parr)[3]=arr;
printf("sizeof(parr):%d\n", sizeof(parr));
printf("&arr:%d\n", &arr);
printf("parr:%d\n", parr);
printf("parr[0]:%d\n", parr[0]);
printf("&arr[1]:%d\n", arr[1]); //*(arr+1)
printf("parr+1:%d\n", parr + 1);
printf("*(parr+1):%d\n", *(parr+1)); //parr[1]
}
위의 코드는 배열포인터와 2차원 배열간의 주소관계를 알아보기 위한 코드이다. 위에 결과를 보면, 2차원 배열의 주소와 parr의 값은 같다. 이는 포인터에 저장되어 있는 값은 2차원 배열의 주소값으로 배열명과 똑같은 값을 가진다는 것을 알 수 있다.
추가적으로 배열 포인터를 증감할 경우, 배열 포인터의 출력값이 12만큼 증가하는 것을 알 수 있다. 이는 2차원 배열의 다음 요소의 주소인데, 배열 포인터를 초기화 할때, 배열의 이름만 저장한 것이 아닌, 2차원 배열의 행 요소 주소들을 배열화하여 저장한 것을 알 수 있다. 즉, 포인터명[행요소]는 그 행의 1차원 배열 1개를 지정하는 것이며, 배열 포인터에 증감을 하면 1차원 배열의 크기 만큼 증감하는 것을 알 수 있다.
번외) 문자열의 포인터배열
문자열의 포인터배열은 정수형이나 실수형의 포인터배열과 다르게 문자열을 대입할 수 있다.
#include<stdio.h>
void main() {
char* name = "Lee";
printf("이름: %s\n", name);
}
이것이 가능한 이유는 문자열이 생성된 뒤에 문자열의 시작위치(위의코드에선 "L")의 주소를 포인터에 저장하기 때문에, 문자열 포인터에 문자열을 대입할 수 있는 것이다. 즉, 문자열을 만들고 그 문자열의 주소를 따로 저장할 필요 없이, 문자열 포인터에 문자열을 대입하면 해당 주소에 문자열이 생성이 된다.
이를 이용해 문자열 포인터 배열을 만들어 보자
#include<stdio.h>
void main() {
char* name2[5] = { "RAM","CPU","HDD","GPU","SSD" };
/*위의 초기화를 풀어 쓴 것
name2[0] = "RAM";
name2[1] = "CPU";
name2[2] = "HDD";
name2[3] = "GPU";
name2[4] = "SSD";
*/
int code_number;
printf("code number(1~5):");
scanf("%d", &code_number);
if ((code_number >= 1) && (code_number <= 5)) {
printf("제품명:%s\n", name2[code_number-1]);
}
else {
printf("제품코드 입력 오류");
}
}
위의 코드를 보면, 문자열 포인터 배열의 요소에 문자열을 대입한 뒤, 조건문을 통해 입력받은 숫자에 따라 문자열을 출력한다. 즉, 문자열 포인터 배열은 문자열(글자)을 일반 배열처럼 "배열명[ ]"의 형식으로 출력 할 수 있게 해준다.
'프로그래밍 언어 > C언어' 카테고리의 다른 글
동적 메모리 할당 2 (0) | 2023.02.28 |
---|---|
동적 메모리 할당 1 (0) | 2023.02.25 |
이중 포인터 (0) | 2023.02.23 |
함수와 포인터 (0) | 2023.02.22 |
배열과 포인터 (0) | 2023.02.21 |