***혹시 구조체 struct 에 대해서 잘 모르시는 분들은 미리 struct에 대해서 공부한 후에 보세요!***
<C++ 기초> 구조체 struct, 구조체 응용(*포인터 인수로 사용하기)
1. 클래스의 원리
클래스에 대한 접근은, 현실 세계에 존재하는 특정한 '사물'이 어떠한 일반적인 요소를 가지고 있는지를 관찰하는 것부터 시작된다. 예를 들어, '자동차'라는 사물을 프로그램으로 표현한다고 생각해 보면, 자동차는 차량 번호를 가지고 있을 것이고 어느 정도 양의 연료를 싣고 있을 것이다. 또한 자동차는 차량 번호를 결정하고, 자동차에 연료를 넣고, 차량 번호와 남은 연료의 양을 표시하기 등의 기능을 가지고 있을 것이다.
클래스란, 이러한
사물의 상태 및 특성, 그와 관련된 기능을 정리하여 프로그램으로 표현하기
위해 사용하는 개념이다. 코드로 표현한 클래스는 다음과 같다.
블록 안에 자동차의 '상태 및 특성' 그리고 '기능'을 정리했다. 그리고 이 코드 뭉치에 '자동차'라고 이름 붙였다.
1) 클래스 선언하기
클래스 작성법은 앞선 게시글에서 학습한 구조체의 작성방법과 거의 같다. C++의 클래스는 사용자 정의형의 일부분이기 때문이다.
물건의 상태 및 특성, 기능을 정리한 클래스를 작성하는 작업을 클래스의 선언(declaration)이라고 부른다.
class 클래스명
{
접근 지정자;
변수 선언;
...
함수 선언;
...
};
구조체를 선언할 때는 struct 키워드를 사용했지만 클래스의 경우에는 class 키워드를 사용한다.
클래스 안에는 변수와 함수를 함께 선언한다. 이 변수와 함수들은 구조체와 마찬가지로 멤버(member)라고 부른다. 단, 클래스의 경우는 변수를 데이터 멤버(data member), 함수를 멤버 함수(member function)라고 구분해서 부른다.
이 중, 멤버 함수를 선언할 때에는 주의가 필요하다. 클래스 구문을 자세히 보면 멤버 함수는 선언만 되어 있음을 알 수 있다. 즉, 멤버 함수의 본체는 클래스 안에 정의하지 않는다. 멤버 함수의 본체는 클래스 바깥에서 정의한다.
리턴 값의 형 클래스 명::멤버 함수명(인수 리스트)
{
....
}
'::' 는 범위 결정 연산자(scope resolution operator) 라고 한다. :: 연산자를 사용해서 멤버가 어떤 클래스의 멤버 함수인지를 알리는 것이다.
2) 클래스를 선언하는 코드
다음 코드는 자동차의 차량 번호와 연료의 양을 관리하여 출력하는 기능을 가진 'CAR(자동차)' 클래스를 선언한다.
이 CAR 클래스는 다음과 같은 데이터 멤버와 멤버 함수를 가지고 있다.
데이터 멤버 | 설명 |
num | 차량 번호가 저장되는 변수 |
gas | 연료량이 저장되는 변수 |
멤버 함수 | 설명 |
show() | 차량 번호와 연료량을 출력하는 함수 |
멤버 함수의 정의는 클래스 선언 바깥에, 'CAR ::' 라고 지정하여 이 함수가 일반적인 함수가 아닌 CAR클래스의 멤버 함수임을 알리고 있다.
데이터 멤버 - 사물의 성질 및 상태
멤버 함수 - 사물의 기능
3) 클래스 이용하기
새로 선언한 클래스는 새로운 형으로 인정받게 된다. 즉, 클래스 형 변수를 선언할 수 있게 되는 것이다.
개체 생성:
클래스명 변수명;
클래스는 새로운 형으로 인식되므로, 일반적인 변수와 동일한 방법으로 선언할 수 있다.
이 코드는 'CAR 클래스의 값'을 저장할 수 있는 변수 Car1의 선언이다. 이 클래스형 변수는 객체(object) 또는 인스턴스(instance)라고 부른다. 이제부터 클래스 형을 저장하는 변수를 객체라고 부르자!
-객체란 클래스 형 값을 저장하는 변수이다.-
4) 멤버에 접근하기
그러면, 위 코드에서 선언한 객체를 코드 안에서 이용해 보자.
객체를 이용하기에 앞서 객체의 각 멤버에 접근하여 값을 저장해야 한다. 이때에는 구조체와 마찬가지로 도트 연산자(.)를 사용한다.
멤버 함수를 호출할 경우에도 도트 연산자를 사용한다.
클래스를 선언해서 실제 이용하는 코드를 입력해 보도록 하자.
이 코드의 앞 부분에서는 CAR클래스를 선언하고 있고, main()함수 부분에서는 CAR클래스의 객체를 선언하고 있다.
Car1이라는 객체가 선언되고 나서, 차량 번호와 연료량이 대입되었고 그 후에 값이 출력되었다.
즉, Car1이 선언되었기에 다음과 같은 작업이 가능해 진 것이다.
1) 한 대의 '자동차(Car1)를 만들었다(객체를 선언했다.).
2) 번호와 연료량을 설정했다(데이터 멤버에 값을 대입했다).
3) 차량 번호와 연료의 양을 표시했다(멤버 함수를 호출했다).
5) 객체 생성하기
객체를 저장시킬 메모리를 확보하는 작업(위 코드 예제에서는 Car1을 선언하는 작업)을 '객체를 생성한다' 라고 한다.
이와 반대로, 객체의 이용이 끝난 후 메모리를 해제하는 작업을 '객체를 소멸시킨다' 라고 한다.
위 코드에서 만든 객체 Car1은 main()함수 안에서 선언된 지역 변수다. 따라서 main()함수가 시작될 때 생성되고, main()함수가 종료될 때 소멸된다. 자동차 한 대가 만들어진 후 폐차되는 과정을 떠올려 보면 된다.
또한 객체는 필요할 때 동적으로 생성할 수 있다. new연산자를 사용하여 객체를 저장시킬 메모리를 확보하고, delete 연산자를 사용하여 메모리를 해제시키면 된다.
CAR클래스를 가리킬 포인터 pCar을 준비하고, 동적으로 할당된 메모리의 주소를 저장한다. 포인터를 사용하여 멤버에 접근할 때에는 구조체와 마찬가지로 화살표 연산자(->)를 사용할 수 있다.
6) 2개 이상의 객체 생성하기
객체는 얼마든지 만들어 낼 수 있다. 예를 들어 자동차를 2대 만든다고 하면 새로운 변수인 Car2를 준비한 후 new를 사용해서 생성하면 된다.
그러면 이 두 대의 자동차가 각각 고유한 차량 번호와 연료량을 가지게 된다. 객체를 여러 개 생성하면 복잡한 프로그램을 만들 수 있다.
7) 클래스 사용순서 정리
1. 클래스를 선언하기.
2. 클래스로 객체를 생성하기.
첫 번째 작업인 '클래스 선언하기'는 '자동차의 설계도(클래스)'를 작성하는 작업이라고 할 수 있다.
두 번째 작업인 '객체 만들기'는 그 설계도(클래스)를 바탕으로 '각각의 자동차'(객체)를 만드는 작업, 데이터를 저장하고 설정하는 작업이라고 할 수 있다.
이 예제에서는 첫 번쨰 코드와 두 번째 코드를 모두 1개의 파일에 작성했지만, 두 개의 코드를 다른 사람이 각각 다른 파일에 작성하는 것 또한 가능하다.
2. 멤버에 대한 접근 제한
위의 코드에서는 데이터 멤버에 차량 번호와 연료량의 값을 대입했다. 이를 통해 실제 자동차에 차량 번호를 부여하고 연료량을 설정하듯 코드로 표현할 수 있었다. 그러나 이러현 표현은 문제가 발생할 수 있다. 예를 들어, 자동차의 연료량을 -10으로 바꾸는 코드를 작성했을 경우를 생각해 볼 수 있다. 실제 자동차의 연료량을 마이너스로 만들기란 불가능하기 때문에 이상한 코드가 되어 버린다.
클래스는 '사물'에 보다 근접한 코드를 만들기 위해 설계되었다. 따라서 클래스를 사용한 복잡한 프로그램 작성 중에 이런 모순된 조작을 수행하면 프로그램에 예기치 못한 에러를 발생시킬 가능성이 높아지게 된다.
1) private 멤버 만들기
위에서 연료의 양이 마이너스가 되어 버린 이유는, 멤버에 아무런 제약 없이 접근하여, 있을 수 없는 값(-10)을 대입해 버렸다는 것이다. C++에서는 이런 실수를 하지 않도록,
클래스 외부에서 마음대로 접근할 수 없는 멤버를 만드는 기능
을 지원한다. 이런 멤버를 private 멤버라고 한다.
이 코드에서는 멤버에 private라는 제한자를 붙였다.
이렇게 하면 Car클래스의 외부(main()함수)에서 필드에 접근할 수 없게 된다.
자동차의 연료량으로 마이너스 값이 대입될 수 없는 상태가 된 것이다.
- private 멤버로 만들면 클래스 외부에서 접근할 수 없다 -
2) public 멤버 만들기
데이터 멤버를 private 멤버로 만들면, 클래스 외부에서 마음대로 접근할 수 없게 된다. 그러나 이렇게 만들어도 private 멤버에 접근하는 방법이 존재한다. 즉, 아직도 특정한 방법을 사용하면 main()함수 안에서 차량 번호와 연료량을 설정할 수 있다.
이 코드에는 번호와 연료의 양을 저장하기 위해 SetNumGas()라는 함수를 새로 추가했다. 특히, 연료의 양이 올바른지 점검한 다음에 데이터 멤버에 값을 저장한다는 점에 주목해야 한다!
CAR 클래스 외부에서 차량 번호와 연료량을 직접 설정할 수 없게 되었다. 그 대신, SetNumGas()함수가 차량 번호와 연료량의 수정을 담당한다. 이 멤버 함수를 사용하면, 반드시 그 값이 올바른지 확인을 거친 연료량만 저장된다. 즉, 잘못된 연료량이 저장될 가능성이 사라지는 것이다.
SetNumGas()함수앞에는public이라는제한자가붙어있다.이멤버를public멤버라고부른다.public을붙인멤버는클래스외부에서사용할수있다.
-public 멤버는 클래스 외부에서 접근할 수 있다.-
3) 캡슐화의 원리 이해
-캡슐화: 클래스의 데이터와 기능을 하나로 묶어, 구성원을 보호하는 기능을 일컬어 말한다.-
이제 위의 예제의 CAR 클래스는 연료량이 올바른지의 여부를 스슬 확인하는 기능을 갖췄다. 이 메커니즘을 도입하면, 잘못된 값이 저장되지 않는 클래스를 설계할 수 있다.
클래스를 다루는 프로그램은 클래스를 선언하는 부분과 클래스를 이용하는 부분을 각각 다른 사람이 작성하는 경우가 있다. 따라서 클래스를 설계하는 사람이 클래스 멤버들을 적절하게 private 멤버와 public 멤버로 분류해 두면 다른 사람이 그 클래스를 이용하여 견고한 프로그램을 만둘 수 있기 때문에 편리하다.
이처럼, 클래스에 데이터(필드)와 기능(메소드)를 한 곳에 모은 다음, 보호하고 싶은 멤버에 private를 붙여 접근을 제한하는 기능을 일컬어 캡슐화(encapsulation)라고 부른다. 일반적으로 위의 예제처럼
데이터 멤버 ------> private 멤버
멤버 함수 --------> public 멤버
로 지정하곤 한다. 캡슐화는 클래스의 중요한 기능 중 하나다!
*private과 public을 생략할 경우엔?
: private과 public은 접근 지정자(access specifier)라고 부른다. 물론 접근 지정자는 생략하는 것 또한 가능하다. 접근 지정자를 생략하면 private 멤버가 된다.
또한 C++에서는 앞선 게시글에서 설명했던 구조체의 각 멤버에도 접근 지정자를 지정할 수 있다. 단, 구조체의 접근 지정자를 생략하면 클래스의 경우와 다르게 public 멤버가 된다.
접근 지정자에는 public, private 외에도 protect가 있다. protect는 상속에 대하여 공부할 때 설명하도록 하겠다.
4) 멤버 함수를 인라인 함수로 만들기
주의사항!
: private 멤버에 접근하려면 반드시 public 멤버 함수를 경유해야 한다!
따라서, 클래스를 이용할 때, 멤버 함수가 빈번하게 호출될 수 있다. 하지만 이러한 함수의 빈번한 호출은 프로그램의 속도 저하를 유발한다. 그래서 간단한 멤버 함수는 인라인 함수로 만들어 버리는 것이 일반적이다.
일반적인 함수를 인라인 함수로 만들 때에는 inline을 붙인다. 그러나, 멤버 함수는 보다 쉽게 인라인 함수로 만들 수 있다.
-클래스 선언부에 함수 본체를 정의하면 자동적으로 인라인 함수가 된다.-
이 사실을 기억하자.
위의 코드 getNum() 함수와 getGas() 함수는 클래스 선언부 안에 정의되어 있으므로 자동적으로 인라인 함수가 된다. 반면에 클래스 선언부 바깥에 정의된 show() 함수는 일반적인 함수가 된다.
'C++ 코딩 > C++ 기초개념' 카테고리의 다른 글
C++ Operator Overloading 연산자 오버로딩 (0) | 2022.09.26 |
---|---|
STL 표준 템플릿 라이브러리 (Standard Template Library) (0) | 2022.04.01 |
<C++ 기초> 공용체 union (0) | 2021.03.18 |
<C++ 기초> 구조체 struct, 구조체 응용(*포인터 인수로 사용하기) (0) | 2021.02.24 |
<C++ 기초> 식과 연산자, 연산자의 종류와 우선순위 (0) | 2021.02.23 |
<C++ 기초> 동적배열, sti vector, 멤버함수 (0) | 2021.02.18 |
<C++ 기초> typedef, 열거형 enum (0) | 2021.02.16 |
<C++ 기초> 문자열 조작하기 -char[], string (0) | 2021.02.15 |