본문 바로가기

코딩/객체지향프로그래밍

[C++] 클래스와 객체 (2)

출처: https://www.guru99.com/java-oops-class-objects.html

 


 

1. 객체 전달하기

C++은 pass by value가 default임 → 메모리 복사가 많이 일어나게 되고(메모리 비효율), 프로그램의 성능과 실행 속도를 저하시킬 수 있다.

 

*) const의 위치에 따른 의미를 잘 구분하기!!

  • pass by reference의 효율성: 메모리 상의 위치(주솟값)를 통해 호출 객체에 접근할 수 있다. pass by ref.는 메모리 주소를 함수로 보내기 때문에 객체의 멤버 데이터들을 함수에 복사 할 필요가 없다.
  • 함수의 입력 파라미터에 &const를 동시에 걸어줬을 때: 함수는 실제 입력 변수를 수정하지 않는다. (메모리 복사도 일어나지 않고 값이 아무 때나 바뀌지 않음을 보장)
  • const 메소드: 멤버 함수의 타입을 const로 선언한다 → 해당 메소드가 멤버 변수의 값을 수정하지 않는다는 뜻!!)
    • ex) int get_numerator() const { return numerator; } 형태
    • const인 매개 변수는 const 메소드를 호출, 사용할 수 있다.
    • 리턴 타입이 const인 경우에도 또 const 메소드를 호출할 수 있다.

*) 1 example

class Point() {
private:
	int x;
	int y;
public:
	/* 생성자 생략 */
	int getX() const { return this->x; }
	int getY() const { return this->y; }
	void print() const { cout << this->x << ", " << this->y << endl; }
}

//1.
const Point mul(const Point &pt1, const Point &pt2){
	Point result(pt1.getX() * pt2.getX(), pt1.getY() * pt2.getY())
	return result;
}

/* main 내부 Point의 객체 pt1, pt2 (간략히) */
//2.
(mul(pt1, pt2)).print();
  • 위에서 const 메소드는 getX, getY, print 세 가지이다. → 세 함수 모두 객체의 멤버 변수를 수정하지 않음!!
  • mul 함수는 리턴 타입이 const Point객체 이다.
  • const인 매개 변수getX, getY라는 const메소드를 호출했다. (두 함수 모두 객체의 멤버 변수를 수정하지 않는다고 보장하니까)
  • mul함수의 반환 값const Point 객체print라는 const메소드를 호출했다.

결론:

  • 객체는 pass by ref.로 함수에 전달한다.
  • const를 사용하면 객체의 멤버 변수를 변경하지 않는다.
  • 이 때 const 메소드는 메소드 내부에서 멤버 변수를 변경하지 않는 뜻이다. (따라서 이 함수 내부에서는 멤버 변수의 값을 바꿀 수 없다.)

*) pass by reference 와 call by reference 차이

→ 둘 다 값을 복사하는 것이 아닌 '주솟값'을 사용한다는 동일한 개념. pass는 '인자'의 입장에서 '전달한다'는 의미이고, call은 '함수'의 입장에서 '호출한다'는 의미이다.

 

2. 객체에 대한 포인터

1. 객체 포인터->멤버 변수(or 함수)

*) 'Point class' example1

#include <iostream>
using namespace std;

class Point {
public:
	double x;
	double y;
};

Point 클래스의 멤버 변수 x, y는 public으로 선언되었다. → 클래스 외부 (ex) main함수) 에서도 접근 가능하다.

 

*) 복습

int main() {
	Point pt1, pt2; //Point 객체 인스턴스화
	pt1.x = 8.5;
	pt1.y = 0.0;
	pt2.x = -4;
	pt2.y = 2.5;
    
	//1.
	cout << "pt1 = (" << pt1.x << "," << pt1.y << ")\n";
	cout << "pt2 = (" << pt2.x << "," << pt2.y << ")\n";
    
	//2.
	pt1 = pt2;
	cout << "pt1 = (" << pt1.x << "," << pt1.y << ")\n";
	cout << "pt2 = (" << pt2.x << "," << pt2.y << ")\n";
    
	//3.
	pt1.x = 0;
	cout << "pt1 = (" << pt1.x << "," << pt1.y << ")\n";
	cout << "pt2 = (" << pt2.x << "," << pt2.y << ")\n";
    
}
  1. pt1과 pt2의 멤버 변수 x, y를 화면에 출력한다. pt1, pt2는 Point 클래스의 객체로, public인 멤버 변수 x와 y를 가지기 때문에 main함수에서도 pt1.x로 접근할 수 있다.  pt1.x = 8.5, pt1.y = 0.0  pt2.x = -4.0, pt2.y = 2.5 
  2. pt2를 pt1에 재할당한다. 즉 pt1.x = pt2.x; pt1.y = pt2.y; 를 수행한 것이다. 여기서 주의할 점은, pt1과 pt2는 alias(별명)관계가 아니라는 것이다!! (pass by ref. 하지 않음)  pt1.x = -4.0, pt1.y =  2.5  pt2.x = -4.0, pt2.y = 2.5 
  3. pt1.x에 0을 재할당하고 pt1, pt2를 출력해보면 pt1은 x = 0, y = 2.5 이지만 pt2는 여전히 x = -4, y = 2.5 이다.  pt1.x = 0.0, pt1.y = 2.5  /  pt2.x = -4.0, pt2.y = 2.5 

 

*) case 1. 객체에 대한 포인터

int main() {
	Point pt1;
	Point *pt2;
    
	pt2 = &pt1;
    
	pt1.x = 100.0;
	pt1.y = 200.0;
    
	cout << (*pt2).x << ":" << (*pt2).y;
}
  • pt2: Point 객체의 시작 주솟값을 저장하는 포인터 / Point 객체를 가리키는 포인터
  • &pt1: '&' 연산자로 pt1객체의 주솟값을 포인터 변수에 저장
  • *pt2: '*' 연산자로 pt2포인터가 가리키는 변수로 jump!

*) case 2. 객체에 대한 포인터 (new!!)(

int main() {
	Point pt1;
	Point *pt2;
    
	pt2 = &pt1;
    
	pt1.x = 100.0;
	pt1.y = 200.0;
    
	cout << pt2->x << ":" << pt2->y;
}
  • (*pt.2).x = 1.0pt2->x = 1.0 은 같은 의미!!

 

2. 포인터 동적 할당 (new, delete)

*) 'Point class' example2

#include <iostream>
using namespace std;

class Point {
private:
	int x;
	int y;
public:
	Point (int _x = 0, int _y = 0) :x(_x), y(_y) {}
	int getX() const { return this->x; }
	int getY() const { return this->y; }
	void setXY(int _x, int _y) { x = _x; y = _y; }
};
  • 멤버 변수 x, y가 private이므로 클래스 외부에서는 getX, getY라는 public 메소드로 접근할 수 있음
int main() {
	Point pt1(1, 2);

	Point* pPt = new Point(10, 20); //pointer: (.)대신 ->로 access
	cout << (*pPt).getX() << ", " << (*pPt).getY() << endl; //비추
	cout << pPt->getX() << ", " << pPt->getY() << endl; //추천
	cout << endl;

	delete pPt;

	return 0;
}
  • pPt는 Point객체를 가리키는 포인터 변수
  • new 키워드를 이용해 포인터 변수에 객체를 동적으로 할당해준다.
  • 객체 포인터 사용 시 (*)참조와 (.) 사용 대신 (->)로 액세스하는 걸 추천!
  • delete동적 할당을 해제해준다.
  • 동적으로 할당된 지역 변수는 반환(return)시 delete로 해제돼야 한다.

 

3. this 포인터

  • this 포인터: 현재 객체 자기 자신의 시작 주솟값을 저장하는 포인터 변수이다. (코드 상에서 좀 더 명확하게 표현하기 위함)
  • "현재 객체 자신의 주솟값"이란 메소드 작성 시 함수 내에서 사용자가 정의한 지역 변수 이외에 자동 생성되는 숨겨진 지역 변수(local variable)을 말한다.
  • 매개 변수 혼동을 방지하거나 특정 주소를 명확하게 포인팅 하기 위하는 등에 사용된다.

*) 2-3. example

class Point {
private:
	int x;
	int y;
public:
	void setX(int x) {
	this->x = x;
	}
};

 

3. 오버로딩

1. 함수 오버로딩

  • 같은 이름을 갖는 함수지만 입력 변수(입력 파라미타)를 다르게 설정할 수 있다.
  • 오버로딩 된 함수가 있다면 입력값에 맞는 함수가 호출된다.
  • 둘 이상의 사용자 정의 지정, 즉 오버로드가 가능하다.

*) print 함수: int형 변수를 출력 & Point 객체의 멤버 변수를 출력

void print(const Point &pt) { //객체라 call by ref.로, 수정 방지를 위해 const
	cout << pt.getX() << ", " << pt.getY() << endl;
}

void print(int a) {
	cout << a << endl;
}
  • [print]함수는 입력 값의 데이터 타입 (Point객체 or int형 정수) 에 따라 그에 맞는 print가 호출된다.
  • 여기서 getX, getY는 Point객체의 멤버 데이터 x, y를 반환하는 메소드이다. (public으로 선언)

2. 연산자 오버로딩

Point 객체끼리 연산을 하고싶을 때 → 특정 객체에 대해 동작하는 연산자를 정의할 수 있다. (*연산자도 모두 함수이다!)

*)모양: [리턴타입] operator[오버로딩 할 연산자] (피연산자1, 피연산자2, ...) { return 리턴타입에 맞는 지역 변수; }

 

*)클래스 외부 내부에서 연산자 오버로딩의 방식은 동일하나(함수 body부분 유사), 동작하는 매커니즘은 다름!

  • 클래스 외부:  +(pt1, pt2)->Point  +라는 함수, 입력값(피연산자)은 pt1, pt2 두 개, 리턴 타입은 Point 객체
  • 클래스 내부 pt1.+(pt2)->Point  +라는 메소드, pt1도 Point 객체니까 +메소드를 호출 가능(pt1.+), 메소드의 입력값은 pt2 한 개, 리턴 타입은 Point 객체

 

*) Point 객체 사이의 덧셈 연산: Point 클래스 외부에서  +(pt1, pt2)

Point operator+(const Point& pt1, const Point& pt2) {
	Point result(pt1.getX() + pt2.getX(), pt1.getY() + pt2.getY());
	return result;
}

 

*) Point 객체 사이의 덧셈 연산: Point 클래스 내부에서  pt1.+(pt2)

(위 예제에서 좌측 피연산자가 자기 자신일 때 클래스의 함수=메소드로 만듦)

Point operator+(const Point& pt) {
	Point result(this->x + pt.x, this->y + pt.y);
	return result;
}

 

 

*) cout << 에서 '<<' 연산자 오버로딩  <<(cout, pt)

ostream& operator<<(ostream& cout, const Point& pt) {
	cout << pt.getX() << ", " << pt.getY();
	return cout;
}
  • return by reference: ostream& operator<<(ostream& cout, const Point& pt)
  • pass by reference: ostream& operator<<(ostream& cout, const Point& pt)
    • → 입력받은 cout과 출력하는 cout이 완전히 동일하도록!!!
  • <<로 읽고 cout에 저장, 또 <<로 읽고 cout에 저장 반복... → 마지막에 cout으로 읽었던 내용 화면으로 출력
  • ostream객체: 파일을 읽거나 쓸 때 어디까지 읽고 썼는지 저장하는 '포인터'가 있다. → 내부적으로 읽은 위치를 계속해서 업데이트, 트래킹

 

4. static 멤버 변수, 함수

  • 모든 객체가 공유하고 접근할 수 있는 데이터/함수이다.
  • 각각의 객체가 만들어질 때 static으로 선언된 멤버 변수는 초기화 불가! → 클래스 외부에서 초기화해야 함
  • 클래스 외부이기 때문에 초기화 시 '::' 로 범위 지정해주기!!
class Point {
private:
	int x;
	int y;
	static int numCreatedObjects; //만들어진 객체 개수
public:
	Point (int _x = 0, int _y = 0) :x(_x), y(_y) {
		numCreatedObjects++; //객체가 만들어질 때마다 count
	}
	static int getNumCreatedObject() { return numCreatedObjects };
};

//static 멤버 변수 초기화
int Point::numCreatedObjects = 0;

 

5. Friend 관계

  • 어떤 한 클래스(A)가 클래스 외부의 정보(B)를 Friend 관계라고 인정하면 B는 A의 모든 정보(private까지 모두) 접근, 사용이 가능하다.
  • 사용: A클래스 내에서 함수, 연산자(오버로딩), 클래스를 친구로 인정할 때 앞에 friend 키워드를 붙이기!

*) 1.~5. example

#include <iostream>
using namespace std;

class Point {
private:
	int x;
	int y;
	static int numCreatedObjects; //만들어진 객체 개수
public:
	Point (int _x = 0, int _y = 0) :x(_x), y(_y) {
		numCreatedObjects++;
	}
	int getX() const { return this->x; }
	int getY() const { return this->y; }
	void setXY(int _x, int _y) { x = _x; y = _y; }

	//pt2.+(pt3) == pt2 + pt3
	Point operator+(const Point& pt) {
		Point result(this->x + pt.x, this->y + pt.y);
		return result;
	}

	//a = b = c = d; 왼<-오 로 연속적으로 계산 가능
	Point operator=(const Point& pt) {
		this->x = pt.x;
		this->y = pt.y;
		return *(this);
	}

	static int getNumCreatedObject() { return numCreatedObjects };
	friend void print(const Point& pt);
	friend ostream& operator<<(ostream& cout, const Point& pt);
	friend class Spy; //Point class는 Spy를 친구로 인정
};

//static 멤버 변수 초기화
int Point::numCreatedObjects = 0;

void print(const Point& pt)
{
	cout << pt.x << ", " << pt.y << endl;
}

void print(int a) {
	cout << a << endl;
}

ostream& operator<<(ostream& cout, const Point& pt) {
	cout << pt.x << ", " << pt.y;
	return cout;
}

class Spy {
public:
	void hack_all_info(const Point& pt) {
		cout << "Hacked by Spy" << endl;
		cout << "x: " << pt.x << endl;
		cout << "y: " << pt.y << endl;
		cout << "numCreatedObj: " << pt.numCreatedObjects << endl;
		cout << endl;
	}
};

int main() {
	//pass by ref와 const
	Point pt1(1, 2);
	print(pt1);
	cout << pt1.getNumCreatedObject() << endl;
	cout << endl;

	//포인터와 동적 할당
	Point* pPt = new Point(10, 20); //pointer: (.)대신 ->로 access
	cout << (*pPt).getX() << ", " << (*pPt).getY() << endl; //비추
	cout << pPt->getX() << ", " << pPt->getY() << endl; //추천
	cout << pt1.getNumCreatedObject() << endl;
	cout << endl;

	//연산자 오버로딩(연산자도 모두 함수임)
	int a = 2 + 3; //+(2, 3)
	Point pt2(10, 20), pt3(30, 40);
	Point pt4 = pt2 + pt3;
	cout << pt2 << endl;
	cout << pt3 << endl; 
	cout << pt4 << endl;
	cout << pt1.getNumCreatedObject() << endl;
	cout << endl;

	//friend ft
	Spy spy;
	spy.hack_all_info(pt1);
	spy.hack_all_info(pt4);

	return 0;
}
  • 여기서 Spy 클래스의 hack_all_info 메소드의 body를 보면 pt.x, pt.y, pt.numCreatedObjects 와 같이 Point에서 private으로 선언된 멤버 변수들에도 접근할 수 있는 것을 볼 수 있다! (public인 get메소드를 쓰지 않아도 됨)
  • <<연산자 오버로딩에서도 getX, getY 대신 pt.x, pt.y로 Point의 멤버 변수에 접근한다.
  • Friend는 단방향 관계이기 때문에 Point가 Spy 클래스를 friend 선언해도 Point가 Spy 클래스의 private 정보까지 접근할 수 있는 것은 아님! (Spy가 Point의 private 접근은 가능)

 

6. 소멸자(Destructor)

객체가 할당 해제될 때 (메모리에서 없어질 때) 한 번 자동으로 호출되는 함수, 객체가 lifetime동안 획득했을 수 있는 리소스를 해제함

모양(입력값 필요 x, 리턴값 x): ~[클래스이름]() { body } 

 

*) 6. example

class Point {
private:
	int x;
	int y;
	static int numCreatedObject;
	int* ary;
public:
	Point(int _x = 0, int _y = 0) :x(_x), y(_y) 
	{ 
		numCreatedObject++;
		ary = new int[10]; //dynamic array 할당
	}

	~Point() {
		cout << "destructed" << endl;
		delete[] ary; //동적 할당시 반드시 해제!!
	}
};
  • ary 멤버 변수: 아래 생성자에서 동적 배열 할당됨 → int* ary = new int[10]; (크기 10인 동적 배열)

*) 전체 내용 example

#include <iostream>
using namespace std;

class Point {
private:
	int x;
	int y;
	static int numCreatedObject;
	int* ary;
public:
	Point(int _x = 0, int _y = 0) :x(_x), y(_y) 
	{ 
		numCreatedObject++;
		ary = new int[10];
	}

	~Point() {
		cout << "destructed" << endl;
		delete[] ary; //동적 할당 후 반드시 해제
	}

	int getX() const { return this->x; }
	int getY() const { return this->y; }
	void setXY(int _x, int _y) { this->x = _x; this->y = _y; }
	static int getNumCreatedObj() { return numCreatedObject; }

	Point operator+(const Point& pt) const {
		Point result(this->x + pt.x, this->y + pt.y);
		return result;
	}

	Point operator=(Point pt) {
		this->x = pt.x;
		this->y = pt.y;
//		this->ary = pt.ary; X->주솟값만 복사->pt (pt 사라지면 ary에는 쓰레기 값이 들어감)
//		for (int i = 0; i < 10; i++)
//			ary[i] = pt.ary[i]; ->다음과 같이 deep copy가 필요
		return (*this);
	}

	friend ostream& operator<<(ostream& cout, const Point& pt);
	friend class SpyPoint;
};

int Point::numCreatedObject = 0;

ostream& operator<<(ostream& cout, const Point& pt) {
	cout << pt.getX() << ", " << pt.getY();
	return cout;
}

class SpyPoint {
public:
	void hack_point_info(const Point& pt) {
//		cout << pt.getX() << ", " << pt.getY() << endl; 아직 친구 아닐 땐 Point의 pubic만
		cout << pt.x << ", " << pt.y << endl;
	}
};

int main() {
	Point pt1(1, 2), pt2(10, 20); 
	Point pt3 = pt1 + pt2;
	cout << pt3 << endl;
	// 1) (cout << pt3)->cout 수행하고 cout을 return 한 다음에야
	// 2) cout << endl; endl수행 가능
	cout << pt3.getNumCreatedObj() << endl; //3
	cout << endl;

	//friend ft/class
	SpyPoint spy;
	spy.hack_point_info(pt1);
	spy.hack_point_info(pt2);
	cout << pt3.getNumCreatedObj() << endl; //3
	cout << endl;

	//object array
	//Point ptAry[5]; :정적할당
	//동적 할당&해제 (동적으로 할당했으면 delete로 해제)
	int size = 5;
	Point* ptAry = new Point[size]; //Point 객체의 시작주솟값 저장하는 포인터변수 ptAry
	for (int i = 0; i < size; i++)
		cout << ptAry[i] << endl;
	delete[] ptAry;
	cout << pt1.getNumCreatedObj() << endl; //8

	return 0;
}
  • *)주의: 멤버 데이터에 [배열]이 있을 때 =(대입)연산자 오버로딩 시 this->ary = pt.ary 만 하면 배열의 주솟값만 복사되어 전달됨! (만약 pt가 사라지면 =으로 대입한 객체의 ary에는 쓰레기 값이 들어가므로) → 모든 인덱스[i]마다 일일이 전달해 넣어줘야 함 (deep copy)

 


 

*)sample codes: https://github.com/halterman/CppBook-SourceCode

반응형

'코딩 > 객체지향프로그래밍' 카테고리의 다른 글

[C++] 상속  (0) 2020.08.06
[C++] 클래스와 객체 (1)  (0) 2020.08.02
[C++] 포인터, 벡터, 배열 (3)  (0) 2020.07.30
[C++] 포인터, 벡터, 배열 (2)  (0) 2020.07.29
[C++] 포인터, 벡터, 배열 (1)  (0) 2020.07.27