<객체> 4. 객체 포인터와 객체 배열, 객체의 동적 생성

정지민·2023년 10월 14일
0

객체 프로그래밍

목록 보기
4/11

객체 포인터

  • 객체의 주소값을 가짐
  • 포인터로 멤버를 접근할때 형식: 객체포인터 -> 멤버
Circle donut;
double d = donut.getArea();

Circle *p; //객체에 대한 포인터 선언
p=&donut; //포인터에 객체 주소 저장
d=p->getArea(); //멤버 함수 호출
d=(*p).getArea(); //위와 동일

<예제 4-1> 객체 포인터 선언 및 활용

#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle() { radius = 1; }
	Circle(int r) { radius = r; }
	double getArea();
};

double Circle::getArea() {
	return 3.14 * radius * radius;
}

int main() {
	Circle donut;
	Circle pizza(30);

	// 객체 이름으로 멤버 접근
	cout << donut.getArea() << endl;

	// 객체 포인터로 멤버 접근
	Circle* p;
	p = &donut;
	cout << p->getArea() << endl; // donut의 getArea() 호출
	cout << (*p).getArea() << endl; // donut의 getArea() 호출

	p = &pizza;
	cout << p->getArea() << endl; // pizza의 getArea() 호출
	cout << (*p).getArea() << endl; // pizza의 getArea() 호출
}

//결과
//3.14
//3.14
//3.14
//2826
//2826

객체 배열, 생성 및 소멸

Circle c[3]; //Circle 타입 배열 선언

  • 각 원소 객체마다 생성자 실행
    -> c[0]의 생성자, c[1]의 생성자, c[2]의 생성자 실행

  • 매개변수 없는 생성자 호출
    Circle circleArray[3](5); //오류

  • 각 객체마다 소멸자 호출: 생성의 반대순으로 소멸

#include <iostream>
using namespace std;

class Circle {
	int radius;
    //private라 메인에서 접근x -> set,get함수(setRadius) 사용해 바꾼다.
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]; 		// (1) Circle 객체 배열 생성

	// 배열의 각 원소 객체의 멤버 접근
	circleArray[0].setRadius(10); 	// (2)
	circleArray[1].setRadius(20);
	circleArray[2].setRadius(30);

	for (int i = 0; i < 3; i++) // 배열의 각 원소 객체의 멤버 접근
		cout << "Circle " << i << "의 면적은 " << circleArray[i].getArea() << endl;

	Circle* p; 		// (3)
	p = circleArray; 		// (4) 자체가 주소값이라 & 없다.
	for (int i = 0; i < 3; i++) { 	// 객체 포인터로 배열 접근
		cout << "Circle " << i << "의 면적은 " << p->getArea() << endl;
		p++; 															// (5)
	}
}

//for문 i<3 범위 조심하자! 3은 안된다!

//결과
//Circle 0의 면적은 314
//Circle 1의 면적은 1256
//Circle 2의 면적은 2826
//Circle 0의 면적은 314
//Circle 1의 면적은 1256
//Circle 2의 면적은 2826

객체 배열 생성시 기본생성자

객체 배열 초기화

#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 배열 초기화
//circleArray[0] 객체가 생성될 때, 생성자 Circle(10) 호출

	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차원 배열

#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 circles[2][3];

	circles[0][0].setRadius(1);
	circles[0][1].setRadius(2);
	circles[0][2].setRadius(3);
	circles[1][0].setRadius(4);
	circles[1][1].setRadius(5);
	circles[1][2].setRadius(6);
    
    //Circle circles[2][3] = 
    // { { Circle(1), Circle(2), Circle(3) },
    //   { Circle(4), Circle(5), Circle() } };
    //두개 다 가능한 형태지만 실행되는 함수는 다름!


	for (int i = 0; i < 2; i++)  	// 배열의 각 원소 객체의 멤버 접근
		for (int j = 0; j < 3; j++) {
			cout << "Circle [" << i << "," << j << "]의 면적은 ";
			cout << circles[i][j].getArea() << endl;
		}
}

//결과
//Circle [0,0]의 면적은 3.14
//Circle [0,1]의 면적은 12.56
//Circle [0,2]의 면적은 28.26
//Circle [1,0]의 면적은 50.24
//Circle [1,1]의 면적은 78.5
//Circle [1,2]의 면적은 113.04

동적 메모리 할당 및 반환

정적 할당

Circle circleArray[2];

동적 할당

  • 필요한 양이 예측되지 않는 경우
  • 힙으로부터 할당

할당: new 연산자

  • 객체의 동적 생성 - 힙 메모리로부터 객체를 위한 메모리 할당 요청
  • 객체 할당 시 생성자 호출

반환: delete 연산자

  • 객체의 동적 소멸 - 소멸자 호출 뒤 객체를 힙에 반환
  • 포인터는 살아있음
  • 주의사항(오류)
    • 동적으로 할당받지 않는 메모리 반환
      int n; //정적으로 할당
      int *p=&n;
      delete p;
    • 동일한 메모리 두번 반환

<예제 4-5> 정수형 공간의 동적 할당 및 반환 예

#include <iostream>
using namespace std;

int main() {
	int* p;

	p = new int;
	if (!p) {
		cout << "메모리를 할당할 수 없습니다.";
		return 0;
	}
	*p = 5; //할당 받은 정수 공간에 5 삽입
	int n = *p;
	cout << "*p = " << *p << '\n';
	cout << "n = " << n << '\n';

	delete p;
}

배열의 동적 할당 및 반환

  • 형식
    데이터타입 *포인터변수 = new 데이터타입[배열의 크기];
    delete [] 포인터변수;

<예제 4-6> 정수형 배열의 동적 할당 및 반환

#include <iostream>
using namespace std;

int main() {
	cout << "입력할 정수의 개수는?";
	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; //반환, []지워도 에러 안남
}

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

  • 배열은 동적 할당시 초기화 불가능
int *pInt = new int(20);  //가능
//데이터타입 *포인터변수 = new 데이터타입(초기값);

int *pArray = new int[10](20); //불가능
//배열은 동적할당시 초기화 불가능
  • delete시 [] 생략 - 오류는 안나지만 비정상적 반환

<예제 4-7> Circle 객체의 동적 생성 및 반환

#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle();
	Circle(int r);
	~Circle();
	void setRadius(int r) { radius = r; }
	double getArea() { return 3.14 * radius * radius; }
};

Circle::Circle() {
	radius = 1;
	cout << "생성자 실행 radius = " << radius << endl;
}

Circle::Circle(int r) {
	radius = r;
	cout << "생성자 실행 radius = " << radius << endl;
}

Circle::~Circle() {
	cout << "소멸자 실행 radius = " << radius << endl;
}

int main() {
	Circle* p, * q;
	p = new Circle;
	q = new Circle(30);

	cout << p->getArea() << endl << q->getArea() << endl;

	//생성한 순서와 상관없이 원하는 순서대로 소멸시킬 수 있음
	delete p; 
	delete q;
}

//실행결과

//생성자 실행 radius = 1
//생성자 실행 radius = 30
//3.14
//2826
//소멸자 실행 radius = 1
//소멸자 실행 radius = 30

<예제 4-8> Circle 객체의 동적 생성 및 반환 (응용)

#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle();
	Circle(int r);
	~Circle();
	void setRadius(int r) { radius = r; }
	double getArea() { return 3.14 * radius * radius; }
};

Circle::Circle() {
	radius = 1;
	cout << "생성자 실행 radius = " << radius << endl;
}

Circle::Circle(int r) {
	radius = r;
	cout << "생성자 실행 radius = " << radius << endl;
}

Circle::~Circle() {
	cout << "소멸자 실행 radius = " << radius << endl;
}

int main() {
	int radius;
	while (true) {
		cout << "정수 반지름 입력(음수이면 종료) >>";
		cin >> radius;
		if (radius < 0)break; //아랫줄과 바뀌면 안됨
		Circle* p = new Circle(radius); //동적 객체 생성
		cout << "원의 면적은 " << p->getArea() << endl;
		delete p; //없으면 안됨
	}
}

//실행 결과

//정수 반지름 입력(음수이면 종료) >>1
//생성자 실행 radius = 1
//원의 면적은 3.14
//소멸자 실행 radius = 1
//정수 반지름 입력(음수이면 종료) >>-1

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

  • 형식
    클래스 이름 *포인터변수 = new 클래스이름[배열 크기];
    delete []포인터변수; // 반환, 포인터p는 살아있지만 p가 가리키는 객체 배열 반환

<예제 4-9> Circle 배열의 동적 생성 및 반환

#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle();
	Circle(int r);
	~Circle();
	void setRadius(int r) { radius = r; }
	double getArea() { return 3.14 * radius * radius; }
};

Circle::Circle() {
	radius = 1;
	cout << "생성자 실행 radius = " << radius << endl;
}

Circle::Circle(int r) {
	radius = r;
	cout << "생성자 실행 radius = " << radius << endl;
}

Circle::~Circle() {
	cout << "소멸자 실행 radius = " << radius << endl;
}

int main() {
	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() << '\n';
	}

	Circle* p = pArray; //포인터 p에 배열의 주소값으로 설정
	for (int i = 0; i < 3; i++) {
		cout << p->getArea() << '\n';
		p++; //다음 원소 주소로
	}
	
	delete[] pArray; //객체 배열 소멸

}

//실행 결과

//생성자 실행 radius = 1
//생성자 실행 radius = 1
//생성자 실행 radius = 1
//314
//1256
//2826
//314
//1256
//2826
//소멸자 실행 radius = 30 //생성 반대순으로 소멸
//소멸자 실행 radius = 20
//소멸자 실행 radius = 10

<예제 4-10> 객체 배열의 동적 생성과 반환 응용

#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle();
	~Circle() {	}
	void setRadius(int r) { radius = r; }
	double getArea() { return 3.14 * radius * radius; }
};

Circle::Circle() {
	radius = 1;
}

int main() {
	cout << "생성하고자 하는 원의 개수? >>";
	int n, radius;
	cin >> n; //원의 개수 입력

	Circle* pArray = new Circle[n]; //n개의 Circle 배열 생성
	for (int i = 0; i < n; i++) {
		cout << "원" << i + 1 << ":"; 
		cin >> radius; 
		pArray[i].setRadius(radius); //각 Circle 객체를 반지름으로 초기화

	}

	int count=0;
	Circle* p = pArray;
	for (int i = 0; i < n; i++) {
		cout << p->getArea() << ' ';
		if (p->getArea() >= 100 && p->getArea() <= 200)
			count++;
		p++;
	}
	cout << endl << "면적이 100에서 200 사이인 원의 개수는"
		<< count << endl;
	
	delete[] pArray; 
}

//실행 결과

//생성하고자 하는 원의 개수? >>4
//원1:7
//원2:9
//원3:5
//원4:2
//153.86 254.34 78.5 12.56
//면적이 100에서 200 사이인 원의 개수는1

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

this 포인터

  • 객체 자신 포인터

  • 클래서의 멤버 함수 내에서만 사용

  • 컴파일러가 선언

  • 각 객체 속의 this는 다른 객체의 this와 다름
    다음의 경우에 필요

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

      Circle (int radius){
      	this-> radius = radius;
       }
    • 멤버 함수가 객체 자신의 주소를 리턴할 때

      class Sample{
      public:
      	Sample* f(){
        	...
            return this;
        }
      };
  • 제약사항

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

string 클래스를 이용한 문자열

  • 라이브러리 헤더 파일에 선언
#include <string>
using namespace std;
  • 문자열 생성
    string str; //빈 문자열 가진 스트링 객체
    string address("서울 특별시"); //문자열 리터럴로 초기화
    string copyAddress(address); //adress를 복사한 copyAddress생성
    char text[]={'l','o','v','e'};
    string title(text); //"love"문자열을 가진 title 생성
    문자열 숫자 변환: stoi() 함수 (string to int)
    string s="123";
    int n=stoi(s);

string 객체의 동적 생성

  • new / delete 이용
string *p=new string("C++"); //스트링 객체 동적 생성
  
cout << *p;
p->append("Great!"); //p가 가리키는 스트링이 "C++ Great!"가 됨
cout << *p;
  
delete p;

<4-11> string 클래스를 이용한 문자열 생성 및 출력

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

int main() {
	//스트링 생성
	string str;
	string address("서울시 송파구 잠실로 62");
	string copyAddress(address); //address의 문자열을 복사한 스트링 객체 생성

	char text[] = { 'L','O','V','E','\0'}; //'\0' 써줘야함
	string title(text); //"LOVE"문자열 가진 스트링 객체 생성

	cout << str << endl; //빈 문자열
	cout << address << endl;
	cout << copyAddress << endl;
	cout << title << endl;
}
  
//실행결과

//(빈칸)
//서울시 송파구 잠실로 62
//서울시 송파구 잠실로 62
//LOVE

<예제 4-12> 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], '\n');
	}

	string latter = names[0];
	for (int i = 1; i < 5; i++) {
		if (latter < names[i]) {
			latter = names[i];
		}
	}
	cout << "사전에서 가장 뒤에 나오는 문자열은" << latter << endl;
}

//실행결과
                                                   
//이름 >>jimin
//이름 >>hayoon
//이름 >>lex
//이름 >>jenny
//이름 >>lose
//사전에서 가장 뒤에 나오는 문자열은lose

0개의 댓글