[개념 정리]
클래스란?
C++에서 구조체의의 상위 호환으로 이해할 수 있다.
C++의 구조체는 멤버로 함수를 포함할 수 있기에, C언어의 구조체보다 확장된 의미를 가진다.
C++에서 구조체와 클래스의 차이는 기본 접근제어의 차이가 있다.
객체를 만들어 내기 위한 틀과 같은 개념이 바로 class(클래스)이다.
[클래스]
멤버 변수: Property(프로퍼티)
멤버 함수: Method(메소드) 라고 한다.
객체 지향 프로그래밍(OOP, Object Oriented Programming)
OOP에서는 모든 data를 object(객체)로 취급한다.
객체의 state(상태)와behavior(행동)을 구체화하는 형태의 programming이다.
OOP 특징
1. 추상화(abstraction)
2. 캡슐화(encapsulation)
3. 정보 은닉(data hiding)
4. 상속성(inheritance)
5. 다형성(polymorphism)
instance(인스턴스)
클래스를 사용하기 위해서는 해당 클래스 타입의 객체를 선언해야 한다.
선언된 해당 클래스 타입의 객체를 instance(인스턴스)라고 한다.
인스턴스는 메모리에 대입된 객체를 의미한다.
하나의 클래스에서 여러 개의 인스턴스를 생성할 수 있다.
인스턴스는 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가진다.
그러나 멤버 함수는 모든 인스턴스가 공유한다.
c++에서 클래스 정의
class 클래스이름
{
접근제어지시자1:
멤버변수1의타입 멤버변수1의이름;
멤버변수2의타입 멤버변수2의이름;
...
멤버함수1의 원형
멤버함수2의 원형
...
};
class tree // class: 키워드, tree: 클래스 이름
{
private: // private 제어 지시자는 생략가능
int current_page; // 멤버 변수
void set_percent(); // 멤버 함수
public: // 나머지 제어 지시자는 생략 불가능
int total_page_;
};
// private부터 public 끝까지: 클래스의 멤버
public 영역은 모든 객체에서 접근할 수 있지만,
private 영역은 해당 객체 내의 멤버 변수나 멤버 함수만이 접근할 수 있다.
* 클래스의 기본 접근 제어 권한은 private이며, 구조체 및 공용체는 public이다.
멤버 함수의 정의
클래스의 선언 밖에서 멤버 함수를 정의할 때에는 범위 지정 연산자(::)를 사용하여
해당 함수가 어느 클래스에 속하는지를 명시해야 한다.
하나의 클래스에서 생성된 인스턴스는 각각 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가지지만,
멤버 함수는 모든 인스턴스가 공유하게 된다.
멤버 함수의 호출
멤버 함수는 멤버 참조 연산자(.)를 사용하여 호출할 수 있습니다.
객체이름.멤버함수이름(); // 매개변수가 없는 멤버 함수의 호출
객체이름.멤버함수이름(전달할인수목록); // 매개변수가 있는 멤버 함수의 호출
this 포인터
c++에서 모든 멤버 함수가 자신만의 this 포인터를 가지고 있다.
this 포인터는 해당 멤버 함수를 호출한 객체를 가리키게 되며, 호출된 멤버 함수의 숨은 인수로 전달된다.
호출된 멤버 함수는 자신을 호출한 객체가 무엇인지 정확히 파악할 수 있다.
* this는 포인터이므로, 반환할 때에는 참조 연산자(*)를 사용하여 호출한 객체 그 자체를 반환해야 한다.
* this 포인터는 암시적으로도 사용될 수 있지만, 화살표(->) 연산자를 통해 명시적으로 사용해야 좋다.
this 포인터 특징
1. this 포인터는 클래스, 구조체 또는 열거체 타입의 비정적 멤버 함수에서만 사용할 수 있다.
2. 정적(static) 멤버 함수는 this 포인터를 가지지 않는다.
3. this 포인터는 언제나 포인터 상수이며, 따라서 값을 재할당할 수 없다.
Constructor(생성자)
객체의 생성과 동시에 멤버 변수를 초기화해주는 constructor(생성자)라는 멤버 함수를 제공한다.
클래스 생성자의 이름은 해당 클래스의 이름과 같다.
생성자의 특징
1. 생성자는 초기화를 위한 데이터를 인수로 전달받을 수 있다.
2. 생성자는 반환값이 없지만, void형으로 선언하지 않는다.
3. 객체를 초기화하는 방법이 여러 개 존재할 경우에는 오버로딩 규칙에 따라 여러 개의 생성자를 가질 수 있다.
(* 클래스의 생성자는 어떠한 반환값도 명시하지 않음에 주의합니다.)
생성자의 호출
클래스에서 객체를 생성할 때마다 해당 클래스의 생성자가 컴파일러에 의해 자동으로 호출된다.
Destructor(소멸자)
객체의 수명이 끝나면 생성자의 반대 역할을 수행할 멤버 함수가 필요하다.
이러한 역할을 하는 멤버 함수를 destructor(소멸자)라고 한다.
소멸자는 객체의 수명이 끝나면 컴파일러에 의해 자동으로 호출되며, 사용이 끝난 객체를 정리해 줍니다.
* 클래스 소멸자의 이름은 해당 클래스의 이름과 같다. 이름 앞에 물결 표시(tilde, ~)를 붙여 생성자와 구분한다.
ex) Book 클래스의 소멸자는 ~Book()이라는 이름을 가지게 된다. //소멸자 앞에 void형으로 선언 하지 않는다.
* 생성자에서 new 키워드를 이용해 동적으로 메모리를 할당했다면,
소멸자에서는 delete 키워드를 이용해 동적으로 할당받은 메모리를 반환해야한다
그렇지 않으면 해당 프로그램에 메모리 누수(memory leak)가 계속해서 발생하게 될 것이다..
Destructor(소멸자)의 특징
1. 소멸자는 인수를 가지지 않는다.
2. 소멸자는 반환값이 없지만 void형으로 선언하지 않는다.
3. 객체는 여러 개의 생성자를 가질 수 있지만, 소멸자는 단 하나만 가질 수 있다.
4. 소멸자는 const, volatile 또는 static으로 선언될 수는 없지만,
const, volatile 또는 static으로 선언된 객체의 소멸을 위해서 호출될 수있다.
[클래스 예제]
#include <iostream>
#include <windows.h>
using namespace std;
class Tiger
{
// 멤버 변수
// Tiger 클래스의 속성을 가지고 있다.
// 필드
int a, b;
// 멤버 함수
// 메쏘드: 클래스 내부의 있는 멤버
// Tiger 클래스의 동작(행위)를 가지고 있다.
void f1() {
}
};
// 밖에 있는 함수는 그냥 함수
void f1() {
}
int main()
{
}
[클래스의 기본형태1]
class AirPlane {
int 연료, 주행시간;
void 비행하다() {
연료;
}
void 이륙하다() {
주행시간;
} // 이들을 다 이용해야 클래스를 이용할 수 있다고 말한다.
};
[클래스의 기본형태2]
class 사과 {
int 색깔, 무게, 원산지, kg당 가격, 당수치, 칼로리;
void 던지다() {
무게;
}
void 요리하다() {
칼로리;
}
void 요리하다() {
주행시간;
} // 이들을 다 이용해야 클래스를 이용할 수 있다고 말한다.
};
[구조체 개념]
#include <iostream>
#include <windows.h>
using namespace std;
struct STRU {
};
class Apple {
int a, b;
void f1() {
}
};
// OOP(object oriented programming)
int main() {
STRU s; // s라는 변수를 선언
Apple a; // a라는 객체(object,행위를 가짐)를 생성
}
[예제1]
#include <iostream>
#include <windows.h>
using namespace std;
struct STRU {
};
class Apple {
public:
int a, b;
void f1() {
printf("f1");
}
};
// OOP(object oriented programming)
int main() {
Apple app;
app.a = 10;
app.f1();
}
출력값 : f1
[생성자 함수 설명]
// 반대의미: 소멸자 함수
#include <iostream>
#include <windows.h>
using namespace std;
class Apple {
public:
Apple() {
}
};
int main() {
Apple app;
}
// 클래스 이름과 동일한 함수명이어야 한다.
// 다르게 하면 그냥 멤버함수가 된다.
[객체가 생성되는 시점]
#include <iostream>
#include <windows.h>
using namespace std;
class Apple {
public:
Apple() {
printf("2\n");
}
};
int main() {
printf("1\n");
Apple app; // <<< 여기가 객체가 생성되는 시점이다.
printf("3\n");
}
출력값: 1
2
3
1. 클래스 이름과 동일한 함수명이어야 한다. // 다르게 하면 그냥 멤버함수가 된다.
2. 리턴타입이 없다. 즉 Apple() 앞에 void를 넣을 수 없다. (= void Apple() <- 안된다 )
3. 임의로 호출할 수 없고, 객체가 생성될때 자동 호출된다.
4. Apple cpp(); // () >>> 객체를 생성할 때 괄호를 넣으면 안된다.
5. 생성자함수도 함수이므로 함수 재정의가 가능하다.
6. 생성자함수 사용 목적: 멤버 변수 초기화
7. 인수 전달이 없는 생성자를 default 생성자라고 한다.
8. 생성자를 작성하지 않으면 자동으로 디폴트를 생성자가 만들어진다.
[클래스 기본 원형]
#include <iostream>
#include <windows.h>
using namespace std;
class CApple {
public:
CApple() {}
void f1() {
}
};
int main() {
CApple app;
app.f1();
}
* this 사용법 숙지하기
[파일 분리하기_1]
[원형_main.cpp]
클래스 이름이 CApple이면 파일명은 1. apple.cpp 2. apple.h 두개다. (암묵적 규칙)
#include <iostream>
#include <windows.h>
using namespace std;
// 헤더로 들어간다
class CApple {
public:
CApple();
void f1();
void f2();
};
// cpp로 들어간다.
CApple::CApple() {
printf("생성자\n");
}
void CApple::f1() {
printf("f1\n");
}
void CApple::f2() {
printf("f2\n");
}
int main() {
CApple app;
app.f1();
app.f2();
}
[apple.cpp]
#include "apple.h"
#include <stdio.h>
CApple::CApple() {
printf("생성자\n");
}
void CApple::f1() {
printf("f1\n");
}
void CApple::f2() {
printf("f2\n");
}
[apple.h]
#pragma once
class CApple {
public:
CApple();
void f1();
void f2();
};
** 추가적인 설명 (아래 두개의 코드는 같은 뜻을 의미함)
#pragma once // 한번만 compile 하라는 뜻
#ifndef _APPLE // APPLE 글자 본적이 있는지
#define _APPLE
#endif
[분리_main.cpp]
#include <iostream>
#include <windows.h>
using namespace std;
#include "apple.h"
int main() {
CApple app;
app.f1();
app.f2();
}
<분리 순서>
1. 원형 코드에서 헤더파일과 cpp 파일 범위를 나눈다
2. 솔루션 탐색기에서 소스파일과 헤더파일 폴더 우클릭 -> 추가 -> 새항목 생성
3. 생성된 cpp파일과 .h 파일에 코드를 옮긴 후 자체적으로 컴파일이 되는지 확인한다.
4. 메인코드를 실행하여 결과값을 도출한다
** tip) 소스파일 및 헤더파일 생성할 때 클래스로 생성하면 동시에 생성된다.
[ifndef 사용 예제]
#include <iostream>
#include <windows.h>
using namespace std;
#define _NETWORK
int main() {
// 클라이언트 코드(UI) 추가
#ifndef _NETWORK
// 서버 코드(통신 프로그램)
#endif
// 클라이언트 코드
#ifndef _NETWORK
// 서버 코드(통신 프로그램)
#endif
// 클라이언트 코드
#ifndef _NETWORK
// 서버 코드(통신 프로그램)
#endif
}
[파일 분리하기_2]
[원본 코드]
#include <iostream>
#include <windows.h>
using namespace std;
class CApple {
public:
void f2(CBanana* app) { // 이게 네번 째 실행
printf("f2\n");
app->f3(); // 이게 다섯번 째 실행
}
};
class CBanana {
public:
void f1() { // 이게 두번 째 실행
printf("f1\n");
CApple app;
app.f2(this); // 이게 세번 째 실행
}
void f3() { // 이게 여섯번 째 실행
printf("f1\n");
}
};
int main() {
CBanana bnn; // 함수 f1,f3 사용가능
bnn.f1(); // 이게 첫번 째 실행한다
}
[main.cpp]
#include <iostream>
#include <windows.h>
using namespace std;
#include "banana.h"
int main() {
CBanana bnn; // 함수 f1,f3 사용가능
bnn.f1(); // 이게 첫번 째 실행한다
}
[banana.cpp]
#include "banana.h"
#include "apple.h"
#include <stdio.h>
void CBanana::f1()
{
printf("f1\n");
CApple app;
app.f2(this);
}
void CBanana::f3()
{
printf("f3\n");
}
[apple.cpp]
#include "apple.h"
#include <stdio.h>
void CApple::f2(CBanana* app) { // 이게 네번 째 실행
printf("f2\n");
app->f3(); // 이게 다섯번 째 실행
};
[banana.h]
#pragma once
class CBanana {
public:
void f1();
void f3();
};
[apple.h]
#pragma once
#include "banana.h"
class CApple {
public:
void f2(CBanana* app);
};
[생성자 및 소멸자 코드1]
#include <iostream>
#include <stdio.h>
class CTest {
public:
CTest(){
printf("생성자를 콜한다\n");
}
// 틸이 들어가면 소멸자가 된다.
// 객체가 사라질때 소멸가가 콜된다.
~CTest() {
printf("소멸자를 콜한다\n");
}
};
int main() {
{
printf("1\n");
CTest t;
printf("2\n");
}
printf("3\n");
}
[생성자 및 소멸자 코드2]
#include <iostream>
#include <stdio.h>
class CTest {
public:
CTest(){
printf("생성자를 콜한다\n");
}
// 틸이 들어가면 소멸자가 된다.
// 객체가 사라질때 소멸가가 콜된다.
~CTest() {
printf("소멸자를 콜한다\n");
}
};
int main() {
CTest t1;
printf("1\n");
CTest* t2;
printf("2\n");
t2 = new CTest;
printf("3\n");
delete t2; // 포인터에 소멸자 콜
} // 일반 변수 소멸자 콜
[생성자 및 소멸자 코드3]
#include <iostream>
#include <stdio.h>
class CTest {
public:
CTest() {
printf("생성자를 콜한다\n");
}
// 틸이 들어가면 소멸자가 된다.
// 객체가 사라질때 소멸가가 콜된다.
~CTest() {
printf("소멸자를 콜한다\n");
}
};
int main() {
{
CTest* t2 = new CTest; // new는 무조건 delete 만나야 한다.
//즉 스코프 안에서 delete 일어나야 한다.
delete t2;
}
}
[소멸자 코드]
#include <iostream>
#include <stdio.h>
class CTest {
public:
int* p = NULL;
CTest() {
// 위에 선언했다해도 p = NULL로 초기화를 시켜주는 것이 안전하다.
if(p ==NULL)
p = new int[10];
}
~CTest() {
if(p != NULL)
delete[]p;
}
void f1() {
if (p == NULL){
// 메세지를 나타내고
return;
}
p[0] = 10;
printf("%d\n", p[0]);
delete[] p;
p = NULL;
}
};
[set, get]
#include <iostream>
#include <stdio.h>
// 멤버 변수는 99% private: 내에 써야한다.
class CTest {
private:
// data hiding
int width, height;
public:
void f1() {
width = 10;
height = 20;
}
// 값을 변경 하고 싶으면 함수를 정의한다.
//void setWidth(int width) { // setter 함수 (설정하는 함수).
// this->width = width;
//}
int getWidth() {
return width;
}
void setHeight(int height) { // setter 함수 (설정하는 함수).
this->height = height;
}
};
int main() {
CTest t;
t.setHeight(100);
printf("%d\n", t.getWidth() );
}
data hiding(정보 은닉)
사용자가 굳이 알 필요가 없는 정보는 사용자로부터 숨겨야 한다는 개념입니다.
그렇게 함으로써 사용자는 언제나 최소한의 정보만으로 프로그램을 손쉽게 사용할 수 있게 됩니다.
출처 : tcpschool.com
'Education > Edu | .net' 카테고리의 다른 글
# 9.1) [C/C++] c++ 기본문법4 (0) | 2021.01.15 |
---|---|
# 8) [C/C++] c++ 기본문법3 (0) | 2021.01.14 |
# 6.3) [C/C++] c++ 기본문법1 (0) | 2021.01.12 |
# 6.2) [MFC] 시작하기1 (0) | 2021.01.12 |
# 6.1) [Win32API] 대화상자 다루기 (0) | 2021.01.12 |