본문으로 바로가기

[C++] 클래스

category 개발자과정준비C++ 4년 전
반응형

클래스는 구조체 + 멤버 함수이다.

구조체의 변수는 멤버 변수로써 필드(field)라고 부르고, 멤버 함수는 메서드(method)라고 부른다.

구조체의 확장

멤버 함수

구조체는 타입이 다른 멤버를 하나의 범주 안에 모은 것이다.

데이터의 집합이기때문에 정보만 저장할뿐 동작은 표현할 수 없다. 

따라서 혼자서는 아무것도 할 수 없기때문에 구조체를 사용하는 별도의 함수가 있어야한다.

#include <iostream>

//struct Human     // 구조체 선언 : 멤버 변수만 존재
//{
//	char name[20];
//	int age;
//	float height;
//	float weight;
//	char blood;
//
//};

class Human         // 구조체와는 다르게 클래스에 멤버 변수 + 함수 선언
{
public:             // 실제로 클래스 멤버변수는 public이 생략되있음.
	// 멤버 변수 선언
	char name[20];
	int age;
	float height;
	float weight;
	char blood;

	// 기능 역할을하는 함수(메서드) 선언
	void view()
	{
		printf("이름   : %s\n", name);
		printf("나이   : %d\n", age);
		printf("신장   : %.2f\n", height);
		printf("체중   : %.2f\n", weight);
		printf("혈액형 : %c\n", blood);
	}
};


int main() 
{
	Human my = { "홍길동", 30, 190, 100, 'A' };

	my.view();  // 클래스에 있는 함수 호출

	return 0;
}

 

 

캡슐화 : 밀접한 관계를 가지는 구조체와 함수를 한쌍으로 묶는 것.

멤버 변수를 필드(field), 멤버 함수를 메서드(method)라고 부른다.

 

멤버 함수 외부 작성

클래스 안의 함수를 외부로 만들고 싶을때는 그 함수를 전역함수로 만들어주고 범위지정연산자 '::'를 사용해서 해당 함수가 클래스에 소속되어있는 것을 표시해주면 된다.

 

위에서했던 예제를 변형시켜보자. 클래스안에 만들었던 view() 함수를 밖으로꺼내주면된다.

#include <iostream>

class Human         // 구조체와는 다르게 클래스에 멤버 변수 + 함수 선언
{
public:             // 실제로 클래스 멤버변수는 public이 생략되있음.
	// 멤버 변수 선언
	char name[20];
	int age;
	float height;
	float weight;
	char blood;

	void view();

};

void Human::view()  // 전역함수가 Human 클래스에 소속되어있음을 표시해줌.(범위지정연산자 '::' 사용)
{
	printf("이름   : %s\n", name);
	printf("나이   : %d\n", age);
	printf("신장   : %.2f\n", height);
	printf("체중   : %.2f\n", weight);
	printf("혈액형 : %c\n", blood);
}

int main() 
{
	Human my = { "홍길동", 30, 190, 100, 'A' };

	my.view();  // 클래스에 있는 함수 호출

	return 0;
}

실행결과는 위에서봤던 예제와 똑같다.

함수 본체가 어디에있건 구조체에 소속되는 것은 마찬가지이기때문에 동작은 같다. 그러나 본체 정의 위치에 따라 다음과 같은 차이가 있다.

 

- 내부 정의 : 인라인 속성을가지면서 실제로 함수가 호출되는 것이 아니라 멤버 함수를 호출하는 코드가 함수의 본체 코드로 대체된다.

 

- 외부 정의 : 일반적인 함수를 호출 하듯이 멤버 함수를 호출한다. 스택을 경유하여 인수를 넘기고 제어의 분기가 발생한다.  => 보통 일반적으로 외부 정의를 사용한다.

 

 

액세스 지정

구조체는 기본적으로 public이기때문에 '.' 연산자를 사용하면 외부에서 언제든지 속성에 쉽게 접근할 수 있다.

 

C++은 액세스지정자를 통해 외부에서 멤버 참조 여부를 통제할 수 있다.

- private : 외부에서 액세스 할 수 없고, 내부적으로만 사용하는 멤버. 존재가 알려지지않아서 쓸수없으면서 읽을 수 없다.

- public : 외부로 공개되어 누구나 읽고 쓸 수 있으며 함수는 자유롭게 호출할 수 있다. 자신의 속성이나 동작을 외부로 공개하는 멤버이다. (외부와 연결하는 역할을 하는 것이 인터페이스라고 부른다.)

- protected : 외부에서 액세스할 수 없고 상속 관계의 자식 클래스는 액세스할 수 있는 중간 단계의 지정자이다.

 

선언문 내에 액세스 지정자를 붙이면 이후의 멤버는 다른 지정자가 나올때까지 같은 속성이 적용된다.

액세스 지정자의 순서는 상관없으나 보통은 private, protected, public 순으로 선언한다.

앞의 예제의 구조체를 좀 더 안전하게 코드를 작성해보자.

// 파일이름 : access.cpp
#include <stdio.h>

struct SHuman   // 캡슐화, 추상화의 예시로도 볼 수 있다.
{

private:             // private를 통해 은닉
	char name[20];
	int age;

public:
	void intro() 
	{
		printf("이름 : %s\n", name);
		printf("나이 : %d\n", age);
	}
};

int main() 
{
	SHuman kim;

	// kim.age = 300;  // 멤버변수가 private이기때문에 외부에서는 접근할 수 없다. (에러)

	kim.intro();  // public 으로 지정했기때문에 외부에서 호출가능.
}

예제를 살펴보면 구조체 멤버 변수를 private를 통해 외부로부터 숨겼는데, 클래스 외부에서는 이 값을 액세스 할 수 없고 초기식으로 값을 대입할 수도 없다. (main에 kim.age = 300; 이 에러가 뜨는 것을 확인할 수 있다)

반면 intro 함수는 public 영역에 뒀기때문에 외부에서 호출할 수 있다.

 

private로 처리하면 외부에서 멤버 변수를 읽을 수 없기때문에 엑세스 위반으로 에러처리된다.

이럴 경우 멤버 변수를 대신 읽거나 변경해주는 public 메서드를 제공하는데 이런 함수를 액세서(Accessor)라고 부른다.

 

private로 객체의 정보를 숨김으로써 안정성을 높이는 기능을 정보 은폐라고한다.

이게 왜 해야하는지, 왜 좋은지는 나중에 알아보도록하고 지금은 지정자의 기능만 파악하고 넘어가도록하자

 

 

클래스

구조체는 다양한 타입의 변수를 모아 놓은 것인데 C++에서는 함수까지 포함하여 속성 뿐만 아니라 동작까지 표현할 수 있게 되었다. 구조체 변수는 객체가 됨으로써 객체 지향의 부품이 된다.

확장된 구조체에 이름을 붙인 것이 바로 클래스이다. 클래스도 일종의 구조체이지만 C의 전통적인 구조체와 구분하기위해 새로운 이름을 붙이는 것이다.

 

클래스는 함수를 포함할 수 있는 구조체라고 볼 수 있다. 구조체 선언문의 struct를 class로 바꾸면 클래스가 되는 것이다.

구조체와 클래스의 유일한 차이점은 디폴트 액세스 지정자 뿐인데 구조체 public이고 클래스 private이다.

구조체는 별다른 지정이 없을때 멤버를 외부로 공개하지만, 클래스는 가급적이면 멤버를 숨긴다는 차이점이있다.

 

디폴트 액세스 지정자를빼면 다른 차이점은 없다.

'.' 연산자, '->' 연산자, 구조체 대입, 구조체 중첩은 클래스에도 그대로 적용되고, 상속, 다형성, 연산자 오버로딩도 구조체에 동일하게 적용된다.

앞에서도 해봤지만 구조체를 클래스로 바꿔보자.

 

struct 대신 class를 사용하여 클래스로 선언했는데, 클래스의 디폴트 액세스 지정자가 private이므로 이전 소스와 구조체와 호환되도록 액세시 지정자를 public으로 지정했다.

// 파일이름 : struct를 class로.cpp
#include <stdio.h>

class Human   
{
public:            
	char name[20];
	int age;

	void intro()
	{
		printf("이름 : %s\n", name);
		printf("나이 : %d\n", age);
	}
};

int main()
{
	Human kim = { "김길동", 30 };

	kim.intro(); 
}

확장된 구조체는 클래스와 사실상 같지만 멤버 함수를 가질때는 클래스로 선언하는 것이 일반적이다. 구조체는 변수의 집합을 구현할때만 사용한다.

 

구조체에는 구조체이름에 SHuman처럼 S를 붙였는데, 꼭 필요하지는 않지만 S가 구조체를 의미하고, class에는 CHuman을 붙일수도 있는데 실제로는 귀찮아서 클래스에는 짧은 명사를 쓴다고한다.

또, 변수나 함수와 구분하기위해 첫 자를 대문자로 시작하는 것을 알아두자.

 

 

클래스는 타입이다

클래스는 일종의 타입이기때문에 클래스로부터 파생형 타입을 정의할 수 있다. 클래스한테서 포인터와 배열이 가능하기도하므로 클래스의 배열이나 객체를 가리키는 포인터도 만들 수 있다.

// 파일이름 : HumanType.cpp
#include <stdio.h>

class Human
{
public:
	char name[12];
	int age;

	void intro() 
	{
		printf("이름 : %s\n", name);
		printf("나이 : %d\n", age);
	}
};

int main() 
{
	Human arFriend[10] = 
	{
		{"김길동", 49},
		{"홍길동", 49},
		{"최길동", 49}
	};

	Human* pFriend;
	pFriend = &arFriend[1];
	pFriend->intro();
}

두번째 요소가 출력된다

Human 타입으로 크기 10인 배열 arFriend를 선언하고 3개의 초기값을 주었다.

 

Human 타입으로 객체를 가리키는 포인터인 pFriend는 사람 한 명의 정보를 가리킨다.

예제에서는 arFriend[1]의 주소를 대입받아 이 구조체의 intro 함수를 호출한다. 구조체와 마찬가지로 포인터로부터 멤버 함수를 호출할때는 '->' 연산자를 사용한다.

 

arFriend 배열이나 pFriend 포인터를 사용하는 방식은 int형 배열이나 int*형변수와 완전히 같다.

C문법이 클래스에 대해서도 일관되게 적용되어 이차원 배열이나 이중포인터도 선언할 수 있다.

이는 클래스가 하나의 타입으로 볼 수 있기 때문에 가능한 것이다.

 

 

인스턴스

클래스는 어떤 멤버가 포함되어 있는지 컴파일러에게 알리는 타입 선언 일뿐 그 자체가 정보를 저장하는 변수는 아니다.

정보를 저장하려면 클래스 타입의 변수를 선언해야한다.

 

클래스 타입으로 선언된 변수를 인스턴스(instance) = 객체라고 한다.

클래스가 메모리에 구현된 실체이며, 지금까지 변수라고 불러왔던 개념을 인스턴스라고 할 수 있다.

인스턴스는 타입에 대해 여러개를 동시에 선언할 수도 있다.

Human kim, hong, choi;

각 인스턴스는 멤버 변수를 따로 가져 독립적인 정보를 저장할 수 있다.

인스턴스의 크기는 클래스에 선언된 멤버 변수 크기의 총합과 같다.

(위의 예제는 Human 클래스의 객체를 3개를 생성했는데, 각 인스턴스의 크기는 12 + 4로 16바이트라고 볼 수있다.)

다르게 보면 멤버함수는 별도로 생성이 안된다.

 

멤버변수들은 인스턴스별로 생성되는데, 멤버함수는 인스턴스별로 생성이 되지 않는다.

멤버함수는 코드 영역에 저장되기때문에 모든 인스턴스가 멤버함수를 공유한다.

 

A, B, C라는 객체가 멤버함수에 접근한다고 할때, 멤버함수는 공유하고있기때문에 멤버함수를 호출할때 각 객체의 디스포인트를 전달해서 멤버함수를 호출하게된다.

 

인스턴스는 멤버 변수는 따로 가지지만 멤버함수는 공유한다.

인스턴스와 멤버는 같은 대상을 가리키지만, 사용되는 문맥만 다르다.

인스턴스는 클래스가 메모리에 구현된 실체라는 의미이며, 객체는 프로그램을 구성하는 독립적인 부품으로 볼 수 있다.

 

 

클래스의 예시

실세계의 모든 사물은 각 속성을 가지고 고유한 동작을 한다.

속성을 멤버 변수로 표현하고 동작을 멤버 함수로 기술하여 실세계의 사물을 모델링하는 것으로 볼 수 있다.

// 파일이름 : Time.cpp
#include <stdio.h>

class Time
{
private:
	int hour, min, sec;

public:
	void SetTime(int h, int m, int s)
	{
		hour = h;
		min = m;
		sec = s;
	}
	void OutTime() 
	{
		printf("현재 시간은 %d:%d:%d입니다.\n", hour, min, sec);
	}
};

int main() 
{
	Time now;

	now.SetTime(11, 30, 30);
	now.OutTime();
}

클래스 이름은 Time으로 짓고 시간은 시, 분, 초의 요소로 구성되는데 각 요소를 정수형 hour, min, sec 멤버 변수로 선언한다. 현재값을 설정하는 기능과 출력하는 동작은 각각 SetTime, OutTime 멤버 함수로 선언했다.

여러 요소로 구성되는 시간이라는 복잡한 정보를 클래스로 선언해두면 간편하게 사용할 수 있다.

 

main에서 Time형의 객체 now를 선언하고 SetTime 멤버 함수를 호출하여 초기화하고 OutTime 멤버함수를 호출하여 현재 시간을 출력한다.

 

이외에 시간을 증가, 감소시키는 동작과 시간끼리 비교 연산하는 동작도 멤버 함수로 작성할 수도 있다.

 

 

셀프테스트) Time클래스처럼 년, 월, 일을 멤버로 가지는 Date 클래스를 정의, 오늘 날짜를 출력하는 멤버 함수도 작성

/*
시, 분, 초의 멤버를 가지는 Time 클래스를 정의하듯이
년, 월, 일을 멤버로 가지는 Date 클래스를 정의하라.
오늘 날짜는 출력하는 멤버 함수도 정의한다.
*/

#include <stdio.h>

class Date
{
private:
	int year, mon, day;

public:
	void SetDate(int y, int m, int d)
	{
		year = y;
		mon = m;
		day = d;
	}

	void OutDate() 
	{
		printf("현재 날짜는 %d년 %d월 %d일 입니다.\n", year, mon, day);
	}
};

int main() 
{
	Date now;

	now.SetDate(21, 05, 31);
	now.OutDate();
}

 

 

클래스 모듈

독립된 부품이라고 볼 수 있는 클래스는 보통 별도의 모듈로 작성하며, 클래스 이름과 같은 파일명을 사용한다.

Time 클래스를 만든다면 Time.h 헤더 파일에 클래스 선언문을 작성하고 멤버 함수의 본체는 Time.cpp 구현 파일에 작성한다.

클래스를 사용하는 모듈은 Time.h를 포함하여 클래스에 대한 정보를 파악한다.

클래스 관련 코드가 Time.h, Time.cpp에 작성되어 있으므로 다른 프로젝트에 재사용하려면 두 파일을 가져가서 포함시키면 된다.

반응형