한번 완성한 프로젝트는 계속 사용되는 경우가 많다고 한다. 개발하는 시간보다 유지보수를 해야 하는 시간이 더 많고, 수정에 필요한 비용도 점점 커지게 된다. 그러면 당연히 유지보수가 편하고 수정하기 편하게 만드는 것이 중요할 것이다.

 

다시 말해서 유지보수를 용이하게, 재사용성을 높이는 방향으로 구현해야 한다. 이를 위해 C++에서는 객체지향 프로그래밍을 활용한다.

대표적으로 Class 개념을 활용할 수 있다.

 

1. class

class 는 멤버 함수(동작)와, 멤버 변수(데이터)로 구성된다.

 

1) class 멤버 함수 구현

class 의 멤버 함수를 구현하는 방법은 두가지가 있다.

클래스 내부에서 멤버 함수의 본문까지 직접 정의하거나, 클래스 내부에서는 멤버 함수의 선언만 작성하고 클래스 외부에서 구현하는 방법이다.

 

2) 접근 제어

구현한 클래스의 멤버에 접근하기 위해서는 객체 뒤에 멤버 접근 연산자 . 을 사용한다.

그리고 C++의 접근 지정자인 public, private, protected  를 사용해서 멤버의 접근 권한을 제어할 수 있다. class 키워드를 설정할 때 접근 지정자를 설정하지 않으면 기본적으로 private로 설정이 되며, private멤버는 클래스 외부에서 직접 접근하면 컴파일 에러가 발생하게 된다. 반대로 public은 클래스 외부에서 직접 접근이 가능하다.

 

만약 private에 있는 변수를 제어해야 할 때는 어떻게 할까. 이런 상황에서 사용하는 대표적인 방법이 gettersetter 이다.

setter는 멤버 변수를 바꿀 때,

getter는 값을 가져올 때 사용한다.

이렇게 하는 이유는 변수를 직접 제어하지 않고 데이터를 안전하게 제어하기 위해서이다.

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

//클래스 내부에서는 멤버 함수의 선언만 작성한 경우
class Student
{
//동작을 정의하는 멤버 함수는 접근 가능
public:
	double getAvg();
	int getMaxScore();
	
		void setMathScore(int math);
		{
		this -> math = math; //this 포인터. 멤버 함수가 호출된 객체의 주소를 가리키는 숨겨진 포인터임.
		}
	
		void setEngScore(int eng);
		{
		this -> eng = eng;
		}
		
		void setKorScore(int kor);
		{
		this -> kor = kor;
		}

	int getMathScore() { return math; }
	int getEngScore() { return eng; }
	int getKorScore() { return kor; }

//데이터를 정의하는 멤버 변수는 접근 불가능
private:
	int kor;
	int eng;
	int math;

};

//클래스 외부에서 멤버 함수를 구현
double Student::getAvg()
{
	return (kor + eng + math) / 3.0;
}

int Student::getMaxScore()
{
	return max(max(kor, eng), math);
}


//메인 함수
int main()
{
	Student s;
	
	s.setEngscore(32);	//s. setter 사용
	s.setKorScore(52);
	s.setMathScore(74);

	cout << "평균점수: " << s.getAvg() << endl;
	cout << "최대점수: " << s.getMaxScore() << endl;

	return 0;
}

 

뭔가 에러가 뜨기도 하고, 아무 값도 도출되지 않았다. 이제 여기에 생성자를 적용해서 소스코드를 완성시켜 나가려고 한다.

 

3) 생성자

생성자는 객체를 생성할 때마다 한번씩 자동으로 호출되는 특별한 멤버 함수이다. 보통 필요한 멤버 변수를 초기화하거나 객체가 동작할 준비를 하기 위해 사용한다.

생성자는 반환형을 명시하지 않으며, class 이름과 동일한 이름을 가진 함수로 정의된다.

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

//클래스 내부에서는 멤버 함수의 선언만 작성한 경우
class Student
{
	//접근 가능한 생성자와 멤버함수
public:

	//생성자
	Student(int math, int eng, int kor)
	{
		this->math = math;
		this->eng = eng;
		this->kor = kor;
	}

	double getAvg();
	int getMaxScore();

	//클래스 내부에서 동작을 정의하는 멤버 함수 선언
	void setMathScore(int math)
	{
		this->math = math;
	}
	void setEngScore(int eng)
	{
		this->eng = eng;
	}
	void setKorScore(int kor)
	{
		this->kor = kor;
	}

	int getMathScore() { return math; }
	int getEngScore() { return eng; }
	int getKorScore() { return kor; }

//데이터를 정의하는 멤버 변수는 접근 불가능
private:
	int kor;
	int eng;
	int math;
};

//클래스 외부에서 멤버 함수를 구현
double Student::getAvg()	//평균값 구하기
{
	return (kor + eng + math) / 3.0;
}

int Student::getMaxScore()	//최대값 구하기
{
	return max(max(kor, eng), math);
}


//메인 함수
int main()
{
	Student s(32, 52, 74);

	cout << s.getAvg() << endl;
	cout << s.getMaxScore() << endl;
	return 0;
}

 - 이 경우에는 과목 점수 3개를 메인함수에서 받는 Student s(32, 52, 74); 생성자를 사용했고,

class Student
{
	//접근 가능한 생성자와 멤버함수
public:

	//생성자
	Student(int math = 32, int eng = 17, int kor = 52)
	{
		this->math = math;
		this->eng = eng;
		this->kor = kor;
	}

	double getAvg();
	int getMaxScore();
//...

//메인 함수
int main()
{
	Student s;

	cout << s.getAvg() << endl;
	cout << s.getMaxScore() << endl;
	return 0;
}

이런식으로 기본값이 적용된 생성자를 사용할 수도 있다. 이러면 메인함수에서 Student s; 이렇게 사용되어도 기본값이 잘 호출이 된다. 기본값이 적용된 생성자는 입력받지 않는 경우 기본값을 인자로 받게 된다.

 

4) 코드 나누기

 

클래스를 하나의 파일에 모두 구현하는 것보다, 굳이 사용자가 알 필요 없는 내부 부분은 클래스의 헤더(클래스의 목차)로 분리하고, 구현 부분은 소스파일로 분리한다. 이후 필요한 곳에서 헤더를 include해서 제공하는 방식을 사용한다. (#include "Student.h")

 

헤더 파일(Student.h)은 새로 만들어서 다음과 같이 작성하고,

#ifndef STUDENT_H_	//STUDENT_H_가 정의되어있지 않은 경우에만 아래 코드를 수행하라는 의미
					//헤더 파일을 여러곳에서 사용할 때, class가 중복 선언되지 않게 하기 위해 #ifndef 사용
#define STUDENT_H_	//STUDENT_H_를 정의함. #ifndef일때만 #define이 수행되므로, 단 한번만 수행될 수 있다.
					//단 한번만 수행되므로 #pragma once 필요없음
class Student
{

public:		//생성자와 멤버함수 선언은 public
	Student(int math = 32, int eng = 17, int kor = 52)
	{
		this->math = math;
		this->eng = eng;
		this->kor = kor;
	}
	double getAvg();
	int getMaxScore();

private:	//멤버변수는 private
	int kor;
	int eng;
	int math;

};

#endif				//#ifndef가 끝났다는 것을 알리기 위해 #endif 사용

 

소스 파일(Student.cpp)은 다음과 같이 작성했다. 아까보다 많은 요소(생성자, 멤버함수 선언, 멤버변수)가 헤더 파일에 들어가면서 간략해진 것을 확인할 수 있었다.

#include "Student.h"	//헤더파일을 include 한다.
#include <algorithm>	//max 함수를 사용하기 위해 include한다.

using namespace std;

double Student::getAvg()	
{
	return (kor + eng + math) / 3.0;
}

int Student::getMaxScore()
{
	return max(max(kor, eng), math);
}

 

그리고 마지막으로 메인함수를 작성해서 컴파일했다. 결과값이 잘 도출되었다.

#include <iostream>
#include "Student.h"

using namespace std;

int main(void)
{
	Student s;
	Student s2(1);
	Student s3(1, 2);
	Student s4(32, 52, 74);

	cout << s.getAvg() << endl;
	cout << s.getMaxScore() << endl;

	return 0;
}

 

이런 식으로 코드를 나눠놓으니까 훨씬 가독성도 좋아지고 필요한 변수만 수정하거나 유지보수하기 훨씬 수월해 보였다.

+ Recent posts