wn42
코딩이랑 이것저것
wn42
전체 방문자
오늘
어제
  • 분류 전체보기 (113)
    • 프로그래머스 (23)
      • LV1 (11)
      • LV2 (1)
      • LV3 (3)
      • 연습 (8)
    • 딥러닝 공부 (0)
      • 머신러닝&딥러닝 이론 (0)
    • 임베디드 (17)
      • Adventure Design (1)
      • 센서기반모바일로봇 (5)
      • ROS (9)
      • Google Coral (2)
    • C++ (38)
      • C++ 기초 (34)
      • 자료구조 및 알고리즘 (4)
    • Python (14)
      • 기본 파이썬 문법 (6)
      • Python 기초 (8)
    • 빅데이터 (9)
      • 빅데이터 첫걸음 시작하기(국비지원) (5)
      • 빅데이터 공부 (4)
    • 알고리즘 공부 (2)
      • 기본 알고리즘 (2)
    • 전자공학 (10)
      • 반도체 공정 (3)
      • 무선데이터통신 (7)
      • 반도체공학 (0)
    • C# (0)
      • C# 기본 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 반복문
  • 조건문
  • 정렬
  • ROS
  • 내일배움카드
  • c++
  • Python
  • 노드
  • numpy
  • google coral
  • 큐
  • 파이썬
  • 빅데이터 첫걸음 시작하기
  • 바이트디그리
  • 스택
  • 인스턴스
  • 데이터분석 인강
  • 딥러닝
  • 상속
  • 프로그래머스
  • stl
  • K디지털크레딧
  • 클래스
  • 패스트캠퍼스
  • 데이터분석
  • 변수
  • 스택/큐
  • 소멸자
  • Queue
  • 빅데이터

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
wn42
C++/C++ 기초

[C++] 포인터와 배열

[C++] 포인터와 배열
C++/C++ 기초

[C++] 포인터와 배열

2022. 12. 24. 16:50

포인터와 배열

포인터는 메모리 주소에 직접 접근하므로 배열 등의 자료구조를 처리하는 데 매우 유용히 사용된다.

 

배열의 주소

int 형 배열의 주소

  • 배열은 여러 개의 데이터가 데이터형의 크기만큼 순차적으로 메모리 공간에 할당된다.

 

배열 원소의 주소값 확인

#include <iostream>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; i++) {
std::cout << "arr[" << i << "]의 주소값: " << &arr[i] << std::endl;
}
return 0;
}
arr[0]의 주소값: 006FFB7C
arr[1]의 주소값: 006FFB80
arr[2]의 주소값: 006FFB84
arr[3]의 주소값: 006FFB88
arr[4]의 주소값: 006FFB8C
  • arr의 원소의 데이터형인 int의 크기가 4byte이므로, 각각의 원소들은 메모리에 4byte씩 순차적으로 할당된다.

 

포인터로 배열의 원소 값 및 원소의 주소값 접근

  • 포인터 변수에 배열의 첫 번째 원소의 주소값인 &arr[0]를 대입한다.
  • (ptr + i)와 &arr[i]는 정확히 같은 주소값을 가리킨다.
#include <iostream>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
int* ptr = &arr[0];
for (int i = 0; i < 5; i++) {
std::cout << "arr[" << i << "]의 값: " << arr[i] << std::endl;
std::cout << "(ptr + " << i << ")가 가리키는 값: " << *(ptr + i) << std::endl << std::endl;
std::cout << "arr[" << i << "]의 주소값: " << &arr[i] << std::endl;
std::cout << "(ptr + " << i << ")의 주소값 " << ptr + i << std::endl;
std::cout << "----------------------------" << std::endl << std::endl;
}
return 0;
}
arr[0]의 값: 1
(ptr + 0)가 가리키는 값: 1
arr[0]의 주소값: 010FFAFC
(ptr + 0)의 주소값 010FFAFC
----------------------------
arr[1]의 값: 2
(ptr + 1)가 가리키는 값: 2
arr[1]의 주소값: 010FFB00
(ptr + 1)의 주소값 010FFB00
----------------------------
arr[2]의 값: 3
(ptr + 2)가 가리키는 값: 3
arr[2]의 주소값: 010FFB04
(ptr + 2)의 주소값 010FFB04
----------------------------
arr[3]의 값: 4
(ptr + 3)가 가리키는 값: 4
arr[3]의 주소값: 010FFB08
(ptr + 3)의 주소값 010FFB08
----------------------------
arr[4]의 값: 5
(ptr + 4)가 가리키는 값: 5
arr[4]의 주소값: 010FFB0C
(ptr + 4)의 주소값 010FFB0C
----------------------------

 

배열 이름 = 첫 번째 원소의 주소값

#include <iostream>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
std::cout << "arr[0]의 주소값: " << &arr[0] << std::endl;
std::cout << "배열명 arr이 가리키는 값: " << arr << std::endl;
return 0;
}
arr[0]의 주소값: 00AFFC7C
배열명 arr이 가리키는 값: 00AFFC7C
  • 사실 배열의 이름은 첫 번째 원소의 주소값을 가리킨다. 따라서 arr과 &arr[0]이 정확히 같은 주소값을 나타낸다.
  • 배열의 이름을 사용할 시 자동으로 첫 번째 원소를 가리키는 포인터 타입으로 변환된다.
  • 단, 배열의 이름이 첫 번째 원소를 가리키는 포인터를 말하는 것은 아니다.
    • 배열은 선언과 동시에 데이터를 저장할 연속적인 메모리 공간을 가지지만, 포인터는 데이터를 저장하는 공간을 갖는 것이 아니라, 데이터를 저장하는 메모리의 시작 주소를 저장한다. 이는 sizeof() 연산자를 통해 알 수 있다.
    • 포인터는 변수이지만, 배열은 포인터 상수이다. 따라서 배열의 이름으로 다른 주소를 가리키는 행위는 불가능하며, 증감연산자의 사용도 불가능하다.

 

예제 1) 배열의 크기, 포인터의 크기 확인

#include <iostream>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
int* ptr = arr;
std::cout << "ptr의 크기: " << sizeof(ptr) << std::endl;
std::cout << "arr의 크기: " << sizeof(arr) << std::endl;
return 0;
}
ptr의 크기: 4
arr의 크기: 20
  • sizeof(ptr)은 포인터의 크기(배열의 첫 번째 원소의 주소 크기만큼)를 나타낸다.
  • 반면에 sizeof(arr)은 배열 arr의 전체 크기를 나타낸다.
  • 즉, 배열의 이름 arr과 배열의 시작 주소를 나타내는 ptr은 엄연히 다른 것이다.

 

예제 2) 배열 이름에 증감연산자 적용하기 (오류 발생)

#include <iostream>
using namespace std;
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
int* ptr = arr;
cout << "ptr에 증감연산자 사용: " << ptr++ << endl;
cout << "arr에 증감연산자 사용: " << arr++ << endl;
return 0;
}

  • 포인터는 변수이기에 증감연산자 사용이 가능하지만, 배열의 이름인 arr은 포인터 상수이기 때문에 증감연산자 사용이 불가능하다.

 

 

[ ] 연산자

  • 놀랍게도 [ ]는 연산자이다.
  • [ ] 연산자는 array[i]의 형태처럼 사용되며, C 및 C++에서는 자동으로 *(array + i)로 변환하여 처리한다.
  • i[array] 역시 *(i + array)로 변환되기 때문에 동일한 기능을 수행하지만, 가독성이 떨어져 잘 사용되지 않는다.
#include <iostream>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; i++) {
std::cout << "arr[" << i << "]의 값: " << arr[i] << std::endl;
std::cout << "*(arr + " << i << ")의 값: " << *(arr + i) << std::endl << std::endl;
}
return 0;
}
arr[0]의 값: 1
*(arr + 0)의 값: 1
arr[1]의 값: 2
*(arr + 1)의 값: 2
arr[2]의 값: 3
*(arr + 2)의 값: 3
arr[3]의 값: 4
*(arr + 3)의 값: 4
arr[4]의 값: 5
*(arr + 4)의 값: 5

 

 

배열 포인터

  • 배열을 가리키는 포인터
  • datatype(*name)[n];으로 선언한다. ▶ 일차원 배열을 가리키는 배열 포인터에서 n은 원소의 개수이다.
  • 만약 int(*parr)[5]에서 () 소괄호를 적지 않으면 int *parr[5]가 되고, 이는 int* 포인터를 원소로 5개 갖는 배열 parr이 생성된다는 의미가 된다. (이는 포인터 배열임). 따라서 배열 포인터를 생성할 때 꼭 () 소괄호를 적어주어야 한다.
#include <iostream>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
int(*parr)[5];
parr = &arr;
for (int i = 0; i < 5; i++) {
std::cout << "arr[" << i << "]의 값: " << arr[i] << std::endl;
std::cout << "*(parr)[" << i << "]의 값: " << (*parr)[i] << std::endl << std::endl;
}
return 0;
}
arr[0]의 값: 1
*(parr)[0]의 값: 1
arr[1]의 값: 2
*(parr)[1]의 값: 2
arr[2]의 값: 3
*(parr)[2]의 값: 3
arr[3]의 값: 4
*(parr)[3]의 값: 4
arr[4]의 값: 5
*(parr)[4]의 값: 5

 

 

2차원 배열과 포인터

2차원 배열 (출처: Dive Into Systems)

  • 2차원 배열은 위와 같이 데이터타입의 크기만큼 메모리에 할당되어 순차적으로 저장된다.

 

arr[i]와 &arr[i][0]의 관계

  • 1차원 배열에서 arr과 &arr[0]는 같은 값을 나타냄을 알 수 있었다. (정확히 둘이 같은 것은 아님. 컴퓨터가 암묵적으로 arr을 arr[0]을 가리키는 포인터로 타입 변환한 것임)
  • 2차원 배열에서도 암묵적인 변환에 의해 arr[i]와 &arr[i][0]가 동일한 값을 나타낸다.
#include <iostream>
int main() {
int arr[3][4];
for (int i = 0; i < 3; i++) {
std::cout << "arr[" << i << "]가 나타내는 값: " << arr[i] << std::endl;
std::cout << "&arr[" << i << "][0]가 나타내는 값: " << &arr[i][0] << std::endl << std::endl;
}
return 0;
}
arr[0]가 나타내는 값: 012FF944
&arr[0][0]가 나타내는 값: 012FF944
arr[1]가 나타내는 값: 012FF954
&arr[1][0]가 나타내는 값: 012FF954
arr[2]가 나타내는 값: 012FF964
&arr[2][0]가 나타내는 값: 012FF964

 

2차원 배열에서 행과 열의 개수 구하기

  • 배열은 포인터와 달리 그 자체로 배열 전체의 크기를 담고 있기 때문에 sizeof() 연산자를 이용하여 행과 열의 개수를 구할 수 있다.
  • 앞서 sizeof(arr)은 배열이 차지하고 있는 메모리의 전체 크기를 나타냄을 알 수 있었다.
  • sizeof(arr[0])은 arr[0](행)가 차지하고 있는 메모리의 전체 크기를 나타낸다.
#include <iostream>
int main() {
int arr[3][4];
std::cout << "전체 크기: " << sizeof(arr) << std::endl;
std::cout << "총 원소의 개수: " << sizeof(arr) / sizeof(int) << std::endl;
std::cout << "총 행의 개수: " << sizeof(arr) / sizeof(arr[0]) << std::endl;
std::cout << "총 열의 개수: " << sizeof(arr[0]) / sizeof(arr[0][0]) << std::endl;
return 0;
}
전체 크기: 48
총 원소의 개수: 12
총 행의 개수: 3
총 열의 개수: 4

 

2차원 배열의 배열 포인터

  • 2차원 배열 arr이 존재할 때, arr을 가리키는 포인터를 int**로 만든다고 착각할 수 있다.
  • (왜냐하면, 1차원 배열에서는 int*를 사용하여 arr을 가리키는 포인터를 만들었기 때문이다.)

 

Ex) int**로 arr을 가리킬 때 (오류 발생)

#include <iostream>
int main() {
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
int** parr;
parr = arr;
std::cout << arr[0][1];
std::cout << parr[0][1];
return 0;
}

  • int **을 이용하여 2차원 배열을 가리키는 포인터를 생성하였을 때 오류가 발생한다.
  • 2차원 배열은 메모리 상에서 행과 열로 저장되는 것이 아니라, 일렬로 저장된다. (이전 2차원 배열 그림 참고)
  • 따라서, 2차원 배열에서 열에 해당하는 값을 포인터가 알지 못하면 포인터 parr은 다음 행의 데이터가 위치한 메모리 주소가 어디인지 알 수 없다.
  • 2차원 배열을 가리킬 때 배열 포인터를 사용한다. 아래의 예시들을 보면서 이를 알아보자.

 

Ex) arr과 arr + 1

#include <iostream>
int main() {
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
std::cout << "arr의 값: " << arr << std::endl;
std::cout << "arr + 1의 값: " << arr + 1 << std::endl;
return 0;
}
arr의 값: 006FFDB8
arr + 1의 값: 006FFDC8
  • arr은 2차원 배열의 시작주소를 나타낸다.
  • arr + 1과 arr의 차이는 16이다. (16진수) ▶ 이는 배열의 열이 차지하는 메모리 크기와 동일하다. 4byte(int) * 4 = 16
  • 즉, arr + 1은 다음 열인 arr[0][1]의 시작주소를 나타내는 것이 아닌, 2번째 행인 arr[1]의 시작주소를 나타낸다.
  • 이처럼 2차원 배열을 가리키는 포인터를 생성하기 위해서는 열의 크기를 정확히 명시할 필요가 있다.

 

Ex) 배열 포인터에 열의 개수를 명시

#include <iostream>
int main() {
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
int(*parr)[4];
parr = arr;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
std::cout << "arr[" << i << "][" << j << "]: " << arr[i][j] << ", ";
std::cout << "parr[" << i << "][" << j << "]: " << parr[i][j] << std::endl;
}
}
return 0;
}
arr[0][0]: 1, parr[0][0]: 1
arr[0][1]: 2, parr[0][1]: 2
arr[0][2]: 3, parr[0][2]: 3
arr[0][3]: 4, parr[0][3]: 4
arr[1][0]: 5, parr[1][0]: 5
arr[1][1]: 6, parr[1][1]: 6
arr[1][2]: 7, parr[1][2]: 7
arr[1][3]: 8, parr[1][3]: 8
arr[2][0]: 9, parr[2][0]: 9
arr[2][1]: 10, parr[2][1]: 10
arr[2][2]: 11, parr[2][2]: 11
arr[2][3]: 12, parr[2][3]: 12
  • 2차원 배열의 열의 개수를 정확히 명시함으로써 2차원 배열을 가리키는 포인터를 생성할 수 있다.
  • parr은 크기가 4인 배열을 가리키는 포인터이다. ▶ 2차원 배열의 이름이 첫 번째 행을 가리키는 포인터로 타입 변환된 것이다.
  • [4]는 해당 포인터에서 1을 증가시킬 시 주소값은 4만큼 증가한다는 것을 의미한다. ▶ 열의 개수를 명시

 

다차원 배열 포인터 (3차원 이상)

  • 2차원 배열 포인터에서 차원의 개수만 늘려주면 된다.
#include <iostream>
int main() {
int arr[2][3][4] = { {{1, 2, 3, 4}, {1, 2, 3, 4}, {1, 2, 3, 4}}, {{5, 6, 7, 8}, {5, 6, 7, 8}, {5, 6, 7, 8}} };
int(*parr)[3][4];
parr = arr;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
std::cout << "arr[" << i << "][" << j << "]" << "[" << k << "]: " << arr[i][j][k] << ", ";
std::cout << "parr[" << i << "][" << j << "]" << "[" << k << "]: " << parr[i][j][k] << std::endl;
}
}
}
return 0;
}
arr[0][0][0]: 1, parr[0][0][0]: 1
arr[0][0][1]: 2, parr[0][0][1]: 2
arr[0][0][2]: 3, parr[0][0][2]: 3
arr[0][0][3]: 4, parr[0][0][3]: 4
arr[0][1][0]: 1, parr[0][1][0]: 1
arr[0][1][1]: 2, parr[0][1][1]: 2
arr[0][1][2]: 3, parr[0][1][2]: 3
arr[0][1][3]: 4, parr[0][1][3]: 4
arr[0][2][0]: 1, parr[0][2][0]: 1
arr[0][2][1]: 2, parr[0][2][1]: 2
arr[0][2][2]: 3, parr[0][2][2]: 3
arr[0][2][3]: 4, parr[0][2][3]: 4
arr[1][0][0]: 5, parr[1][0][0]: 5
arr[1][0][1]: 6, parr[1][0][1]: 6
arr[1][0][2]: 7, parr[1][0][2]: 7
arr[1][0][3]: 8, parr[1][0][3]: 8
arr[1][1][0]: 5, parr[1][1][0]: 5
arr[1][1][1]: 6, parr[1][1][1]: 6
arr[1][1][2]: 7, parr[1][1][2]: 7
arr[1][1][3]: 8, parr[1][1][3]: 8
arr[1][2][0]: 5, parr[1][2][0]: 5
arr[1][2][1]: 6, parr[1][2][1]: 6
arr[1][2][2]: 7, parr[1][2][2]: 7
arr[1][2][3]: 8, parr[1][2][3]: 8

 

 

포인터 배열

  • 이것은 진짜로 포인터들의 배열이다. (배열 포인터는 말그대로 배열을 가리키는 포인터임)
  • datatype *arr[n] 또는 datatype* arr[n] 으로 선언한다. ▶ n은 저장할 포인터의 개수
// int형 포인터 배열
int *arr[n];
int* arr[n];

 

 

포인터 배열 생성

#include <iostream>
int main() {
int a = 1, b = 2, c = 3, d = 4;
int* ptr[4];
ptr[0] = &a;
ptr[1] = &b;
ptr[2] = &c;
ptr[3] = &d;
std::cout << "a: " << a << ", *ptr[0]: " << *ptr[0] << ", &a: " << &a << ", &ptr[0]: " << ptr[0] << std::endl;
std::cout << "b: " << b << ", *ptr[1]: " << *ptr[1] << ", &b: " << &b << ", &ptr[1]: " << ptr[1] << std::endl;
std::cout << "c: " << c << ", *ptr[2]: " << *ptr[2] << ", &c: " << &c << ", &ptr[2]: " << ptr[2] << std::endl;
std::cout << "d: " << d << ", *ptr[3]: " << *ptr[3] << ", &d: " << &d << ", &ptr[3]: " << ptr[3] << std::endl;
return 0;
}
a: 1, *ptr[0]: 1, &a: 0058FC54, &ptr[0]: 0058FC54
b: 2, *ptr[1]: 2, &b: 0058FC48, &ptr[1]: 0058FC48
c: 3, *ptr[2]: 3, &c: 0058FC3C, &ptr[2]: 0058FC3C
d: 4, *ptr[3]: 4, &d: 0058FC30, &ptr[3]: 0058FC30
  • 포인터와 배열
  • 배열의 주소
  • [ ] 연산자
  • 배열 포인터
  • 2차원 배열과 포인터
  • 포인터 배열
'C++/C++ 기초' 카테고리의 다른 글
  • [C++] 디버깅(Debugging)
  • [C++] 함수(Function)
  • [C++] 포인터(Pointer) 기초
  • [C++] 문자열(String)
wn42
wn42
코딩이랑 이것저것 하는 블로그

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.