클래스와 객체(Class and Object)
-
C언어에서는 절차 지향 프로그래밍만 지원함. 절차 지향 프로그래밍이란 프로그램을 단순히 데이터와 이를 처리하는 방법으로 작성하는 것을 의미함.
-
C++에서는 객체 지향 프로그래밍도 지원하는데, 객체 지향 프로그래밍이란 실세계의 특정 대상을 객체로 나타내어 수많은 객체들의 상호작용으로 프로그램을 서술하는 방식을 의미함.
-
객체(Object)의 개념
-
객체 지향 프로그래밍에서는 데이터와 기능을 하나로 묶어서 다룰 수 있는 자료구조.
-
프로그램을 처리하는 절차보다도 동작되는 자료에 중점을 둔 것.
-
객체는 프로그램의 기본 단위이며, 객체 간의 상호 관계로 프로그램의 처리를 표현함.
-
객체는 멤버 변수(특성, Attribute)와 이를 대상으로 처리하는 동작인 멤버 함수(메서드, Method)를 하나로 묶어 만든 요소
-
-
구조적 프로그래밍에서는 변수와 함수가 합쳐져 프로그램을 만들지만, 객체 지향 프로그래밍에서는 멤버 변수와 멤버 함수가 합쳐져 하나의 객체를 만들고, 객체와 객체가 합쳐져 하나의 프로그램을 만듬.
-
C언어에서 구조체는, 서로 다른 타입의 변수들을 저장할 수 있는 사용자 정의 타입으로만 사용되었으며, 함수를 저장할 수 없어서 구조체를 매개변수로 받는 함수를 따로 만들어 처리해야 했음.
-
C++에서는 변수와 함수를 함께 저장할 수 있는 클래스(Class)라는 개념을 도입하였는데, 클래스를 통해 만들어지는 변수를 객체라고 부름. 참고로 구조체(struct)도 클래스로 분류되며, 함수를 저장할 수 있음.
-
클래스는 설계도, 객체는 이 설계도를 기반으로 만들어진, 런타임(Run-time) 시 동작하는 실체로 정의. 클래스에서 객체로 바꾸는 작업을 인스턴스화라고 함.
-
클래스 선언 방법
-
class 클래스 이름 { 멤버 변수; 멤버 함수; ... } -> 외부에서 멤버 변수나 함수에 접근하는 범위를 지정 가능
-
struct 클래스 이름 { 멤버 변수; 멤버 함수; ... } -> 외부에서 항상 멤버 변수나 함수에 접근 가능
-
-
클래스의 멤버 함수
-
함수의 내용은 클래스 내부나 클래스 외부에서 구현 가능.
-
클래스 내부에서 구현된 멤버 함수들은 모두 인라인 함수(inline function)로 정의. 인라인 함수란 C언어에서 매크로 함수와 비슷한 개념으로 프로그램의 실행 속도를 높이기 위해 도입되었음.
-
함수의 본체가 긴 경우, 멤버 함수를 외부에서 정의하는 것이 편리함. -> 클래스 이름::함수 이름(매개변수) {...}
-
-
데이터 추상화: 현실 세계의 사물을 데이터적인 측면과 기능적인 측면을 통해서 정의하여 추상화하는 방법
-
예를 들어, 강아지는 다리가 4개이고, 종류(말티즈 등)와 색상(흰색 등) 등의 데이터로 정의할 수 있으며, 짖을 때는 "멍멍"이라는 소리를 내며 달릴 수도 있음.
-
다리 개수, 종류, 색상이라는 멤버 변수와 짖기, 뛰기라는 멤버 함수로 강아지라는 클래스를 작성할 수 있음.
-
#include <iostream>
using namespace std;
// Define class
class Dog {
public:
int numLegs;
string kind;
string color;
void bark();
void run();
};
// Instantiation: create object!
int main() {
Dog dog = { 4, "시고르자브종", "검은색" };
dog.bark();
dog.run();
return 0;
}
void Dog::bark() {
cout << kind << ": 멍멍!" << endl;
}
void Dog::run() {
cout << numLegs << "개 다리로 후다닥!" << endl;
}
-
C++에서는 구조체를 이용해서 클래스를 정의할 수 있음.
struct Dog {
int numLegs;
string kind;
string color;
void bark(); // { cout << "멍멍!" << endl; }
void run(); // { cout << "후다닥!" << endl; }
};
void Dog::bark() {
cout << kind << ": 멍멍!" << endl;
}
void Dog::run() {
cout << numLegs << "개 다리로 후다닥!" << endl;
}
-
C언어의 경우 위와 같은 데이터 추상화는 다음과 같이 표현됨.
#include <stdio.h>
typedef struct Dog {
int numLegs;
char kind[100];
char color[10];
} Dog;
void bark(Dog* dog) {
printf("%s: 멍멍!\n", dog->kind);
}
void run(Dog* dog) {
printf("%d개 다리로 후다닥!\n", dog->numLegs);
}
int main() {
Dog dog = { 4, "시고르자브종", "검은색" };
bark(&dog);
run(&dog);
return 0;
}
접근 제한자(Access Modifier)
-
C++에서는 접근 제한자를 통해 클래스 외부에서 멤버 변수나 멤버 함수에 접근할 수 있는 범위를 정함.
-
private: 자신의 멤버 함수 외에는 접근할 수 없음.
-
protected: 자식 클래스의 멤버 함수로부터의 접근만 허용
-
public: 모든 곳으로부터의 접근을 허용
-
접근 제한 정도: private > protected > public
Class Parent
{
private:
int priv;
protected:
int prot;
public:
int pub;
void accessAllMembers();
};
void Parent::accessAllMembers() {
priv = 100; // Success
prot = 100; // Success
pub = 100; // Success
}
// 자식 클래스
class Child : public Parent {
public:
void accessParents() {
int n;
n = priv; // Fail
n = prot; // Success
n = pub; // Success
}
};
int main() {
Parent parent;
int n;
n = parent.priv; // Fail
n = parent.prot; // Fail
n = parent.pub; // Success
}
정보 은닉과 캡슐화
-
정보 은닉(Information Hiding)
-
프로그램을 사용하는 사용자가 알아야 하는 것은 프로그램을 사용하는 방법이기 때문에, 내부 동작이나 상세 구조를 드러낼 필요는 없음.
-
정보 은닉이란 사용자가 굳이 알 필요가 없는 정보를 숨김으로써, 최소한 정보만으로 프로그램을 쉽게 사용하도록 하는 방법.
-
C++에서는 클래스의 정보 은닉 기능을 지원하기 위해 접근 제한자 키워드를 제공함.
-
숨길 멤버와 공개할 멤버의 블록을 구성하여 공개된 멤버는 외부에서 자유롭게 접근 가능하나 숨겨진 멤버를 직접 참조하려고 하면 오류를 발생하도록 처리.
-
따라서 간접적으로 접근할 필요가 있을 때는 메소드를 활용.
-
Getter: 값을 읽기 위한 멤버 함수
-
Setter: 값을 쓰기 위한 멤버 함수
-
일반적으로, 멤버 변수는 private이고 멤버 함수는 public이며, 프로그램 설계에 따라 달라질 수 있음.
-
class Point {
private:
double x;
double y;
public:
double getX();
double getY();
void setX(double _x);
void setY(double _y);
};
-
캡슐화(Encapsulation)
-
관련 있는 데이터와 함수를 하나의 단위로 묶는 것.
-
정보 은닉과는 다른 개념.
-
// 정보 은닉 없이 캡슐화만 된 경우
class Point {
public:
double x;
double y;
};
생성자(Constructor)
-
객체가 선언될 때 실행되는 코드로, 멤버 변수들을 초기화하는 역할을 함.
-
클래스와 같은 이름을 함수의 이름으로 가지며, 인수나 리턴 값이 없는 상태로 선언.
-
생성자는 public 이지만 싱글턴 패턴과 같이 private 으로 선언하는 경우도 존재함.
-
생성자가 호출되는 시기
-
객체를 만들 때: Person p;
-
메모리를 할당할 때: Person* p = new Person;
-
-
함수 오버로딩과 같은 조건으로 생성자들도 오버로딩이 가능함.
-
디폴트 생성자(default constructor)
-
생성자를 만들지 않을 경우 C++에서 자동으로 만들어주는 생성자.
-
디폴트 생성자는 인수를 가지지 않지만, 멤버 변수들이 초기화됨.
-
단, int나 char 등의 기본 타입은 초기화되지 않음.
-
단, 생성자를 하나라도 선언할 경우 디폴트 생성자는 만들어지지 않음.
-
#include <iostream>
class Person {
private:
double height;
double weight;
public:
Person(double _height, double _weight) {
height = _height;
weight = _weight;
}
};
int main() {
Person p(178.5, 70.8);
Person p; // Error
return 0;
}
-
const나 레퍼런스 멤버 변수는 선언과 동시에 초기화를 해야하므로, 디폴트 생성자가 아닌 멤버 이니셜라이저(Member Initializer)로 초기화를 해주어야 함.
class Person {
private:
const string SSN;
double height;
double weight;
public:
Person(const string _SSN, double _height, double _weight)
: SSN(_SSN), height(_height), weight(_weight)
{ }
};
소멸자(Destructor)
-
생성자가 객체를 초기화하듯, 반대로 객체가 더 이상 필요하지 않을 때 정리하는 코드.
-
객체가 삭제될 때 파괴: Person* p = new Person(); delete p;
-
블록 밖으로 넘어갈 때 파괴: if (true) { Person p; } // 여기서 p의 소멸자 호출
-
다른 소멸자에 의한 파괴: A 클래스가 멤버 변수로 B 클래스를 가질 때, A의 소멸자가 호출된 후 B의 소멸자가 호출됨.
-
-
생성자가 메모리를 할당하면 사용 후에는 운영체제로 리턴되어야 함.
-
소멸자는 생성자의 이름 앞에 ~를 붙여서 정의함.
class IntPointer {
private:
int* intPtr;
public:
IntPointer() {
pi = new int[5];
}
~IntPointer() {
delete[] pi;
pi = nullptr;
}
};
-
생성 순서와 소멸 순서는 반대
-
Person a; Person b; 가 있으면 생성 순서는 a, b가 되고 소멸 순서는 b, a가 된다.
-
먼저 선언한 객체가 먼저 생성
-
먼저 선언한 객체가 나중에 소멸
-
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A() called!" << endl; }
~A() { cout << "~A() called!" << endl; }
};
class B {
public:
B() { cout << "B() called!" << endl; }
~B() { cout << "~B() called!" << endl; }
};
int main() {
A a;
B b;
return 0;
}
/* output:
A() called!
B() called!
~B() called!
~A() called!
*/
this 포인터
-
클래스에서 사용할 수 있는 특별한 포인터로, 현재 멤버 함수가 호출된 인스턴스의 메모리 주소를 가리킴.
-
멤버 변수들과 멤버 함수들을 연결해주는 이유.
-
멤버 변수들은 각각의 인스턴스에서 저장할 내용이 다르기 때문에 반드시 별도로 존재해야 하지만, 멤버 함수들은 인스턴스가 늘어나더라도 바뀔 필요가 없음.
-
따라서, 프로세스 구조상 멤버 변수들이 보관되는 영역(스택, 힙, 데이터)과 멤버 함수들이 존재하는 영역(코드)은 나누어져 있음.
-
코드 영역은 실행 중에 변경을 막기 위해 보호되어 런타임 중에는 수정될 수 없음.
-
즉, 멤버 함수는 멤버 변수에 접근하기 위해 인스턴스를 식별할 필요가 있음.
-
-
인스턴스는 독자적인 멤버 변수들을 가지지만, 클래스 공통의 멤버 함수와 매칭됨.
-
멤버 함수를 호출하게 되면, 멤버 함수에 호출한 인스턴스의 포인터를 같이 보내고, 멤버 함수는 인스턴스의 포인터(this)를 가지고 멤버 변수들에 접근함.
-
멤버 함수 내에서 명칭의 우선순위: 지역 변수 > 멤버 변수 > 전역 변수
#include <iostream>
using namespace std;
class Person {
private:
double height;
double weight;
public:
Person() { }
Person(double _height, double _weight)
: height(_height), weight(_weight) { }
void setHeight_wrong(double height) {
height = height; // Not changing
}
void setHeight_1(double height) {
this->height = height;
}
double getHeight() { return height; }
};
int main() {
Person p(183.4, 78.5);
p.setHeight_wrong(182.8);
cout << p.getHeight() << endl; // 183.4
p.setHeight(182.8);
cout << p.getHeight() << endl; // 182.8
return 0;
}
'Programming Language > C++' 카테고리의 다른 글
예외 처리(Exception Handling) (0) | 2020.11.16 |
---|---|
객체 지향 프로그래밍(Object-Oriented Programming) - 4 (0) | 2020.11.15 |
객체 지향 프로그래밍(Object-Oriented Programming) - 3 (0) | 2020.11.10 |
객체 지향 프로그래밍(Object-Oriented Programming) - 2 (0) | 2020.11.06 |
From C to C++ (0) | 2020.11.04 |