C++

[강의] 10월 25일 수업정리

k-codestudy 2024. 10. 27. 05:03

포인터의 주의사항과 레퍼런스에 대한 수업을 들었다.

 

1. 클래스 사용 시 포인터 주의 사항

 

[header.h]

#pragma once


class C_DATA
{
private:
	int m_nData;

public:
	C_DATA() = default;
	void setData(int nData);
	int* getData();
};

[header.cpp]

#include "data.h"

void C_DATA::setData(int nData)
{
	m_nData = nData;
}

int* C_DATA::getData()
{
	return &m_nData;
}

[main.cpp]

#include <iostream>
#include "data.h"

int main()
{
	C_DATA cData{};
	int* pData{};

	cData.setData(100);
	pData = cData.getData();

	printf("%d\n", *pData);

	*pData = 9;
	printf("%d\n", *pData);
}

1.1 해석 : 

  • setData를 통해 m_nData의 값을 저장하고, getData는 m_nData의 주소를 반환한다
  • pData를 통해 m_nData에 접근하면 값을 직접 변경할 수 있다.

1.2 문제점

  • 포인터를 통해 클래스 내부에 데이터를 직접 변경하는 것이 문제이다.
  • 클래스를 만든 사람이 퍼블릭에 제공하는 것만 가지고 접근을 해야 한다는 룰이 존재한다. 그렇기에 클래스 외부에서 공개된 인터페이스만 사용하지 않고 포인터를 통해 클래스 내부 데이터를 직접 변경을 하였기에 클래스 설계 규칙을 위반한 것이다.

1.3 해결 방법 

  • getData를 const 포인터로 변경하여 외부에서의 데이터 수정을 막아야 한다.

 

  •  

 

2. 레퍼런스 

2.1 정의

  • C언어에서는 어떠한 변수를 가리키고 싶을 땐 반드시 포인터를 사용해야 했었다. 하지만 C++에서는 다른 변수나 상수를 가리키는 방법으로 또 다른 방법을 제공하였는데, 이를 바로 참조자(레퍼런스 - reference)라고 한다.

2.2 개념 :

  • 레퍼런스의 존재 보장 : 레퍼런스는 항상 유효한 변수를 참조해야 하며, 초기화 이후 다른 대상을 가리킬 수 없다. 따라서 레퍼런스는 항상 존재가 보장된다.
    즉, 필요 있다 없다를 결정하는 주체가 나이므로, 내가 있다고 생각하면 그 존재의 보장은 확실한 것이다.
  • 포인터와의 차이점 : 레퍼런스는 항상 유효한 대상을 참조해야 하지만, 포인터는 nullptr을 가질 수 있어 존재가 불확실하다.
    즉, 레퍼런스는 내가 주체라고 하면 포인터는 내가 주체가 아니라 있는지 물어보면 어디에 있다고 가르쳐주고 그곳을 가봐야 그 존재가 있는지 없는지를 알 수 있기에 존재의 보장이 불확실한 것이다.

 

2.3 예시 

#include <iostream>

int main()
{

	int nData{};
	int &nRef = nData;

	nData = 100;
	printf("data : %d\n", nData);

	nRef = 5;
	printf("ref : %d\n", nRef);
	printf("data : %d\n", nData);

}
  • 레퍼런스 선언 : &를 사용하여 레퍼런스를 선언하며, nRef는 nData를 참조하게 된다.
  • C++ 만의 규칙 : C언어에서는 존재를 보장할 수 없기에 문법에 선언을 할 경우 네가 지칭하는 것의 메모리에 붙여 선언해야 한다.
  • 동일한 메모리 : nRef와 nData는 동일한 메모리를 가리키며, 같은 값과 주소를 공유한다. 간단히 말하면 한 사람에게 두 개의 이름을 붙인 것과 동일하다
  • 변경 불가 : 한 번 초기화된 레퍼런스는 다른 변수를 참조할 수 없으며, 항상 초기화된 대상과 연결되어 있다.
  • 즉, 위의 코드에서  nRef와 nData는 같은 메모리이며 nRef와 nData라는 2개의 이름을 가지게 된 것이다. 
    (한 사람에 2개의 이름을 붙인 것으로 값, 주소값, 메모리 등등 모든 것이 같으며, 선언을 하게 되면 절대 떨어트릴 수 없게 된다. )

2.4 함수에 레퍼런스 사용

C++에서는 레퍼런스는 함수의 인수로 밖에 사용되지 않으며 함수인수로 사용되는 경우 변수의 값을 직접 변경할 수 있다.

 

포인터와 레퍼런스의 차이 :

  • 포인터 : 메모리 주소를 통해 간접적으로 값을 참조하여, nullptr을 가질 수 있어 존재 여부가 불확실하다.
    즉, 주소값을 call by value 해서 들어오기 때문에 포인터의 메모리를 잡아 사용해야 하며, 있다 없다의 개념이지 존재를 보장하자 않는다.
  • 레퍼런스 : 실제 변수를 참조하여, 초기화 시 대상이 항상 존재한다. nullptr이 될 수 없고 존재를 보장한다.
    즉 레퍼런스는 존재 자체가 넘어간 거 기에 메모리를 잡지 않으며 존재를 무조건 보장하기 때문에 레퍼런스로 넘어오게 되면 그 값은 null이 아니다

2.5 함수에 레퍼런스 예시

#include <iostream>

void testPointer(int* p);
void testReference(int &n);

int main()
{
	int nData{};

	testPointer(&nData);
	printf("%d\n", nData);

	testReference(nData);
	printf("%d\n", nData);
}

void testPointer(int* p) // 참조와 역참조 / 존재의 경로를 받아서 넘어감 (간접적, 주소값)
{
	*p = 5;
}

void testReference(int& n) // 존재가 직접 넘어감 (직접적, n)
{
	n = 100;
}
  • 포인터: testPointer 함수는 변수의 주소를 받아 간접적으로 값을 변경한다. 이는 주소를 통해 참조하는 방식식
  • 레퍼런스: testReference 함수는 레퍼런스를 사용하여 변수를 직접 참조하며 값을 변경한다. 이는 실제 변수가 함수에 전달된 것처럼 동작한다.

 

2.6 레퍼런스 사용 시 주의사항

  • 레퍼런스의 존재 보장: 레퍼런스는 항상 유효한 대상을 참조해야 하며 초기화된 변수와만 연결되기에 nullptr 상태가 될 수 없다
  • 예외적인 상황: 레퍼런스를 잘못 사용하여 유효하지 않은 메모리를 참조할 수 있는 경우가 있다. 예를 들어, 초기화되지 않은 포인터를 사용해 레퍼런스를 생성하면 오류가 발생할 수 있다.
int * p{};

int & nRef = *p;
  • 위험: 이 경우 p가 nullptr이거나 유효하지 않은 주소를 참조하고 있을 때 nRef를 생성하면, 프로그램은 런타임 오류를 일으킬 수 있다
  • 주의점: 레퍼런스를 사용할 때는 항상 유효한 변수를 참조하도록 하며, 잘못된 포인터에서 레퍼런스를 생성하지 않도록 주의해야 한다

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

[강의] 10월 30일 수업정리  (2) 2024.10.31
[강의] 10월 29일 수업정리  (1) 2024.10.29
[강의] 10월 24일 수업정리  (0) 2024.10.25
[강의] 10월 23일 수업정리  (2) 2024.10.24
[강의] 10월 22일 수업정리  (1) 2024.10.23