C++/C++

10. 객체와 클래스

메카인 2023. 7. 17. 21:43

◆OOP

  • 프로그래머는 객체를 서술하는 데 필요한 데이터와, 사용자가 그 데이터를 다루는 방법에 대해서 생각하면서, 사용자가 이해하는 방식대로 객체에 초점을 맞춘다.

 

◆데이터 은닉(data hiding)

  • 프로그램이 직접 데이터에 접근하지 못하게 차단하는 것

 

◆캡슐화(encapsulation)

●세부적인 구현들을 따로 결합하여 추상화화 분리하는 것이다.

  • 데이터 은닉은 캡슐화의 한 예이다.
  • 또 다른 예는 클래스 함수들의 정의를 클래스 선언과 독립된 파일에 넣는 것이다.

 

◆클래스 

◆클래스 설계

  • public 인터페이스와 세부적인 구현을 private에 분리하려고 노력한다.
  • public 인터페이스는 설계의 추상화를 나타낸다.

 

◆클래스의  접근제한자

●private

  • 클래스 내부에서만 접근할 수 있다.
  • 클래스 객체에 대한 디폴트 접근 제어이다.

●public

  • 어디에서든 접근할 수 있다.
  • 구조체 객체에 대한 디폴트 접근 데어이다.

◆클래스 멤버 메서드의 정의

  • 함수머리를 추가하여 일반 함수처럼 정의한다.

●함수머리(function heading)

  • 함수가 어느 클래스에 속하는지 나타내기 위해서 사용 범위 결정 연산자(::)를 사용한다.
void Cat::cry();
  • cry()가 클래스 사용 범위(class scope)를 가지고 있다.
  • ※해당 함수가 클래스 안에서 쓰일때는, 함수 머리 없이 사용할 수 있다.

 

◆클래스 멤버 메서드 정의하는 법

  • 메서드 정의는 클래스 선언과 같은 파일에 또는 독립된 파일에 넣을 수 있다.
  • 선언은 헤더에서, 정의는 메서드를 사용하는 파일에서 해주는것을 권장한다.

 

◆인라인 메서드

  • 클래스 선언 안에 정의를 가지고 있는 모든 함수는 자동으로 인라인 함수가 된다.
  • 원한다면 함수를 정의할 때  inline이라는 제한자를 앞에 붙여서 외부에 멤버 함수를 인라인 선언할 수 있다.
class Cat{
	void cry();
    }

inline void Cat::cry(){
	cout<<"Meow";
}

 

  • ※코드 수정 규칙(rewrite rule)에 따르면, 클래스 선언 안에 메서드를 정의하는 것은 그 메서드 정의를 원형으로 대체하고, 클래스 선언 바로 뒤에 그 메서드 정의를 인라인 함수로 다시 작성하는 것과 같다.

 

●각각 선언한 클래스를 통해 메서드를 호출하는 것은 각각의 멤버 변수를 인자로 같은 하나의 멤버 메서드를 호출하여 두개의 객체에 적용한다는 것이다.

●멤버 함수를 호출한다는 말은, 다른 OOP 언어에서 메시지를 보낸다는 말과 같은 뜻이다.

 

 

◆클라이언트-서버 모델(OOP)

  • 클라이언트는 클래스를 사용하는 프로그램이고, 서버는 그것을 필요호 하는 프로그램들이 사용할 수 있는 리소스이다.
  • 이것은, 클라이언트의  행동에 예측할 수 없는 영향을 일으키는 변화를 서버에 가져오지 않고, 클라이언트와 서버의 기능을 프로그래머들이 서로 독립적으로 개선하는 것을 허용한다.

 


◆플래그를 고정 소수점 표기를 사용하기 위해 cout 개체 안에 플래그를 세팅하는 코드.

std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);

◆cout으로 소수점 이하 3 자리까지 보여 주도록 하는 코드

std::cout.precision(3);

◆원본 클래그를 저장하고 리셋하는 코드

std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed);
std::cout.setf(orig, std::ios_base::floatfield);

◆클래스의 생성자

  • C++는 클래스 객체를 표준 데이터형을 사용하듯이 사용할 수 있게 만드는 것이 목표인데, 멤버들이 private로 정의되기 때문에 초기화를 실현하는데에 어려움을 겪는다.
  • 일반적으로, 모든 객체는 그것을 생성할 때 초기화하는 것이 가장 바람직함으로 C++는 새로운 객체를 생성하고 그들의 데이터 멤버에 값을 대입해 주는 클래스 생성자(class constructor)라는 특별한 멤버 함수를 제공한다.
  • 생성자의 이름은 클래스의 이름과 같다.
  • 생성자는 리턴값이 없는데도 불구하고 void형으로 선언하지 않는다.(데이터 형을 선언하지 않는다.)
//생성자 정의
Cat::Cat(const char & name,int & age){
	catName=name;
    catAge=age;
}

 

◆멤버 이름과 매개변수 이름

  • 멤버 이름과 매개변수의 이름은 구분을 위해서 달라야한다.
  • 일반적으로 매개변수에 m_ 접두사를 사용하거나 _ 접미사를 사용한다.
class Cat{
	char * name;
    int * age;
	Cat::Cat(const char & m_name,int & age_ ){
    	name = m_name;
        age = age_;
    }
}

 

◆생성자 사용하기 

●C++는 생성자를 이용하여 객체를 초기화하는 두 가지 방법을 제공한다.

1. 생성자를 명시적으로 호출

Cat black = Cat("black", 1);

2. 생성자를 암시적으로 호출

Cat black("black", 1);

+ new를 사용해 생성자를 사용하는 방법

Cat *gray = new Cat("gray", 2);

 

◆디폴트 생성자(default constructor)

●명시적인 초기화 값을 제공하지 않을 때 객체를 생성하는 데 사용하는 생성자이다. 

Cat red;	//디폴트 생성자를 사용한다.

Cat::Cat(){}을 호출한 것과 같다.
  • ※사용자가 어떠한 생성자도 정의하지 않을 경우에만 컴파일러가 디폴트 생성자를 제공함으로, 생성자를 만들었다면 디폴트 생성자의 책임이 사용자에게 넘어옴으로 유의해야 한다.

 

●디폴트 생성자를 정의하는 두가지 방법

1. 기존의 생성자에 있는 모든 매개변수에 디폴트 값을 제공

Cat(const char & name = "NONAME", int & age = 0);

2. 함수 오버로딩을 사용하여 매개변수가 없는 또 하나의 생성자를 정의

Cat(){}
  • ※단, 사용자는 하나의 디폴트 생성자만 가질 수 있다.
  • ※디폴트 생성자를 암시적으로 호출할 때는 괄호를 사용하면 안된다.

●아래는 black()[메서드,함수]가 Cat객체를 리턴하는 함수임을 나타내는 것이다.

Cat black();

 

◆파괴자(destructor)

  • 객체의 수명이 끝나는 시점에서, 프로그램은 파괴자라는 특별함 멤버함수를 자동으로 호출하여 메모리를 해제한다.
  • 파괴자는 앞에 틸데(~)가 붙은 클래스 이름으로부터 만들어진다.
Cat::~Cat(){}
  • 파괴자는 파괴하는 것 외에 할일이 없으면 하는일이 없는 코드로 작성한다.
  • new를 사용해 객체를 생성한다면, 이는 힙 메모리 또는 자유 기억 공간에 저장되므로, 그것의 메모리를 해제하기 위해 delete를 사용할 때 파괴자가 자동으로 호출된다.

 

●어떤 작업을 수행하기 위해 임시적인 객체를 생성할 수 있다. 이때 파괴자는 임시적인 객체를 삭제하는데 사용된다.

Cat gray;	//암시적 선언
gray = Cat("gray", 2);
  • 오른쪽의 Cat("gray", 2)은 rvalue임으로 gray에 복사를 하고 파괴자를 호출한다.

 

★객체의 값을 초기화로도 설정할 수 있고 대입으로도 설정할 수 있다면, 초기화를 사용하라, 그것이 더 효율적이다.

 

◆리스트 초기화

  • C++는 중괄호 안의 값들과 생성자의 매개변수들을 매칭시키는 것을 제공한다.
Cat black = {"black",1};
Cat white{"white"};	//디폴트 나이 0으로 초기화

 

◆const 멤버 함수

  • 클래스에 const를 작성하여도 내부 멤버 변수는 const가 아님으로 변할 수 있다.
  • 이것을 해결하는 방법은 함수 괄호 뒤에 const를 집어 넣는것이다. (이것이 const 멤버 함수이다.)
  • const 멤버 함수는 호출 객체를 변경할 수 없다.
void Cat::cry() const;

 

◆매개변수가 하나인 생성자 초기화 방법

class Cat{
	Cat(int age){};
}

//1.
Cat first = Cat(1);
//2.
Cat second(2);
//3.
Cat third = 3;

 

◆this

  • 클래스 내부에서 자신은 반환(표현)하기 위해 사용한다.
  • 생성자와 파괴자를 포함한 각각의 멤버 함수는 하나의 this 포인터를 가진다.
  • this 포인터의 특별한 특징은 호출한 객체를 지시하는 것이다. 
  • 호출한 객체를 메서드가 전체적으로 참조할 필요가 있을 경우에는 *this를 사용할 수 있다.
  • 함수의 매개변수 괄호 뒤에 const 제한자를 사용하면, this 포인터를 const로 제한한다.
  • 그러한 경우에는, this 포인터를 사용하여 그 객체의 값을 변경할 수 없다.

 

◆객체 배열

  • 객체 배열은 표준 데이터형의 배열을 선언하는 것과 완전히 동일한 방법으로 선언한다.
  • 생성자를 사용하여 배열 원소들을 초기화할 수 있다.
Cat cats[3]={
	Cat("black",1),
    Cat("white",0),
    Cat("red")
}

 

◆클래스 사용 범위

  • 클래스는 상수를 선언할 수 없다.

●해결법 2가지

1. 클래스 안에 열거체를 선언할 수 있다.

class Bakery{

private:
	enum {Months=12}
    double consts[Months];
}

2. static을 사용하여 상수를 정의한다.

class Bakery{

private:
	static const int Months = 12;
    double consts[Months];
}
  • 이는 객체 안이 아닌 다른 정적 변수들과 함께 저장되는 Months라는 하나의 상수를 생성한다.

 

◆범위가 정해진 열거(전통적인 열거의 문제점)

  • 두개의 다른 enum 정의로부터 온 열거자는 충돌한다는 문제점이 있다.
//충돌!
enum chicken {S,M,L,XL};
enum T_shirt {S,M,L,XL};

 

●해결법 2가지

1. 열거자에게 클래스 범위를 갖게 한다.

enum class chicken {S,M,L,XL};
enum class T_shirt {S,M,L,XL};

2. 클래스 대신에 struct  키워드를 사용한다.

enum struct chicken {S,M,L,XL};
enum struct T_shirt {S,M,L,XL};
  • 두 경우 모두 사용시 enum이름을 사용해야 한다.
단, 사용시 enum 이름을 사용해야 한다.
chicken choice = chicken::XL;
T_shirt coolTshirt = T_shirt::XL;
  • ※범위가 지정된 열거는 int형으로의 암시적 전환이 이루어지지 않는다. 
  • ※그러나 꼭 필요하다면 명시적 형 변환을 할 수 있다.
int chicken = int(chicken::S);	//0

 

◆추상화 데이터형

  • 클래스를 잘성할때 데이터(멤버)를 추상화 해서 만들것!