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)으로부터 할당
- 힙은 운영체제가 소유하고 관리하는 메모리
- 힙(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 |