연재 완료/C++ Lang 이론

7. 클래스의 활용

라이피 (Lypi) 2018. 6. 27. 19:05
반응형

PART 2. 객체 지향 프로그래밍

  - chapter 7. 클래스의 활용


1. 객체 포인터 (Object Pointer)

  - 객체도 일반 변수처럼 정적 메모리 할당이나 동적 메모리 할당을 할 수 있다.

  - 정적 생성된 객체에 접근할 때는 .(dot) 연산자를 사용하고, 동적 생성된 객체에 접근할 때는 ->(Arrow)연산자를 사용한다.

 

2. this 포인터 (this Pointer)

  - 자기 자신을 가리키는 객체 포인터.

  - this는 C++의 예약어이므로 식별자로 사용할 수 없으며, 따로 선언하지 않아도 자동으로 생성된다.

  - 멤버 변수의 이름과 멤버 함수의 매개 변수의 이름이 동일한 경우 멤버 변수의 이름 앞에 'this->'를 붙여야한다.

  - 그 외에는 함수의 매개변수나 반환값으로 자기자신을 사용할 때 등의 경우에 사용된다.


3. const 한정자 (const modifier)

  - const 멤버 변수 (const member variable) : 한번 초기화하면 이후 값을 변경할 수 없는 매개 변수.

  - 멤버 초기화 목록을 사용해서 초기화시킨다.


  - const 멤버 함수 (const member function) : 멤버 변수의 값을 변경시킬 수 없는 멤버 함수.

  - 비 멤버 함수에는 한정자를 붙일 수 없다. 

  - const 멤버 함수내에서 선언한 변수의 값은 변경할 수 있다.

  - const 멤버 함수와 일반 멤버 함수는 중복 정의가 가능하다.

  - 중복 정의시에는 const 객체가 호출하는 함수는 const 멤버 함수가 되고, 일반 객체가 호출하는 함수는 일반 멤버 함수가 된다.


  - const 객체 (const object) : 멤버 변수의 값을 변경할 수 없는 객체.

  - 객체를 생성할 때 class name 앞에 const를 붙인다. 

  - const 객체로는 const 멤버 함수만 호출 할 수 있다.

  - 함수의 매개 변수로 객체 참조자를 사용하는 경우에 객체 참조자 앞에 const를 붙이는 경우가 많다.


4. 객체와 연산자의 관계 (Relation with object and function)

  - 객체가 함수의 매개변수로 전달되는 경우

  - 객체 포인터가 함수의 매개변수로 전달되는 경우

  - 객체 참조자가 함수의 매개변수로 전달되는 경우


  - 함수가 객체를 반환하는 경우

  - 함수가 객체 포인터를 반환하는 경우

  - 함수가 객체 참조자를 반환하는 경우


  - ...


5. 임시 객체 (temporary object)

  - 임시객체는 수식의 중간 계산 결과를 저장하거나 함수가 객체를 반환하는 등의 경우에 생성된다.

  - 임시 객체는 주소값을 따로 저장하지 않으면 다음 문장으로 넘어갈 때 소멸된다.


6. 정적 멤버 (static member)

  - 정적 멤버 변수 (static member variable) = 전역 멤버 변수 : class의 모든 객체가 공유하는 변수.

  - 정적 멤버 변수는 전역 변수처럼 사용되지만 사용 범위가 해당 클래스 내부로 제한된다.

  - 특정 객체의 소속이 아니므로 객체를 생성하지 않아도 사용할 수 있다.

  - static멤버는 class 내부에서 초기값을 줄 수 없으므로 class 외부에서 초기화를 시켜줘야 한다.

  - 멤버 함수내에서 값을 변경하는 것도 가능하나 생성자 등에서 초기화하면 매번 초기화되므로 의미가 없다.


  - 정적 멤버 함수 (static member function) = 전역 멤버 함수 : class의 모든 객체가 공유하는 함수.

  - 정적 멤버 함수도 특정 객체의 소속이 아니므로 객체를 생성하지 않아도 호출 할 수 있다.

  - 정적 멤버 함수는 객체가 생성되기 전에 이미 메모리를 할당 받음으로 일반 멤버 변수나 일반 멤버 함수, this 포인터를 사용할 수 없다.

  - 대신 정적 변수와 지역 변수, 정적 함수는 사용 가능하다.

  

  - 정적 상수 멤버 변수 (const static member variable) : class의 모든 객체가 공유하는 상수.

  - 상수는 어차피 class의 모든 객체가 사용하는 변하지 않는 값이므로 정적 상수 변수로 선언하면 메모리를 아낄 수 있다.

  - 정수형 정적 상수 멤버 변수는 클래스 내부에서 초기값을 줄 수 있다.

  - 정수형이 아닌 경우 멤버 초기화 목록을 사용해서 초기값을 줘야한다.


7. 객체 배열(object array)

  - 객체도 일반 변수처럼 배열을 만들 수 있다.

  - 초기화를 할 때는 생성자를 호출해서 초기화한다.

  

  - 배열의 이름은 포인터 첫번째 요소를 가리키는 포인터 상수이다.

  - 따라서 객체 배열의 이름은 객체 배열의 첫번째 객체에 대한 포인터와 같다.

 

8. 클래스와 클래스 간의 관계 (relate with class and other class)

  - 사용(use) : 하나의 클래스가 다른 클래스를 사용한다.

  - A클래스의 멤버 함수에서 B클래스의 멤버 함수들을 호출하는 관계. 그러므로 A클래스에서 B클래스의 객체를 갖고 있어야 한다. (포함)


  - 포함(has-a) : 하나의 클래스가 다른 클래스를 포함한다.

  - A클래스가 다른 클래스(들)을 갖고 있는 관계. 


  - 상속(is-a) : 하나의 클래스가 다른 클래스를 상속한다.

  - 다음 장에서 설명.


car.h

#pragma once

#include <iostream>
#include <string>

using std::cout;
using std::cin;
using std::endl;
using std::string;

class Car
{
protected:
	int speed;
	int gear;
	string color;

public:
	int pub;

	void print() const;
//	void print();


	int getSpeed() { return speed; }
	void setSpeed(int speed);
	void isFaster(Car* p);

	Car(int s = 0, int g = 1, string c = "white");
	~Car();
};

class SerialCar : public Car
{
	const int serial;		//const 멤버 변수

public:
	int temp;	//const 객체 실험용

	int getSerial() const { return serial; }
	void print() const;		//const 멤버 함수
	SerialCar(int Num, int s = 0, int g = 1, string c = "white");
};

class Temp
{
	int t;
public:
	Temp(int n) 
	{
		t = n;
		cout << "temp 생성자 호출 값은 " << t << endl; 
	}
};

class StaticClass
{
	static int n;
	// static int n2 = 100; //불가


public:

	//정수형 정적 상수 변수는 in-class initializer가 가능
	static const int R = 1000;
	//static const double d = 100.23; //불가

	static void showN();
	//static void showR() const;	//에러! 정적 멤버 함수에서 형식 한정자를 사용할 수 없습니다.

	StaticClass();
	~StaticClass();
};

class useclass
{
	Car useCar;

public:
	void useT() { useCar.print(); }
};

car.cpp

#include "Car.h"



void Car::print() const
{
	cout << "속도 : " << speed << " 기어 : " << gear << " 색상 : " << color << endl;
}
//
//void Car::print()
//{
//	cout << "오버로드된 print 함수" << endl;
//}

void Car::setSpeed(int speed)
{
	if (speed > 0) {
		this->speed = speed;		
	// this->speed는 멤버 변수, speed는 매개 변수
	}
	else {
		this->speed = 0;
	}
}

void Car::isFaster(Car* p)
{
	if (this->getSpeed() > p->getSpeed()) {
	//현재 객체의 멤버 함수 > 매개 변수의 객체 함수
		cout << this->color;
		//현재 객체의 멤버 변수
	}
	else {
		cout << p->color;
		//매개 변수의 멤버 변수
	}

	cout << " color 자동차가 더 빠름" << endl;
}

Car::Car(int s, int g, string c)
{
	cout << "생성자 호출" << endl;
	speed = s;
	gear = g;
	color = c;
}

Car::~Car()
{
	cout << "소멸자 호출" << endl;
}

void SerialCar::print() const	
{
	cout << "시리얼 넘버" << serial;
	Car::print(); //const 함수 내에서는 const 함수만 호출 가능하다.
	
	//speed = 10; //const 함수 내에서 변수의 값 변경 불가

	//setSpeed(50); //const가 아닌 함수 사용 불가(설정자)

	//cout << getSpeed() << endl; // 값을 변경하지 않는 함수여도 const 함수가 아니면 사용할 수 없다. 
	cout << getSerial() << endl;	//그러므로 접근자의 경우에는 const 함수로 만드는 것이 일반적이다.
	
}

SerialCar::SerialCar(int Num, int s, int g, string c) : Car(s, g, c), serial(Num)
{}

int StaticClass::n = 1;

void StaticClass::showN()
{
	cout << n << endl;
}


StaticClass::StaticClass()
{
	n++;
}

StaticClass::~StaticClass()
{
	n--;
}

main.cpp

#include "Car.h"

void swapObject1(Car c1, Car c2);
void swapObject2(Car* c1, Car* c2);
void swapObject3(Car& c1, Car& c2);
void swapObject4(Car* c1, Car& c2);

Car returnObject1();


int main()
{
	cout << "객체의 정적 생성과 동적 생성" << endl;
	Car myCar;							// 객체의 정적 생성 (기본 생성자로 초기화)
	myCar.print();						// 정적 생성된 객체의 메소드 호출
	Car* pCar = new Car(60, 3, "black");	// 객체의 동적 생성
	pCar->print();							// 동적 생성된 객체의 메소드 호출

	Car c1(0, 1, "blue");
	Car c2(100, 3, "red");
	c1.isFaster(&c2);		//정적 생성된 객체의 주소값을 받기 위해서 &연산자 사용
	myCar.isFaster(pCar);	//동적 생성된 객체는 &연산자가 필요하지 않음

	
	

	cout << endl << "일반 객체와 const 객체" << endl;
	SerialCar sc(0000);			//일반 객체
	const SerialCar csc(0001);	//const 객체
								
	//const 객체로는 public 멤버 변수의 값도 변경할 수 없다.
	sc.temp = 10;			
	//csc.temp = 10;		

	//const 객체로는 const 함수만 호출 가능하다.
	//cout << csc.getSpeed();		
	cout << csc.getSerial();
//	csc.print();


	cout << endl << "디폴트 대입 연산자에 의한 객체 대입" << endl;
	//객체끼리의 대입은 따로 연산자 재정의를 하지 않아도 디폴트 대입 연산자에 의해서 가능하다.
	c1 = myCar;
	c1.print();
	myCar.print();

	cout << endl << "객체를 함수의 매개변수로 전달하는 경우" << endl;
	cout << endl;
	swapObject1(c1, c2);
	cout << "swapObject1 밖에서 호출" << endl;
	cout << "c1 : ";
	c1.print();
	cout << "c2 : ";
	c2.print();

	cout << endl;
	swapObject2(&c1, &c2);
	cout << "swapObject2 밖에서 호출" << endl;
	cout << "c1 : ";
	c1.print();
	cout << "c2 : ";
	c2.print();

	cout << endl;
	swapObject3(c1, c2);
	cout << "swapObject3 밖에서 호출" << endl;
	cout << "c1 : ";
	c1.print();
	cout << "c2 : "; 
	c2.print();

	cout << endl;
	swapObject4(&c1, c2);
	cout << "swapObject4 밖에서 호출" << endl;
	cout << "c1 : ";
	c1.print();
	cout << "c2 : ";
	c2.print();


	cout << endl << "임시 객체" << endl;

	string s1 = "temporary ";
	string s2 = "object";
	const char* sp = (s1 + s2).c_str();	// .c_str() : string의 첫번째 문자의 주소를 반환한다.
	cout << "sp : " << sp << endl;		// sp는 임시 객체의 시작 주소를 가지고 있으므로 이 문장에서는 이미 사라진 주소를 가지고 있는 셈이다.
	// 쓰레기 값이 출력됨

	string s3;
	s3 = s1 + s2;			// 대입 연산자는 임시객체의 값을 복사한다.
	sp = s3.c_str();		// 여기서는 사라진 임시 객체가 아니라 새롭게 생성된 s3의 주소를 가지고 출력하는 셈이다.
	cout << "sp : " << sp << endl;

	Car(10, 2, "wow");		// 명시적으로 생성한 임시객체. 이 문장이 끝나면 사라진다.

	cout << endl << "임시 객체를 참조자에 저장" << endl;
	//Car& rc = Car(10, 5, "rc");		// 컴파일 에러! 비const 참조에 대한 초기 값은 lvalue여야 합니다.
	{
		const Car& rc = Car(10, 5, "rc");	// 임시객체를 참조자로 저장. 참조자가 존재하는 동안에는 사라지지 않는다. (
		rc.print();
	}
	//rc.print(); //범위를 벗어나면 참조자도 소멸된다.

	Temp(3);					// 임시 객체를 생성할 때는 생성자가 호출된다.
	Temp tempC = Temp(5);		// 임시 객체를 객체 변수에 복사한다는 의미.	
	//(객체 변수라는 표현이 생소한데 그냥 그 객체를 받을 수 있는 공간이 만들어진 것으로 보임, 생성자 호출 X)

	cout << endl << "함수가 객체를 반환하는 경우" << endl;
	returnObject1().print();			// 반환된 임시 객체를 통해 멤버 함수 호출
	//임시 객체의 포인터나 참조를 반환하는 경우는 어차피 의미가 없으므로 생략.

	cout << endl << "정적 멤버 변수와 정적 멤버 함수" << endl;
	
	//정적 멤버 변수나 함수는 객체 생성 전에도 호출 가능
	cout << StaticClass::R << endl;
	StaticClass::showN();

	StaticClass sca;
	StaticClass scb;
	StaticClass scc;

	//정적 멤버 변수이므로 값은 모두 같다.
	sca.showN();
	scc.showN();

	//scope를 벗어난 변수도 사라지긴 하는데 아무래도 동적 할당, 해제 한 수를 세는게 일반적이다.
	StaticClass* scp = new StaticClass;
	StaticClass::showN();

	delete scp;
	StaticClass::showN();

	cout << endl << "객체 배열" << endl;
	
	//객체 배열 선언과 초기화 (생성자를 호출해서 초기값을 준다. 각각 다른 생성자를 사용할 수도 있다.)
	Car objArray[3] = { Car(1000,2,"obj"),Car(200) };
	
	objArray[0].print();
	objArray[1].print(); 
	objArray[2].print(); //초기값을 주지 않은 객체 배열의 멤버는 디폴트 생성자로 초기화 된다.

	//객체 배열을 이용한 멤버 함수 호출 
	cout << "objArray->speed : " << objArray->getSpeed() << endl;

	//객체 배열을 이용한 멤버 변수 접근
	objArray[1].pub = 1;
	cout << "(objArray+1)->pub : " << (objArray+1)->pub << endl;
	//+연산이 ->연산보다 먼저 실행되어야 하므로 괄호가 꼭 필요하다. 
	//*를 안 붙이는 이유는 아직 정리가 안되므로 패스
	

	cout << endl << "객체 배열과 포인터" << endl;
	//배열의 이름은 포인터이다.
	cout << objArray->getSpeed() << endl;
	cout << (objArray+1)->getSpeed() << endl;	//*()을 쓰지 않는다.


	

	cout << endl << "클래스와 클래스 간의 관계" << endl;
	useclass use, has_a;
	SerialCar is_a(0000);

	//사용을 하려면 포함을 하고 있을 수 밖에 없다.
	cout << "사용 관계(use) : ";
	use.useT();

	cout << "포함 관계(has-a) : ";
	has_a.useT();

	cout << "상속 관계(is-a) : ";
	cout << is_a.getSpeed();
}


//함수의 인자로 객체를 전달 할 수 있다. 
//물론 그냥 전달하면 call by value이므로 값이 복사된다.
//즉 실제 값은 변경되지 않는다.
void swapObject1(Car c1, Car c2)
{
	Car temp;
	temp = c1;
	c1 = c2;
	c2 = temp;

	cout << "swapObject1 안에서 호출" << endl;
	cout << "c1 : ";
	c1.print();
	cout << "c2 : ";
	c2.print();
}

//함수의 인자로 객체의 포인터도 전달 할 수 있다.
//포인터로 전달하면 주소가 전달되므로 call by reference이다.
//즉 실제 값을 변경할 수 있다.
//다만 *연산자를 사용하여야 하므로 조금 번거롭다.
void swapObject2(Car* c1, Car* c2)
{
	Car temp;
	temp = *c1;
	*c1 = *c2;
	*c2 = temp;

	cout << "swapObject2 안에서 호출" << endl;
	cout << "c1 : ";
	(*c1).print();
	cout << "c2 : ";
	(*c2).print();
}

//함수의 인자로 객체의 참조자를 전달 할 수 있다.
//참조자로 전달하면 call by reference이므로 값을 변경할 수 있다.
//포인터 변수를 만들지도 않고, 안에서 *연산자도 쓰지 않으므로 C++에서는 가장 많이 쓰이는 방식이다.
void swapObject3(Car& c1, Car& c2)
{
	Car temp;
	temp = c1;
	c1 = c2;
	c2 = temp;

	cout << "swapObject3 안에서 호출" << endl;
	cout << "c1 : ";
	c1.print();
	cout << "c2 : ";
	c2.print();
}

//참조자와 레퍼런스를 같이 쓸 수도 있다.
void swapObject4(Car* c1, Car& c2)
{
	Car temp;
	temp = *c1;
	*c1 = c2;
	c2 = temp;

	cout << "swapObject4 안에서 호출" << endl;
	cout << "c1 : ";
	(*c1).print();
	cout << "c2 : ";
	c2.print();
}

Car returnObject1()
{
	Car temp(100, 3, "ro1");
	return temp;
}



반응형