TIL 2024/7/31

Sung Joo Lee·2024년 7월 31일

Const and Pointers

어쩔 수 없다.. 외워라…이건 규칙임

  • const의 포인터 ( pointers to const )
    • 데이터가 const/ 포인터는 다른 데이터를 가리킬 수 있음

      ```cpp
      #include <iostream>
      
      int main()
      {
      	int highScore = 100;
      	int lowScore = 60;
      	const int* scorePtr = &highScore;
      
      	*scorePtr = 80; //err
      	scorePtr = &lowScore;
      
      	return 0;
      }
      ```

      데이터 타입 앞에 const가 있으니까 데이터가 변할 수 없다! 라고 생각하자

  • const인 포인터 ( const pointers)
    • 포인터가 const/ 데이터는 변할 수 있음/ 다른 데이터를 가리킬 수 없음
#include <iostream>

int main()
{
	int highScore = 100;
	int lowScore = 60;
	int* const scorePtr = &highScore;

	*scorePtr = 80; //err
	scorePtr = &lowScore;

	return 0;
}

포인터 변수 앞에 const가 있으니까 다른 데이터를 가리 킬 수 없다! 라고 생각하자

  • const의 const인 포인터 ( const pointers to const)
    • 둘 다 const

      #include <iostream>
      
      int main()
      {
      	int highScore = 100;
      	int lowScore = 60;
      	const int* const scorePtr = &highScore;
      
      	*scorePtr = 80; //err
      	scorePtr = &lowScore;
      
      	return 0;
      }

const가 변수 타입, 포인터 변수 앞에 붙어 있기 때문에 데이터와 가리키는 변수 둘 다 수정 할 수 없다.

passing Pointers to a Fucntion

  • 포인터를 함수의 인자로 전달
    • pass - by - address / 변수의 주소를 전달
#include <iostream>

void doubleData(int* intPtr) {

	*intPtr *= 2;
}

void doubleData2(int& intRef) {

	intRef *= 3;
}

int main()
{
	int value = 10;
	int* ptr = &value;
	
	std::cout << "이전 " << *ptr << std::endl;

	doubleData(ptr);
	//double Data(&value); 가능

	std::cout << "이후 " << *ptr << std::endl;

	doubleData2(value);

	std::cout << "참조자 사용 " << value << std::endl;

	return 0;
}

  • 주소를 가지고 있으니 호출 Stack 밖의 값도 바꿀 수 있다.

포인터의 반환

  • 인자로 전달된 포인터를 반환 가능
int* largerInt(int* a, int* b)
{
	if(*a > *b)
		return a;
	else
		return b;
		
}
  • 함수 내부에서 동적으로 할당된 메모리의 주소를 반환 가능
#include <iostream>

int* creatArray(int size, int initValue = 0) { //동적으로 배열을 생성 해주고 해당 포인터 반환
	int* newStorage = nullptr;
	
	newStorage = new int[size];
	
	for (int i = 0; i < size; i++)
		*(newStorage + i) = initValue;

	return newStorage;

}

int main()
{
	int* myArr = nullptr;
	int size;

	std::cin >> size;
	myArr = creatArray(size, 10);
	delete[] myArr;

	return 0;
}
  • 주의!!!!! 💡 **지역 변수**에 대한 **포인터 반환**은 **불가능 → 지역 변수가 끝나면 로컬 변수는 해제가 되는데 어떻게 반환을 하는가…** ```cpp int* returnLoacl() { int num = 10; int* numPtr = # return numPtr; } void main() { int* a = nullptr; a = returnLocal(); // err cout << *a << endl; } ```

주의

  • 초기화의 필요
  • Dangling Pointer
    • 두 포인터가 동일 데이터를 가리키다, 하나의 포인터가 메모리를 해제할 경우

    • 지역 변수를 참조하고, 호출 스택이 끝나는 경우

      #include <iostream>
      
      int main()
      {
      	int* first = nullptr;
      	int* second = nullptr;
      
      	int size = 3;
      
      	first = new int[size];
      	second = first;
      
      	delete[] first;
      
      	return 0;
      }
  • new의 실패
    • 가끔 발생할 수 있음. 이런 경우 예외처리 필요 ( 나중에 다시 설명)
  • 메모리 누수
    • 동적 할당으로 사용한 메모리는 반드시 해제해야함.
    • 메모리 누수가 발생시 사용하지 않는 데이터가 메모리를 차지하고 있기 때문에 데이터 부족 현상이 발생한다.

참조자

  • 변수의 별명

  • 참조자는 (새로운) 변수가 아님

    • 메모리 공간을 차지하지 않음 ( 메모리 절약 가능)
  • 선언과 동시에 초기화 되어야 함 (Null일 수 없음)

  • 한번 초기화되면, 다른 변수의 참조자가 될 수 없음

  • Const Pointer이면서 , 사용 시 자동으로 역참조를 수행하는 개념

    • 포인터의 간단하고 편리한 버전으로 생각
  • 함수의 매개변수로 자주 사용

  • int& , float& 등 변수 정의시에 &를 붙인 타입을 사용\

  • 포인터와 마찬가지로, 동일한 타입에 대해서만 참조자 생성 가능

  • 참조자를 사용할 대는 마치 변수인 것 처럼 그냥 사용한다.

  • Copy가 필요한 경우가 아니라면, cosnt &를 쓰는 것이 기본

    • 여기서 말하는 Copy란 값의 변화를 주고 싶지 않고 단순히 출력하는 등의 동작 할 때

    • cpp에서 가장 많이 사용하는 패턴임

    • Const & rvalue 전달 가능

      #include <iostream>
      #include <vector>
      
      int sum(const std::vector<int>& numbers) {
          int total = 0;
          for (const int& num : numbers) {
              total += num;
          }
          return total;
      }
      
      int main() {
          std::vector<int> myNumbers = {1, 2, 3, 4, 5};
          std::cout << "Sum: " << sum(myNumbers) << std::endl; // 복사본을 만들지 않고 합계를 계산
          return 0;
      }
      

      위의 코드와 같이 값의 변경을 원하지 않고 단순히 출력 및 합계를 낼 때 ‘const &’ 를 사용하여 메모리를 절약한다.

  • 아래의 코드는 모두 a의 값인 5를 그대로 출력함

    • 네개의 Print 함수의 장/단점은?

Pointer vs References

    • 의 사용
      • 변수를 정의할 때 붙는다 → 포인터의 정의 (포인터 변수의 생성)
      • 변수를 사용할 때 붙는다 → 포인터의 역참조
int* ptr; //정의
*ptr = 10; // 역참조

int& b;// 정의
int* ptr = &b // 변수의 주소값 반환
  • & 의 사용
    • 변수를 정의할 때 - > 참조자의 정의( 생성)
    • 변수를 사용할 때 붙는다 → 변수의 주소값 반환
    • 참조자 b를 사용할 때는 일반적인 변수 사용하듯이 사용하면 된다.

pass by value

  • 함수가 실제 매개변수를 수정하지 않는 경우 사용
  • int, char, double과 같은 크기가 작은 기본 자료형의 전달
    • 기본 자료형이 아닌 자료형 : stiring, vector,class
    • 복사의 비용이 높기 때문

pass by address

  • 함수가 실제 매개변수를 수정해야 하는 경우
    • 즉, 지역 범위 밖의 메모리에 저장된 값을 수정해야 하는 경우
  • 복사의 오버헤드가 클 때
    • 복사의 부담이 적다
  • 포인터가 nullptr이 되어도 상관 없을 때
    • 참조자는 null이 될 수 없음

pass by address with const pointer

  • 함수가 실제 매개변수를 수정하지 않는 경우
  • 복사의 오버헤드가 클때
  • 포인터가 nullptr이 되어도 상관없을 때

Pass by address with const pointer of const

  • 함수의 실제 매개변수를 수정하지 않는 경우
  • 복사의 오버헤드가 클 때
  • 포인터가 nullptr이 되어도 상관 없을 때
    • 참조자는 null이 될 수 없음
  • 포인터가 바뀌지 않아야 할 때

참조자를 이용한 pass by reference

  • 함수가 실제 매개변수를 수정하는 경우
  • 복사의 오버헤드가 클 때
  • 매개변수가 null이 되지 않는 것이 보장 될 때

const 참조자를 이용한 pass by reference

  • 함수가 실제 매개변수를 수정하지 않는 경우
  • 복사의 오버헤드가 클 때
    • 예를 들면 함수의 매개변수에 복사해야 할 데이터 크기가 1000G 일때 너무 오버헤드가 크다
  • 매개변수가 null이 되지 않는 것이 보장 될 때
profile
개발로그

0개의 댓글