프로그래밍 언어/C++

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

busy맨 2023. 11. 3. 21:30

1. 객체 배열과 객체 포인터

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

  • 기본 타입 배열 선언과 형식 동일
  • 객체 배열 정의 시 따로 지정하지 않으면 항상 디폴트 생성자로 초기화
Circle circleArray[3];	//디폴트 생성자로 초기화
  • 배열의 각 원소 객체 당 생성자를 지정하는 방법
    • { } 안에 생성자 나열
Circle circleArray[3]={Circle(10), Circle(20), Circle()}
// 0번 객체 생성될 때, 생성자 Circle(10) 호출
// 1번 객체 생성될 때, 생성자 Circle(20) 호출
// 2번 객체 생성될 때, 생성자 Circle() 호출

  • Ex) 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]; // (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)
    }
}

#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;
}

2) 객체 포인터

  • 객체의 주소 값을 가지는 변수
  • '객체 포인터 -> 변수' 로 접근

#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() 호출
}

2. 동적메모리 할당 및 반환

1) 동적 메모리의 필요성

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

2) 정적 할당과 동적 할당

  • 정적 할당
    • 변수 선언을 통해 필요한 메모리 할당
      • 많은 양의 메모리는 배열 선언을 통해 할당
  • 동적 할당
    • 필요한 양이 예측되지 않는 경우, 프로그램 작성 시 할당 받을 수 없으므로 실행 중에 운영체제로부터 할당 받음
      • heap으로부터 할당
        • heap: 모든 프로세스가 공유할 수 있는 메모리로 운영체제가 소유하고 관리

 

3) new와 delete

  • C++의 기본 연산자로 동적 메모리 사용 시 선언

 

  • 형식

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

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

  • delete 주의 사항
    • 동적으로 할당된 메모리 주소를 저장하는 포인터 변수가 없어지는 것이 아님
    • 따라서 delete 연산자로 동적 메모리를 해제한 다음에 동적 메모리의 주소를 저장하는 포인터 변수를
      NULL로 지정하는 것이 안전
    • 동적으로 할당 받지 않는 메모리 반환하면 오류 발생
    • 동일한 메모리를 두 번 반환하면 오류 발생

 

  • Ex) 정수형 공간의 동적 할당 및 반환
#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 << endl;
    cout << "n = " << n << endl;
    
    delete p;
}

4) 배열의 동적 할당 및 반환

  • new/delete 연산자의 사용 형식

#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; // 배열 메모리 반환
}

  • 동적 할당 메모리 초기화
    • 동적 할당 시 초기화 가능
    • 배열의 경우, 동적 할당 시 초기화 불가
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;로 하여야 함.

 

  • 메모리 누수

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

1) 객체의 동적 생성 및 반환

 

#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;
}

 

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

  • 동적으로 생성된 배열도 보통 배열처럼 사용

#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() << endl;
    }
    
    Circle *p = pArray; // 포인터 p에 배열의 주소값으로 설정
    
    for(int i=0; i<3; i++) {
    	cout << p→getArea() << endl;
    	p++; // 다음 원소의 주소로 증가
    }
    delete [] pArray; // 객체 배열 소멸
}

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];

 

4. 멤버함수의 this 포인터

1) this 포인터

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

  • this 포인터의 장점
    • 매개 변수의 이름과 멤버 변수의 이름이 같은 경우
    • 멤버 함수가 객체 자신의 주소를 리턴할 때

  • this 제약 사항
    1. 멤버 함수가 아닌 함수에서 this 사용 불가
      • 객체와의 관련성이 없기 때문에
    2. static 멤버 함수에서 this 사용 불가
      • 객체가 생기기 전에 static 함수 호출이 존재할 수 있기 때문에

'프로그래밍 언어 > C++' 카테고리의 다른 글

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