[따배씨++]Chapter8-1. 클래스

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모님의 홍정모의 따라하며 배우는 C++ 강의를 듣고 정리한 내용입니다.
공부하는 식빵맘 님의 블로그를 참고했습니다.
learncpp 교재 내용을 참고했습니다.


🚆 OOP - Object Oriented Programming

객체: 데이터 + 함수
객체 지향 프로그래밍(OOP)은 속성과 동작 모두를 독립적이고 재사용 가능한 패키지로 묶는 객체를 생성할 수 있는 기능을 제공합니다.


class Friend
{
public:
	string _name;
	string _address;
	int _age;
	double _height;
	double _weight;

	void print()
	{
		cout << _name << " " << _address << " " << _age << " " << endl;
	}
};

int main()
{
    Friend my_friend;

    return 0;
}
  • 클래스 이름은 대문자로 시작
  • 자바와 달리 new 가 필요 없다
  • 사실 구조체(struct)도 member function 가질 수 있음


Struct vs Class

데이터 전용 구조에는 struct를 쓰고
데이터와 기능이 모두 있는 객체에는 class를 쓰자

형식적으로 구조체와 클래스의 차이는 ‘멤버들의 접근 지정자 기본 값이 public(struct)이냐, private(class)이냐’ 이다. 구조체에 함수를 선언할 수 있게 된 것은 c++들어와서 인데, 많은 개발자들이 잘못된 결정이라고 생각한다. 예를 들어, 클래스가 자체적으로 정리될 것이라고 가정하지만 (뒤에서 배울 소멸자 - 클래스가 소멸되기 전에 메모리 할당을 해제함.) 구조체가 그렇게 할 것이라고 가정하는 것은 안전하지 않다. 구조체는 데이터라고 가정하기 때문이다.


🚆 Encapsulation & Access Specifier

멤버 변수를 비공개로 만드는 이유??
현대 생활에서 우리는 많은 장치에 접근할 수 있다. 대부분의 장치는 작업을 수행할 수 있는 간단한 인터페이스를 제공한다. 그러나 이러한 장치가 실제로 작동하는 원리는 숨겨져 있다. 예를 들어 자동차의 가속 폐달을 밟았을 때 내연기관이 어떻게 바퀴를 회전시키는지 우리는 알 필요가 없다. 이와 마찬가지로 인터페이스와 구현을 분리하는 것은 매우 유용하다.


Encapsulation - 캡슐화

Information hiding(정보 은닉)이라고도 함

  • 사용자가 쓰기 편리함
  • 데이터 보호 & 오용 방지
  • 코드 수정 용이 (클래스 내에서 해결 가능)
  • 디버깅이 쉬움


Access Specifier - 접근 지정자

  1. private
    • 클래스 내의 멤버 함수에서만 접근 가능
    • 접근 지정자를 명시하지 않았을 때 default 값
  2. public
    • 외부 모든 곳에서도 접근 가능
  3. protected
    • 클래스 내의 멤버 함수에서 접근 가능
    • 클래스를 상속 받는 자식 클래스에서도 접근 가능


Access Function - 접근 함수

private 멤버 변수들은 public access function을 통해서 접근이 가능


class Date
{
	int _month;
	int _day;
	int _year;

public:
	void setDate(const int& month_input, const int& day_input, const int& year_input)
	{
		_month = month_input;
		_day = day_input;
		_year = year_input;
	}

	const int& getMonth()
	{
		return _month;
	}

	const int& getDay()
	{
		return _day;
	}
	const int& getYear()
	{
		return _year;
	}

	void copyFrom(const Date& original)
	{
		_month = original._month;
		_day = original._day;
		_year = original._year;
	}
};
  • Getter : private 멤버 변수 반환
    • 보통 const reference로 반환
  • Setter : private 멤버 변수 값 설정
    • copy setter..?(뭐라 불러야 할지..)
    • 두 객체가 같은 클래스에 속한다면 외부라 할지라도 private 멤버 변수 접근 가능


🚆 Constuctor

Constructor(생성자) : 클래스의 객체가 생성될 때 자동으로 호출되는 특별한 종류의 클래스 멤버 함수.

class Fraction
{
private:
	int _numerator;
	int _denominator;

public:
	// default constructor
	Fraction()
	{
		_numerator = 0;
		_denominator = 1;
	}

	// list constructor : constructor with parameters
	Fraction(int numerator, int denominator)
	{
		_numerator = numerator;
		_denominator = denominator;
	}
};

int main()
{
	Fraction frac1;
	Fraction frac2{ 3,1 };

	return 0;
}
  • 생성자는 클래스와 이름이 같음
  • 생성자는 리턴 타입이 없음 (void 아님)
  • 생성자는 public member function
  • 모든 클래스는 default constructor가 숨어있음
  • constructor 여러 개 만들 시 ambiguous주의 (함수 오버로딩)
  • 모든 멤버 변수를 초기화 하는 것이 좋음
  • Fraction frac1; : default constructor로 객체 생성 시 괄호 없음
  • Fraction frac2{3,1}; : list constructor는 중괄호 초기화를 선호
    • uniform initialization은 타입 변환 허용 안 함!


생성자 줄이기

class Fraction
{
private:
	int _numerator;
	int _denominator;

public:
	Fraction(int numerator = 0, int denominator = 1)
	{
		assert(denominator != 0);

		_numerator = numerator;
		_denominator = denominator;
	}
};

int main()
{
	Fraction frac1;		 // call Fraction(0,1)
	Fraction frac2{};	 // call Fraction(0,1)
	Fraction frac3{ 3 };	 // call Fraction(3.1)
	Fraction frac4{ 5,4 };	 // call Fraction(5,4)

	return 0;
}
  • default value를 이용!


기본 생성자가 필요 없는 경우

class Fraction
{
private:
	int _numerator = 0;
	int _denominator = 1;

public:
	Fraction() = default;

	Fraction(int numerator, int denominator)
	{
		assert(denominator != 0);

		_numerator = numerator;
		_denominator = denominator;
	}
};
  • 변수 선언 시 초기화 (non-static만)
  • Fraction() = default;
  • 이 방법을 선호한다고 함


Member Initializaer List

class Something
{
private:
	int _i;
	double _d;
	char _c;
	int _arr[5];

public:
	Something()
		:_i{ 10 }, _d{ 3.14 }, _c{ 'a' }, _arr{ 1,2,3,4,5 }
	{}
};
  • 선언하고 assignment로 넣어주기보다 바로바로 초기화 해주는게 더 효율적임
  • static variable은 assignment로 초기화 못함
  • 이러한 문제들을 해결하기 위해 멤버 초기화 목록 등장!
  • 초기화 순서는 멤버 초기화 목록에 지정된 순서가 아닌 클래스에서 선언된 순서


위임 생성자 - delegating constructor

같은 클래스 내에서 한 생성자가 다른 생성자를 호출

class Student
{
private:
	int _id = 0;
	string _name = "";

public:
	Student() = default;

	Student(const string& name_in)
		:Student(0, name_in)
	{}

	Student(const int& id_in, const string& name_in)
		:_id{ id_in }, _name{name_in}
	{}
};
  • 코드 중복을 방지할 수 있음


init 멤버 함수 이용

class Student
{
private:
	int _id = 0;
	string _name = "";

public:
	Student() = default;

	Student(const string& name_in)
	{
		init(0, name_in);
	}

	Student(const int& id_in, const string& name_in)
	{
		init(id_in, name_in);
	}

	void init(const int& id_in, const string& name_in)
	{
		_id = id_in;
		_name = name_in;
	}
};
  • 이런 코드들도 많다고 함


🚆 Destructor - 소멸자

Destructor(소멸자) : 클래스의 객체가 소멸될 때 호출되는 특별한 종류의 클래스 멤버 함수.
단순 클래스(일반 멤버 변수의 값만 초기화하는 클래스)의 경우 C++가 자동으로 메모리가 정리하기 때문에 소멸자가 필요하지 않다. 그러나 클래스 객체가 리소스(동적 메모리 등)를 보유하고 있거나 객체가 소멸되기 전에 유지 관리를 수행해야 하는 경우 소멸자가 필요하다.

class IntArray
{
private:
	int* _arr = nullptr;
	int _length = 0;

public:
	IntArray(const int& length_in)
	{
		_length = length_in;
		_arr = new int[_length]; // 동적할당 멤버 변수
	}

	~IntArray()
	{
		delete[] _arr; // 동적할당 메모리 반납
	}
};
  • ~클래스 이름
  • 리턴타입, 파라미터 없음
  • 프로그래머가 직접 소멸자를 호출하는 것은 권장하지 않음


🚆 this 포인터

아래와 같은 멤버 함수가 있을 때 _id 가 해당 클래스의 멤버 변수라는 것을 어떻게 추적할 수 있을까?

void setID(int id) { _id = id; }

simple.setID(2);

사실 멤버 함수는 컴파일러에 의해 다음과 같이 변환된다.

void setID(Simple* const this, int id) { this->_id = id };

simple.setID(&simple, 2);
  • this 는 작업 중인 객체를 가리키는 포인터 이다.


this를 활용한 연쇄 호출

class Calc
{
private:
	int _value = 0;

public:
	Calc(int init_value)
		:_value(init_value)
	{}

	Calc& add(int value) { _value += value; return *this; }
	Calc& sub(int value) { _value -= value; return *this; }
	Calc& mult(int value) { _value *= value; return *this; }
	void print() { cout << _value << endl; }
};


int main()
{
	Calc cal(10);
	cal.add(10).sub(1).mult(2).print(); // 38 출력

	return 0;
}
  • Calc& 참조 리턴이 아닌 일반 Calc 를 리턴 받으면 메모리가 사라질 임시 변수를 받음
  • 복잡한 프로그램에서 쓰기에는 조금 불안하다고 함



맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기