BumCode

C언어 포인터/배열

헷갈리기 쉬운 부분이니 확실하게 정리하고 넘어가는게 좋을 것 같다.

배열과 포인터의 관계를 이해하기위해 알아야하는 것이 있다.

arr[i] == *(arr+i)

이게 같은 표현이라는 것. 이걸 이용해 2차원 배열까지 포인터로 표현할 수가 있다.

1차원배열

배열은 정적배열과 동적배열로 구분된다.

정적배열

우리가 흔히 떠올리는 int arr[]; 는 정적배열이다. 선언 시 배열의 크기를 지정해주어야하며, 크기의 자리에 변수가 올 수 없다.

우선 간단하게 1차원 정적배열을 만들어보자.

int arr1[3] = { 1,2,3 };
int* ptr1 = arr1;

cout << ptr1[1] << ',' << *(ptr1 + 1) << endl;
cout << ptr1[3] << endl; //쓰레기값!

ptr1[1]과 *(ptr1 + 1)은 같은 표현이다.

동적배열

1차원 배열의 동적할당은 (자료형의 크기)*(배열의 원소개수)만큼 할당해주면 된다.

int* ptr2 = (int*)malloc(sizeof(int) * 3);

//할당된 배열 직접 초기화
ptr2[0] = 1;
ptr2[1] = 2;
ptr2[2] = 3;
//출력해서 값 확인
cout << *(ptr2 + 0) << ',' << *(ptr2 + 1) << ',' << *(ptr2 + 2) << endl;

tr2[1] == (ptr2+1) …. 이라는 것을 알 수 있다.
물론 초기화 할 때 포인터를 사용해 대입하는 것도 가능하다.

2차원배열

정적배열

우선 2차원배열을 하나 선언해주자.

int arr2[2][2] = { {1,2},{3,4} };

arr2[0][1]을 포인터를 이용해 표현해보는 과정이다.

arr2[0][1]
=>(*(arr2+0))[1]

(arr2[0]) == (*(arr2+0))이며 뒤에 [1]이 있는 모양이다.
*보다 []의 우선순위가 높기에 [1]이 먼저 연산되지 않도록 ()를 통해 *먼저 연산해준다.

남은 [1]도 포인터로 표현해주었다.

==> *(*(arr2+0)+1)

즉,  arr2[0][1] == (*(arr2+0))[1] == *(*(arr2+0)+1)
cout << arr2[0][1] <<','<< (*(arr2 + 0))[1] <<','<< *(*(arr2 + 0) + 1) <<endl; 
출력해보면 모두 2로 같음을 알 수 있다.

동적배열

어떻게 포인터로 2차원 배열이 생기는지 이해를 돕기위해 메모리를 그려보았다. arr 2차원 배열의 동적할당은 int** ptr3에 (int*)자료형이 원소인 (1차원)배열을 동적할당하고, 할당 된 각각의 원소에 (int)자료형이 원소인 1차원 배열을 동적할당해 대입해주면 된다.

[3][3]크기의 배열
int** ptr3 = (int**)malloc(sizeof(int*) * 3);

for (int i = 0; i < 3; i++)
{
	//ptr3[i] = (int*)malloc(sizeof(int) * 3);
	*(ptr3 + i) = (int*)malloc(sizeof(int) * 3);
}

위의 내용을 이해했다면 위 두가지 식이 같은 표현인 것을 알 수 있을 것이다.

2차원배열이 할당되었으니 0으로 초기화를 해보자.

for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 3; j++)
	{
		//ptr3[i][j] = 0;
		*(*(ptr3 + i) + j) = 0;
	}
}

위의 식 또한 같은 표현이다.
위와 같이 생성했다면, 한 번 출력해보자.

//출력
for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 3; j++)
	{
		cout << ptr3[i][j] << ' ';
	}
	cout << endl;
}

ptr[][] 또는 *((*ptr+i)+j)중 어느것을 사용하든 괜찮다.

2차원 배열의 인덱스에 대해 알아보자.

다음과 같은 10*10 크기의 2차원 배열이 있을 때,

	int arr3[10][10];
	int t = 1;
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
			arr3[i][j] = t++;
	}
	//0행 1 2 3 4 5 6 7 8 9 10
	//1행 11 12 .........
	//9행       ............100

arr[0][50] 또는 arr[5][30] 에 접근하면 어떻게될까?

  • [0][0]은 첫 행에서 시작해 1번째 값을 의미함.
  • [0][50]은 첫 행에서 시작해 51번째 값을 의미함.
  • [5][30]은 5행(여섯번째행)에서 시작해 31번째 값을 의미함.

2차원 배열이지만 실제 메모리에는 0행(0열1열2열….) 1행(..) 2행(..) …. 순으로 일렬로 되어있다.

cout << arr3[0][0] << endl; //1
cout << arr3[0][50] << endl; //51
cout << arr3[5][30] << endl; //81

cout << (*(arr3 + 0))[0] <<','<< * (*(arr3 + 0) + 0) <<','<< * *arr3 << endl;//1
//0은 생략가능하기에 **arr3이랑 같은표현임
cout << (*(arr3 + 0))[50] << ',' << *(*(arr3 + 0) + 50) << ',' << *(*arr3+50) << endl;//51
cout << (*(arr3 + 5))[30] << ',' << *(*(arr3 + 5) + 30) << ',' << *(*(arr3+5)+30) << endl;//81

위의 다양한 표현들은 다 같은 표현이고 변환이 가능하다.