6장 구조체와 클래스

2023. 11. 22. 15:57프로그래밍 공부/OOP

6.1 구조체

디스플레이 6.1 구조체 정의

//Program to demonstrate the CDAccountV1 structure type.
#include <iostream>
using namespace std;

//Structure for a bank certificate of deposit:
//An improved version of this structure will be given later in this chapter.
struct CDAccountV1
{
	double balance;
	double interestRate;
	int term; //months until maturity
};

void getData(CDAccountV1& theAccount);
//Postcondition: theAccount.balance, theAccount.interestRate, and
//theAccount.term have been given values that the user entered at the
//keyboard.

int main( )
{
	CDAccountV1 account;
	getData(account);
    
	double rateFraction, interest;
	rateFraction = account.interestRate/100.0;
	interest = account.balance*(rateFraction*(account.term/12.0));
	account.balance = account.balance + interest;
	
	cout.setf(ios::fixed);
	cout.setf(ios::showpoint);
	cout.precision(2);
	cout << "When your CD matures in "
	     << account.term << " months,\n"
	     << "it will have a balance of $"
	     << account.balance << endl;
         
	return 0;
}
//Uses iostream:
void getData(CDAccountV1& theAccount)
{
	cout << "Enter account balance: $";
	cin >> theAccount.balance;
	cout << "Enter account interest rate: ";
	cin >> theAccount.interestRate;
	cout << "Enter the number of months until maturity: ";
	cin >> theAccount.term;
}

 

샘플 대화 상자

Enter account balance: $100.00
Enter account interest rate: 10.0
Enter the number of months until maturity: 6
When your CD matures in 6 months,
it will have a balance of $105.00

 

구조체 타입

구조체(structure): 어떤 멤버 함수도 없는 객체

구조체의 데이터가 다양한 종류의 데이터 항목들의 집합이 될 수 있다.

 

struct CDAccountV1
{
    double balance;
    double interestRate;
    int term; //months until maturity
};

 

키워드 struct: 이것이 구조체(structure type) 정의임을 알린다. 

구조체 태그(structure tag): 구조체의 이름. 식별자이다. 보통 대문자로 시작한다. ex) CDAccountV1

멤버 이름(member name): 괄호 {} 안에 선언된 식별자들.

이 예제에 표시된 것처럼 구조체 정의는 중괄호, } 및 세미콜론으로 모두 끝난다.

 

구조체 정의가 배치되는 장소: 일반적으로 모든 함수 정의 외부

 

구조체 타입은 구조체 정의를 따르는 모든 코드에서 사용할 수 있는 전역 정의이다.
구조체 타입 정의가 주어지면 구조체 타입을 int, char 등에서 미리 정의된 타입과 동일하게 사용할 수 있다.

예시)

디스플레이 6.1에서 구조체 타입 CDAcountV1은 함수 메인의 변수 선언에 사용되며

함수 getData의 parameter type 이름으로 사용된다.


구조체 변수는 다른 변수처럼 값을 가질 수 있다.

구조체 값(structure value): 멤버 값(member value)이라고 불리는 작은 값들의 집합

구조체 정의에 선언된 멤버 이름마다 하나의 멤버 값이 있다.

예시) CDAccountV1 타입의 값은 두 개의 double형과 하나의 int형을 가진 세 개의 멤버 값의 집합이다.

구조 값을 구성하는 멤버 값들은 멤버 변수에 저장된다.

 

각 구조체 타입은 멤버 이름 목록을 지정한다.

ex) 그림 6.1에서 구조 CDAcountV1은 balance, interestRate, term의 세 가지 멤버 이름을 갖는다.

이러한 멤버 이름은 각각 더 큰 구조체 변수의 일부인 더 작은 변수를 선택하는 데 사용될 수 있다.

멤버 변수: 더 큰 구조체 변수의 일부인 더 작은 변수

멤버 변수는 구조체 변수의 이름 뒤에 점, 그리고 나서 멤버 이름을 지정함으로써 지정된다.

예) 계정이 CDAcountV1 유형의 구조체 변수인 경우, 구조체 변수 account은 다음 세 가지 멤버 변수를 갖는다:

 

account.balance
account.interestRate
account.term

 

두 개 이상의 구조 유형이 동일한 멤버 이름을 사용할 수 있다.

예를 들어, 같은 프로그램에서 다음 두 가지 유형 정의를 갖는 것은 완벽하게 합법적이다:

struct FertilizerStock
{
    double quantity;
    double nitrogenContent;
};
and
struct CropYield
{
    int quantity;
    double size;
};

 

점 연산자(dot operator)
점 연산자: 구조체의 멤버 변수를 지정하는 데 사용된다.


SYNTAX
Structure_Variable_Name.Member_Variable_Name

EXAMPLES
struct StudentRecord
{
    int studentNumber;
    char grade;
};
int main( )
{
    StudentRecord yourRecord;
    yourRecord.studentNumber = 2001;
    yourRecord.grade = 'A';

 

어떤 이들은 점 연산자를 구조 멤버 접근 연산자라고 부르지만, 우리는 그 용어를 사용하지 않을 것이다.

 

단순 구조체
여기에 표시된 것처럼 구조체를 정의한다. Structure_Tag은 구조체의 이름이다.


SYNTAX
struct Structure_Tag
{
    Type_1 Member_Variable_Name_1;
    Type_2 Member_Variable_Name_2 ;

    .
    .
    .

    Type_Last Member_Variable_Name_Last;
}; //세미콜론 잊지 말기
EXAMPLE
struct Automobile
{
    int year;
    int doors;
    double horsePower;
    char model;
};

 

이 기능을 사용하지는 않지만 같은 유형의 멤버 이름을 쉼표로 구분된 하나의 목록에 결합할 수 있다. 

예를 들어 다음은 이전 구조체 정의와 동일하다:


struct Automobile
{
    int year, doors;
    double horsePower;
    char model;
};
구조체의 변수는 다른 유형의 변수와 동일한 방법으로 선언할 수 있다.
예를 들면,
Automobile myCar, yourCar;

멤버 변수는 점 연산자를 사용하여 지정된다. 예를 들어, myCar.year,
myCar.doors, myCar.horsePower, and myCar.model.

 

함정: 구조체 정의에서 세미콜론 잊기

구조체 정의에 마지막 중괄호 } 뒤에 세미콜론을 배치해야 한다.

해당 마지막 중괄호와 해당 마지막 세미콜론 사이에 구조체 변수 이름을 나열하여

구조체 변수를 선언하는 데에도 사용할 수 있기 때문이다.

예) 다음은 WeatherData라는 구조체를 정의하고

WeatherData 타입의 두 구조체 변수인 dataPoint1과 dataPoint2를 선언한다:

struct WeatherData
{
    double temperature;
    double windVelocity;
} dataPoint1, dataPoint2;


함수 인수로서의 구조체

함수는 구조체 타입의 call-by-value 매개변수 또는 구조체 타입의 call-by-reference 매개변수, 또는 둘 다 가질 수 있다.

예를 들어, 디스플레이 6.1의 프로그램은

구조체 타입 CDAcountV1의 call-by-reference 매개변수를 갖는 getData라는 함수를 포함한다.

 

구조체 타입은 함수가 반환하는 값의 타입이 될 수도 있다. 

예를 들어, 다음은 CDAcountV1 타입의 인수 하나를 가져다가 CDAcountV1 타입의 다른 구조체를 반환하는 함수를 정의한다.

반환되는 구조체는 인수와 잔액 및 기간이 동일하지만 인수가 지불하는 이자율의 두 배를 지불한다.

CDAccountV1 doubleInterest(CDAccountV1 oldAccount)
{
    CDAccountV1 temp;
    temp = oldAccount;
    temp.interestRate = 2*oldAccount.interestRate;
    return temp;
}

CDAcountV1 유형의 로컬 변수 temp에 주목하라.

temp는 원하는 종류의 완전한 구조체 값을 구축하는 데 사용되며, 이 값은 함수에 의해 반환된다.

myAccount가 멤버 변수에 대한 값이 지정된 CDAcountV1 타입의 변수인 경우

다음은 myAccount의 이자율의 두 배인 계정에 대한 계정 값을 제공한다:

CDAccountV1 yourAccount;
yourAccount = doubleInterest(myAccount);


팁: 계층적 구조체 사용

때때로 멤버들이 그들 자신이 더 작은 구조체를 갖는 구조체를 갖는 것이 타당하다.

예를 들어, 사람의 키, 몸무게, 생년월일을 저장하는 데 사용될 수 있는

PersonInfo라는 구조체 타입은 다음과 같이 정의될 수 있다:

struct Date
{
    int month;
    int day;
    int year;
};
struct PersonInfo
{
    double height; //in inches
    int weight; //in pounds
    Date birthday;
};

 

PersonInfo 타입의 구조체 변수는 일반적인 방법으로 선언된다:

PersonInfo person1;

구조체 변수 person1이 태어난 연도가 다음과 같이 출력될 수 있다:

cout << person1.birthday.year;

person1 // PersonInfo 타입의 구조체 변수

birthday이라는 이름을 가진 멤버 변수를 얻기 위해서는 다음과 같은 점 연산자를 사용한다:

person1.birthday // birthday라는 이름을 가진 멤버변수이자 Date형의 구조체 변수

구조체 변수 person1.birthday의 멤버 변수는 점과 앞에서 나타낸 person1.birthday.year라는 표현을 생성하는 year 등의 멤버 변수 이름을 더하여 얻어진다.


디스플레이 6.2에서 디스플레이 6.1에서 예금증서에 대한 클래스를 다시 작성했다.

이 새로운 버전은 만기일을 보유하는 구조체 타입 Date의 멤버 변수를 가지고 있다.

또한 단일 balance 멤버 변수를 초기 잔액과 만기 잔액을 제공하는 두 개의 새로운 멤버 변수로 대체했다.

 

디스플레이 6.2 구조체 멤버를 가진 구조체

//Program to demonstrate the CDAccount structure type.
#include <iostream>
using namespace std;

struct Date
{
	int month;
	int day;
	int year;
};

//This is an improved version of the structure CDAccountV1 defined in Display 6.1.
//Improved structure for a bank certificate of deposit:
struct CDAccount
{
	double initialBalance;
	double interestRate;
	int term; //months until maturity
	Date maturity; //date when CD matures
	double balanceAtMaturity;
};

void getCDData(CDAccount& theAccount);
//Postcondition: theAccount.initialBalance, theAccount.interestRate,
//theAccount.term, and theAccount.maturity have been given values
//that the user entered at the keyboard.

void getDate(Date& theDate);
//Postcondition: theDate.month, theDate.day, and theDate.year
//have been given values that the user entered at the keyboard.

int main( )
{
	CDAccount account;
	cout << "Enter account data on the day account was opened:\n";
	getCDData(account);
	double rateFraction, interest;
	rateFraction = account.interestRate / 100.0;
	interest = account.initialBalance*(rateFraction*(account.term / 12.0));
	account.balanceAtMaturity = account.initialBalance + interest;
    
	cout.setf(ios::fixed);
	cout.setf(ios::showpoint);
	cout.precision(2);
	cout << "When the CD matured on "
	     << account.maturity.month << "-" << account.maturity.day
	     << "-" << account.maturity.year << endl
	     << "it had a balance of $"
	     << account.balanceAtMaturity << endl;
	return 0;
}
//uses iostream :
void getCDData(CDAccount& theAccount)
{
	cout << "Enter account initial balance: $";
	cin >> theAccount.initialBalance;
	cout << "Enter account interest rate: ";
	cin >> theAccount.interestRate;
	cout << "Enter the number of months until maturity: ";
	cin >> theAccount.term;
	cout << "Enter the maturity date:\n";
	getDate(theAccount.maturity);
}

//uses iostream:
void getDate(Date& theDate)
{
	cout << "Enter month: ";
	cin >> theDate.month;
	cout << "Enter day: ";
	cin >> theDate.day;
	cout << "Enter year: ";
	cin >> theDate.year;
}

 

샘플 대화 상자

Enter account data on the day account was opened:
Enter account initial balance: $100.00
Enter account interest rate: 10.0
Enter the number of months until maturity: 6
Enter the maturity date:
Enter month: 2
Enter day: 14
Enter year: 1899
When the CD matured on 2-14-1899
it had a balance of $105.00

 

구조체 초기화하기

구조체가 선언될 때 초기화할 수 있다. 구조체 변수에 값을 부여하려면 등호와 괄호 안에 포함된 멤버 값의 목록이 뒤따른다.
예시) 날짜에 대한 구조체의 타입에 대한 다음 정의가 이전 하위 섹션에서 제공되었다:

struct Date
{
    int month;

    int day;
    int year;
};

Date 타입이 정의되면 다음과 같이 dueDate라는 구조체 변수를 선언하고 초기화할 수 있다:

Date dueDate = {12, 31, 2012};

초기화 값은 구조 유형 정의의 구성원 변수 순서에 해당하는 순서로 제공되어야 한다.

예시) dueDate.month =12, dueDate.day = 31, dueDate.year = 2012
struct member보다 initializer 값이 많으면 오류이다.

struct member보다 initializer 값이 적으면 제공된 값을 순서대로 데이터 멤버를 초기화하는 데 사용한다.

initializer가 없는 각 데이터 멤버는 변수에 적합한 유형의 0 값으로 초기화된다.

 

6.2 클래스

클래스 및 멤버 함수 정의

디스플레이 6.3 멤버함수를 가진 클래스

//Program to demonstrate a very simple example of a class.
//A better version of the class DayOfYear will be given in Display 6.4.
#include <iostream>
using namespace std;
//Normally, member variables are private
//and not public, as in this example. This is
//discussed a bit later in this chapter.
class DayOfYear
{
public:
	void output( ); //Member function declaration
	int month;
	int day;
};

int main( )
{
	DayOfYear today, birthday;
	
	cout << "Enter today's date:\n";
	cout << "Enter month as a number: ";
	cin >> today.month;
	cout << "Enter the day of the month: ";
	cin >> today.day;
	cout << "Enter your birthday:\n";
	cout << "Enter month as a number: ";
	cin >> birthday.month;
	cout << "Enter the day of the month: ";
	cin >> birthday.day;
	cout << "Today's date is ";
	today.output( ); //Calls to the member function output
	cout << endl;
	cout << "Your birthday is ";
	birthday.output( ); //Calls to the member function output
	cout << endl;
    
	if (today.month = = birthday.month && today.day = = birthday.day)
		cout << "Happy Birthday!\n";
	else
		cout << "Happy Unbirthday!\n";
	return 0;
}
//Member function definition
//Uses iostream:
void DayOfYear::output( )
{
	switch (month)
	{
	case 1:
		cout << "January "; break;
	case 2:
		cout << "February "; break;
	case 3:
		cout << "March "; break;
	case 4:
		cout << "April "; break;
	case 5:
		cout << "May "; break;
	case 6:
		cout << "June "; break;
	case 7:
		cout << "July "; break;
	case 8:
		cout << "August "; break;
	case 9:
		cout << "September "; break;
	case 10:
		cout << "October "; break;
	case 11:
		cout << "November "; break;
	case 12:
		cout << "December "; break;
	default:
		cout << "Error in DayOfYear::output.";
	}

	cout << day;
}

 

샘플 대화 상자

Enter today's date:
Enter month as a number: 10
Enter the day of the month: 15
Enter your birthday:
Enter month as a number: 2
Enter the day of the month: 21
Today's date is October 15
Your birthday is February 21
Happy Unbirthday!

 

클래스(class): 구조체 유형과 유사한 유형이지만 클래스 유형은 일반적으로 멤버 변수뿐만 아니라 멤버 함수도 가지고 있다. 

예시) 디스플레이 6.3 DayOfYear 클래스

멤버 변수: month, day

멤버 함수: output

public: 접근 지정자(access, specificer)락 불린다. 뒤따르는 멤버들에게 제한이 없다는 것을 의미

 

객체(object): 클래스 유형의 변수 값

객체는 데이터 멤버(data member)와 함수 멤버(function member)를 모두 가지고 있다. 

객체는 멤버 함수의 호출이 가능하기 때문에 상호 작용할 수 있다. 

클래스 유형의 변수는 객체를 값으로 유지한다. 

클래스 유형의 변수는 미리 정의된 유형의 변수와 동일한 방식으로 그리고 구조체 변수와 동일한 방식으로 선언된다.

 

 

멤버 함수 정의
멤버 함수는 Class_Name과 범위 지정 연산자인 ::가 함수 제목에 주어진 것을 제외하고는 다른 함수와 유사하게 정의된다.
SYNTAX
Returned_Type Class_Name:: Function_Name(Parameter_List)
{
Function_Body_Statements
}
EXAMPLE
디스플레이 6.3을 참조하라. 멤버 변수(month, day)는 멤버 함수 정의에서 객체 이름과 점 앞에 나타나지 않는다.

 

점 연산자(dot operator)와 범위 지정 연산자(scope resolution operator)
점 연산자와 범위 지정 연산자는 모두 멤버 이름과 함께 사용되어 멤버가 무엇인지 지정한다.

예를 들어, DayOfYear라는 클래스를 선언하고 today 객체를 다음과 같이 선언했다고 가정한다:
DayOfYear today;
점 연산자를 사용하여 today 객체의 멤버를 지정할 수 있다.

예를 들어 output은 Class DayOfYear(Display 6.3에서 정의됨)의 멤버 함수이며

다음 함수 호출은 today 객체에 저장된 데이터 값을 출력한다:
today.output( );
멤버 함수에 대한 함수 정의를 부여할 때 범위 지정 연산자 ::를 사용하여 클래스 이름을 지정한다.

예를 들어 멤버 함수 output에 대한 함수 정의의 제목은 다음과 같다:
void DayOfYear::output( )
참고로 범위 지정 연산자 ::는 클래스 이름과 함께 사용되고 점 연산자는 해당 클래스의 객체와 함께 사용된다.

 

클래스는 본격적인 유형이다
클래스는 int 및 double과 같은 유형이다. 

클래스 유형의 변수를 가질 수 있고, 

클래스 유형의 매개변수를 가질 수 있고, 

함수는 클래스 유형의 값을 반환할 수 있으며, 

더 일반적으로 다른 유형과 마찬가지로 클래스 유형을 사용할 수 있다.

 

캡슐화

데이터 유형(data type): 값의 집합과 함께 이러한 값에 정의된 기본 연산 집합으로 구성된다. ex) 0, 1, -1, 2, +, -, *, / %...

추상 데이터 유형(abstract data type, 약칭 ADT): 데이터 유형을 사용하는 프로그래머가 값과 연산이 구현되는 방법에 대한 세부 정보에 액세스할 수 없는 경우의 데이터 유형 ex) int와 같이 미리 정의된 유형, 프로그래머 정의 유형인 클래스

 

프로그래머 정의 유형인 클래스도 ADT여야 한다.

즉, "연산"이 어떻게 구현되는지에 대한 세부 정보는

클래스를 사용하는 프로그래머에게 숨겨지거나 적어도 무관해야 한다.

클래스의 연산은 클래스의 (공용) 멤버 함수이다.
클래스를 사용하는 프로그래머도 클래스의 데이터가 어떻게 구현되는지 알 필요가 없어야 한다.

데이터의 구현은 멤버 함수의 구현만큼 숨겨져야 한다.


클래스를 정의하여 멤버 함수의 구현과 객체 내 데이터의 구현이 알려져 있지 않거나 

적어도 클래스를 사용하는 프로그래머와는 무관한 것으로 여러 다른 용어로 알려져 있다.

가장 많이 사용되는 용어는 정보 숨기기(informaton hiding), 데이터 추상화(data abstraction) 및 캡슐화(encapsulation)이며,

이는 각각 클래스 구현의 세부 사항이 클래스를 사용하는 프로그래머에게 숨겨져 있음을 의미한다.

이 원리는 객체 지향 프로그래밍(OOP)의 주요 원리 중 하나이다.

OOP를 논의할 때 가장 자주 사용되는 용어는 캡슐화이다.

이 캡슐화 원리를 클래스 정의에 적용하는 방법 중 하나는

모든 멤버 변수를 비공개로 만드는 것이며, 이는 다음 하위 섹션에서 논의한다.

 


접근 지정자(public, private)

디스플레이 6.3에 주어진 유형의 DayOfYear의 정의를 다시 살펴보라.

해당 클래스를 사용하기 위해서는 month와 day로 명명된 두 개의 int형의 멤버 변수가 있다는 것을 알아야 한다.

이는 이전 서브섹션에서 논의했던 캡슐화(정보 숨기기) 원칙에 위배된다.

 

디스플레이 6.4는 이 캡슐화 원칙을 더 잘 준수하는 클래스 DayOfYear를 다시 작성한 버전이다.
디스플레이 6.4에 단어 private: 와 public: 를 주목하라.

private:이라는 단어 뒤에 오는 모든 항목들(이 경우 멤버 변수는 month, day)은 private라고 하며,

이는 클래스 DayOfYear의 멤버 함수에 대한 정의를 제외하고는 이름으로 참조할 수 없음을 의미한다.

예를 들어 클래스 DayOfYear의 이 변경된 정의로 인해 다음 두 가지 할당 및 기타 표시된 코드는

더 이상 프로그램의 메인 함수에서 허용되지 않으며

클래스 DayOfYear의 멤버 함수를 제외한 다른 함수 정의에서는 허용되지 않다:

 

DayOfYear today; //This line is OK.
today.month = 12; //ILLEGAL
today.day = 25; //ILLEGAL
cout << today.month; //ILLEGAL
cout << today.day; //ILLEGAL
if (today.month = = 1) //ILLEGAL
cout << "January";

 

일단 멤버 변수를 private 멤버 변수로 만들면 멤버 함수 중 하나를 사용하는 것 외에는 값을 변경할 수 있는 방법이 없다.

즉, 컴파일러가 클래스 DayOfYear에 대한 데이터 구현을 숨기도록 강제한다.

디스플레이 6.4의 프로그램을 주의 깊게 살펴보면 멤버 변수 이름의 month과 day가 사용되는 곳은

멤버 함수의 정의에서 유일하게 확인할 수 있다. 

멤버 함수의 정의를 벗어난 곳에는 today.month, today.day, bachBirthday.month 또는 bachBirthday.day에 대한 언급이 없다.
public:(이 경우 멤버 함수)이라는 단어 뒤에 오는 모든 항목은 public이라고 하여 어느 곳이든 이름으로 참조할 수 있다.

public 멤버의 사용에 대한 제한은 없다.
임의의 멤버 변수는 public이거나 private일 수 있다.

임의의 멤버 함수는 public이거나 private일 수 있다.

그러나 일반적인 좋은 프로그래밍 방법은 모든 멤버 변수가 private이어야 하며,

일반적으로 대부분의 멤버 함수는 public일 것을 요구한다.

만약 첫 번째 멤버 그룹에 public:인 것도, private:인 것도 없다면, 그 그룹의 멤버들은 자동적으로 private이 된다.

 

디스플레이 6.4 private 멤버를 가진 클래스

#include <iostream>
#include <cstdlib>
using namespace std;
//This is an improved version of
//the class DayOfYear that
//we gave in Display 6.3.
class DayOfYear
{
public:
	void input( );
	void output( );
	void set( int newMonth, int newDay);
	//Precondition: newMonth and newDay form a possible date.
	void set( int newMonth);
	//Precondition: 1 <= newMonth <= 12
	//Postcondition: The date is set to the first day of the given month.
    
	int getMonthNumber( ); //Returns 1 for January, 2 for February, etc.
	int getDay( );
private:
	int month; //Private members
	int day; //Private members
};

int main( )
{
	DayOfYear today, bachBirthday;
	cout << "Enter today's date:\n";
	today.input( );
	cout << "Today's date is ";
	today.output( );
	cout << endl;
	bachBirthday.set(3, 21);
	cout << "J. S. Bach's birthday is ";
	bachBirthday.output( );
	cout << endl;
    
	if ( today.getMonthNumber( ) = = bachBirthday.getMonthNumber( ) &&
	     today.getDay( ) = = bachBirthday.getDay( ) )
		cout << "Happy Birthday Johann Sebastian!\n";
	else
		cout << "Happy Unbirthday Johann Sebastian!\n";

	return 0;
}
//Note that the function name set is overloaded. You can
//overload a member function just like you can overload any
//other function.
//Mutator function
//Uses iostream and cstdlib:
void DayOfYear::set( int newMonth, int newDay)
{
	if ((newMonth >= 1) && (newMonth <= 12))
		month = newMonth;
	else
	{
		cout << "Illegal month value! Program aborted.\n";
		exit(1);
	}
	if ((newDay >= 1) && (newDay <= 31))
		day = newDay;
	else
	{
		cout << "Illegal day value! Program aborted.\n";
		exit(1);
	}
}
//Mutator function
//Uses iostream and cstdlib:
void DayOfYear::set( int newMonth)
{
	if ((newMonth >= 1) && (newMonth <= 12))
		month = newMonth;
	else
	{
		cout << "Illegal month value! Program aborted.\n";
		exit(1);
	}
	day = 1;
}
//Accessor functions
int DayOfYear::getMonthNumber( )
{
	return month;
}
//Accessor functions
int DayOfYear::getDay( )
{
	return day;
}
//Private members may be used in member
//function definitions (but not elsewhere).
//Uses iostream and cstdlib:
void DayOfYear::input( )
{
	cout << "Enter the month as a number: ";
	cin >> month; 
	cout << "Enter the day of the month: ";
	cin >> day;
	if ((month < 1) || (month > 12) || (day < 1) || (day > 31))
	{
		cout << "Illegal date! Program aborted.\n";
		exit(1);
	}
}

void DayOfYear::output( )
<The rest of the definition of DayOfYear::output is given in Display 6.3 .>

샘플 대화 상자

Enter today's date:
Enter the month as a number: 3
Enter the day of the month: 21
Today's date is March 21
J. S. Bach's birthday is March 21
Happy Birthday Johann Sebastian!


접근자와 설정자

당신은 항상 클래스의 모든 멤버 변수를 private로 만들어야 한다.

그러나 클래스 객체에 있는 데이터에 대해 때때로 무언가를 해야 할 수도 있다.

멤버 함수는 당신이 객체에 있는 데이터에 대해 많은 일을 할 수 있게 해주겠지만,

멤버 함수가 없는 데이터에 대해 당신은 조만간 무언가를 하고 싶거나 할 필요가 있을 것이다.

당신은 객체에 있는 데이터에 대해 어떻게 새로운 일을 할 수 있을까?

 

답은 당신의 클래스에 적절한 접근자(accessor)와 설정자(mutator) 함수를 갖춘다면

당신이 합리적으로 원하는 모든 일을 할 수 있다는 것이다.

이것들은 당신이 객체에 있는 데이터에 접근하고 매우 일반적인 방법으로 변경할 수 있게 해주는 멤버 함수이다.

 

접근자(accessor): 데이터를 읽을 수 있는 함수 

예시) 디스플레이 6.4의 멤버 함수 getMonthNumber와 getDay

접근자는 문자 그대로 각 멤버 변수의 값을 반환할 필요는 없지만 해당 값과 동등한 값을 반환해야 한다.

예시) DayOfYear와 같은 클래스의 경우 액세스 함수가

숫자로 월을 반환하는 대신 해당 월의 이름을 문자열 값으로 반환하도록 할 수 있다.

 

설정자(mutator):사용하면 데이터를 변경할 수 있는 함수

예시 디스플레이 6.4의 set이라는 이름의 두 함수

 

접근자의 경우 get이라는 단어가 포함된 이름을 사용하고

설정자의 경우 set이라는 단어가 포함된 이름을 사용하는 것이 전통적이다.
클래스 정의는 항상 적절한 접근자 및 설정자 모음을 제공해야 한다.
접근자 및 설정자가 멤버 변수를 private으로 만드는 목적을 포기하는 것처럼 보일 수 있지만, 그렇지 않다.

 

예시)

그림 6.4에 설정된 설정자에 주목하라.

월 구성원 변수를 13으로 설정하거나 한 달을 나타내지 않는 숫자로 설정할 수 없다.

마찬가지로 일 구성원 변수를 1에서 31까지의 범위에 속하지 않는 숫자로 설정할 수 없다.

변수가 공개된 경우 데이터를 날짜에 대해 의미가 없는 값으로 설정할 수 있다.

 

설정자를 사용하면 데이터의 변경 사항을 제어하고 필터링할 수 있다.


팁: 별도의 인터페이스 및 구현

캡슐화의 원리는 클래스를 사용하는 프로그래머가 수업을 어떻게 구현하는지

세세한 부분까지 신경 쓸 필요가 없도록 클래스를 정의해야 한다는 것이다.

클래스를 사용하는 프로그래머는 클래스를 어떻게 사용하는지에 대한 규칙만 알면 된다.

 

인터페이스(interface) 혹은 API: 클래스를 어떻게 사용하는지에 대한 규칙

이니셜 API는 응용 프로그래머 인터페이스(application programmer interface) 혹은

추상적 프로그래밍 인터페이스(abstract programming interface)이다.

이 책에서는 이러한 규칙을 클래스를 위한 인터페이스라고 부를 것이다.

 

인터페이스와 클래스의 구현 사이의 명확한 구분을 염두에 두어야 한다.

만약 여러분의 클래스가 잘 설계되어 있다면

클래스를 사용하는 프로그래머라면

클래스의 위한 인터페이스만 알면 되고

클래스의 구현에 대한 어떤 세부 사항도 알 필요가 없다.

ADT(Abstract Data Type): 인터페이스와 구현이 분리된 클래스, 또는 잘 캡슐화된 클래스

 

C++ 클래스의 경우 인터페이스는 두 가지 종류로 구성된다:

1) 코멘트(comment): 보통 클래스의 정의의 시작에서 객체의 데이터가 무엇을 나타내야 하는지 알려준다.

2) 클래스의 public 멤버 함수: public 멤버 함수를 사용하는 방법을 알려주는 코멘트가 포함되어 있다.

잘 설계된 클래스에서는 프로그램에서 클래스를 사용하기 위해 클래스의 인터페이스만 알면 된다.

클래스의 구현(implementation)은 클래스 인터페이스가 C++ 코드로 어떻게 구현되는지를 알려준다.

구현은 클래스의 private 멤버와 public 멤버 함수 및 private 멤버 함수의 정의로 구성된다.

클래스를 사용하는 프로그램을 실행하기 위해서는 구현이 필요하지만,

클래스를 사용하는 나머지 프로그램을 작성하기 위해서는 구현에 대해 아무것도 알 필요가 없다.

 

클래스의 인터페이스와 구현을 완전히 분리함으로써 얻을 수 있는 가장 분명한 이점은

프로그램의 다른 부분을 변경하지 않고도 구현을 변경할 수 있다는 것이다.

대규모 프로그래밍 프로젝트에서 인터페이스와 구현 사이의 이러한 구분은

다른 프로그래머가 작업을 분할하는 것을 용이하게 할 것이다.

잘 설계된 인터페이스가 있으면 한 프로그래머가 클래스에 대한 구현을 작성하고

다른 프로그래머들이 클래스를 사용하는 코드를 작성할 수 있다.

 

팁: 캡슐화 테스트

클래스 정의가 ADT를 생성하는 경우

(즉, 인터페이스와 구현을 적절하게 구분하는 경우)

클래스 정의를 사용하는 프로그램의 코드를 변경할 필요 없이 클래스의 구현을 변경할 수 있다

(즉, 데이터 표현을 변경하거나 일부 멤버 함수의 구현을 변경할 수 있다). 

이 테스트는 ADT를 정의했는지 또는 제대로 캡슐화되지 않은 일부 클래스를 정의했는지에 대한 확실한 테스트이다.
예를 들어, 디스플레이 6.4의 클래스 DayOfYear 구현을 다음과 같이 변경할 수 있으며 

이 클래스 정의를 사용하는 프로그램은 변경할 필요가 없다:

 

class DayOfYear
{
    public:
    void input( );
    void output( );


    void set( int newMonth, int newDay);
    //Precondition: newMonth and newDay form a possible date.
    //Postcondition: The date is reset according to the
    //arguments.


    void set( int newMonth);
    //Precondition: 1 <= newMonth <= 12
    //Postcondition: The date is set to first day of the month.

 

    int getMonthNumber( );
    //Returns 1 for January, 2 for February, etc.
    int getDay( );
private:
    char firstLetter; //of month
    char secondLetter;//of month
    char thirdLetter;//of month
    int day;
};

 

이 버전에서 한 달은 이름의 첫 세 글자(예: January의 'J', 'a', 'n')로 표시된다.

멤버 함수도 물론 다시 써야 하지만, 이전과 똑같이 행동하기 위해 다시 쓸 수 있다. 

예를 들어, getMonthNumber 함수의 정의는 다음과 같이 시작할 수 있다:

 

int DayOfYear::getMonthNumber( )
{
    if (firstLetter = = 'J' && secondLetter = = 'a'
    && thirdLetter = = 'n')
        return 1;
    if (firstLetter = = 'F' && secondLetter = = 'e'
    && thirdLetter = = 'b')
        return 2;
    ...

 

구조체 대 클래스

구조체는 일반적으로 모든 멤버 변수가 공개되고 멤버 함수가 없는 상태에서 사용된다.

하지만 C++에서 구조체는 공개 멤버 변수와 공개 멤버 함수와 비공개 멤버 함수를 모두 가질 수 있다.

C++ 구조체는 표기법의 차이를 제외하고 클래스가 할 수 있는 모든 것을 할 수 있다.

 

구조체와 클래스의 한 가지 차이점은 public 또는 private 접근 지정자가 없는 초기 멤버 그룹을 다루는 방법이 다르다는 것이다.

정의에서 첫 번째 멤버 그룹에 public: 또는 private:가 라벨로 지정되지 않으면

구조체는 그룹이 public이라고 가정하는 반면 클래스는 그룹이 private라고 가정한다.

 

클래스 및 객체
클래스는 변수가 멤버 변수와 멤버 함수를 모두 가질 수 있는 유형이다.
클래스 정의의 구문은 다음과 같다.
SYNTAX
class Class_Name
{

                           .
public: //public 멤버
    Member_Specification_N+1
    Member_Specification_N+2

                           .
                           .
                           .
private: // private 멤버
    Member_Specification_1
    Member_Specification_2

                           .
                           .
                           .

    Member_Specification_N
}; //세미콜론 잊지 말기
각 Member_Specification_i는 Member 변수 선언 또는 Member 함수 선언(prototype)이다.

추가 public: 및 private: 섹션이 허용된다.

첫 번째 Member 그룹에 public: 또는 private: 라벨이 없으면 첫 번째 그룹 앞에 private:가 있는 것과 같다.
EXAMPLE
class Bicycle
{
public:
    char getColor( );
    int numberOfSpeeds( );
    void set( int theSpeeds, char theColor);
private:
    int speeds;
    char color;
};
클래스가 정의되면 객체 변수(클래스 유형의 변수)는 다른 유형의 변수와 동일한 방식으로 선언될 수 있다. 

예를 들어, 다음은 Bicycle 유형의 두 객체 변수를 선언한다:
Bicycle myBike, yourBike;

 

팁: 객체 생각하기

클래스로 프로그래밍을 할 때는 알고리즘이 아니라 데이터가 중심을 잡는다.

하지만 데이터를 알고리즘에 맞게 설계하는 것이 아니라 알고리즘을 데이터에 맞게 설계한다.

많은 사람들이 최고의 스타일이라고 여기는 극단적인 경우에는 전역 함수가 전혀 없고 멤버 함수가 있는 클래스만 존재한다.

이 경우 데이터 위에서 작동하는 알고리즘이 아니라 객체를 정의하고 객체가 상호 작용하는 방식을 정의한다.

 

'프로그래밍 공부 > OOP' 카테고리의 다른 글

8장 오버로딩, friend, 그리고 참조 연산자  (1) 2023.11.25
7장 생성자와 다른 도구  (1) 2023.11.24
5장 배열  (0) 2023.11.21
4장 매개변수와 오버로딩  (0) 2023.11.05
3장 함수 기초  (1) 2023.11.04