OOP
l 정의
프로그램을 객체라는 기본 단위로 나누고, 객체들의 상호작용으로 서술하는 방식이다.
l 객체란?
현실의 사물을 데이터로 표현하는 방법
하나의 역할을 수행하는 데이터의 묶음
l OOP 특징
특징 | 구현 방법 | 설명 |
추상화 ( Abstraction ) |
캡슐화와 정보 은폐 |
사물들의 공통적 특징을 파악하여 이를 하나의 개념으로 다루는 것 객체의 효율적이고 안전한 사용을 위해 인터페이스를 설계하는 것이다. |
캡슐화 ( Encapsulation ) |
클래스 |
캡슐화는 변수와 함수를 하나의 단위로 묶는 것이다. 클래스를 통해 구현되며 해당 클래스의 인스턴스 생성을 통해 변수와 메소드 접근하도록 하는 것 |
정보 은닉/은폐 ( Information Hiding ) |
접근 제한자 |
외부에서 사용하는 기능만 제공하고 필요 없는 정보는 외부에서 접근하지 못하도록 숨기는 것 허락되지 않은 동작, 예상치 못한 동작이 일어나지 않도록 방지해준다. |
상속 ( Inheritance ) |
클래스 |
자식 클래스가 부모 클래스의 특성과 기능을 그대로 물려받는 것 물려받은 것에서 필요한 것을 추가하거나 필요 없는 것은 변경가능 상속은 캡슐화를 유지하면서 재사용성 높여준다. |
다형성 ( Polymorphism ) |
오버라이딩, 오버로딩, 템플릿, 제네릭, 형 변환 |
하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석될 수 있는 것 일관성을 유지할 수 있다.
Ø 서브타입 다형성 : 오버라이딩 Ø 매개변수 다형성 : 템플릿, 제네릭 Ø 임시 다형성 : 함수/연산자 오버로딩 Ø 강제 다형성 : (명시적/암시적)형 변환 |
l OOP 5대 원칙 SOLID
키워드 | 원칙 | 설명 |
S |
단일 책임의 원칙 ( Single Responsibility Principle ) |
각각의 클래스는 단 하나의 책임만 가져야 한다. |
O |
개방-폐쇄 원칙 ( Open Closed Principle ) |
확장에 대해서는 개방적이고, 수정에 대해서는 폐쇄적이여야 한다. |
L |
리스코프 치환 원칙 ( Liskov Substitution ) |
자식 클래스는 언제나 부모 클래스의 역할을 대체할 수 있어야 한다. ( 오버라이드를 하지 않으면 된다. ) |
I |
인터페이스 분리 원칙 ( Interface Segregation Principle ) |
자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다. 인터페이스는 단일 책임을 가져야 한다. |
D |
의존 역전 원칙 ( Dependency Inversion Principle ) |
의존 관계를 맺을 때 변화하기 어려운 것에 의존해야 한다. 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺어야 한다는 것 |
※ 객체지향 vs 절차지향
절차지향 (Procedural Programming)의 절차적은 사실 프로시저(함수)를 잘못 해석한 것
프로시저 호출을 통해 추상화와 재사용성을 얻어내는 것이 본질이다.
객체지향이라고 해서 절차적으로 진행되지 않는 것은 아니다.
절자치향 프로그래밍은 프로그램의 순서와 흐름을 먼저 세우고, 필요한 자료구조와 함수를 설계하는 방식
객체지향 프로그래밍은 자료구조와 이를 중심으로 한 모듈을 먼저 설계한 다음에
이들의 실행순서와 흐름을 짜는 방식이다.
C++ vs JAVA
l JAVA 제네릭
모든 종류의 타입을 다룰 수 있도록 일반화된 타입 매개변수로 클래스나 메소드를 선언
타입 제거라는 개념에 근거한다. 바이트코드로 변환할 때 인자로 주어진 타입을 제거함
제네릭이 있다고 크게 달라지는 것이 없으며 코드를 예쁘게 할 뿐이다.
l C++ 템플릿
하나의 클래스를 서로 다른 여러 타입에 재사용할 수 있도록 하는 방법
컴파일러는 인자로 주어진 각각의 타입에 대해 별도 템플릿 코드 생성한다.
정적 변수를 공유하지 않는다.
l 차이
1) 처리하는 방법이 다르다.
2) C++ 템플릿은 주어진 타입으로부터 객체를 만들 수 있지만 자바에서는 불가능
3) JAVA 객체는 제네릭 인자에 관계 없이 만든 객체는 모두 동등 타입이다.
C++ 템플릿 타입 인자로 만든 객체는 서로 다른 타입의 객체 ㅂ그래서 정적 메소드나 변수 선언에 사용가능
4) JAVA는 제네릭 타입 인자를 특정한 타입이 되도록 제한 가능
JAVA Generic (제네릭) | C++ Template (템플릿) | |
방식 |
삭제에 의해 정의됨, 기존 유형을 제한 |
확장에 의해 정의됨, 새로운 유형 생성 |
코드 생성 |
동일한 코드를 공유하여 사용 컴파일 시 타입이 올바른 지 검사하고 ( Generic type-safe )
유형이 지워지는 삭제라는 기술을 사용하여 바이트 코드 생성 ( Type erasure ) ( 삭제 기술은 Java Generics가 너무 늦게 나와서 이전 버전과의 호환성을 깨뜨리고 싶지 않기 때문에 사용함
Vector클래스는 제네릭 클래스로 정의되어 있지만 Vector<T> vector로 쓰지 않고 Vector vector 로 사용해도 괜찮다는 뜻이다. 이를 Low Type이라고 한다. ) |
컴파일 시 각각의 타입에 대한 별도의 코드를 생성 ( 사용하지 않으면 인스턴스 생성하지 않는다. )
유효 검사는 실제로 사용되는 곳에서 수행된다. |
코드 생성 예시 |
컴파일 전 Vector<String> vector;
컴파일 후 Vector vector 로 유형이 삭제
|
컴파일 전 template<typename T> void f(T s) { std::cout << s << '\n'; } int x = 0; f(x) = 0;
컴파일 후 void f_generated_with_int(int s) { std::cout << s << '\n'; } int x = 0; f_generated_with_int(x); 라는 코드로 확장
|
정적 변수 공유 |
다른 유형 끼리도 정적변수 공유함 |
다른 유형끼리 정적 변수 공유하지 않음 같은 유형의 인스터스 끼리는 공유함 |
특징 |
런타임에 효율 떨어짐 컴파일에는 큰 패널티 없음 |
런타임에서 부담적지만 컴파일 속도 느려짐 |
인자 타입 |
기본 데이터 타입을 쓸 수 없고 Object만 사용 가능하므로 Wrapper 클래스를 쓴다. (int -> Integer) 대신 오토 박싱 가능
|
기본 데이터 타입 사용 가능 |
l 언어적 차이
C++은 저수준 시스템 프로그래밍의 강점을 가지는 C를 이어받아
객체 지향과 일반화 프로그래밍 같은 멀티 패러다임을 지원하고자 하는 시도에서 탄생하였고
고수준, 저수준 개념을 모두 포함하다보니 복잡해졌다.
JAVA는 처음부터 객체지향 언어로 개발되었다.
원시 타입 (int 같은 거) 은 객체로 취급하지 않기 때문에 순수 객체지향은 아니다. (순수는 Python)
웹 앱 백엔드, 안드로이드 앱 등 더 널리 사용됨
속도가 중요한 부분은 C, C++로 개발된다.
l 언어별 Call By Value, Call By Reference
C 언어 | C++ | JAVA | |
'언어적'으로 지원되는 함수 호출 방법 |
Call By Value만 지원 |
Call By Value와 Call By Reference 모두 지원 |
Call By Value만 지원 |
Call By Reference |
포인터로 구현 가능 |
포인터로 구현 가능 제공되는 참조자를 써도 된다. |
참조 자료형(객체, 배열)을 인자로 넘겨서 구현 가능 |
Call By Reference 추가 설명 |
Call By Address 를 구현 하여 ( 주소 값을 Call By Value로 전달하고, 이를 명시적으로 참조해서 ) Call By Reference에 준하는 효과를 흉내낼 수 있다. |
명시적으로 Reference Type인 참조자(&)가 존재 그래서 언어적으로 지원 해준다고 말할 수 있다.
C언어 처럼 포인터로 사용자가 구현도 할 수 있고 제공되는 참조자를 써도 된다. |
참조 자료형(객체, 배열)을 인자로 넘기면 C언어 처럼 주소 값을 복사해서 넘긴다. 그 주소 값으로 멤버 변수의 값에 졉근하여 Call By Reference 처럼 변경할 수는 있지만
주소 값 자체를 바꿀 수는 없기 때문에 new키워드로 다른 객체를 만들어 넣어도 바뀌지 않는다. |
결론 |
Call By Value만 지원한다. Call By Address를 포인터로 구현하여 Call By Reference 흉내 가능 |
Call By Value, Call By Address, Call By Reference 모두 가능하다. |
Call By Value만 지원한다. 참조형 변수를 인자로 넘기면 Call By Reference로 값은 변경 가능, 객체 자체를 바꿀 순 없음 |
l Call By Reference
참조자를 통해서 레퍼런스 변수( 별칭 )을 전달한다.
호출 측에서 값을 전달하는 것처럼 보이지만 받는 쪽에서는 포인터로 받는다.
별칭은 똑같은 기억 공간을 가리키는 다른 이름이다.
따라서 전달 받은 인수의 값을 변경 가능하고
복사본을 만들지 않아 효율적이다.
void test(int &value)
{
value = 6;
}
int main()
{
int v = 10;
test(v);
}
// 배열은 이렇게 넘겨야 한다.
void test(int (&arr)[5])
{
...
}
l Call By Address
매개 변수로 주소 값을 전달한다.
명시적인 포인터를 통한 호출을 한다.
주소 값을 전달하기 때문에 본질적으로는 Call By Value지만
Value가 주소 값이기 때문에 Address라고 부르는 것
void test(int *ptr)
{
*ptr = 6; // 포인터 역 참조
}
int main()
{
int v = 1;
test(&v); // & 연산자는 변수의 주소를 알려준다.
}
l Call By Address, Call By Reference 차이?
컴파일 하면 결과는 같다.
처음부터 포인터를 명시적으로 전달하느냐 아니냐의 차이일 뿐이다.
'C++' 카테고리의 다른 글
C/C++ 궁금한 것 정리 (0) | 2019.10.14 |
---|