배열 포인터 : 배열을 가리키는 포인터. 연속적인 메모리만 가리킬 수 있다.
포인터 배열 : 배열에 메모리 주소 값을 저장할 수 있는 배열. 즉 포인터들이 배열이다.
- 배열명은 변수가 아니라 상수로 시작주소를 갖고 있다. 따라서 값을 변경할 수 없다.
- 포인터는 값을 변경할 수 있다. - 배열은 내부적으로 포인터다. - 배열과 포인터는 서로 호환된다. - 배열명은 a = a + 1; 불가능하다. - 포인터는 p = p + 1; 가능하다.
|
예제1.
#include <stdio.h> #include <iostream> // C++에선 <iostream>에 정의되어 있는 std::를 이용해서 입출력을 사용한다.
int main() { // 배열 포인터 : 배열을 가리키는 포인터. 연속적인 메모리만 가리킬 수 있다. // 포인터 배열 : 배열에 메모리 주소 값을 저장할 수 있는 배열. 즉 포인터들이 배열이다.
/* 1. ptr == &ptr[0] 2. *ptr = ptr[0] 3. ptr + 1 = ptr 에 sizeof(*ptr)을 더한 값 */
int arr[5] = {1,2,3,4,5};
int(*ptr_arr)[5]; // int형 타입의 인덱스를 5개 가지고 있는 배열을 가리키는 배열 포인터를 선언 ptr_arr = &arr;
printf("\n***** 배열 포인터 *****\n");
// 배열의 주소를 가진 포인터는 +1 / -1 연산으로 어느 원소든 쉽게 접근이 가능하다. // 포인터에 1을 더했지만 주소는 배열의 단위인 int(4byte) 만큼 더해진 것을 확인할 수 있다. std::cout << "arr[0] 주소 값 :: " << *ptr_arr << "\n"; std::cout << "arr[1] 주소 값 :: " << *(ptr_arr + 1) << "\n"; std::cout << "arr[2] 주소 값 :: " << *(ptr_arr + 2) << "\n"; std::cout << "arr[3] 주소 값 :: " << *(ptr_arr + 3) << "\n"; std::cout << "arr[4] 주소 값 :: " << *(ptr_arr + 4) << "\n\n";
// 입력은 std::cin >> "변수" , 출력을 std::cout << "변수 or 문자열", 문자열 변경은 std::endl;로 사용
for (int i = 0; i < 5; i++) { printf("arr[%d] 주소 값 :: %p\n",i, ptr_arr[i]); } printf("\n");
for (int i = 0; i < 5; i++) { printf("%d\n", (*ptr_arr)[i]); }
// 포인터 배열 int* ptr[5]; // 포인터 배열 선언
for (int i = 0; i < 5; i++) { ptr[i] = &arr[i]; }
printf("\n***** 포인터 배열 *****\n"); for (int i = 0; i < 5; i++) { printf("arr[%d] 주소 값 :: %p\n", i, ptr[i]); }
for (int i = 0; i < 5; i++) { printf("arr[%d] 값 :: %d\n", i, *ptr[i]); } printf("\n");
printf("\n***** 2차원 배열을 포인터에 넣기 *********\n"); // 2차원 배열을 포인터에 넣기
int Arr[2][3] = { {1,2,3},{4,5,6} };
// 포인터 선언 : 자료형 (*포인터이름)[가로크기] int (*Ptr)[3] = Arr; // Ptr 은 행 전체를 가리키는 포인터 (가로크기가 4인 배열을 가리키는 포인터)
printf("%p\n", *Ptr); printf("%p\n", *Arr); printf("%d\n", Ptr[1][1]); printf("%d\n", sizeof(Arr)); // 결과 : 4 X 5 = 20 printf("%d\n", sizeof(Ptr)); // 결과 : 4 printf("\n");
for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf("%d ", Ptr[i][j]); } printf("\n"); }
printf("**************\n"); for (int(*row)[3] = Arr; row < Arr + 2; row++) { for (int *col = *row; col < *row + 3; col++) { printf("%d ", *col); } printf("\n"); } }
|
아래 코드는 https://dojang.io/mod/page/view.php?id=317 에 있는 걸 Visual Studio 2019 Community 로 테스트해서 에러나는 부분은 수정했다.
배열의 크기를 동적으로 할당받는 걸 시도해보면...
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>
int main() { int size; scanf("%d", &size); // 배열의 크기를 입력받음
int Arr[size]; // Visual Studio 2019에서는 컴파일 에러 }
|
에러가 발생한다.
C에서 배열의 크기를 동적으로 할당하는 방법은
포인터에 메모리를 동적으로 할당하여 배열처럼 사용하는 방법으로는 가능하다.
포인터에 malloc 함수로 메모리를 할당해주면 된다.
예제2.
#include <stdio.h> #include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
int main() { // 포인터에 할당된 메모리를 배열처럼 사용하기
// 자료형 *포인터이름 = malloc(sizeof(자료형) * 크기);
int* numPtr = (int*)malloc(sizeof(int) * 10); // int 10개 크기만큼 동적 메모리 할당 // malloc 함수 : 동적으로 메모리를 할당하는 함수 (힙(Heap) 영역에 메모리를 할당) // malloc은 메모리만 할당하는 함수이기 때문에 개발자가 어떠한 데이터 형을 저장하는지 예측할 수 없다. // int형 데이터를 저장하기 위해서는 리턴되는 void*을 int*로 변환해야 한다.
numPtr[0] = 10; // 배열처럼 인덱스로 접근하여 값 할당 numPtr[9] = 20; // 배열처럼 인덱스로 접근하여 값 할당
printf("\n**** 메모리 동적 할당 ****\n"); printf("%d\n", numPtr[0]); // 배열처럼 인덱스로 접근하여 값 출력 printf("%d\n", numPtr[9]); // 배열처럼 인덱스로 접근하여 값 출력
free(numPtr); // 동적으로 할당한 메모리 해제 // 동적할당 후 더 사용할 이상 필요가 없다면 꼭 free함수로 메모리를 해제 시켜주어야 한다.
return 0; // 굳이 적어주지 않아도 된다.
}
|
힙(heap)은 C 언어나 자바와 같은 프로그래밍 환경에서 원시 자료형이 아닌 보다 큰 크기의 데이터를 담고자 동적으로 할당하는 메모리 공간을 지칭한다.
- 장점: 상황에 따라 원하는 크기만큼의 메모리가 할당되므로 경제적이며,
이미 할당된 메모리라도 언제든지 크기를 조절할 수 있다.
- 단점: 더 이상 사용하지 않을 때 명시적으로 메모리를 해제해 주어야 한다.
C++ 에서는 new 연산자를 통해 동적 할당하고, delete 연산자를 통해 메모리를 해제한다.
C++ 배열 및 포인터에 대한 예제다.
char myString[255];
cin >> myString 으로 입력 받으면 공백 이후 입력값은 무시된다.
cin.getline(myString, 255); 로 입력을 받으면 공백도 입력이 된다.
예제3.
#include <iostream>
using namespace std;
void ShowArray(int arr[]) { cout << "function size : " << sizeof(arr) << endl; // 4 cout << "function value : " << *arr << endl; // 배열의 첫번째 값 반환 cout << endl; }
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(arr)/sizeof(int); i++) { cout << arr[i] << endl; } cout << endl;
int* ptr = arr; // 포인터 선언 및 주소 할당
cout << "arr 값 : " << *arr << endl; // 배열의 첫번째 값 반환 cout << "ptr 값 : " << *ptr << endl; // 배열의 첫번째 값 반환 cout << endl;
cout << "arr 주소 : " << arr << endl; // 배열명이 배열의 첫번째 요소의 주소 반환 cout << "arr[0] 주소 : " << &arr[0] << endl; // 배열의 첫번째 요소의 주소 반환 cout << endl;
cout << "arr[0] 주소 : " << (uintptr_t)&arr[0] << endl; // 10진수 주소 반환 cout << "ptr 주소 : " << uintptr_t(ptr) << endl; // 10진수 주소 반환 cout << endl;
cout << "ptr1 주소 : " << uintptr_t(ptr + 1) << endl; // 10진수 주소 반환 cout << "ptr2 주소 : " << uintptr_t(ptr + 2) << endl; // 10진수 주소 반환 cout << "ptr3 주소 : " << uintptr_t(ptr + 3) << endl; // 10진수 주소 반환 cout << endl;
// 포인터 연산 for (int i = 0; i < sizeof(arr) / sizeof(int); i++) { cout << "ptr" << i << " 값 : " << *(ptr + i) << " , 주소 : " << (uintptr_t)(ptr + i) << endl; } cout << endl;
cout << "arr size : " << sizeof(arr) << endl; // 4 X 5 = 20 cout << "ptr size : " << sizeof(ptr) << endl; // 4 cout << endl;
ShowArray(arr);
char name[] = "GilDong, Hong"; // 문자 배열로 마지막에 NULL 값이 들어가 있다. char* ptr_n = name; // 배열명이 주소를 반환하므로, 포인터 변수 선언 및 주소 할당
cout << name << endl; // 값을 반환. 주소를 출력할 것으로 생각되는데 값을 반환한다. cout << (uintptr_t)&name << endl; // 주소 반환
const int n_name = sizeof(name)/sizeof(char);
for (int i = 0; i < n_name; i++) { cout << *(ptr_n + i); // 마지막 NULL 값까지 반환됨 }
return 0; } |
참조하면 좋은 강좌
https://www.youtube.com/watch?v=H-FfGUwSU-U&t=296s