[따배씨++]Chapter8-1. 클래스
카테고리: Cpp
태그: Programming
인프런에 있는 홍정모님의 홍정모의 따라하며 배우는 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 - 접근 지정자
private
- 클래스 내의 멤버 함수에서만 접근 가능
- 접근 지정자를 명시하지 않았을 때 default 값
public
- 외부 모든 곳에서도 접근 가능
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
를 리턴 받으면 메모리가 사라질 임시 변수를 받음- 복잡한 프로그램에서 쓰기에는 조금 불안하다고 함
댓글 남기기