[따배씨++]Chapter6-2. 배열, 포인터, 참조

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모님의 홍정모의 따라하며 배우는 C++ 강의를 듣고 정리한 내용입니다.
공부하는 식빵맘 님의 블로그를 참고했습니다.


🚆 메모리 동적 할당

특징

  • { } 범위에 상관 없이 필요할 때 할당받고 사용 후 반납
  • 메모리 크기를 런타임에 결정 가능
  • OS를 거쳐야 해서 조금 느림
    • 정적 할당 → stack 메모리 할당 (용량 작음)
    • 동적 할당 → heap 메모리 할당 (용량 큼)
    • 큰 메모리는 정적 할당(stack memory) 못받음
  • 할당받은 메모리 주소를 포인터로 받음
  • 반납 까먹으면 memory leak (누수 발생)


할당 - new / 반납 - delete

int* ptr = new int;
*ptr = 7;
int* ptr2 = ptr;

delete ptr;
ptr = nullptr;

cout << ptr << endl;	// null
cout << ptr2 << endl;	// 원래 ptr 주소
cout << *ptr2 << endl;	// 쓰레기 값
  • 메모리 할당시(new 선언) 힙 메모리 크기를 기억해 둠
  • delete시 해당 주소에 기억해둔 메모리 크기만큼 반납
  • delete 해도 포인터 주소는 그대로 존재 (쓰레기 값 들어있음)
  • 따라서 delete 후에는 ptr = nullptr 해주는게 좋음
  • 간접 참조하는 포인터들 일일히 초기화 하기 힘듬… → 스마트 포인터가 해결해줌
  • 주의) delete 하기 전에 nullptr인지 체크하는게 좋음
    • nullptr을 delete하는 경우 ‘do nothing’ 이지만 퍼포먼스에 민감하 경우 이야기가 다르다고 함
    • nullptr 체크 없이 delete를 하고 싶다면 차라리 스마트 포인터를 쓰는 것이 ‘현대적’ 스타일


동적 할당 배열

int length;
cin >> length;

int* array = new int[length]; // OK 쓰레기 값 들어있음
int* array = new int[length](); // OK 0으로 초기화
int* array = new int[length](1, 2, 3, 4, 5); // Error : 컴파일 타임에 초기화 안하려 함


delete[] array;
  • 동적 메모리는 초기화 살짝 까다로움
  • 정적 배열과 달리 array로 다른 메모리 영역 가리킬 수 있음
  • 사이즈 늘리고 싶은 경우 더 큰 메모리 할당 받아서 내부 값 복사 붙여넣기
    • 정적 배열은 이것도 못함
    • 보통 벡터 써서 편하게 해결


🚆 포인터와 Const

const + pointer

int a = 5;
int b = 10;
const int* ptr_a = &a;

*ptr_a = 10; // Error
ptr_a = &b; // OK
a = 7; // OK
  • de-reference만 불가능
  • 다른 건 전부 가능


const int a = 5;

const int* ptr1 = &a;	// OK
int* ptr2 = &a;			// Error

int* ptr3;
ptr3 = ptr1; // Error
  • const value는 const poointer로만 지정 가능
  • 일반 포인터는 const 포인터 받을 수 없음


pointer + const

int a = 20;
int b = 5;
int* const ptr = &a;

*ptr = 10; // OK
ptr = &b;  // Error
  • 포인터가 가리키는 주소값을 못 바꾸게 함
  • de-reference는 가능


const int a = 5;
const int* const ptr = &a;
  • 아무것도 못 함


🚆 참조 변수 - Reference Variable

int value = 5;
int &ref = value;

int &ref = 123; // Error
int &ref;       // Error
  • 포인터는 변수의 주소를 담고있음
  • 반면 참조는 같은 메모리를 공유! (별명 느낌)
  • 초기화 반드시 해야함
  • R-value로 초기화 못 함(메모리가 없음)
  • const 상수는 const reference만 가능


함수 파라미터로 넘어갈 때

void doSomething(int &n){};
void doSomething(int (&arr)[5]){};
  • 변수를 복사해서 넘어가는 것이 아닌 변수 자체가 넘어감
  • 포인터도 주소 복사까지는 함
  • 퍼포먼스 측면에서도 reference 좋음
  • 단 배열을 참조할 때는 사이즈 명시해야 함


구조체(Struct)에서 아주 유용

struct Something
{
	int v1;
	float v2;
};

struct Other
{
	Something st;
};


int main()
{
	Other ot;

	int& v1 = ot.st.v1;
	v1 = 1;

	return 0;
}
  • 변수 이름이 복잡할 때 참조쓰면 개꿀


참조와 Const

void doSomething(const int& x);

int main()
{
const int x = 5;
int& ref = x;		// Error
const int& ref = x;     // OK

int& ref2 = ref;	// Error

const int& x = 5;	// OK
doSomething(10);

return 0;
}
  • const 상수는 const 참조만 가능
  • 일반 참조는 const 참조 못 받음 (우회해서 바꿀까봐)
  • const 참조는 R-value initialize 가능!
  • const 참조로 변수 넘겨주면 최적화 가능


포인터와 참조의 멤버 접근

struct Person
{
	int age;
	double weight;
};

int main()
{
	// 일반적인 접근
	Person person;
	person.age = 5;

	// 참조로 접근
	Person& ref = person;
	ref.age = 15;

	// 포인터로 접근
	Person* ptr = &person;
	ptr->age = 30;
	(*ptr).age = 20; // 연산자 우선순위 .이 *보다 높음

	return 0;
}


🚆 for-each 반복문

int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };

for (auto number : fibonacci)
  number *= 10;
cout << endl;
  • 배열 안에 있는 원소들이 한 번씩 돌아감
  • 값은 복사해서 들어감
    • 값을 수정하고 싶으면 reference!
    • 성능 면에서도 reference가 좋음
  • 동적 할당은 for-each 사용 불가 → 벡터는 사용 가능


🚆 void 포인터

int i = 5;
float f = 3.0;
char c = 'a';

void* ptr = nullptr;

ptr = &i; // OK
ptr = &f; // OK
ptr = &c; // OK


cout << ptr + 1 << endl; // Error
cout << static_cast<char>(*ptr) << endl; // Error
cout << *static_cast<char*>(ptr) << endl; // OK
  • 자료형과 상관없이 포인터 사용 가능
  • Generic pointer 라고도 함
  • 포인터 연산 불가능
  • 자료형 캐스팅해서 써야 함 (*char 한 후 *)


🚆 이중 포인터

int value = 5;
int* ptr = &value;
int** ptrptr = &ptr;

int row, col;
int** matrix = new int* [row];
for (int r = 0; r < row; r++)
  matrix[r] = new int[col];


for (int r = 0; r < row; r++)
  delete[] matrix[r];
delete[] matrix;
  • delete시 순서 주의


🚆 std::array & std::vector

#include <array>
#include <vector>

int main()
{
	std::array <int, 5> my_arr = { 1,2,3,4,5 };
	std::vector <int> array = { 1,2,3,4, };
	
	return 0;
}
  • array : 정적 array 상위호환
    • class로 한 번 더 감싸준 형태
    • 여러가지 편리한 함수들 내장
    • 변수로 넘어가도 사이즈 기억 (포인터로 안 바뀜)
    • for-each 사용 가능
    • my_arr[10] → 예외처리
    • 정적 array는 index 넘어가도 일단 접근은 함 (오류 X)
  • vector : 동적 array 상위호환
    • 사이즈 자유롭게 변경 가능
    • 변수로 넘어가도 길이 기억
    • delete 알아서 해줌!



맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기