[전공]

[명품 C++] 04 객체 포인터와 객체 배열, 객체의 동적 생성

danhan 2022. 6. 2. 11:09

출처 :

  • 명품 C++ Programming (저자 황기태)
  • 객체지향프로그래밍

객체 포인터

  • 객체의 주소 값을 가지는 변수
  • 포인터로 멤버를 접근할 때 . 연산자 대신 - > 객체포인터 - > 멤버
Circle donut;
double d = donut.getArea();

// p의 자료형은 Circle *
Circle *p;           // 객체에 대한 포인터 선언
p = &donut;          // 포인터에 객체 주소 저장
d = p -> getArea();  // 멤버 함수 호출

*포인터로도 private 에는 접근 불가능

p->getArea() == (*p).getArea()
// (*p)는 객체 자체가 된다.

객체 배열, 생성 및 소멸

객체 배열 선언

  1. 객체 배열을 위한 공간 할당
  2. 배열의 각 원소 객체마다 생성자 실행
    • 매개 변수 없는 생성자가 호출됨
    *매개 변수 없는 생성자를 선언하지 않았다면 매개 변수 있는 생성자도 선언하지 않아야 자동으로 매개 변수 없는 생성자가 생성된다.
class Circle {
	int radius;
public:
	Circle(int r) { radius = r; }
	double getArea() {
		return 3.14 * radius * radius;
	}
};

int main() {
	Circle waffle(15);      // 매개 변수 있는 생성자 Circle(int r)을 호출
	Circle circleArray[3];  // 컴파일 오류!! 객체 배열은 매개 변수 "없는" 생성자를 실행해야하는데
}                         // Circle(int r)이 선언되어 있어 매개 변수 "없는 기본 생성자가 자동 삽입되어 있지 않다.
Circle *p;
p = circleArray;    // p는 cicleArray의 첫 요소의 주소값을 담고 있다.

for (int i = 0; i < 3; i++) {
	cout << "Circle" << i << "의 면적은 " << circleArray[i].getArea() << endl;
	// 윗 줄과 아랫 줄은 같은 결과
	cout << "Circle" << i << "의 면적은 " << p->getArea() << endl;
	p++;  // 객체 요소 하나의 크기만큼 증가한다.
}

객체 배열 생성 및 소멸

선언

  1. 객체 배열을 위한 공간 할당
  2. 배열의 각 원소 객체마다 생성자 실행
    • c[0]의 생성자, c[1]의 생성자...
    • c[0][0], c[0][1], c[1][0] ... 순서
    • 매개 변수 없는 생성자 호출 (매개 변수 있는 생성자 호출 불가능)

소멸

  • 각 원소 객체마다 “생성의 반대순”으로 소멸

객체 배열 초기화

  • 배열의 각 원소 객체당 생성자 지정
Circle circleArray[3] = { Circle(10), Circle(20), Circle() };
// CircleArray[0] 객체가 생성될 때, 생성자 Circle(10) 호출

동적 메모리 할당 및 반환

정적 할당

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

동적 할당

  • 필요한 양이 예측되지 않는 경우. 프로그램 작성시 할당 받을 수 없음.
  • 실행 중에 힙 메모리에 할당
  • 메모리가 더 필요한 즉시 힙 heap으로부터 할당
  • 힙 : 운영체제가 프로세스(프로그램)의 실행을 시작 시킬 떄 동적 할당 공간으로 준 메모리 공간

c++ 동적 메모리 할당/반환

  • new 연산자
    • 기본 타입 메모리, 배열, 객체, 객체 배열 할당
    • 객체의 동적 생성 - 힙 메모리부터 객체를 위한 메모리 할당 요청
    • 객체 할당 시 생성자 호출
  • delete 연산자
    • new로 할당 받은 메모리 반환 - 안하면 메모리 누수
    • 객체의 동적 소멸 → 소멸자 호출 뒤 객체를 힙에 반환
    • 생성한 순서와 관계 없이 원하는 순서대로 delete 가능
    • 적절치 못한 포인터로 delete 하면 실행 시간 오류 발생
    int n;
    int *p = &n; // 포인터 p가 가리키는 메모리는 동적으로 할당 받은 것이 아님
    delete p;    // 실행 시간 오류
    
    int *p = new int;
    delete p;    // 정상
    delete p;    // 실행 시간 오류 - 이미 반환한 메모리를 중복 반환할 수 없음    
    
  • 사용 형식
데이터 타입 *포인터변수 = new 데이터타입;   // heap에 할당
데이터 타입 *포인터변수 = new 데이터타입(초기값);
delete 포인터 변수;                        // heap에 할당된 건 포인터 변수로 접근

테이터 타입 *포인터변수 = new 데이터타입 [배열의 크기];  // 동적 배열 할당
// 배열은 동적 할당 시 초기화 불가능
delete [] 포인터 변수;
int *pInt = new int;
Circle *pCircle = new Circle();

delete pInt;   // delete 후에도 포인터는 살아있지만 더이상 포인터가 가리키는 곳에 접근하면 안됨
delete pCircle;
#include <iostream>
using namespace std;

int main() {
	int *p;

	p = new int;

	if (!p) {
		cout << "메모리 할당할 수 없음";
		return 0;
}

	*p = 5;    // 할당 받은 정수 공간에 5 삽입
	int n = *p;
	
	delete p;  // 프로그램이 돌아가는 중간에 명시적으로 delete 해주는 게 좋다
};

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

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

pArray[0].setRadius(10);
pArray[1].setRadius(20)'
pArray[2].setRadius(30)'

for (int i = 0; i < 3; i++
	cout << pArray[i].getArea();

// 포인터로 배열 접근
pArray->setRadius(10);
(pArray+1)->setRadius(20);
(pArray+2)->setRadius(30);

// 배열 소멸
delete [] pArray;
// pArray[2], [1], [0] 객체의 소멸자 실행

예제 정수형 배열의 동적 할당 및 반환

#include <iostream>
using namespace std;

int main() {
	int n;
	cin >> n;

	if (n <= 0) return 0;

	int *p = new int[n];   // n개의 정수 배열 동적 할당
	if (!p) {
		cout << "메모리를 할당할 수 없습니다.";
		return 0;
	}

	for (int i = 0; i < n; i++) {
		cout << i + 1 << "번째 정수 : ";
		cin >> p[i];
	}

	int sum = 0;
	for (int i = 0; i < n; i++)
		sum += p[i];
	cout << "평균 = " << sum / n << endl;

	delete [] p;         // 배열 메모리 반환
}

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

char n = 'a';
char *p = new char[1024];  // 힙의 char [1024]  주소를 가리킨다.
p = &n;                    // n을 가리키게 되면서 이전에 할당 받은 char [1024]는
                           // 반환할 수도 없고 사용하지도 않는 누수 메모리가 된다.
                           // 프로그램이 종료되면, 운영체제는 누수 메모리를 모두 힙에 반환

this 포인터

  • 포인터, 객체 자신 포인터
  • 클래스의 멤버 함수 내에서만 사용
  • 개발자가 선언하는 변수가 아닌 컴파일러가 선언한 변수
  • 멤버 함수에 컴파일러에 의해 묵시적으로 삽입되는 매개 변수
  • 각 객체 속의 this는 다른 객체의 this와 다름
class Circle {
	int radius;
public:
	Circle() { this->radius=1; }
	void setRadius(int radius) { this->raadius = radius; }
                         // Circle의 멤버변수   밖에서 입력한 readius

this 가 필요한 경우

  • 매개변수의 이름과 멤버 변수의 이름이 같은 경우
Circle (int radius) {
	this->radius = radius;
}
  • 멤버 함수가 객체 자신의 주소를 리턴할 때
class Sample {
public:
	Sample * f() {
		...
		return this;
	}
}

this의 제약 사항

  • 멤버 함수가 아닌 함수에서 this 사용 불가 - 객체와의 관련성이 없음
  • static 멤버 함수에서 this 사용 불가 객체가 생기기 전에 static 함수 호출이 있을 수 있기 때문

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

// 개발자가 작성한 클래스
class Sample {
	int a;
public:
	void setA(int x) {
		this->a = x;
	}
};

// 컴파일러에 의해 변환된 클래스
class Sample {
	...
public:
	void setA(Sample *this, int x) {
		this->a = x;
	}
};

// 객체의 멤버 함수를 호출하는 코드의 변환
ob.setA(5);
ob.setA(&ob, 5);  // ob는 객체 자신, &ob는 주소
                  // ob의 주소가 this 매개변수에 전달됨

String 클래스를 이용한 문자열

C++ 문자열

  • C- 스트링
  • C++ string 클래스의 객체

String 클래스

  • <string> 헤더파일에 선언
  • 가변크기의 문자열
  • 문자열 복사, 문자열 비교, 문자열 길이 등 다양한 문자열 연산자와 멤버 함수 포함
  • 문자열, 스트링, 문자열 객체, string 객체 등으로 혼용

문자열 생성

string str;                               // 빈 문자열
string address("블라블라");
string copyAdress(address);
char text[] = {'L','O','V','E','\\0'};
string title(text);                      // "LOVE" 문자열을 가진 title 생성

문자열 숫자 변환 stoi() 함수 이용

string s = "123";
int n = stoi(s);

string 객체의 동적 생성

new/delete를 이용하여 문자열을 동적 생성/반환 가능

string *p = new string("C++");   // 스트링 객체 동적 생성

cout << *p;
p->append(" Great!!");

delete p;

예제 - string 배열 선언과 문자열 키 입력 응용

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

int main() {
	string names[5];

	for (int i = 0; i < 5; i++) {
		cout << "이름 >> ";
		getline(cin, names[i], '\\0');
	}

	string latter = names[0];
	for (int i = 1; i < 5; i++ )
		if (latter < names[i])
			latter = names[i];
}

밑에 예제 더 있음~~!! ppt 39 부터