공부/C++

7. 여러가지 객체의 생성방법

bokob 2024. 5. 14. 00:34

7.1 객체배열과 객체포인터

객체 배열 및 포인터의 생성 및 사용

객체 배열 선언 가능

  • 기본 타입 배열 선언과 형식 동일
  • 객체 배열 정의 시 따로 지정하지 않으면 항상 디폴트 생성자로 초기화
Circle circleArraay[3]; // 디폴트 생성자로 초기화

 

객체 배열의 초기화

  • 배열의 각 원소 객체당 생성자 지정하는 방법
    • { } 안에 생성자 나열
Circle circleArray[3] = {Circle(10), Circle(20), Circle()};

- circleArray[0] 객체가 생성될 때, 생성자 Circle(10) 호출

- circleArray[1] 객체가 생성될 때, 생성자 Circle(20) 호출

- circleArray[2] 객체가 생성될 때, 생성자 Circle() 호출

 

객체 배열 생성시 디폴트 생성자 호출

 

객체 배열의 인자있는 생성자로 초기화

#include <iostream>
using namespace std;
class Circle {
	int radius;
public:
	Circle() { radius = 1; }
	Circle(int r) { radius = r; }
	void setRadius(int r) { radius = r; }
	double getArea();
};
double Circle::getArea() {
	return 3.14 * radius * radius;
}
int main() {
	Circle circleArray[3] = { Circle(10), Circle(20), Circle() }; // Circle 배열 초기화
	for (int i = 0; i < 3; i++)
		cout << "Circle " << i << "의 면적은 " << circleArray[i].getArea() << endl;
}

/*
Circle 0의 면적은 314
Circle 1의 면적은 1256
Circle 2의 면적은 3.14
*/

 

2차원 객체 배열

 

객체 포인터

객체에 대한 포인터

  • C 언어의 포인터와 동일
  • 객체의 주소 값을 가지는 변수

포인터로 멤버를 접근할 때

  • 객체 포인터 -> 멤버

 

7.2 동적메모리 할당 및 반환

동적 메모리의 필요성

동적 메모리

프로그램 실행 중에 메모리의 할당과 해제가 결정되는 메모리

 

동적 메모리를 사용하면

  • 메모리 낭비 해결
    • 실행 중에 꼭 필요한 만큼 메모리를 할당 받아서 사용
    • 미리 정해진 크기가 아니라 원하는 크기만큼 할당 받는 것도 가능
  • 메모리의 할당과 해제 시점을 전적으로 프로그래머가 제어할 수 있음

 

정적 할당과 동적 할당

정적 할당

  • 변수 선언을 통해 필요한 메모리 할당
  • 많은 양의 메모리는 배열 선언을 통해 할당

동적 할당

  • 필요한 양이 예측되지 않는 경우, 프로그램 작성시 할당 받을 수 없음
  • 실행 중에 운영체제로부터 할당 받음
    • 힙(heap)으로부터 할당
      • 힙은 운영체제가 소유하고 관리하는 메모리

 

정적 메모리 vs 동적 메모리

특징 정적 메모리 동적 메모리
메모리 할당 컴파일 시간에 이루어짐 실행시간에 이루어짐
C: malloc()
C++: new 연산자
메모리 해제 자동으로 해제 명시적으로 해제해야 함
C: free()
C++: delete 연산자 사용
사용 범위 지역변수는 선언된 블록 내
전역변수는 프로그램 전체
명시적으로 해제해야 함
delete 연산자 사용
프로그래머가 원하는 동안만큼
메모리 관리 책임 컴파일러 책임 프로그래머의 책임

 

 

new와 delete 연산자

C++의 기본 연산자

new/delete 연산자의 사용 형식

데이터타입 *포인터변수 = new 데이터타입;
delete 포인터변수;

 

new/delete의 사용

int* pInt = new int;            // int 타입의 메모리 동적 할당
char* pChar = new char;         // char 타입의 메모리 동적 할당
Circle* pCircle = new Circle(); // Circle 클래스 타입의 메모리 동적 할당

delete pInt;    // 할당 받은 정수 공간 반환
delete pChar;   // 할당 받은 문자 공간 반환
delete pCircle; // 할당 받은 객체 공간 반환

 

기본 타입의 메모리 동적 할당 및 반환

delete 사용 시 주의해야 할 점 

delete는 특정한 주소에 있는 메모리 공간을 해제한다는 뜻이다.

  • 동적으로 할당된 메모리 주소를 저장하는 포인터 변수가 없어지는 것이 아님(dangling pointer)
  • 따라서 delete 연산자로 동적 메모리를 해제한 다음에는 동적 메모리의 주소를 저장하는 포인터 변수를 널 포인터로 지정하는 것이 안전함.

 

delete 사용 시 주의 사항

적절치 못한 포인터로 delete하면 실행 시간 오류 발생

  • 동적으로 할당 받지 않는 메모리 변환 - 오류
int n;
int *p = &n;
delete p; // 실행 시간 오류, heap에 있는 메모리 공간에만 사용가능, 컴파일 에러가 나는 컴파일러도 있음

동일한 메모리 두 번 반환 - 오류

int *p = new int;
delete p; // 정상적인 메모리 반환
delete p; // 실행 시간 올퓨, 이미 반환된 메모리를 중복 반환할 수 없음, 허용하는 컴파일러도 있음

 

배열의 동적 할당 및 반환

new/delete 연산자의 사용 형식

데이터타입 *포인터변수 = new 데이터타입[배열의 크기]; // 동적 배열 할당
delete [] 포인터변수; // 배열 반환

 

 

동적 메모리 초기화 및 delete시 유의사항

동적 할당 메모리 초기화

  • 동적 할당 시 초기화
데이터타입 *포인터변수 = new 데이터타입(초기값);
int *pInt = new int(20); // 20으로 초기화된 int 타입 할당
char *pChar = new char('a'); // 'a'로 초기화된 char 타입 할당
  • 배열은 동적 할당 시 초기화 불가능
int *pArray = new int[10](20); // 구문 오류, 컴파일 오류 발생
int *pArray = new int(20)[10]; // 구문 오류, 컴파일 오류 발생

 

delete 시 [ ] 생략

  • 컴파일 오류는 아니지만 비정상적인 반환
int *p = new int [10];
delete p; // 비정상 반환. delete [] p; 로 하여야 함.

int *q = new int;
delete [] q; // 비정상 반환. delete q;로 하여야 함

 

동적 메모리 할당과 메모리 누수

 

기본적인 동적 메모리 할당과 해제

동적 메모리 할당을 사용하는 방법의 정리

  • 메모리를 할당할 때는 타입과 크기를 지정한다. 그러면 컴퓨터가 메모리를 할당한 후에 그 메모리의 주소를 보관한다. 이 주소를 변수에 보관해야 한다.
int num;
int* data = new int;
  • 보관해둔 주소를 통해서 메모리 공간을 사용할 수 있다. 이 때는 배열의 원소를 가리키는 포인터처럼 사용할 수 있다.
*data = 6;
  • 사용이 끝난 후에는 반드시 보관해 둔 주소를 알려주면서 메모리를 해제한다.
delete data;

 

동적 메모리 할당의 규칙

1. new, delete와  new[], delete[] 쌍을 맞춰서 사용

2. NULL 포인터를 해제하는 것은 안전하다.

  • delete, delete[] 연산자는 메모리가 주소 값으로 nullptr이 넘겨져 온 경우에는 아무일도 하지 않음
  • delete, delete[] 연산자가 알아서 nullptr인 경우를 처리함
char* p = nullptr;
delete p; // OK

 

3. 적절치 못한 포인터로 delete하면 실행시간 오류 발생

  • 동적으로 할당 받지 않는 메모리 반환 - 오류
int n;
int* p = &n;
delete p; // 실행 시간 오류
// 포인터 p가 가리키는 메모리는 동적으로 할당 받은 것이 아님

 

  • 동일한 메모리 두 번 변환 - 오류
int* p = new int;
delete p; // 정상적인 메모리 반환
delete p; // 실행 시간 오류, 이미 반환된 메모리를 중복 반환할 수 없음

 

7.3 객체(배열)의 동적 생성 및 반환

객체의 동적 생성 및 반환

클래스이름 *포인터변수 = new 클래스이름;
클래스이름 *포인터변수 = new 클래스이름(생성자매개변수리스트);
delete 포인터변수;

 

객체 배열의 동적 생성 및 반환

클래스이름 *포인터변수 = new 클래스이름[배열크기];
delete[] 포인터변수; // 포인터변수가 가리키는 객체 배열 반환

동적 객체 배열은 항상 디폴트 생성자로 초기화
만일 클래스가 디폴트 생성자를 제공하지 않으면 동적 객체 배열을 생성하는 것은 불가능

 

객체 배열의 사용, 배열의 반환과 소멸자

  • 동적으로 생성된 배열도 보통 배열처럼 사용
Circle* pArray = new Circle[3]; // 3개의 Circle 객체 배열의 동적 생성

pArray[0].setRadius(10); // 배열의 첫 번째 객체의 setRadius() 멤버 함수 호출
pArray[1].setRadius(20); // 배열의 두 번째 객체의 setRadius() 멤버 함수 호출
pArray[2].setRadius(30); // 배열의 세 번째 객체의 setRadius() 멤버 함수 호출

for (int i = 0; i < 3; i++)
{
	cout << pArray[i].getArea(); // 배열의 i 번째 객체의 getArea() 멤버 함수 호출
}
  • 포인터로 배열 접근
pArray->setRadius(10);
(pArray+1)->setRadius(20);
(pArray+2)->setRadius(30);

for(int i=0; i<3; i++)
{
    (pArray+i)->getArea();
}
  • 배열 소멸
delete [] pArray;
// pArray[2] 객체의 소멸자 실행 (1)
// pArray[1] 객체의 소멸자 실행 (2)
// pArray[0] 객체의 소멸자 실행 (3)
// 각 원소 객체의 소멸자 별도 실행. 생성의 반대순

 

객체에 대한 포인터 배열의 생성 및 사용

객체의 주소를 저장하는 포인터 배열

Point *arr[3] = {new Point(10, 10), new Point(20, 20), new Point(30, 30)};
for(int i=0; i<3; i++)
    arr[i]->Print(); // arr[i]는 객체에 대한 포인터이므로 -> 사용

for(int i=0; i<3; i++)
    delete arr[i];

 

번외) 포인터 객체를 원소로 갖는 배열 동적으로 생성

#include <iostream>
using namespace std;

class Point {
	int x, y;
};

int main() {
	int n;
	cin >> n;
	Point* pArray = new Point[n];
	Point** ppArray = new Point* [n]; // 동적 생성
	// Point* ppArray[3] = { new Point(), new Point(), new Point() }; // 정적 생성
	for (int i = 0; i < 3; i++)
		delete* ppArray;
	delete[] ppArray;
}

 

 

7.4 멤버함수의 this 포인터

this 포인터

this

  • 포인터, 멤버 함수를 소유한 객체를 가리키는 포인터, 자신을 가리키는 포인터, 키워드로 간주
  • 클래스의 멤버 함수 내에서만 사용 (인스턴스 멤버 함수 안에서만)
  • 개발자가 선언하는 변수가 아니고, 컴파일러가 선언한 변수
    • 멤버 함수에 컴파일러에 의해 묵시적으로 삽입 선언되는 매개 변수
class Circle{
    int radius;
public:
    Circle() {this->radius=1;}
    Circle(int radius) {this->radius = radius;}
    void setRadius(int radius) {this->rardius = radius;}
    ...
};

 

this와 객체

각 객체 속의 this는 다른 객체의 this와 다름

 

this가 유용한 경우

매개변수의 이름과 멤버변수의 이름이 같은 경우

 

멤버 함수가 객체 자신의 주소를 리턴할 때

 

this의 제약 사항

멤버 함수가 아닌 함수에서 this 사용 불가

  • 객체와의 관련성이 없기 때문

static 멤버 함수에서 this 사용 불가

  • 객체가 생기기 전에 static 함수 호출이 있을 수 있기 때문에

 

this 포인터의 실체 - 컴파일러에서 처리

 

(번외) 초기화 리스트

class Point {
	const int MAX_VALUE;
	int x, y;
public:
    // 초기화리스트는 객체가 생성되기 전에 이루어지기 때문에 가능한 일
	Point(int x = 0, int y = 0, int maxV = 100) : MAX_VALUE(maxV)
	{
		this->x = x;
		this->y = y;
	}
};

class Rect {
	Point tl, br;
public:
    // 만약 br(x2, y2)를 빼버리면 br에 자동으로 디폴트 매개변수가 들어간다.
	Rect(int x1, int y1, int x2, int y2) : tl(x1, y1), br(x2, y2)
	{

	}
};

 

'공부 > C++' 카테고리의 다른 글

9. friend와 연산자 중복  (0) 2024.05.16
8. 함수와 참조, 복사생성자  (1) 2024.05.14
6. 접근지정자, const 객체, static 멤버  (1) 2024.04.18
5. 생성자와 소멸자  (0) 2024.04.11
4. 클래스와 객체의 기본  (0) 2024.04.09