Program Languege/more

포인터 자세히 알기

Frozen0113 2013. 3. 7. 22:45

포인터 자세히 알기

 

 

Ex>        int   a = 10;

  int   b =  5;

    1) int  *p;

    2) p = &a;

    3) cout << p << endl;

    4) cout << *p << endl;

    5) b  = *p;

    6) *p  = 7;

    7) int *q = p;

 

1) 여기서 *는 int형 포인터 p라는 변수를 선언하는데에서 p가 포인터라는 것을 명시하는데에 사용됩니다. 한마디로 지금 선언한 이 변수

    p는 포인터다! 라는 말입니다.

 

2) p는 포인터이기 때문에 &를 붙여 일반 변수의 주소를 넣어줍니다.

    - &는 포인터 차수를 올려주고, *는 차수를 낮춰준다고 생각하면 이해가 쉬울거라고 생각합니다.

       일반 변수를 0차, int *p는 1차, int **pp는 2차... 이런 식으로 같은 차수의 대입만 가능하기 때문에 잠시 일반 변수를 일반 변수의

 주소 값으로 치환하면서 1차 포인터처럼 쓰이는 것입니다.

 

3) 여기서 p는 포인터 변수 자체를 의미합니다. 포인터 p가 가리키고 있는 값을 의미하는 것이 아닌, 자기 자신이 가지고 있는 값 다르게

    생각하면 자신이 가리키고 있는 주소를 의미하지만 일단 int a = 0; 에서 a에는 0이라는 값이 들어있다고 생각하듯 간단히 p에 들어있

    는 값, p가 가지고 있는 값이라고 생각합시다. 포인터 변수에 아무런 연산자 없이 단순히 포인터 변수만 쓰인다면 일반 변수처럼 안에

    들어 있는 값을 그대로 사용하는 것입니다. 그렇기 때문에 p에 저장되어 있는 값을 그대로 출력합니다.

 

4) 이번에는 *가 붙었습니다. *는 이동을 의미합니다. p가 가리키는 곳으로 가라. 그리고 위에서 말했듯이 차수를 줄인다고 생각하라고했

   는데 그럼 일반 변수처럼 되기때문에 일반 변수의 값과 치환이 될 겁니다. p가 가리키고 있는 주소, a를 찾아가 그곳에 있는 10을 전해

   줄 것 입니다. 그래서 *p는 int형 값 10이 됩니다. 그리고 10이 출력됩니다.

 

5) b는 일반 변수입니다. 그리고 =이 대입연산자의 경우 포인터 차수가 같아야 대입 연산이 가능합니다. 그래서 0차인 일반 변수에 대입

    하기 위해 1차인 포인터 p를 *를 사용해서 차수를 0으로 줄여서 대입합니다. 이 과정은 p가 가리키고 있는 주소에 찾아가 그곳에 있는

    값을 가져와 치환합니다. 그렇기 때문에 b = 10; 과 같은 결과가 나옵니다.

 

6) 여기서 *는 위에 5)처럼 포인터 p가 가리키는 곳에 가라입니다. 하지만 대입연산자의 왼쪽에 사용되었기때문에 가리키는 곳을 따라

    가서 그곳에 이 값을 주고와라가 됩니다. 그렇기때문에 p가 가리키는 주소, a를 찾아가 그곳에 7을 놓고 옵니다. 그렇기 때문에 결과

    적으로 a = 7;과 같은 결과가 나옵니다.

 

7) 포인터끼리의 대입연산입니다. 여기서 중요한 것은 q에는 *이 있고 p에는 없습니다. q에 붙은 *은 위에서 말했듯이 이 변수는 포인터

   변수라는 것을 알리는데 사용됩니다. p와의 대입 연산에는 직접적인 영향을 끼치지 않습니다. 단순히 q를 1차 포인터로 선언하는데에

   쓰입니다. 풀어쓰면 다음과 같습니다.   

 

   int *q;

   q = p;

 

   q = p의 대입연산에서는 *가 필요 없습니다. 같은 1차 포인터 대입연산이기때문에 p의 값이 q에 대입됩니다.(같은 차수) 그래서 q도 p

   와 같은 곳을 가리키는 포인터가 됩니다.

 

포인터를 사용할 때 어떤 상황에서 *를 써야하는지 &와 *는 어떤 의미인지 감이 오시나요?

 

위에서 본 바로는 몇가지 특징을 알 수 있습니다. *은 선언시에는 포인터 변수라는 명시적 표현. 선언 이외에 사용에서는 포인터의 차수를 낮추는 효과를 지닙니다.(포인터가 가리키는 곳으로 이동하기 때문에 이동한 곳은 일반 변수의 공간이기때문에 차수가 내려가는 것으로 볼 수 있습니다. 가리키는 곳으로 이동하여 해당 공간에 접근하는 것을 참조라고 합니다.) 포인터가 대입연산자 왼쪽에 위치하면 대입하는 값을 넣을 공간을 의미하고 오른쪽에 위치하면 대입할 값을 의미합니다. 그리고 같은 차수의 연산에서는

*가 필요하지 않습니다.(하지만 경우에 따라 *는 붙을 수 있습니다.)

 

위에서 같은 포인터끼리의 연산에서는 *가 필요하지 않지만 경우에 따라 *는 붙을 수 있다고 하였는데 어떤 경우에 붙을까요?

 

Ex>    int a =  7;

   int b = 10;

   int *p = &a;

   int *q = &b;

   int **pp;

 

   1) *q = *p;

   2) pp = &p;

 

1)의 경우에 사용됩니다. 둘다 *이 붙었습니다. 둘다 동일한 1차 포인터이기 때문에 한쪽에 *이 붙어 차수가 내려가면 다른 쪽도 차수를

  맞춰주어야합니다. 위의 경우는 p와 q가 가리키는 값들을 사용하는 경우입니다. 포인터끼리 대입연산은 같은 것을 가리키는 연산이 되

  는데 여기서는 q가 가리키는 곳에 p가 가리키는 곳에 있는 값을 넣으라는 뜻입니다. q와 p에게 각자 가리키는 곳에 가보라고 한 뒤에 p

  는 대입 연산자 오른쪽에 위치했기때문에 p가 간 곳에 있는 값을 가져오고 q는 대입 연산자 왼쪽에 위치하기 때문에 p가 가져온 값을 q

  에 넣는 것입니다. 그렇기 때문에 b = a;의 결과와 같습니다.

 

2)는 2차에 대한 예시를 보여주기 위해 적었습니다. pp는 2차 포인터입니다. 그렇다면 2차 포인터에는 어떤 값이 들어갈까요? 포인터는

  자신보다 1차수 낮은 변수의 주소를 저장합니다. 그렇기때문에 2차 포인터인 pp는 1차 포인터의 주소를 값으로 저장합니다. 그렇기 때

  문에 1차 포인터인 p의 주소 값을 대입해주면 됩니다. &는 대상의 주소를 값으로 가져오는 연산인데 당연히 포인터는 주소를 저장하기

  때문에 &를 붙여줘야하는데 1차, 2차 포인터간의 이해를 돕기위해 차수를 올린다고 표현하는 것입니다. 오히려 혼란을 올 수 있는데 잘

  이해하시길 바랍니다.

 

  포인터는 주소를 저장한다 -> 포인터엔 변수의 주소를 넣는다 -> 1차 포인터에는 (0차)일반 변수의 주소를 넣는다 -> &는 차수를 높

  여준다 -> 포인터끼리는 차수가 같아야 대입연산이 가능하다 -> &를 사용하여 같은 차수가 되어 대입연산이 가능하다.

 

잠시 차수에 따른 정리를 해보자면 다음과 같습니다.

 

 차수

 저장 값 

참조 대상(가리키는 대상) 

 0차  일반 변수, int a

일반 자료형 

 1차  int *p

주소 값 (일반 변수의 주소)

 0차 일반 변수

 2차  int **pp

주소 값 (1차 포인터의 주소)

 1차 포인터

 ...

... 

 ...

 

포인터와 포인터 안에 저장된 값, 참조하는 값 간의 규칙?을 잘 알아보셨기를 바랍니다.

 

 

그럼 이제 다음으로 넘어가겠습니다. 왜 어째서 이런 복잡한 포인터를 사용하는가?라는 생각을 합니다. 그냥 변수끼리 값을 주고 받으면

되는데 왜 포인터를 사용해야하는가?

 

여기 설명부터는 함수와 저장 공간(stack), call by value, call by address 정도를 알고있다고 생각하고 설명하겠습니다.

 

간단히 말하면 함수를 사용하기때문이라고 할 수 있습니다. 함수의 사용, 함수를 사용하므로써 코드의 간결성, 유지보수, 가독성 등을 높

일 수 있는데 이 때 함수를 사용하면 생기는 문제점이라고 할 수 있습니다. 무슨 문제가 생기는가?

 

코드에서 {}를 사용하면 {와 }사이는 한 지역으로 묶이게 됩니다. 이 지역이 무엇이냐? 이 지역안에서 변수를 선언하여 사용할 때 이 변수의 생명주기는 지역안에서('{' 이후) 선언시 생성되며 지역의 끝. '}'를 만나면 소멸됩니다. 생성되고소멸된다. 이 말은 {} 지역안에서 변수를 생성해서 사용하다가 {}지역이 끝나면 이 변수가 사라져서 안에 저장한 값을 사용 할 수 없다는 뜻입니다. 다음 함수를 예로들어 설명하겠습니다.

 

void swap(int a, int b)

{

int temp;

 

temp = a;

a = b;

b = temp;

}

 

간단히 넘겨 받은 a와 b의 값을 바꿔주는 함수입니다. 넘겨 받은 a와 b? 그럼 main()에 있던 애들을 넘겨준거잖아? 그럼 상관 없잖아라고 생각할 수 있습니다. 다음 예시를 들어 설명하겠습니다.

 

int main()

{

int a = 10;

int b =  7;

 

1)swap(a, b);

 

return 0;

}

 

1)이 실행되고나면 어떻게 될까요? a와 b는 바뀔까요? 안바뀝니다. 함수의 호출에는 call by value, call by address, call by reference 이 세가지가 있습니다. 여기서 저 swap함수 호출은 call by value입니다. 실행되는 과정을 보면

 

swap()함수를 호출하면서 a와 b를 매개변수로 사용합니다. 이때 함수로 넘어가면서 a와 b 자체가 같이 넘어가는 것이 아니라 a와 b의 값을 복사해서 넘겨줍니다. 값만! 그렇기 때문에 a와 b는 swap에게 값을 복사해줬을 뿐 상관이 없다는 뜻입니다. 그리고 swap()함수안에서 int a, int b를 만들면서 전달 받은 값을 넣는 것 입니다.

 

void swap( 10, 7)

{

1)int a = 10;

2)int b = 7;

 

int temp;

 

...

}

 

1) swap()함수안에서 int a선언과 동시에 함수 호출시에 넘겨받은 10으로 초기화.

2) swap()함수안에서 int b선언과 동시에 함수 호출시에 넘겨받은  7로 초기화.

 

이런 과정이 일어납니다. 여기에 a랑 b가 있는데? 여기서 명심할 것은 swap()함수 안에 a, b와 main()함수 안에 a, b는 전혀 다른 변수입니다. 근데 왜 헷갈리게 a와 b로 변수명을 지었느냐... 여러분 헷갈리라고 했습니다. 정확히 알고 넘어가시라는 의미에서 했다고 하는게 좀 더 좋은 표현이겠네요. 지역이 다르면 같은 이름의 변수를 선언할 수 있다는 것은 알고있는 걸 전제로 설명은 넘어가겠습니다.

 

전혀 다른 변수다라는 것은 변수의 저장 공간, 할당 받은 메모리 주소가 다르다는 것입니다. 다음은 stack영역에 들어있는 변수들을 알아보기 쉽게 표현해본 것입니다.

 

 

 ...

... 

... 

 swap()함수 영역

 int temp;

쓰레기 값 

 

 int b;

7

 

 int a;

10

 main()함수 영역

 int b;

7

 

 int a;

10

 ...

... 

... 

 

 

함수 내부를 수행하고나면

 

 

...

...

...

swap()함수 영역

int temp;

10

int b;

10

int a;

7

main()함수 영역

int b;

7

int a;

10

...

...

...

 

 

이렇게 값이 바뀝니다. 그리고 함수 종료후에는

 

 

...

...

...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

main()함수 영역

int b;

7

int a;

10

...

...

...

 

 

 위처럼 swap()영역은 소멸됩니다. 그리고 main()의 a, b는 무슨 일이 있냐는 듯 그대로 아무런 변화 없이 계속 있습니다. 그렇다면 원하는대로 값을 함수의 호출을 통해 바꿔주려면 어떻게 해야하는가? 그때 사용되는 것이 call by address나 call by reference입니다. 여기선 call by reference는 설명하지 않겠습니다.

 

call by address, 주소에 의한 참조. 주소를 찾아가서 바꾸면 되겠다! 라고 생각을 한 것 입니다. 주소를 찾아간다는 것은 바꾸려는 대상을 찾아가 값을 바꾸고 온다는 것입니다. 즉 swap()함수 영역에서 main()함수 영역에 있는 a와 b를 찾아가서 바꾸겠다는 말로 swap()함수의 공간이 소멸되든말든 상관이 없다는 뜻입니다.

 

void swap(int *a, int *b) 

{

1)int temp;

 

2)temp = *a;

3)*a = *b;

4)*b = temp;

}

 

1)은 임시저장을 위한 변수입니다. 무엇을 저장하느냐 포인터가 가지고 있는 주소 값? 아니죠. a와 b의 값을 바꾸기 위해 잠시 a의 값을

   저장하는데 사용할 변수입니다. 그렇기때문에 포인터가 아닌 일반 변수로 선언합니다.

 

2)는 일반변수에 1차 포인터 대입입니다. 일반 변수에는 일반 자료형 값을 저장하기때문에 포인터 a가 참조하고 있는 곳의 값을 가져와

  대입합니다. 0차에 값을 넣기위해 1차를 *을 사용해서 0차로 만들어 대입합니다.

 

3)은 1차 포인터끼리의 대입입니다. 하지만 우리가 바꾸려는 대상은 무엇이다? 포인터들이 가지고 있는 주소 값이 아닌 포인터들이 참조

   하고 있는 변수에 들어있는 값입니다. 그렇기때문에 *를 사용하여 움직입니다. 주소 값을 통한 참조, 대상을 찾아가 값을 대입합니다.

   바꾸려는 대상은 0차이기 때문에 1차를 각각 *를 사용해서 0차로 만들어 대입합니다.

 

4)1차 포인터에 0차 일반 변수를 대입합니다. 근데 여기서 중요한 것은 어떤 값이 필요한가입니다. 일반 변수 값을 변경하려는 것이기때문에(1차 포인터가 참조하고 있는 0차 일반 변수의 값 변경.) 1차 포인터 b를 *를 사용하여 0차로 만들어 대입합니다.

 

위처럼 과정을 표현자면

 

(포인터이기때문에 주소를 추가했습니다. 주소는 임의로 정한 주소로 단순히 구별하기위함입니다.)

...

...

...

 ...

swap()함수 영역

int temp;

쓰레기 값

0x34CC

int *b;

0x25F0

0x67CF 

int *a;

0x54FC

0x47AE

main()함수 영역

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

 

 

함수 내부를 수행하고나면

 

 

...

...

...

...

swap()함수 영역

int temp;

10

0x34CC

int *b;

0x25F0

0x67CF

int *a;

0x54FC

0x47AE

main()함수 영역

int b;

10

0x25F0

int a;

7

0x54FC

...

...

...

 

 

함수가 끝나면

 

 

...

...

...

...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

main()함수 영역

int b;

10

0x25F0

int a;

7

0x54FC

...

...

...

 

 

이렇게 됩니다. 소멸된 부분은 위와 같고 다른 부분은 주소를 통해서 원본에 접근했기때문에 값이 바뀐 것을 알 수 있습니다. 포인터를 사용하는 이유가 좀 이해가되시길 바라지만 포인터를 사용하는 이유는 이 뿐만이 아니기때문에 일단 기본적인 사용을 배운 것으로 만족하고 넘어가겠습니다.


매개변수로 포인터를 사용한다고 모두 call by address라고 할 수 있는 것은 아닙니다. call by address는 일단 call by value로 시작합니다. 먼저 값(주소 값)의 복사가 일어나기때문에 call by value가 되고 그 다음에 복사해서 넘겨받은 주소 값으로 참조를 하면 그때 call by address가 되는 것 입니다. *을 사용한 참조, 움직임이 없다면 call by value가 됩니다.

 

void swap(int *a, int *b)

{

1) int *temp;

 

2) temp = a;

2) a = b;

2) b = temp;

}

 

1) call by value에 해당하는 경우를 보여주기 위해 1차 포인터로 임시 저장 변수(temp)를 선언했습니다.

 

2) *연산자가 없습니다. 이 말은 참조가 일어나지 않았다는 것이고 움직임이 없다, 주소 값을 단순히 주소형 값으로만 사용하여 대입하였

   습니다. 이 말은 아이들에게 주소를 종이에 적어서 심부름을 시켰는데 아이들이 우르르 한쪽에 가더니 자기들끼리 주소가 적힌 종이를

   서로 주고받고 놀고다가 종이와 아이들이 사라진 것과 같습니다. 맨처음에 설명한 call by value와 같은 상황입니다.

 

 

표로 설명해보면

 

 

...

...

...

...

swap()함수 영역

int *temp;

쓰레기 주소 값

0x34CC

int *b;

0x25F0

0x67CF

int *a;

0x54FC

0x47AE

main()함수 영역

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

 

함수 내부를 수행하고나면

 

 

...

...

...

...

swap()함수 영역

int *temp;

0x54FC

0x34CC

int *b;

0x54FC

0x67CF

int *a;

0x25F0

0x47AE

main()함수 영역

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

 

함수가 끝나면

 

 

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

main()함수 영역

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

 

차이를 아시겠습니까? 움직임이 일어나지 않으면! swap()함수 영역 밖으로 나가서 참조가 일어나지 않으면 바뀌는 것은 없는 것입니다. 포인터를 일반 변수처럼 swap()함수 내부에서 사용하고 끝난 것입니다.

 

 

그럼 포인터의 값을 변경하고 싶은 경우를 설명하겠습니다.

 

int main()

{

int a = 10;

int b = 7;

int *p = &a;

int *q = &b;

 

swap(&p, &q);

}

 

void swap(int **pp, int **qq)

{

1) int *temp;

 

2) temp = *pp;

3) *pp = *qq;

4) *qq = temp;

}

 

1) 지금 이 함수에서 바꾸고 싶은 대상은 1차 포인터의 값이기때문에 1차 포인터 임시저장 변수를 선언.

 

2) temp는 1차 포인터이고 pp는 2차 포인터, 이 때 변경할 값의 대상은 1차 포인터이기때문에 *연산자로 2차 포인터에서 참조를 통해 1차

   포인터안에 있는 주소 값을 가져와 대입한다.

 

3) 같은 2차 포인터이지만 변경할 대상은 참조하고 있는 1차 포인터이기때문에 *연산자로 1차로 변경한다.

 

4) temp에 임시 저장한 값은 1차, qq는 2차 포인터 변경 대상은 1차이기때문에 qq에 *연산자를 붙여서 1차로 변경. 참조하고 있는 대상인

   1차 포인터를 찾아가 값을 대입한다.

 

 

2차 포인터가 나왔습니다. 이유가 뭘까요? 처음 call by address설명에서 바꾸고 싶은 대상의 주소를 보냈습니다. 원본의 주소를 보낸다. 이는 함수에서 값을 변경하려면 원본의 주소를 보내야하고 원본보다 1차수 높은 포인터를 사용해야한다는 것을 알 수 있습니다. 그렇기때문에 1차 포인터 p와 q의 주소를 넘겨주었고 이는 &연산자때문에 2차 포인터로 값을 받아야합니다. 그렇기 때문에 swap함수에서는 매개변수가 2차 포인터입니다.

 

 

표로 설명해보면

 

...

...

...

...

swap()함수 영역

int *temp;

쓰레기 주소 값

0x34CC

int **qq;

0x56CA

0x67CF

int **pp;

0x780B

0x47AE

 main()함수 영역

int *q;

0x25F0 

0x56CA

 

int *p;

 0x54FC

0x780B 

 

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

함수 내부를 수행하고나면

 

...

...

...

...

swap()함수 영역

int *temp;

0x780B

0x34CC

int **qq;

0x56CA

0x67CF

int **pp;

0x780B

0x47AE

main()함수 영역

int *q;

0x54FC

0x56CA

int *p;

0x25F0

0x780B

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

함수가 끝나면

 

...

...

...

...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

 ...

... 

 ...

 ...

 ...

main()함수 영역

int *q;

0x54FC

0x56CA

int *p;

0x25F0

0x780B

int b;

7

0x25F0

int a;

10

0x54FC

...

...

...

 

 

 위의 예시들의 결과를 잘 살펴보면 call by address의 특징을 알 수 있다.

 

call by address에서는 변경하려는 대상의 주소를 보내야한다. 이 말은 함수 내부에서 1차수 높은 포인터를 사용해서 참조해야한다는 뜻입니다. 그리고 *연산자를 통한 호출함수 외부에 접근이 있어야한다. 움직임이 있어야한다.

 

그럼 저 함수에서는 main()함수의 a와 b에 접근 할 수 있을까요? 접근이 가능합니다. 2차 포인터 pp에 *을 붙이면? main()함수의 1차 포인터 p에 접근을 합니다. 이게 가능한 것은 위에서 설명했습니다. 그럼 **pp가 되면 어떻게 될까요? *가 두개기때문에 차수가 2개가 내려갑니다. 그 말은 0차 일반 변수가 된다는 뜻입니다.

 

 **pp = *(*pp) = *(p) = *p = a

 

위의 표현이 같은 것을 아시겠습니까? 잘 생각해보시기 바랍니다.

 

 

이번엔 배열과 포인터의 표현 관계를 알아보겠습니다.

 

 

배열을 포인터로 표현이 가능할까?

 

 

배열의 이름은 모배열, 상수 포인터, 배열 포인터라고 불립니다. int a[5]; 가 있을 때 a를 말합니다.

 

int a[5] = {};

 

cout << a << endl;

 

위처럼 했을 때는 배열의 시작 주소가 출력됩니다. a의 시작 주소는 a[0]의 주소와 같습니다. 그러므로 a = &a[0]이 성립됩니다. a는 배열 포인터이다. 그럼 포인터로 값을 받을 수 있을까?

 

int *p = a;

 

이런 식의 대입이 가능합니다. 포인터의 차수는 배열의 차원과 동일합니다. 1차 포인터는 1차원 배열의 주소를 받을 수 있고 2차 포인터는 2차원 배열의 주소를 받을 수 있습니다.

 

int a[5] = {};

int *p = a;

 

1) a[0] = 1;

2) *p = 1;

 

1)과 2)는 같은 의미일까요? a가 갖는 주소는 배열 a의 시작 주소이며 이 시작 주소는 a[0]의 주소와 같다고 했습니다. 그럼 p가 가리키는 곳은 어디일까요? 배열 a의 시작 주소를 가리키기 때문에 a[0]를 가리키는 것과 같습니다. 그렇기 때문에 *p는 a[0]와 같은 의미가 됩니다. 그럼 a[1]은 p로 어떻게 가리킬 수 있을까요?

 

p = &a[1]; 이런 방법도 있지만 배열은 일정한 크기의 연속적인 공간입니다. 포인터에 + 연산을 하면 주소 단위의 연산이 이루어져서 배열에서 한칸을 이동한 것과 같은 효과를 갖습니다. 그렇기 때문에

 

a[1] = *(p + 1)  과 같습니다. 여기서 괄호를 써준 이유는 *연산이 +연산보다 우선 순위가 높기때문에 포인터에 주소 단위 연산을 하고서 참조를 해야하기때문에 괄호를 사용했습니다.

 

 

a[0] 

a[1] 

a[2] 

a[3] 

a[4] 

 *(p + 0)

*(p + 1) 

*(p + 2) 

*(p + 3) 

*(p + 4) 

 

 

이런 규칙을 알 수 있으며 []이 연산자는 *( + 인덱스)와 같다는 것을 알 수 있습니다.

 

그럼 2차원 배열을 보도록 하겠습니다.

 

 

 int a[3][4];

 

 a[][0]

a[][1] 

a[][2] 

a[][3] 

 

 

 ↓

 ↓

 ↓

 a[0][]

 →

 a[0][0]

a[0][1]

a[0][2]

a[0][3]

 a[1][]

 →

 a[1][0]

a[1][1]

a[1][2]

a[1][3]

 a[2][]

 →

 a[2][0]

a[2][1]

a[2][2]

a[2][3]

 

 

행과 열의 기준에 따라 표가 바뀔 수 있겠지만 이 표를 기준으로 설명하겠습니다.

 

2차원 배열에 접근 방법은 가장 가까이 붙은 인덱스부터 접근합니다. a[0][2]로 설명하면 먼저 a의 첫번째 인덱스에서 0번째를 참조하고 그다음 인덱스에서 2번째를 참조하는 방식입니다.

 

그럼 이것을 포인터로 표현할 수 있습니다.

 

a[0][0] = **p = *(*(p + 0) + 0)

 

a[2][3] = *(*(p + 2) + 3)

 

a[0][3] = *(*p + 3) = *(*(p + 0) + 3)

 

a[1][2] = *(*(p + 1) + 2)

 

포인터로 표현하는 규칙은 무엇일까? 첫번째 인덱스와 포인터에서 첫 연산의 관계를 보면 알 수 있습니다. 첫번째 인덱스란 2차원 배열에서 먼저 참조하는 공간이므로 포인터로 접근시에도 가장 먼저 첫 인덱스만큼 연산을 해서 참조하고 그뒤에 인덱스만큼 다시 연산해서 참조를 하면 됩니다.

 

다음은 괄호의 중요서에 대해서 설명하겠습니다.

 

int a[5] = {0, 1, 2, 3, 4};

int *p = a;

 

이렇게 있을 때 *(p + 1) 과 *p + 1의 결과는 같을까? 결과는 같다입니다! 하지만 과정은 다릅니다!

 

*(p + 1) => a[0 + 1] => 1          *p + 1 => a[0] + 1 => 0 + 1 => 1

 

둘의 차이를 잘 살펴보기를 바랍니다. 결과는 같지만 과정이 다르기때문에 이 예시에서는 같은 결과가 나왔을지 몰라도 배열의 값이 1, 3, 5, 7, 9였다면 어떻게 되었을까?

 

int a[5] = {1, 3, 5, 7, 9}

int *p = a;

 

*(p + 1) => a[0 + 1] = 3           *p + 1 = > a[0] + 1 => 1 + 1 => 2

 

배열 안에 들어 있는 값에 따라 결과가 다르기 때문에 꼭 괄호를 잘 사용하기를 바랍니다.