강의/computer science

[ComputerScience] c++의 pointer와 reference 3편 (Call by Value, Call by Reference, Reference)

하기싫지만어떡해해야지 2024. 9. 30. 17:41

이 게시글은

서울대학교 데이터사이언스대학원

조요한 교수님의

데이터사이언스 응용을 위한 컴퓨팅 강의를

학습을 위해 재구성하였습니다.


c++에서의 Pointer와 Reference

마지막 편

 

Call by Value

Call by Reference와

마지막으로

c++에서의 Reference에 대해

정리해보도록하겠다

 

 

Call by Value

 

 

위 ppt의 코드를 잘보자

mySwap이란 함수에서

x와 y를 파라미터로 받고

함수 내부에서 x와 y의 값을

서로 바꿔준다

 

그런 다음 main함수에서

int a와 b를 선언해준뒤

mySwap 함수의

파라미터로 넣어준뒤 a와 b를 출력해본다

 

mySwap 함수 내부에서는

두 파라미터의 값을 서로 교환해주니

main함수에서 정의된 a와 b를

mySwap에 넣어주면

a와 b의 값이 변할까?

 

정답은 변하지 않는다

왜냐하면 mySwap 함수에

a와 b를 넘겨줄 때

변수 자체를 넘겨주는게 아니고

그 값만 넘겨주는 것이기 때문이다

 

 

a에 있는 값인 10과

b에 있는 값인 20만 넘겨줘서

mySwap 함수 내부에서

x와 y의 값만 바뀐다음

mySwap함수의 실행이 끝나면

x와 y는 scope에서 사라지게된다

 

바뀌어도 mySwap 내부끼리 바뀌는거지

main함수의 a와 b까지

침범할 수 없다

 

a와 b 변수 자체를 넘겨준 것이

아니기 때문에 mySwap을 실행해도

a와 b의 값은 변하지 않고

여전히 a=10, b=20이 된다

 

이런걸 Call by Value

한국어로는

값에 의한 호출이라고 부른다

 

 

Call by Reference

 

그렇다면 위의 ppt를 다시 잘보자

이번에는 mySwap함수의 인자로

포인터변수를 받고있다

그런 다음 포인터변수 내부의 주솟값에 접근해

x와 y의 값을 교환해주고있다

 

이 mySwap함수를 main함수에서

a와 b를 인자로 넣은다음 실행시키면

a와 b의 값은 변할까?

 

정답은 변한다

왜냐하면 인자로 값을 넘겨준게아닌

메모리주솟값을 넘겨줬기 때문이다

 

mySwap 함수는 인자로

메모리주솟값을 받았고

함수내부에서 메모리 주솟값에 접근해서

값을 변경하기 때문에

mySwap함수가 끝나도

메모리 주솟값에 들어있는 값은

변경된 상태로 남게된다

 

이러한 것을

Call by Reference

한국어로는

참조에 의한 호출이라고 부른다

 

Reference

 

이런 Call by Reference의 개념을

잘 이해했다면

c++에서의 reference 변수타입에 대해 알아보자

 

reference는 c++의 변수타입으로

c에서는 없는 변수타입이다

 

위 코드를 잘보자

int x = 5;가 선언되었고

그 밑에

int& ref = x;

가 선언되어있다

int&는 int 변수에대한

Reference 변수 선언이고

그렇게 선언된 reference변수 ref에

x의 값이 들어가있다

 

reference 변수는 말그대로

'참조'할 수 있는 변수인데

일종의 별명같은 개념이다

 

위에 이미 int x가 선언되었는데

x의 별명(alias)로

ref도 사용할 수 있게하겠다라는 뜻이다

 

그렇게 선언해주면

ref도 x의 메모리주소에 접근이 가능해지고

ref 값을 변경하면

x도 값이 변경되게된다

 

reference 변수를 선언할 때는

선언과 동시에 반드시 초기화를 해주어야한다

 

 

 

좀 더 현실적인 사례로 예시를 들어보겠다

서울시 관악구 관악로 1 관악캠퍼스 62동이라는

주소에는 건물이 한 개 있다

 

이를 주소로 부르기는 너무 힘드니까

건물 이름을 정해서 불러주기로했다

 

따라서 실제 주소에

어떤 이름을 지정해서

앞으로 이 주소에 있는 것은

이 이름으로 불러주겠다고 약속한 것이다

 

이것이 메모리주소와 변수명의 관계이다

따라서

서울시 관악구 관악로 1 관악캠퍼스 62동이라는

주소에 있는 건물은

합의 하에

서울대학교 관악캠퍼스 중앙도서관

이라는 변수명으로 불리게된다

 

하지만 이 이름도 너무 길고

부르기가 힘들기때문에

별명을 하나 지어서

별명으로도 똑같은 주소를

가리킬 수 있도록 지정해준다

 

따라서 별명으로

서울대 관정도서관

이라는 별명으로 불러주기로 합의했다

 

이것이 바로 Reference 변수의 개념이다

 

 

그렇다면 위 예시에 나왔던

mySwap함수를 reference변수를

사용하는 방식으로 변경해보자

 

mySwap함수의 인자로

int& x와 int& y를 받도록 해준다

 

그런 다음 main함수에 있는 x와 y를

mySwap의 인자로 넣어주면

x가 a의 reference 변수가

y가 b의 reference 변수가 되어서

x와 y로

main함수에 정의되어있는

a와 b의 메모리 주솟값에

접근이 가능해진다

 

따라서 mySwap에서

값을 변경해주어도

메모리주솟값에 접근해서

값을 변경해주는 것이므로

a와 b의 값이 변경된채로 남게된다

 

 

아까 위처럼 포인터변수를 사용해도 되는데

왜 굳이 reference 변수를 사용하냐고하면

pointer변수를 사용하게되면

 

int a와 b가 mySwap함수에 들어가게되면

어쩔 수 없이 value값을 copy해서 들어가게된다

그럼 메모리 공간을 추가로 차지하게되는데

reference 변수는 단순한 별명이기때문에

value값을 copy할 필요가 없어

메모리 사용을 줄일 수 있다

 

또한 포인터변수보다

문법적으로 간단하게 구현이 가능하다

 

 

그렇다면 이런 reference 변수를

아무생각없이 사용하다보면

원하지않게 메모리 주소에 접근해

값을 변경하는 경우가 생길 수 있다

 

따라서 이러한 경우를 대비해

const로 reference 변수를 정의하면

값의 변경을 막아준다고 한다

 

따라서 위 함수처럼 const int&, int&로 선언하면

mySwap함수는 컴파일 단계에서 에러가 뜬다고 한다

 

 

 

이런 Reference 변수는

함수의 Return값으로도 사용할 수 있다

 

위 코드 예시를 잘보자

middleElement 함수는

순서가 있는 데이터타입인 map에서

가운데인 pair를 찾아

그 value값을 반환하는 함수인데

함수의 return type이

int&이므로

반환값은 reference 변수가 된다

 

따라서 오른쪽 코드를 보면

middleElement(m) = 10;

이라는 코드가 있는데

원래라면 문법적으로 있을 수 없는 구문이지만

middleElement 함수의 반환값이

reference 변수이기 때문에 가능하게된다

 

따라서 middleElement(m) = 10;

의 뜻은

위에서 정의해준 map인 m의

가운데 pair를 찾아 value값을

10으로 변경해달라는 의미이다

 

middleElement 함수를 실행한 뒤

m에서 가운데 값인 b의 value를 출력해보면

10으로 변경된 것을 확인할 수 있다

 

 

 

위에서 말했듯이

굳이 reference 변수를 사용하는 이유는

value를 copy 할 필요가 없어져

메모리를 효율적으로 사용할 수 있다

 

따라서 큰 데이터를 사용할 때

유용하게 사용된다

 

 

또한 reference 변수를

return값으로 반환하게하면

chaining operation을 구현할 수 있게된다

 

위의 코드 예시를 통해

이게 무슨 소리인지 알아보자

 

위 코드에서

MyClass라는 클래스가 정의되어있고

이 클래스 내부의 method로

increment 함수가 정의되어있고

함수의 리턴값은 MyClass 클래스의

reference 변수이다

 

increment 함수 내용을 잘 살펴보면

MyClass에 정의되어있는

x의 값을 1개 증가시킨다음

그 값을 출력하고
*this 로 MyClass 자신을 반환한다

그런데 increment 함수의 리턴값이

reference 변수이므로

MyClass의 Reference 변수를 반환하게 되는 것이다

 

이렇게 정의된 MyClass를

main함수에서 초기화한 후 사용해보자

 

main함수에서

MyClass obj;로 정의해준다음

obj.increment().increment().increment()

와 같이 작성해준다

 

위와같은 코드가 어떻게 가능하냐하면

obj.increment()를 실행하면

0이었던 x가 한 개 증가하고

그걸 출력시킨 다음

다시 obj의 reference 값을 반환한다

 

그래서 바로 다음 연속으로

.increment()라고 해줘도

정상적으로 작동하게 되는 것이다

 

이러한 방식을

chaining operation이라고한다

 

따라서 increment()를 3번 실행하게되면

처음에 x: 1

두번째는 x: 2

세번째는 x: 3가 출력되고

멈추게 된다

 

 

이러한 reference 변수를 이용해

chaining operation을 사용하는

가장 대표적인 예시가

cin이다

 

이전에 I/O Stream에서

cin에 대해 배울 때

cin >> value1 >> value2 >> value3;

과 같은 방식으로 프로그래밍하면

value1, value2, value3을

연속으로 할당할 수 있다고 해줬는데

이는

cin >> opeartion이

value값을 reference로 받기때문에

cin >> value1의 반환값은

또 cin이 되고

그 이후로 value2의 값도

연속적으로 할당이 가능해지는 것이다

 

 

 

 

마지막으로

pointer변수와 reference 변수를

비교해보자

 

초기화

pointer변수는 코드의 어디에서

초기화를 해도 상관없다

하지만 reference 변수는

선언과 동시에 반드시 초기화가 되어야한다

 

재할당

포인터는 그냥 일반적인 변수이기때문에

언제든 담고있는 주솟값을 바꿀 수 있다

하지만 reference 변수는

한번 assign이 되면 다른 주소로 reassign이 불가능하다

 

nullability

pointer변수는 역시 일반 변수이기때문에

null값을 가질 수 있다

하지만 reference 변수는

null값을 가질 수 없다

 

memory

포인터는 당연히 일반 변수이기때문에

포인터 변수를 저장하기위해

8바이트의 공간을 차지한다

하지만 reference 변수는 공간이 할당되는

변수가 아니다

기존에 있는 변수를 가리키는 닉네임을

만들어주는 개념이기 때문에

새롭게 공간을 할당받지 않는다

 

주요사용

포인터는 주로 동적 메모리를

할당할 때 사용한다

reference 변수는

주로 함수의 인자나 반환값에 사용한다