본문 바로가기

C++

C++ 배우기 15(더블 포인터, 참조형, 함수 호출 방식)

더블 포인터(이중 포인터)

먼저 포인터는 주소를 가리키는 역할을 한다고 했다. 그렇다면 더블 포인터는 무슨 역할을 할까?

더블포인터는 포인터의 주소를 가리키는 역할을 한다.

쉽게 보자면 이런 식으로 포인터들이 작용한다고 보면 될 것 같다.

포인터 < 더블포인터 < 삼중 포인터 < 사중 포인터...

이렇게 서로의 주소값을 나타낼 수 있다는 것을 알아두면 되겠다.


하지만 우리는 이번에 더블 포인터를 가지고 2차원 배열을 다뤄보도록 할 것이다.

앞서 배웠던 동적 할당하는 방법, new ~ delete를 이용하여 2차원 배열을 선언하겠다.

 

2차원 배열 동적할당
출력결과

먼저 2차원 배열을 담기 위한 더블 포인터 자료형을 만들어준다. (int** bingo)

그리고 변수 bingo에 동적 할당을 해주기 위해선 bingo가 더블 포인터이기 때문에 포인터로 해주어야 한다. (new int*[5])

여기까지만 한다면 int* 자료형의 1차원 배열만 생성이 된 것으로 끝나겠지만, 그 int* 자료형의 1차원배열 안에 하나씩

int 자료형의 1차원 배열을 다시 집어넣어 주면, 결국 우리가 알던 bingo [5][5] 크기의 2차원 배열이 선언되는 것이다.

정리하자면, 1차원 배열 6개가 모여서 2차원 배열을 이루고 있는 것이라 할 수 있다.

 

마지막으로 저번 시간에 동적 할당을 했으면 반드시 해제해주어야 메모리 누수 같은 문제점이 발생하지 않는다고 했다.

따라서 2차원 배열의 동적 할당 해제하는 방법은 다음과 같다.

2차원 배열 bingo 동적할당 해제

우리가 앞서 동적 할당을 하며 2차원 배열을 선언했던 순서의 반대로, 제일 마지막에서부터 bingo의 시작점까지

역순으로 해제해주어야 하는 것을 주의하자.

만약 역순으로 해제하지 않는다면, 중간에 주소값이 사라져서 접근하지 못하는 메모리가 생기는 문제가 발생할 수 있다.


함수와 배열

배열과 포인터

우리는 배열의 이름 = 배열의 시작 주소라는 것을 알고 있다.

그래서 주소값을 담을 수 있는 포인터에 배열의 이름을 대입하면 배열의 시작 주소가 담기는 것을 볼 수 있다.

그렇다면 배열의 시작 주소에 포인터를 대입할 수 있을까? 정답은 불가능하다.

배열의 이름, 즉 배열의 시작 주소는 변하면 안 되는 상수 값이기 때문에 변수인 포인터를 대입할 수 없는 것이다.

 

그리고 만약 함수에서 매개변수로 배열을 받아와야 한다면 어떻게 해야 할까?

위 사진의 2차원 배열 arr와 1차원 배열 arr2가 있다고 하자.

배열을 함수에 받아오자

그리고 Add함수와 Add2함수가 각각 선언되었는데, Add함수의 매개변수는 포인터이고 Add2함수의 매개변수는

더블 포인터이다. main함수에서 두 함수를 호출하려면 어떻게 해야 할까?

 

우리는 함수를 통해 배열의 시작 주소만 매개변수로 받아와도, 해당 배열의 모든 값에 접근이 가능하다는 것만 알면 된다.

함수호출

따라서 위와 같이 배열의 시작 주소(배열의 이름)만 넘기면 자유롭게 해당 배열에 접근이 가능한 것이다.


참조자 & (reference type)

참조형, 즉 자료형 중에 하나이다. 포인터를 공부할 때 이미 한번 본 적이 있을 것이다.

주소값을 나타냄

이처럼 포인터 b에 a의 주소값을 대입할 때 사용하던 것도 참조자이다.

하지만 이번에는 다른 방식의 참조자를 알아볼 것인데, 자료형에 붙어있는 참조형이다.

 

int a = 3;
int* b = &a;
int& c = a; //참조형

위와 같이 사용할 수 있는데, 참조형의 특징으로는 절대 원본이 될 수 없다는 것과 독립성이 없다는 것이다.

참조형 c에 a를 대입한 순간, 그때부터 참조형 c는 변수 a를 그대로 복사해놓은 것과 같은 것이다.

쉽게 말하자면 변수 a와 참조형 c는 한 몸이라고 생각하면 편하다.

 

그래서 변수 a의 값이 변하던지 참조형 c의 값이 변하던지, 둘 중 하나만 변해도 같이 변한다고 보면 된다.

어쩌면 포인터에 대입한 변수의 주소값과 비슷한 느낌이라고 생각이 들지만, 포인터는 따로 자신만의 주소값이 있지만

참조형은 없다는 것이 차이점이라고 생각된다.


함수 호출 방식

//call by value
void Add5(int x)
{
    x += 5;
}

//call by reference
void Add6(int& x)
{
    x += 6;
}

//call by adress(pointer)
void Add7(int* x)
{
    *x += 7;
}

int main()
{
    int n = 10;

    //call by value
    Add5(n);
    cout << n << endl;
    
    //call by reference
    Add6(n);
    cout << n << endl;

    //call by address(pointer)
    Add7(&n);
    cout << n << endl;
}

우리가 사용하는 함수의 호출 방식에는 3가지의 방식이 있다.

1. call by value(값에 의한 호출)

위에 변수 n을 생성하고 Add5() 함수를 호출하였다. 그래서 매개변수로 넘어간 n의 값이 함수 안에서 +5 연산되었지만,

함수가 종료된 후 출력해보면 값이 그대로 10인 것을 알 수 있다. 이는 매개변수로 넘어간 n = 10의 값은 Add5() 함수의

안에서만 사용 가능한 매개변수였기 때문이다.

그렇다면 우리는 값이 아니라 주소를 통해 접근할 필요가 있다.

 

2. call by reference(참조에 의한 호출)

이번에는 Add6() 함수를 호출하여 변수 n을 매개변수로 대입하였다. 그리고 Add6() 함수에서는 매개변수를 참조형으로

받게 되는데, 이렇게 되면 변수 n의 주소값이 매개변수에 저장되는 것이다.

따라서 함수 안의 참조형 x에 +6이 연산이 되면, 참조형 x안에 들어있는 n의 주소값이 가리키는 값 10에 직접 대입이 

되어서 함수 종료 후에도 값이 변해있는 것을 볼 수 있다.

그런데 주소값으로 접근할 수 있는 게 또 하나 있지 않은가?

 

3. call by pointer / address(포인터에 의한 호출)

Add7() 함수에 변수 n을 대입하였다. 그런데 Add7() 함수의 매개변수는 포인터라서 변수 n에 참조자를 사용하여

n의 주소값을 넘겨주게 되었다. 그렇게 *x에 +7이 연산이 됬다. 그런데 *x에는 변수 n의 주소값이 담겨져 있다.

그렇다면 그 주소값을 타고 넘어가 n의 값에 접근이 가능해지고, 따라서 n 값에 직접 +7이 연산되게 되는 것이다.


이렇게 주소값을 이용하면 굳이 전역변수로 선언하지 않아도 여러 함수들에 주소값을 타고 다니며 직접 해당하는 변수의 값에 접근이 가능하게 해 줄 수 있다.

아직 많이 헷갈리고 주소값을 따라가면 어떤 값이 나오는지 어려울 수도 있지만, 이 부분은 프로그래밍의 가장 중요한

부분 중 하나이므로 열심히 공부하자.

'C++' 카테고리의 다른 글

C++ 배우기 17(SAL, 표준 라이브러리)  (0) 2022.11.04
C++ 배우기 16(함수 오버로딩)  (0) 2022.11.02
C++ 배우기 14(동적 할당)  (0) 2022.10.31
C++ 배우기 13(메모리 관리)  (0) 2022.10.31
C++ 배우기 12(포인터)  (0) 2022.10.28