220908 C++ #3

김혜진·2022년 9월 8일
0

C++

목록 보기
3/12

C++ #3

객체 배열

객체 배열이란

  • 사용자가 정의한 자료형(클래스)로 선언한 변수를 객체라고 명명함
  • 객체의 속성은 일반 변수의 속성과 동일함
  • 다수의 변수 사용시 배열을 선언한 것처럼 객체 또는 배열로 선언이 가능한데 이를 '객체 배열'이라고 함

객체 배열의 선언 형태

  • 사용자 정의 자료형인 클래스명을 쓰고, 객체명을 선언하되, 객체명에 배열 표시 [] (대괄호)를 덧붙이고, 그 안에 요소의 개수를 입력함
MousePoint pt[3];

일반 변수의 배열과 동일하다.
다만 데이터 한 단위가 int는 4바이트인데 x,y로 이루어져있는 8바이트다.

객체 배열의 활용

  • 객체 배열로 멤버 함수를 명시적으로 호출하는 경우
pt[0].setXY(10,20);
pt[1].setXY(100,200);
  • 객체 배열의 경우도 생성자를 호출할 수 있다.
  • 각 객체 배열별로 초기화 시 생성자가 호출된다.
MousePoint pt[3] = {MousePoint(10,20), MousePoint(30,40), MousePoint(50,60)};
#include<iostream>

using namespace std;

class MousePoint
{
private:
	int x;
	int y;
public:
	MousePoint();
	MousePoint(int nX, int nY);
	void SetXY(int nX, int nY);
	inline int GetX() { return x; }
	inline int GetY() { return y; }
};

MousePoint::MousePoint()
{
	x = 0;
	y = 0;
}
## 
MousePoint::MousePoint(int nX, int nY)
{
	x = nX;
	y = nY;
}

void MousePoint::SetXY(int nX, int nY)
{
	x = nX;
	y = nY;
}

void main()
{
	MousePoint pt[3] = { 
		MousePoint(10,20),
		MousePoint(30,40),
		MousePoint(50,60)
	};

	for (int i = 0; i < 3; i++)
	{
		cout << "pt[" << i << "] = " << pt[i].GetX() << ", " << pt[i].GetY() << endl;
	}

}

출력결과
pt[0] = 10, 20
pt[1] = 30, 40
pt[2] = 50, 60

한 줄짜리 코드는 클래스 내부에서 처리해도 된다. 인라인으로 처리할 수 있다.


객체 포인터

객체 포인터란

  • 객체의 주소값을 저장하기 위한 변수
  • 객체를 간접 참조하기 위해 사용
MousePoint *pObj;
MousePoint pt(10,20);
pObj =  &pt;

포인터는 주소값만을 저장하고, 주소값은 정수로 저장되므로 4바이트.

  • pObj = &pt;의 메모리 구조를 그림으로 살펴보자.

    포인터 변수가 가리키고 있는 메모리는 바로 객체의 메모리이다.
    한 개의 변수(x or y)에 대한 포인터가 아니라 pt객체에 대한 포인터이다.
void main()
{
	MousePoint* pObj;
	MousePoint pt(10, 20);
	pObj = &pt;

	cout << pObj->GetX() << " " << pObj->GetY() << endl;
}

출력결과
10 20

-> : 간접 참조 연산자
. : 직접 참조 연산자

  • pt의 주소값을 pObj 객체 포인터에 대입함으로써 pObj는 pt 객체의 멤버에 간접 참조될 수 있음.

this 포인터

this 포인터가 필요한 이유

  • 객체 배열에서의 멤버 변수와 멤버 함수의 관계 구조도

멤버 변수의 메모리 블록은 별도. 멤버 함수의 메모리 블록은 공유
멤버 함수 입장에서 어떤 객체가 자기를 호출했는지 알 수 있는가?
어떤 객체가 호출했는지 알 수 없으므로 이 때 필요한 정보가 바로 this 포인터이다.

this 포인터의 활용

  • this 포인터는 멤버함수를 호출한 객체를 가리키는 상수 포인터이다.
void MousePoint::SetXY(int x, int y)
{
	this ->	x = x;
	this -> y = y;
}

void main()
{
	MousePoint pt(10, 20);
	pt.SetXY(100, 200);

	cout << pt.GetX() << " , " << pt.GetY() << endl;
}

출력결과
100, 200

전달인자와 멤버변수의 이름이 같을 때는 구분할 수가 없다.
이런 경우 멤버변수를 나타내는 x,y 앞에 this 포인터를 붙여줌으로써 현재 호출한 객체의 멤버변수임을 명시한다.


전달인자가 객체인 함수

객체 전달 방식

  • 값 전달 방식과 레퍼런스 전달 방식이 있다.

객체에 대한 값 전달 방식

  • 기본 자료형을 값 전달 방식으로 사용했듯이, 객체도 값 전달 방식으로 전달이 가능하다.
  • SetRect() 함수를 통해 두 객체를 전달받도록 한다.
void SetRect(MousePoint pt1, MousePoint pt2)
{
	cout << pt1.GetX() << ", " << pt1.GetY() << endl;
	cout << pt2.GetX() << ", " << pt2.GetY() << endl;

	pt1.SetXY(1000, 2000);
	cout << pt1.GetX() << ", " << pt1.GetY() << endl;
	}

void main()
{
	MousePoint mp1(10,20), mp2(100,200);
	SetRect(mp1, mp2);
	cout << mp1.GetX() << ", " << mp1.GetY() << endl;
}

출력결과
10, 20
100, 200
1000, 2000
10, 20

메모리 공유가 아닌 값이 복사가 되는 방식이기 때문에 setRect 함수에서 변경된 멤버변수의 값이 main 함수에서는 변하지 않는다.

객체가 복사가 된다는 것은 객체의 멤버 변수가 그대로 복사가 된다는 의미이다.

객체에 대한 값 전달 방식의 메모리 구조


결과를 보면 예상과 다름.
외부의 mp1 객체가 pt1 객체로 값을 넘겨준다.
setXY()함수를 통해 pt1 객체의 멤버 변수 값을 1000으로 변경한다.
mp1과 pt값을 복사하고 받는 별도의 메모리이지 결코 하나가 아니다.

값 전달 방식의 단점과 한계

  • 객체의 경우 객체끼리 복사가 이루어지는데 객체의 멤버 변수가 복사
void CopyObject(MousePoint pt1, MousePoint pt2)
{
	pt1 = pt2; // pt2가 pt1에 복사
    cout << pt1.GetX() << "," << mp1.GetY() << endl;
}
void main()
{
	MousePoint mp1(10,20), mp2(100,200);
      cout << pt1.GetX() << "," << mp1.GetY() << endl;
}

출력결과
100, 200
10, 20

CopyObject 함수에 각 객체를 넘겨줌으로써, 객체의 복사가 이루어짐.
출력 결과를 보면 우리가 예상한 100, 200이 아닌 10, 20이 출력.
이유는 실 매개변수와 형식매개변수의 메모리 관리가 별도로 이루어지고 있기 때문이다. 이것이 값 전달 방식의 한계이고 단점이다.

레퍼런스 방식

  • 실 매개변수의 별칭을 형식 매개변수에 부여함으로써 같은 메모리 사용(메모리 공유)
  • 형식 매개변수를 변경하면 실 매개변수도 변경이 된다.
  • 레퍼런스 표시는 객체 앞에 &표시를 해주면 된다.

결국 주소값을 공유하는 포인터.
매개변수를 넘길때는 그냥 넘기지만, 받는 쪽에서는 주소값을 받는다.(=별칭) C++에서는 주소연산자가 아니라 별칭이라 부른다.

void CopyObject(MousePoint &pt1, MousePoint &pt2) // 별칭
{
	pt1 = pt2;
	cout << pt1.GetX() << ", " << pt1.GetY() << endl;
}

void main()
{
	MousePoint mp1(10, 20), mp2(100, 200);
	CopyObject(mp1, mp2);
	cout << mp1.GetX() << ", " << mp1.GetY() << endl;
}

출력결과
100, 200
100, 200

레퍼런스 방식의 장점

  • 레퍼런스를 통해 함수 내에서 실 매개변수의 값을 변경할 수 있다.
  • 실 매개변수와 형식 매개변수 각각 별도의 메모리를 생성하지 않고, 실 매개변수의 별칭을 생성하여 공유함으로써 메모리 절감효과가 있다.

레퍼런스 방식의 반환 형태

  • 객체의 결과값을 반환하려면 반환하려는 값의 타입과 함수의 반환 타입을 일치시켜주면 된다.
MousePoint CopyObject(MousePoint &pt1, MousePoint &pt2)
{
	pt1 = pt2;
    return pt1;
}

함수 결과 반환 시 결과값을 저장하기 위한 별도의 메모리가 할당

  • 함수의 반환값을 레퍼런스 형식으로 처리
  • 함수의 결과값을 저장하기 위한 메모리를 따로 할당할 필요가 없음
MousePoint & CopyObject(MousePoint &pt1, MousePoint &pt2)
{
	pt1 = pt2;
    return pt1;
}


const 멤버함수와 const 객체

const 멤버함수

  • const 사용 목적은 객체의 멤버변수를 변경시킬 수 없도록 하기 위함
class MousePoint
{
private:
	int x;
	int y;
public:
	MousePoint();
	MousePoint(int nX, int nY);
	void SetXY(int nX, int nY);
	inline int GetX() const { return x; }
	inline int GetY() const { return y; }
};

각 멤버 함수명 뒤에 const라는 예약어를 붙여주면 된다.
멤버함수 정의 시에도 const 예약어를 붙여주어야 한다.

  • 멤버함수는 객체의 멤버변수를 변경할 수 없는 읽기 전용 함수

  • const 멤버 함수는 const로 지정되지 않은 다른 멤버함수도 호출할 수 없다.

    읽기전용으로 지정된 const 멤버함수에서 const로 지정되지 않은 멤버함수를 호출함으로써 간접적으로 객체의 멤버변수를 변경시킬지도 모르는 가능성을 배제시키기 위함이다.

  • const로 선언된 멤버함수에서 객체의 데이터를 변경하려고 시도한다면 다음과 같은 에러메시지를 보게 된다.
    l-value specifies const object

set()과 같은 멤버변수에 접근하는 함수는 const 멤버함수로 사용하면 안 되고, get()과 같이 데이터의 변화 없이 단순히 값을 반환하거나 화면에 출력하는 기능을 하는 함수를 const 멤버함수로 선언하는 것이 바람직하다.

  • 생성자 및 소멸자는 const 예약어를 사용할 수 없다.
    생성자와 소멸자는 항상 객체의 데이터를 변경시켜야 하기 때문이다.

const 객체

  • 객체 생성 시 const 예약어를 사용하면 객체는 상수로 취급되어 초기화 된 데이터 외에 다른 데이터로 변경 불가능
const MousePoint pt1(10,20);
  • pt1 객체는 초기값 10, 20의 값을 다른 값으로 변경 불가능
const MousePoint pt1(10,20);
MousePoint pt2(100, 200);
pt1 = pt2; // 에러가 발생한다.
  • 다음과 같은 에러를 유발시킨다.

  • const 객체는 데이터 멤버를 변경할 수 있는 멤버함수 호출 허용 금지

pt1.SetXY(50,100) //에러가 발생하다.
  • 다음과 같은 에러를 유발시킨다.

  • const 멤버 함수들은 호출이 가능하다.

pt1.GetX();
pt1.GetY();

멤버 변수를 변경하지 않고 저장되어있는 값을 반환하고 있음.

profile
알고 쓰자!

0개의 댓글