포털:컴퓨터공학/C++/Reference

위키배움터
둘러보기로 가기 검색하러 가기

이 문서는 대문/포털:컴퓨터공학/C++의 하위문서입니다. 문서를 수정하기 전에 토론:대문/포털:컴퓨터공학/C++을 참고해 주시기 바랍니다.

Reference[편집]

Reference는 포인터(pointer)와 유사한 면이 있습니다. 포인터에 대해 조금 복습해 봅시다.

pointer[편집]

8. 포인터
#include <iostream>

using namespace std;

void swap(int * a, int * b)
{
 int temp=*a;
  *a=*b;
  *b=temp;
}

int main(void)
{

 int num1=10, num2=20;

 cout<<"num1 is "<<num1<<", num2 is "<<num2<<endl;

 swap(&num1, &num2);

 cout<<"called swap. now num1 is "<<num1<<", num2 is "<<num2<<endl;

 return 0;
}
num1 is 10, num2 is 20

called swap. now num1 is 20, num2 is 10

8번 소스는 포인터를 배울때 익히 코딩해봤을 포인터를 이용한 swap을 C++상에서 구현한 것입니다. 포인터를 C++에서도 사용할 수 있다는 것을 알 수 있습니다.

포인터는 C에 있는 강력한 기능입니다. 하지만 너무나도 강력하기 때문에 아주 위험한 기능이기도 합니다. 다음 소스를 실행하면 어떻게 될지 생각해 봅시다.

9. 위험한 포인터
#include <iostream>

using namespace std;

int main(void)
{

 int * danger;
 for(unsigned int i =0; i<0xffff; i++)
 {
   danger = (int *)i;
   *danger = 0;
 }

 return 0;
}

}

9번 소스 프로그램이 실행된다면 아마도 runtime error가 발생할 것입니다. 이 소스는 메모리의 0번지부터 65,535(=0xffff)번지까지에 0을 넣는 소스입니다. 본래 그곳에 어떤 값이 들어있든, 운영체제와 관련된 아주 중요한 값이 들어있어도 프로그램은 상관하지 않고 0을 넣을 것입니다. 이것이 포인터의 무서움입니다. 허나 실제 요즘 윈도우에서는 메모리관리를 권한 위주로 관리하기 때문에 아무런 권한이 없는 응용프로그램이 아무 메모리 번지를 수정하지 못 함으로 이론만 그렇다 쳐야 합니다. 어쨌건 많은 프로그래머들이 포인터가 아주 편리한 요소지만 동시에 너무나도 무서운 요소라고 생각했습니다. C++은 C와 마찬가지로 포인터를 사용할 수 있습니다. 동시에, C++은 포인터와 비슷한 기능을 하면서 포인터의 무서운 요인을 제거한 새로운 기능을 선보였습니다. 그 기능의 이름이 바로 참조자(Reference)입니다.

 T I P 
  runtime error는 여러 원인에 의해 발생합니다.
  9번 소스에서의 runtime error는 운영체제가 보기에, 이 프로그램의 행동이 컴퓨터를 정상적으로 운영하는데 치명적일 수 있기 때문에, 운영체제에 의해서 발생한 것입니다.
  runtime error에 대한 깊은 이해는 이 강의의 수준을 넘어서기 때문에 따로 적지 않겠습니다.

Reference[편집]

참조자는 참조하는 변수를 가르킵니다. 포인터에서 "포인터 변수"를 만들어 변수의 주소를 저장한 것과 같이, 참조자는 참조 변수를 만들어 변수를 참조합니다. 10번 소스를 보면 이 설명이 무슨 뜻인지 더 쉽게 알아들실 수 있으실 겁니다.

10. 참조자
#include <iostream>

using namespace std;

int main(void)
{

 int general_value = 10;
 int & reference_value = general_value;

 cout<<reference_value<<endl;

 reference_value = 20;

 cout<<reference_value<<endl;
 cout<<general_value<<endl;

 return 0;
}

}
10
20
20

&는 비트연산자 AND기호이기도 하고, && 이렇게 두번 연속 쓰면 논리 연산자 AND기호이며, 선언된 변수 앞에 쓰여 변수의 주소값을 나타내기도 합니다. 10번 소스에서 &는 참조연산자입니다. int &라는 건 int형 참조 변수를 선언하는 문장입니다.

 T I P 
  이 부분에 여러가지 설명이 한꺼번에 몰려있습니다.
  작성자는 참조자에 대해 중복 내용을 반복해서 설명할 것입니다. 만약 설명이 이해가 가지 않는다면 넘어가시고 계속 학습해주세요.
  이 챕터가 끝날때까지 참조자에 대해서 이해가 가지 않는다면 그때 다시 처음부터 읽어보세요.


int & reference_value = general_value는 reference_value라는 "int형 참조 변수"를 선언하고, generla_value로 초기화시키는 문장이겠지요?

이것은 refernece_value는 "int형 참조 변수"로 선언하며, general_value를 참조한다는 뜻입니다.


이 말이 쉽게 와닿지 않는다면, reference_value는 general_value의 다른 이름이다라고 설명 드리겠습니다. 다른 이름이라는 건 또 무슨 뜻이냐고요?

reference_value는 general_value와 완전히 동일하다고 생각하세요. 동일한 변수를 부르는 이름이 general_value와 reference_value, 이렇게 2개인 것입니다. reference_value를 출력하면 general_value와 같은 값이 나옵니다. 또 reference_value의 값을 바꾸면 general_value의 값도 바뀝니다. 왜냐하면 둘은 이름은 다르지만 같은 것을 가르키기 때문입니다.

10번 소스를 한 번 더 보여드리겠습니다. 주석과 함께 다시 한번 살펴봅시다.

10. 참조자
#include <iostream>

using namespace std;

int main(void)
{

 int general_value = 10;
 int & reference_value = general_value; // refernece_value라는 "int형 참조 변수"는 general_value의 다른 이름으로 선언되었습니다

 cout<<reference_value<<endl; //reference_value는 general_value와 동일한 값입니다

 reference_value = 20; // reference_value는 general_value의 다른 이름이니, reference_value를 초기화하면 general_value도 초기화됩니다.

 cout<<reference_value<<endl;
 cout<<general_value<<endl;

 return 0;
}

}
10
20
20


 예제 2번 
 변수와 그 변수를 참조하는 참조 변수를 만듭니다.

  변수로 입력 받고, 참조 변수로 출력해보세요. 또 참조 변수로 입력 받고, 변수로 출력해보세요.



다음은 참조자가 가지는 대표적인 규칙입니다.

  1. 참조자는 선언되면서 초기화 되어야 합니다.
  2. 참조자는 변수로 초기화되어야 합니다.
  3. const 참조자는 변수와 상수로 초기화 할 수 있습니다.
  4. 참조자의 참조자는 존재할 수 없습니다.
  5. 참조자는 실제로 존재할 수 없습니다.


참조 변수는 누군가의 "다른 이름"이기 때문에, 선언되면서 반드시 "다름 이름"을 붙일 누군가를 알려줘야 합니다. 실행되지 않는 다음 11번 소스를 봅시다.

11. 실행되지 않는 참조자 소스
#include <iostream>

using namespace std;

int main(void)
{
 int general_vlaue;
 int & reference_value;

 return 0;
}

}

reference_value라는 참조 변수가 초기화 없이 선언되었습니다. 참조 변수는 "다른 이름 붙이기"라는 걸 기억하세요. 참조 변수 선언되었는데 초기화되지 않았다는 건, "다른 이름을 붙이는데, 이름 붙일 대상이 없다"라는 것과 동일한 것입니다. 말도 안돼는 상황인거죠!

 주의하세요! 
  어떤 학습자는 "다른 이름을 붙이는데, 이름 붙일 대상이 없다"라는 설명을 보고 'NULL로 초기화 하면 되면 되지 않나?'하고 생각할 수 있씁니다.
  안됩니다! NULL이라는 것도 '어떤 대상'입니다. 아무 대상도 없는데 이름을 붙이는 건, 다시 생각해도 말이 안된다는 걸 명심하세요!

그래서 참조자는 선언되면서 반드시 초기화되어야 하는 것입니다.

또 참조 변수는 변수만 참조할 수 있습니다. 그래서 12번 소스또한 실행되지 않습니다.

12. 또 실행되지 않는 참조자 소스
#include <iostream>

using namespace std;

int main(void)
{
 int & ref = 10;

 return 0;
}

}

ref는 참조 변수이니까, 변수의 다른 이름만 될 수 있다 겁니다. 그냥 가능해도 별문제 일어나지 않을 수 있다 생각할 수도 있지만, C++은 이런 부분까지 철저하게 검사합니다. 13번 소스는 상수에 다른 이름을 붙이는, 잘 작동되는 소스입니다.

13. 상수를 참조하는 참조자
#include <iostream>

using namespace std;

int main(void)
{
 const int & ref = 10;
 cout<<ref<<endl;

 return 0;
}

}
10

ref를 참조 const 변수로 선언해 10의 다른 이름으로 삼았습니다. const 변수는 상수와 같으니까 문제 없이 작동되네요.


 T I P 
  const 참조자로 변수를 참조할 수도 있습니다.
    int num=10;
   const int & ref = num;

   이 경우 ref로는 num의 값을 바꿀 수 없고, num으로 num의 값을 바꾸는 것은 가능합니다.


 예제 3번 
  "Hello Reference!"라는 문자열을 참조한는 const 참조 변수를 만들고,

  그 const 참조 변수로 문자열을 출력해보세요

Reference의 깊은 이해[편집]

작성자는 학습자 여러분들에게 참조자가 "다른 이름"일 뿐임을 설명했습니다. 다음 소스를 보면서 참조자에 대해 좀 더 확실한 이해를 해봅시다.

14. 두 개의 별명
#include <iostream>

using namespace std;

int main(void)
{

 int real = 10;
 int & nickname1=real;
 int & nickname2=nickname1;

 cout<<"It is Reference of Reference?"<<endl;


return 0;
}

}
It is Reference of Reference?

nickname2이 nickname1을 참조했습니다. 언듯 생각하기에 nickname2는 nickname1의 다른 이름입니다. 엄밀히 말해, 이 생각은 틀렸습니다. 정확하게 표현하자면 nickname2는 nickname1이 가리키는 대상의 다른 이름입니다. 헷갈리시죠? 도대체 뭐가 틀렸다는 거지? 그림을 이용해 설명하겠습니다.

nickname1은 real의 다른 이름이다.

Reference memory 1.jpg


nickname2는 nickname1의 다름 이름이다.

Reference memory 2.jpg


틀렸다고 말했죠? 이렇게 해석해서는 안됩니다. 14번에서 소스에서 int & nickname2=nickname1이란 건 실제로 다음과 같습니다.


Reference memory 3.jpg


nickname2는 nickname1이 가리키는 대상, 즉 real이 가리키는 대상의 또다른 이름일 뿐이란 것입니다! 한 문장으로 말하면 참조자의 참조자는 없다. 원본에 계속 다른 이름을 붙일뿐!


 T I P 
  이 부분은 문장으로 기억하려고 하면 헷갈리기 쉽상입니다. 그림을 통해 이해하세요.
 T I P 
  다음 내용은 어려울 수 있습니다. 이해가 되지 않는다면 넘어가도 좋습니다.

참조자는 메모리에 존재하지 않습니다. 포인터는 메모리에 존재하는 변수였습니다. 하지만 참조자는 메모리에 존재하는 것이 아닙니다. 참조자는 또 다른 이름일 뿐입니다. 변수를 선언하면 실제 존재하는 변수라는 대상이 메모리에 생기고, 포인터를 선언하면 실제 존재하는 포인터라는 대상이 메모리에 생겼습니다. 하지만 참조자는 선언해도 실제 존재하는 대상이 생기지 않습니다. 대상에 이름을 붙일뿐!

참조자는 실제로 존재하지 않습니다. 그래서 참조자의 포인터를 만드는 것은 불가능합니다. 어떤 학습자는 다음과 같은 소스를 들어 질문할 수도 있습니다.


15. 참조자의 포인터일까?
#include <iostream>

using namespace std;

int main(void)
{

 int value=20;
 int & ref = value;
 int * pointer = &ref;

 cout<<*pointer<<endl;
 cout<<"address of ref is "<<&ref<<", address of value is "<<&value<<endl;

 return 0;
}

}
20

address of ref is 0xbfb7e1ec, address of value is 0xbfb7e1ec

이 소스에서 pointer가 참조자 ref의 포인터 아닌가요? 아닙니다. pointer는 ref가 아니키는 대상인, value라고 부르는 변수의 포인터입니다. ref의 주소와 value의 주소가 동일한 것으로도 확인할 수 있습니다.

 T I P 
  학습자가 위 코드를 실행하면 ref의 주소과 value의 주소가 0xbfb7e1ec가 아닌다른 값으로 나올 수 있습니다.

  하지만 여전히 둘의 주소는 서로 같을 것입니다.

 연습 문제 2번 
  다음은 참조자를 이용해 만든 swap함수 입니다.
#include <iostream>

using namespace std;

void swap(int& x, int& y)
{int temp=x;
 x=y;
 y=temp;}

int main(void)
{
  int a=1, b=2;

  cout<<"a is "<<a<<", b is "<<b<<endl;

  swap(a,b);

  cout<<"called swap. now a is "<<a<<", b is "<<b<<endl;

  return 0;
}
  이 소스를 통해 함수 매개변수를 참조자로 받는 법을 알 수 있습니다.

배열을 전달받아 가장 작은 값을 리턴하는 함수를 정의하는 게 학습자 여러분이 해야할 일입니다. main함수는 다음과 같습니다.

int main(void)
{

  int arr[10];
  for(i=0; i<10; i++)
    arr[i]=10-i;

  cout<<"most small number is "<<ReturnMostSmall(arr)<<endl;

  return 0;
}

  이 main에서 호출한 ReturnMostSmall함수를 참조자를 이용하여 정의해보세요.


정리[편집]

[Reference]에서 다음 내용을 배웠습니다. 어떤 내용이었는지 기억해보세요. 복습은 최고의 공부법!

  1. 포인터가 무서운 이유, 그게 바로 참조자가 등장한 이유죠.
  2. 참조 변수를 만드는 연산자, &
  3. 참조자는 선언하면서 초기화되어야 합니다
  4. 참조 변수는 변수만 참조할 수 있습니다. const 참조 변수는 상수와 변수를 참조할 수 있습니다.
  5. 참조자는 "실제로 존재"하지 않습니다.