본문 바로가기
Programming/C++

[C++] 8. 포인터와 동적객체 생성

by Lizardee 2024. 2. 12.
8.1 이번 장에서 만들어 볼 프로그램

8.2 포인터란?
int n = 10;  // 변수 정의
int *p;      // 포인터 선언
p = &n;      // 변수 n의 주소를 포인터 p에 저장

 

#include <iostream>
using namespace std;

int main() {
	int n = 10;
	int* p = &n;  // 포인터 p에 변수 n의 주소 저장

	cout << p << endl;   // 포인터 p의 값(주소) 출력
	cout << *p << endl;  // 포인터 p에 저장된 값 출력

	return 0;
}

 

// nullptr
int *p = nullptr;  // 포인터가 아무것도 가리키고 있지 않을 때 nullptr로 설정함

8.3 동적 메모리 할당(dynamic memory allocation)

: 프로그램이 실행 도중에 동적으로 메모리를 할당받는 것

 

new와 delete

: 동적 메모리는 new 연산자를 이용하여서 할당된다. new 뒤에는 자료형을 적는다. 만약 하나 이상의 요소가 필요하다면 [ ] 안에 요소의 숫자를 적는다. new 연산자는 할당되는 동적 메모리의 시작 주소를 반환한다.

int *p;          // 포인터 선언
p = new int[5];  // 포인터에 동적 메모리의 시작 주소를 저장한다.

 

▶ 정적 배열 vs. 동적 배열

: 정적 배열은 컴파일 시에 크기가 결정되어야 한다.

반면에 동적 배열은 new에 의하여 생성되는데, 그 크기를 변경할 수 있다. 동적 배열은 컴퓨터 시스템에 의하여 히프 메모리 영역에서 할당된다.

컴퓨터 메모리는 한정된 자원이기 때문에 동적 메모리 할당 요청은 항상 만족되지 않는다. 요청한 메모리가 없는 경우에 bad_alloc이라는 오류(exception)가 발생한다.

 

동적 메모리는 사용이 끝나면 반드시 해제하여야 한다. 동적 메모리를 해제하려면 delete 키워드를 사용한다.

delete [] p;

 

#include <iostream>
#include <time.h>
using namespace std;

int main() {
	int* p;  
	srand(time(NULL));
	
	p = new int[10];  // 동적 메모리 할당
	for (int i = 0; i < 10; i++)
		p[i] = rand();

	for (int i = 0; i < 10; i++)
		cout << p[i] << " ";
	cout << endl;

	delete[] p;  // 동적 메모리 반납

	return 0;
}

8.4 스마트 포인터(smart pointer)

: 스마트 포인터를 사용하면 프로그래머가 동적 메모리 할당 후에 잊어버려도 자동으로 동적 메모리가 삭제된다.

#include <iostream>
#include <memory>
using namespace std;

int main() {
	unique_ptr<int> p(new int);  // 스마트 포인터

	*p = 99;  // p를 사용한다.
	// p가 삭제되면서 동적 메모리도 함께 삭제하기 때문에 메모리 누수가 발생하지 않는다.
}

 

#include <iostream>
#include <memory>
using namespace std;

int main() {
	unique_ptr<int[]> p(new int[10]);  // 정수형 배열을 가리키는 스마트 포인터

	for (int i = 0; i < 10; i++)
		p[i] = i;                      // 스마트 포인터 사용

	for (int i = 0; i < 10; i++)
		cout << p[i] << " ";
	cout << endl;

	return 0;
}

8.5 객체의 동적 생성

객체도 new와 delete를 사용하여 동적으로 생성할 수 있다.

어떤 경우에는 객체를 동적으로 생성하는 것이 객체 지향의 관점에서 바람직하다. 가끔은 객체가 몇 개나 생성되어야 하는지 알 수 없는 경우가 종종 있기 때문이다. 

 

#include <iostream>
using namespace std;

class Dog {
public:
	string name;
	int age;

	Dog(string s, int a) : name(s), age(a) {}
};

int main() {
	Dog* pDog = new Dog("Puppy", 1);  // 생성자 호출
	
	delete pDog;                      // 소멸자 호출

	return 0;
}

 

포인터를 통하여 멤버 접근하기
(*pDog).getAge();  또는
pDog->getAge();

 

#include <iostream>
using namespace std;

class Dog {
public:
	string name;
	int age;

	Dog(string n, int a) : name(n), age(a) {}
	~Dog() {}

	int getAge() {
		return age;
	}

	void setAge(int dog_age) {
		age = dog_age;
	}
};

int main() {
	Dog* pDog = new Dog("Puppy", 1);
	cout << "강아지의 나이: " << pDog->getAge() << endl;

	pDog->setAge(5);
	cout << "강아지의 나이: " << pDog->getAge() << endl;

	delete pDog;

	return 0;
}

 

멤버도 동적 생성하기

: 클래스의 멤버도 히프에 동적 생성할 수 있다. 이럴 경우, 생성자에서 동적 할당되어야 하고, 소멸자에서 동적 메모리를 해제하여야 한다.

#include <iostream>
using namespace std;

class Dog {
public:
	string* pName;
	int* pAge;

	Dog() {
		pName = new string{ "Puppy" };  // 동적 객체 초기화
		pAge = new int{ 1 };
	}
	~Dog() {
		delete pName;
		delete pAge;
	}

	int getAge() {
		return *pAge;
	}
	void setAge(int dog_age) {
		*pAge = dog_age;
	}
};

int main() {
	Dog* pDog = new Dog;  // 동적 객체를 가리키는 포인터

	cout << "강아지의 나이: " << pDog->getAge() << endl;

	pDog->setAge(5);
	cout << "강아지의 나이: " << pDog->getAge() << endl;

	return 0;
}

 

this 포인터

: 함수를 호출하고 있는 객체를 알려주는 포인터

#include <iostream>
using namespace std;

class Rectangle {
public:
	int width;
	int height;

	Rectangle() {
		width = 30;
		height = 40;
	}
	~Rectangle() {}

	void setWidth(int w) {
		this->width = w;
	}
	int getWidth() {
		return this->width;
	}

	void setHeight(int h) {
		this->height = h;
	}
	int getHeight() {
		return this->height;
	}
};

int main() {
	Rectangle myRect;
	cout << "사각형의 너비: " << myRect.getWidth() << endl;
	cout << "사각형의 높이: " << myRect.getHeight() << endl;

	myRect.setWidth(20);
	myRect.setHeight(30);
	cout << "사각형의 너비: " << myRect.getWidth() << endl;
	cout << "사각형의 높이: " << myRect.getHeight() << endl;

	return 0;
}

 

스마트 포인터(unique_ptr)를 이용한 동적 객체 생성
#include <iostream>
#include <memory>
using namespace std;

class Dog {
public:
	string name;
	int age;

	Dog() {
		name = "Puppy";
		age = 1;
	}
	~Dog() {}

	int getAge() {
		return this->age;
	}
	void setAge(int dog_age) {
		this->age = dog_age;
	}
};

int main() {
	std::unique_ptr<Dog> pDog(new Dog);  // 스마트 포인터

	cout << "강아지의 나이: " << pDog->getAge() << endl;

	pDog->setAge(5);
	cout << "강아지의 나이: " << pDog->getAge() << endl;

	return 0;
}

8.6 const 포인터
객체 포인터를 언제 사용하는가?