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";
}
- 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
- 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
- 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.0 과 pt2->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 |