C++

13. Pointer And Reference (2)

Kelvin의 게임개발 2024. 2. 14. 01:36
728x90
반응형

1. Pointer Arithmetic

 

포인터는 대입, 비교, 산술 연산이 가능하다

 

포인터에서 ++,--연산은 해당 주소 다음,이전을 가리킨다 (포인터 변수의 타입 크기만큼 증가,감소한다) 

ex) int형 포인터 변수의 주소가 1000이고 해당 포인터 변수를 ++. --하면 1004, 996이 된다

 

일반 변수와 마찬가지로 포인터변수++, 포인터변수--로 사용이 가능하다
이때 *포인터변수++은 주소를 ++하는것이고 (*포인터변수)++은 주소를 타고간 데이터 값을 ++하는것이다

포인터 변수는 덧셈 뺄셈도 가능하며 +=, -=도 사용할 수 있다
마찬가지로 포인터 변수의 타입 크기만큼 증가, 감소된다

포인터 변수와 포인터 변수의 뺄셈은 두 주소 사이의 거리를 계산한다 이때 타입이 다르면 컴파일 에러가 발생한다
포인터와 포인터 덧셈은 불가능하다

포인터 변수들은 ==, !=으로 비교가 가능하다
포인터 변수의 비교는 가리키고 있는 주소를 비교한다 (가리키는 주소가 같으면 T, 다르면 F)

포인터 비교 시 데이터 값 자체는 고려하지 않는다 (데이터 값 비교를 위해서는 *포인터변수 == *포인터변수로 주소에 접근하여 비교해야 한다)

 

int main()
{
	int TestA{ 10 };
	int TestB{ 100 };
	int* TestAPtr = &TestA;
	int* TestBPtr = &TestB;

	//포인터 증감연산
	TestAPtr++; //int타입 크기인 4만큼 증가 -> 다음 주소 
	TestAPtr--; //int타입 크기인 4만큼 감소 -> 이전 주소

	//포인터 산술연산
	TestAPtr += 3; //int타입크기 * 3인 12만큼 증가 -> 3번째 뒤 주소
	TestAPtr -= 3; //int타입크기 * 3인 12만큼 감소 -> 3번째 앞 주소

	//포인터 변수끼리 뺄셈
	int Temp = TestAPtr - TestBPtr; //TestAPtr 주소와 TestBPtr주소의 사이의 거리가 나옴

	int Temp1 = TestAPtr + TestBPtr; // 포인터 변수끼리 덧셈은 불가능

	//포인터 변수끼리 비교
	TestAPtr == TestBPtr; // TestA, TestB의 주소를 가리키고 있기 때문에 false

	return 0;
}

 

 

2. Const And Pointer

 

포인터에 const는 다양한 방식으로 사용이 가능하다

 

const 타입* 포인터변수;로 선언하게 되면 해당 포인터 변수의 주소가 가리키는 데이터를 상수(const)로 만든다 따라서 주소가 가리키는 데이터 변경이 불가능하다
 
타입* const 포인터변수;로 선언하게 되면 해당 포인터 변수의 주소를 상수(const)로 만든다 따라서 포인터 변수의 주소 변경이 불가능하다
 
const 타입* const 포인터변수;로 선언하게 되면 포인터 변수의 주소가 가리키는 값, 포인터 변수의 주소 둘 다 상수로 만들기 때문에 둘 다 변경이 불가능하다

 

int main()
{
	int TestA{ 10 };
	int TestB{ 100 };

	const int* TestAPtr = &TestA; //TestA값을 변경할 수 없는 상수로 만든다
	
	*TestAPtr = 100; //포인터 변수가 가리키는 값이 const이기 때문에 수정이 불가능하여 컴파일 에러 발생

	TestAPtr = &TestB; //포인터 변수 자체가 const는 아니기 때문에 주소 변경은 가능하다


	int* const TestBPtr = &TestB;

	TestBPtr = &TestAPtr; //포인터 변수 주소가 const이기 때문에 수정이 불가능하여 컴파일 에러 발생
	
	*TestBPtr = 1000; //포인터 변수가 가리키는 값은 const가 아니기 때문에 값 변경이 가능하다

	
	const int* const TestCPtr = &TestB;

	//둘 다 불가능 -> 컴파일 에러
	TestCPtr = &TestA;
	*TestCPtr = 10000;

	return 0;
}

 

 

3. Passing Pointer To Function

 

함수의 매개변수를 포인터 타입으로 만들고 함수 호출 시 주소를 인자로 넘기는 방식으로 주소 전달을 할 수 있다

 

값 전달 방식이 아닌 주소 전달 방식이기 때문에 원본값 변경이 가능하며 사본이 생성되지 않아 메모리 비용을 아낄 수 있다

 

void Double_Data(int* Int_Ptr) //포인터 타입 매개변수
{
	*Int_Ptr *= 2; //원본값 변경
}

int main()
{
	int TestA{ 10 };

	cout << TestA << endl; //10

	Double_Data(&TestA); //주소 전달로 원본값 변경이 가능

	cout << TestA << endl; //20

	return 0;
}

 

 

4. Return Pointer From a Function

 

함수의 반환형을 포인터 타입으로 만들어 포인터를 반환할 수 있다
type* Function();
 
주의할 점은 지역변수의 주소를 반환하면 안된다는 것이다
컴파일 에러는 발생하지 않지만 지역변수는 함수가 종료되면 스택에서 소멸된다, 이때 그 소멸된 데이터의 주소를 다른곳에서 참조하게 되면 다른 주소의 데이터를 건들게 될 수 있기 때문이다
 
아래 예제에서의 반환은 new 연산자로 heap메모리에 메모리를 동적할당 하는 것이기 때문에 해당 주소를 return해서 사용해도 된다

 

int* Create_Array(int size, int initvalue = 0) //반환형이 int* 타입이기 때문에 포인터 타입을 반환한다
{
	int* NewStorage{ nullptr };

	NewStorage = new int[size];

	for (int i = 0; i < size; i++)
	{
		*(NewStorage + i) = initvalue;
	}
	
	return NewStorage; //반환형이 포인터 타입이기 때문에 new로 동적할당한 메모리 주소를 반환할 수 있다
}

int main()
{
	int* MyArray{ nullptr };

	//포인터 타입을 반환하는 함수이기 때문에 포인터 변수에 할당이 가능하다
	MyArray = Create_Array(10, 1); //10칸짜리 int타입 배열 크기의 메모리를 동적으로 할당한 후 반환, 해당 메모리의 배열의 값은 전부 1로 초기화 됨

	delete[] MyArray;

	return 0;
}

 

 

5. Potential Pointer Pitfalls

 

포인터는 C++에서 아주 강력한 도구이다 하지만 강력한 만큼 잠재적으로 발생할 수 있는 문제들이 있다
 
첫 번째는 초기화 되지 않은 포인터 변수를 사용하는 것이다, 초기화 되지 않은 포인터 변수를 사용하게 되면 중요한 다른 주소의 데이터를 수정할 수 있게 된다 (매우 위험함)
 
두 번째는 유효하지 않은 포인터 변수를 사용하는 것이다 이는 대표적으로 함수에서 지역변수의 주소를 반환한 후 사용할 때 많이 발생한다 (지역변수는 함수 종료시 스택 영역에서 소멸되기 때문에 함수 종료 후 유효하지 않은 포인터 변수가 된다)
 
세 번째는 nullptr참조이다, nullptr인 포인터 변수를 참조하게 되면 바로 크래시가 발생한다 (그나마 크래시가 발생하기 때문에 안전하다)
 
마지막은 메모리 누수이다, 메모리를 new로 동적할당한 후 delete를 하지 않게 되면 메모리 누수가 발생하니 꼭 다 사용했으면 delete를 해주어야 한다
 
이러한 다양한 문제들은 C++의 스마트포인터를 사용하면서 해결이 가능하다

 

 

6. Reference

 

Reference(참조)는 변수의 별칭이라 생각하면 된다

 

참조타입 변수는 항상 선언과 동시에 초기화 되어야 한다 (null이 될 수 없다)
참조타입은 타입& 변수 = 변수;로 선언이 가능하다
 
참조 타입을 사용하게 되면 포인터 변수의 주소가 가리키는 값을 수정하면 원본값을 수정할 수 있는것과 마찬가지로 해당 변수의 원본값 변경이 가능해진다

 

따라서 함수의 매개변수로 참조타입을 많이 사용한다 (포인터를 매개변수로 사용하는것과 마찬가지로 사본생성이 되지 않아 메모리 비용이 감소하며 원본값 수정이 가능해진다)
 
포인터와 마찬가지로 const 타입& 변수;로 const 사용이 가능하다 (원본값 변경이 불가능해진다)

 

int main()
{
	int num{ 100 };

	int& numref = num; //참조타입 변수 선언 후 num으로 바로 초기화

	numref = 200; //num의 원본값이 200으로 변경된다


	vector<string> TestStrings{ "AA", "BB", "CC" };

	for (auto str : TestStrings)
	{
		str = "JJJ"; //사본이 수정되기 때문에 원본값 변경이 되지 않는다 (배열도 마찬가지)
	}

	for (auto& str : TestStrings)
	{
		str = "New"; //참조타입 수정으로 원본이 수정되기 때문에 New로 변경된다
	}

	return 0;
}

 

 

7. L-Value, R-Value

 

l-value란 이름이 있고 주소를 가질 수 있는 값이며 const가 아니면 수정도 가능한 값을 의미한다
ex) int x {100}; 에서 x가 l-value이다
 
r-value란 l-value가 아닌 모든 값이다
r-value는 표현식에서 오른쪽에 있으며 literal값이고 일시적인 값이다
ex) int x{190};에서 190이 r-value이다
 
참조타입은 l-value값을 참조하기 때문에 r-value값을 할당하면 컴파일 에러가 발생한다

 

int main()
{
	int x{ 100 }; //x는 l-value, 100은 r-value
	string name; //name은 l-value

	int y = x + 100; //x+100은 r-value이다

	100 = x; //100은 l-value가 될 수 없기 때문에 l-value 컴파일 에러가 발생한다


	int& ref = x; //참조변수에 l-value를 할당
	int& ref1 = 100; //참조변수에 r-value를 할당하려고 했기 때문에 컴파일 에러 발생

	return 0;
}

 

 

8. When to use Pointer / Reference

 

값 전달 방식 (Pass-By-Value)을 사용하는 때는 함수에서 원본값 변경이 필요 없고 인자로 넘긴 값의 크기가 작을때 (사본 생성이 유리할 때) 사용한다 ex) int, char, double 등등의 타입일 때
 
주소 전달 방식(pointer)을 사용하는 때는 함수에서 원본값 변경이 필요하고 인자로 넘긴 값의 크기가 커서 사본 생성 시 메모리 사용 비용이 커지는 경우, 그리고 인자로 nullptr값을 넘겨도 되는 경우에 사용한다
const 포인터를 넘기는 상황은 원본값 변경이 필요 없을때이다
 
참조 전달 방식(reference)을 사용하는 때는 함수에서 원본값 변경이 필요하고 인자로 넘긴 값의 크기가 커서 사본 생성 시 메모리 사용 비용이 커지는 경우, 그리고 인자로 nullptr값을 넘겨서는 안되는 경우에 사용한다
(참조타입은 무조건 초기값이 있어야 하기 때문에 null을 가질 수 없다)
const 참조를 넘기는 상황은 원본값 변경이 필요 없을때이다

 

 

 

728x90
반응형

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

15. Constructor & Destructor (1)  (5) 2024.04.03
14. OOP Class & Object (1)  (77) 2024.02.22
12. Pointer And Reference, Memory Allocate (1)  (107) 2024.01.30
11. Function (2)  (108) 2024.01.26
10. Function (1)  (127) 2024.01.25