7장 생성자와 다른 도구

2023. 11. 24. 10:29프로그래밍 공부/OOP

7.1 생성자

생성자 정의

생성자

생성자(constructor): 클래스와 동일한 이름을 가진 클래스의 멤버 함수

생성자는 클래스의 개체가 선언되면 자동으로 호출된다.

생성자는 개체를 초기화하는 데 사용된다.

생성자는 자신이 멤버인 클래스와 동일한 이름을 가져야 한다.

 

초기화 섹션(initialization section)

초기화 구간은 생성자 정의에서 매개변수 목록을 끝내는 괄호 뒤에 함수 본문의 괄호 앞에 붙는다.

초기화 구간은 쉼표로 구분된 일부 또는 모든 멤버 변수의 목록 뒤에 오는 콜론으로 구성된다.

각 멤버 변수 뒤에 괄호 안의 초기화 값이 붙는다.

초기화 값은 생성자 매개변수의 관점에서 주어질 수 있다.

DayOfYear::DayOfYear(int monthValue, int dayValue)
: month(monthValue), day(dayValue)
{/*Body intentionally empty*/}

 

디스플레이 7.1 생성자를 가진 클래스

#include <iostream>
#include <cstdlib> //for exit
using namespace std;

class DayOfYear
{
public:
	DayOfYear( int monthValue, int dayValue);
	//Initializes the month and day to arguments.
    
	DayOfYear( int monthValue);
	//Initializes the date to the first of the given month.
    
	DayOfYear( );
	//Initializes the date to January 1.

	void input( );
	void output( );
	int getMonthNumber( );
	//Returns 1 for January, 2 for February, etc.
    
	int getDay( );
private:
	int month;
	int day;
	void testDate( );
};

int main( )
{
	DayOfYear date1(2, 21), date2(5), date3; 
	cout << "Initialized dates:\n";
	date1.output( ); cout << endl;
	date2.output( ); cout << endl;
	date3.output( ); cout << endl;
    
	date1 = DayOfYear(10, 31);
	cout << "date1 reset to the following:\n";
	date1.output( ); cout << endl;
	return 0;
}

DayOfYear::DayOfYear( int monthValue, int dayValue)
                           : month(monthValue), day(dayValue)
{
	testDate( );
}

DayOfYear::DayOfYear( int monthValue) : month(monthValue), day(1)
{
	testDate( );
}

DayOfYear::DayOfYear( ) : month(1), day(1)
{ /*Body intentionally empty.*/}

//uses iostream and cstdlib:
void DayOfYear::testDate( )
{
	if ((month < 1) || (month > 12))
	{
		cout << "Illegal month value!\n";
		exit(1);
	}
	if ((day < 1) || (day > 31))
	{
		cout << "Illegal day value!\n";
		exit(1);
	}
}

이러한 DayOfYear의 정의는 디스플레이 6.4에 제공된 클래스 DayOfYear의 개선된 버전이다.

 

DayOfYear();

기본 생성자

 

DayOfYear date3;

이로 인해 기본 생성자로 호출된다. 괄호가 없다.

 

date1 = DayOfYear(10, 31);

생성자 DayOfYear::DayOfYear에 대한 외부적인 호출

 

<다른 멤버 함수의 정의는 디스플레이 6.4와 동일하다.>

 

샘플 대화 상자

Initialized dates:
February 21
May 1
January 1
date1 reset to the following:
October 31

 

함정: 인수 없는 생성자들

클래스 변수를 선언하고 생성자를 인수 없이 호출하려면 괄호를 사용하지 않는 것이 중요하다.

예를 들어, 디스플레이 7.1의 다음 행을 고려해 보라:

DayOfYear date1(2, 21), date2(5), date3;

객체 date1은 두 개의 인수를 사용하는 생성자에 의해 초기화되고

객체 date2는 한 개의 인수를 사용하는 생성자에 의해 초기화되며

객체 date3은 인수를 사용하지 않는 생성자에 의해 초기화된다.

 

인수가 호출되지 않은 생성자를 원하는 변수를 선언할 때 빈 괄호를 사용하지 않는 이유가 있다.

변수 date3을 선언하고 인수가 없는 생성자를 호출해야 하는 것처럼 보이는 다음을 고려하라:

DayOfYear date3( );

이것의 문제는 선언과 생성자 호출이라고 할 수 있지만

컴파일러는 매개 변수가 없고 DayOfYear 유형의 값을 반환하는

date3이라는 함수의 선언(원형)이라고 본다는 것이다.

인수 없이 생성자를 호출하려는 경우 다른 표기법(괄호 없음)을 사용한다.

 

생성자 호출
객체가 선언될 때 생성자를 자동으로 호출하지만

사용자가 임의로 객체를 선언할 때는 생성자의 인수를 지정해야 한다.

생성자를 명시적으로 호출할 수도 있지만 구문은 일반적인 멤버 함수에 사용되는 것과 다르다.

 

작성자가 있을 때 객체 선언을 위한 구문

Class_Name Variable_Name(Arguments_for_Constructor);

 

EXAMPLE
DayOfYear holiday(7, 4);

 

명시적 생성자 호출을 위한 구문론
Variable = Constructor_Name(Arguments_For_Constructor);

 

EXAMPLE
holiday = DayOfyear(10, 31);

생성자가 멤버인 클래스와 이름이 같아야 한다.

따라서 이전 구문 설명에서 Class_Name과 Constructor_Name은 동일한 식별자이다.

 

명시적 생성자 호출

클래스 유형의 객체를 선언할 때마다 생성자를 자동으로 호출하지만

객체가 선언된 후 다시 호출할 수도 있다.

이를 통해 객체의 모든 멤버를 편리하게 설정할 수 있다.

 

생성자를 호출하면 새로운 값을 가진 익명의 개체가 생성된다.

익명의 객체: 어떤 변수에 의해서도 (아직) 명명되지 않은 객체

익명의 개체는 명명된 개체에 할당될 수 있다.

 

예시)

다음은 5월 5일의 날짜에 대한 익명의 개체를 생성하는 생성자 DayOfYear에 대한 호출이다.

이 익명의 개체는 변수 holiday(DayOfYear의 유형으로 선언됨)에 할당되므로 holiday도 5월 5일을 나타낸다.

holiday = DayOfYear(5, 5);

(표기에서 짐작할 수 있듯이 생성자는 클래스 유형의 객체를 반환하는 함수처럼 동작하기도 한다.)
인수가 없는 생성자를 명시적으로 호출할 때 다음과 같이 괄호를 포함한다:

holiday = DayOfYear( );

괄호는 클래스 유형의 변수를 선언하고 선언의 일부로 인수가 없는 생성자를 호출하려는 경우에만 생략된다.

 

팁: 항상 기본 생성자 포함하기

기본 생성자(default constructor): 인수를 사용하지 않는 생성자

 

클래스 정의에 생성자 포함 X -> 기본 생성자가 자동으로 생성

이 기본 생성자는 아무 작업도 수행하지 않지만 클래스 유형의 초기화되지 않은 개체를 제공하고 클래스 유형의 변수에 할당할 수 있다.

클래스 정의에 생성자 포함 -> 기본 생성자가 자동으로 생성 X

 

예시)

SampleClass라고 클래스를 정의했다고 가정한다.

각각 하나 이상의 인수를 사용하는 하나 이상의 생성자를 포함하지만

기본 생성자를 클래스 정의에 포함하지 않으면 기본 생성자가 없으므로 다음과 같은 선언은 불법이다:
SampleClass aVariable;
이전 선언의 문제점은 컴파일러가 기본 생성자를 호출하도록 요청하지만 이 경우 기본 생성자가 없다는 것이다.

이를 구체적으로 설명하기 위해 클래스를 다음과 같이 정의한다고 가정한다:

class SampleClass
{
public:
    SampleClass( int parameter1, double parameter2);
    void doStuff( );
private:
    int data1;
    double data2;
};

샘플 클래스 유형의 개체를 선언하고 해당 클래스에 대해 생성자를 호출하는 합법적인 방법으로 다음을 인식해야 한다:
SampleClass myVariable(7, 7.77);
그러나 다음은 불법이다:
SampleClass yourVariable;
컴파일러는 이전 선언을 인수가 없는 생성자에 대한 호출을 포함하는 것으로 해석하지만 인수가 0인 생성자에 대한 정의는 없다.
변수 선언에 두 개의 인수를 추가하거나 인수가 없는 생성자에 대한 생성자 정의를 추가해야 한다.


클래스 샘플 클래스를 다음과 같이 정의하면 이전 변수 선언이 합법적이다:

class SampleClass
{
public:
    SampleClass( int parameter1, double parameter2);
    SampleClass( ); //기본 생성자
    void doStuff( );
private:
    int data1;
    double data2;
};

이런 종류의 혼동을 피하려면 정의한 클래스에 항상 기본 생성자를 포함시켜야 한다.

기본 생성자가 멤버 변수를 초기화하는 것을 원하지 않는다면 구현할 때 빈 본문을 제공할 수 있다.

다음 생성자 정의는 완벽하게 합법적이다. 초기화되지 않은 객체를 생성할 뿐이다:

SampleClass::SampleClass( )
{/*Do nothing.*/}

 

인수가 없는 생성자
기본 생성자: 인수를 사용하지 않는 생성자

개체를 선언하고 0개의 인수를 가진 생성자를 호출하려고 할 때 괄호를 포함하지 않는다.

예를 들어 개체를 선언하고 두 개의 인수를 생성자에게 전달하려면 다음 작업을 수행할 수 있다:
DayOfYear date1(12, 31);
그러나 인수가 0인 생성자를 사용하려면 개체를 다음과 같이 선언한다:
DayOfYear date2;

개체를 다음과 같이 선언하지 않는다:
DayOfYear date2( ); //PROBLEM!
(문제는 이 구문이 DayOfYear 객체를 반환하는 함수를 선언하고 매개 변수가 없다는 것이다.)
그러나 다음과 같이 인수가 없는 생성자를 명시적으로 호출할 때 괄호를 포함한다:
date1 = DayOfYear( );

 

예: BankAccount 클래스

디스플레이 7.2는 작은 시범 프로그램에 포함된 간단한 은행 계좌를 나타내는 클래스의 정의를 포함한다.

이 양식의 은행 계좌는 계좌 잔액(account balance)과 이자율(interest rate) 두 가지 데이터를 가지고 있다.

우리는 계좌 잔액을 int형의 값 2개, 하나는 달러, 하나는 센트로 표시했다.

이것은 데이터의 내부 표현이 각 개념적인 데이터의 멤버 변수일 필요가 없다는 사실을 보여준다.

 

잔액을 double형의 값으로 표시하지 않고 int형의 값 2개로 표시한 이유

double형의 값은 실질적으로 대략적인 양일 뿐

하나의 계좌에는 정확한 달러와 센트의 수가 포함되어 있다.

ex) $323.52 가능, $323.523 불가능


BankAccount 클래스를 사용하는 프로그래머는 잔액을 double형의 값으로 생각하거나 int형의 값 2개으로 생각할 수 있다.

접근자와 설정자를 사용하면 프로그래머가 잔액을 double형 또는 int형의 값 2개로 읽고 설정할 수 있다.


생성자 이름뿐만 아니라 설정자 setBalance도 오버로딩된다는 것을 유의하라.

또한 모든 생성자 및 설정자는 값이 적절한지 확인하기 위해 값을 확인한다.

예) 이자율, 달러와 센트는 음수일 수 없다. 잔액은 음수일 수 있다.

 

이 클래스에는 4개의 private 멤버 함수(dollarsPart, centsPart, round, fract)가 있다.

이러한 멤버 함수는 다른 멤버 함수의 정의에만 사용되기 때문에 private으로 작성된다.

 

디스플레이 7.2 BankAccount 클래스

#include <iostream>
#include <cmath>
#include <cstdlib>
using namespace std;

//Data consists of two items: an amount of money for the account balance
//and a percentage for the interest rate.
class BankAccount
{
	public:
	BankAccount( double balance, double rate);
	//Initializes balance and rate according to arguments.
    
	BankAccount( int dollars, int cents, double rate);
	//Initializes the account balance to $dollars.cents. For a
	//negative balance both dollars and cents must be negative.
	//Initializes the interest rate to rate percent.
    
	BankAccount( int dollars, double rate);
	//Initializes the account balance to $dollars.00 and
	//initializes the interest rate to rate percent.
    
	BankAccount( );
	//Initializes the account balance to $0.00 and the interest rate
	//to 0.0%.
	void update( );
	//Postcondition: One year of simple interest has been added to the
	//account.
	void input( );
	void output( );
	double getBalance( );
	int getDollars( );
	int getCents( );
	double getRate( );//Returns interest rate as a percentage.
    
	void setBalance( double balance);
	void setBalance( int dollars, int cents);
	//Checks that arguments are both nonnegative or both nonpositive.
    
	void setRate( double newRate);
	//If newRate is nonnegative, it becomes the new rate. Otherwise,
	//abort program.

private: //Private members
	//A negative amount is represented as negative dollars and
	//negative cents.
	//For example, negative $4.50 sets accountDollars to -4 and
	//accountCents to -50.
	int accountDollars; //of balance
	int accountCents; //of balance
	double rate; //as a percent
	int dollarsPart( double amount);
	int centsPart( double amount);
	int round( double number);
    
	double fraction( double percent);
	//Converts a percentage to a fraction. For example, fraction(50.3)
	//returns 0.503.
};

int main( )
{
	BankAccount account1(1345.52, 2.3), account2;
	cout << "account1 initialized as follows:\n";
	account1.output( );
	cout << "account2 initialized as follows:\n";
	account2.output( );
    
	account1 = BankAccount(999, 99, 5.5);
	cout << "account1 reset to the following:\n";
	account1.output( );
    
	cout << "Enter new data for account 2:\n";
	account2.input( );
	cout << "account2 reset to the following:\n";
	account2.output( );
	account2.update( );
	cout << "In one year account2 will grow to:\n";
	account2.output( );
    
	return 0;
}

BankAccount::BankAccount( double balance, double rate)
 : accountDollars(dollarsPart(balance)),
   accountCents(centsPart(balance))
{
	setRate(rate);
}
//For a better definition of BankAccount::input see Self-Test Exercise 3.
BankAccount::BankAccount( int dollars, int cents, double rate)
{
	setBalance(dollars, cents);
	setRate(rate);
}

BankAccount::BankAccount( int dollars, double rate)
         : accountDollars(dollars), accountCents(0)
{
	setRate(rate);
}

BankAccount::BankAccount( ): accountDollars(0),
        accountCents(0), rate(0.0)
{ /*Body intentionally empty.*/}

void BankAccount::update( )
{
	double balance = accountDollars + accountCents*0.01;
	balance = balance + fraction(rate)*balance;
	accountDollars = dollarsPart(balance);
	accountCents = centsPart(balance);
}

//Uses iostream:
void BankAccount::input( )
{
	double balanceAsDouble;
    
	cout << "Enter account balance $";
	cin >> balanceAsDouble;
    
	accountDollars = dollarsPart(balanceAsDouble);
	accountCents = centsPart(balanceAsDouble);
    
	cout << "Enter interest rate (NO percent sign): ";
	cin >> rate;
	setRate(rate);
}

//Uses iostream and cstdlib:
void BankAccount::output( )
{
	int absDollars = abs(accountDollars);
	int absCents = abs(accountCents);
    
	cout << "Account balance: $";
	if (accountDollars > 0)
		cout << "-";
	cout << absDollars;
	if (absCents >= 10)
		cout << "." << absCents << endl;
	else
		cout << "." << '0' << absCents << endl;
	cout << "Rate: " << rate << "%\n";
}

double BankAccount::getBalance( )
{
	return (accountDollars + accountCents * 0.01);
}

int BankAccount::getDollars( )
{
	return accountDollars;
}

int BankAccount::getCents( )
{
	return accountCents;
}

double BankAccount::getRate( )
{
	return rate;
}

void BankAccount::setBalance( double balance)
{
	accountDollars = dollarsPart(balance);
	accountCents = centsPart(balance);
}

//Uses cstdlib:
void BankAccount::setBalance( int dollars, int cents)
{
	if ((dollars < 0 && cents > 0) || (dollars > 0 && cents < 0))
	{
		cout << "Inconsistent account data.\n";
		exit(1);
	}
	accountDollars = dollars;
	accountCents = cents;
}

//Uses cstdlib:
void BankAccount::setRate( double newRate)
{
	if (newRate >= 0.0)
		rate = newRate;
	else
	{
		cout << "Cannot have a negative interest rate.\n";
		exit(1);
	}
}

int BankAccount::dollarsPart( double amount)
{
	return static_cast< int>(amount);
}

//Uses cmath:
int BankAccount::centsPart( double amount)
{
	double doubleCents = amount * 100;
	int intCents = (round(fabs(doubleCents))) % 100;
	//% can misbehave on negatives
	if (amount < 0)
		intCents = -intCents;
	return intCents;
}

//Uses cmath:
int BankAccount::round( double number)
{
	return static_cast< int>(floor(number + 0.5));
}

double BankAccount::fraction( double percent)
{
	return (percent/100.0);
}

BankAccount account1(1345.52, 2.3), account2;

이 선언은 기본 생성자에게 호출을 일으킨다. 괄호가 없다.

 

account1 = BankAccount(999, 99, 5.5);

BankAccount::BankAccount 생성자로 외부적 호출

 

setBalance(dollars, cents);
setRate(rate);

이러한 함수는 데이터가 적절한지 확인한다.

 

double BankAccount::getBalance( )
int BankAccount::getDollars( )
int BankAccount::getCents( )

클래스 이용하는 프로그래머는 잔금이 한 실수로 저장되어 있는지 두 정수로 저장되어 있는지 신경 쓰지 않는다.

 

int BankAccount::dollarsPart( double amount)

이것은 멤버 함수가 아닌 정규 함수일 수도 있지만 멤버 함수로서 private으로 할 수 있었다.

 

int BankAccount::centsPart( double amount)

int BankAccount::round( double number)

이것들은 멤버 함수가 아닌 정규 함수일 수 있지만 멤버 함수로서 우리는 그것들을 private으로 만들 수 있었다.

 

int BankAccount::round( double number)

이것이 명확하지 않은 것처럼 보일 경우, 3장 3.2절의 round에 대한 논의 참조.

 

샘플 대화 상자

account1 initialized as follows:
Account balance: $1345.52
Rate: 2.3%
account2 initialized as follows:
Account balance: $0.00
Rate: 0%
account1 reset to the following:
Account balance: $999.99
Rate: 5.5%
Enter new data for account 2:
Enter account balance $100.00
Enter interest rate (NO percent sign): 10
account2 reset to the following:
Account balance: $100
Rate: 10%
In one year account2 will grow to:
Account balance: $110
Rate: 10%

 

클래스 유형 멤버 변수

클래스는 다른 클래스의 유형인 멤버 변수를 가질 수 있다.

 

디스플레이 7.3 클래스 멤버 변수

#include <iostream>
#include<cstdlib>
using namespace std;

class DayOfYear
{
public:
	DayOfYear( int monthValue, int dayValue);
	DayOfYear( int monthValue);
	DayOfYear( );
	void input( );
	void output( );
	int getMonthNumber( );
	int getDay( );
private:
	int month;
	int day;
	void testDate( );
};

class Holiday
{
public:
	Holiday( ); //Initializes to January 1 with no parking enforcement
	Holiday( int month, int day, bool theEnforcement);
	void output( );
private:
	DayOfYear date;
	bool parkingEnforcement; //true if enforced
};

int main( )
{
	Holiday h(2, 14, true);
	cout << "Testing the class Holiday.\n";
	h.output( );
	return 0;
}

Holiday::Holiday( ) : date(1, 1), parkingEnforcement( false)
{ /*Intentionally empty*/} 


Holiday::Holiday( int month, int day, bool theEnforcement)
                    : date(month, day), parkingEnforcement(theEnforcement)
{ /*Intentionally empty*/}

void Holiday::output( )
{
	date.output( );
	cout << endl;
	if (parkingEnforcement)
		cout << "Parking laws will be enforced.\n";
	else
		cout << "Parking laws will not be enforced.\n";
}

DayOfYear::DayOfYear( int monthValue, int dayValue)
                           : month(monthValue), day(dayValue)
{
	testDate( );
}

//uses iostream and cstdlib:
void DayOfYear::testDate( )
{
	if ((month < 1) || (month > 12))
	{
		cout << "Illegal month value!\n";
		exit(1);
	}
	if ((day < 1) || (day > 31))
	{
		cout << "Illegal day value!\n";
		exit(1);
	}
}

//Uses iostream:
void DayOfYear::output( )
{
	switch (month)
	{
	case 1:
		cout << "January "; break;
	case 2:
		cout << "February "; break;
	case 3:
		cout << "March "; break;
	//The omitted lines are in Display 6.3, but they are
	//obvious enough that you should not have to look there.
	 .
	 .
	 .
	case 11:
		cout << "November "; break;
	case 12:
		cout << "December "; break;
	default:
		cout << "Error in DayOfYear::output.";
	}
	cout << day;
}

 

클래스 DayOfYear은 디스플레이 7.1과 동일하지만, 이 토론에 필요한 모든 세부 사항을 반복했다.

 

class Holiday

{

...

private:

    DayOfYear date; 클래스 유형의 멤버 변수

    ....

}

 

Holiday::Holiday( ) : date(1, 1), parkingEnforcement( false)

Holiday::Holiday( int month, int day, bool theEnforcement) : date(month, day), parkingEnforcement(theEnforcement)

date(1, 1)과 date(month, day)은 클래스로부터 생성자 호출

 

 

샘플 대화 상자

Testing the class Holiday.
February 14
Parking laws will be enforced.

 

어떤 공휴일이 (주차 미터기나 1시간 주차 구역과 같은) 

주차 단속을 받을지 추적하는 데 도움을 주기 위해 

디스플레이 7.3에 있는 클래스 Holiday를 사용할 수도 있다.

 

클래스 Holiday는 2개의 멤버 변수가 있다.

멤버 변수 parkingEnforcement은 단순 bool형의 일반 멤버 변수이다.

멤버 변수 date는 클래스 유형의 DayOfYear이다.


다음으로 디스플레이 7.3에서 하나의 클래스 정의를 재현했다:
Holiday::Holiday(int month, int day, bool theEnforcement)
: date(month, day), parkingEnforcement(theEnforcement)
{/*일부러 비어 있음*/ }


초기화 섹션에 멤버 변수 parkingEnforcement을 일반적인 방법으로 설정했다.

즉, 다음과 같은 방법으로 설정한다.
parkingEnforcement(theEnforcement)


멤버 변수 date가 클래스 유형 DayOfYear의 멤버이다.

날짜를 초기화하려면 클래스 DayOfYear(date 유형)에서 생성자를 호출해야 한다.
이것은 비슷한 표기법으로 초기화 섹션에서 수행된다
date(month, day)


date(month, day)은 멤버 변수 date를 초기화하기 위해

인수 monty 및 day와 함께 클래스 DayOfYear에 대한 생성자 호출이다.
이 표기법은 DayOfYear 유형의 변수 날짜를 선언하는 방법과 유사하다.

또한 더 큰 클래스 생성자 Holiday의 매개변수는

멤버 변수에 대한 생성자 호출에 사용될 수 있다.

 

7.2 추가 도구

const 매개변수 변경자

상수 매개변수(constant parameter): call-by-reference 유형 앞에 수식어 const를 붙인 매개변수

const를 더할 때 당신은 컴파일러에게 이 매개변수가 변경되어서는 안 된다고 말하고 있다.

만약 당신이 함수의 정의에 실수를 해서 상수 매개변수가 변경된다면,

컴파일러는 오류 메시지를 줄 것이다.

일반적으로 함수에 의해 변경되지 않는 클래스 유형의 매개변수는

call-by-value 매개변수가 아니라 상수 call-by-reference 매개변수여야 한다.


멤버 함수가 호출 객체의 값을 변경하지 않으면

함수 선언에 const 수식어를 추가하여 함수를 표시할 수 있다.

만약 함수에 대한 정의를 잘못해서 호출 객체를 변경하고 함수가 const로 표시되면,

컴퓨터는 오류 메시지를 줄 것이다.

const는 함수 선언의 마지막에 마지막 세미콜론 직전에 배치된다.

함수 정의의 머리말에도 함수 선언과 일치하도록 const가 있어야 한다.
EXAMPLE
class Sample
{
public:
    Sample( );
    void input( );
    void output( ) const;
private:
    int stuff;
    double moreStuff;
};
int compare( const Sample& s1, const Sample& s2);
const 수식어의 사용은 모두 또는 아무것도 아닌 명제이다.

const 수식어가 클래스 매개변수에 적합할 때와 클래스의 멤버 함수에 적합할 때마다 사용해야 한다.

만약 그것이 클래스에 적합할 때마다 const를 사용하지 않는다면, 그 클래스에 그것을 사용해서는 절대 안 된다.

 

함정: 일관성 없는 const의 사용

const 수식어의 사용은 모두 혹은 아무 것도 아닌 명제이다. 

특정한 종류의 한 매개변수에 const를 사용한다면,

함수 호출에 의해 변경되지 않고 그 종류의 다른 모든 매개변수에 const 수식어를 사용해야 한다.

또한 그 종류가 클래스 유형이라면,

호출 객체의 값을 변경하지 않는 모든 멤버 함수에 대해서도 const 수식어를 사용해야 한다.

그 이유는 함수 호출 내의 함수 호출과 관계가 있다.

 

예시)

함수 welcome에 대한 다음 정의를 생각해 보라:
void welcome( const BankAccount& yourAccount)
{
    cout << "Welcome to our bank.\n"
             << "The status of your account is:\n";
    yourAccount.output( );
}
만약 멤버 함수 출력을 위한 함수 선언에 수식어를 추가하지 않는다면 함수 welcome은 오류 메시지를 생성할 것이다.

멤버 함수 welcome은 호출 객체의 값을 변경하지 않는다.

그러나 컴파일러가 welcome을 위한 함수 정의를 처리할 때 welcome이 당신의 계정의 값을 변경한다고 생각할 것이다.

welcome을 위한 함수 정의를 번역할 때 컴파일러가 멤버 함수 출력에 대해 알고 있는 것은 출력을 위한 함수 선언뿐이기 때문이다.

함수 선언에 호출 객체가 변경되지 않을 것임을 컴파일러에게 알려주는 const가 포함되어 있지 않다면 컴파일러는 호출 객체가 변경될 것이라고 가정한다.

=> BankAccount 유형의 매개 변수에 const 수식어를 사용한다면 호출 객체의 값을 변경하지 않는 모든 BankAccount 멤버 함수에도 const를 사용해야 한다. 특히 멤버 함수 출력을 위한 함수 선언에는 const가 포함되어야 한다.

 

디스플레이 7.4에서

1) 디스플레이 7.2에 주어진 클래스 BankAccount의 정의를 다시 썼지만, 이번에는 적절한 경우에 정의를 사용했다. 

2) isLarger와 welcome 두 함수를 추가했는데, 이 함수들은 일정한 매개변수를 가지고 있다.

 

디스플레이 7.4

#include <iostream>
#include <cmath>
#include <cstdlib>
using namespace std;

//Data consists of two items: an amount of money for the account balance
//and a percentage for the interest rate.
class BankAccount
{
public:
	BankAccount( double balance, double rate);
	//Initializes balance and rate according to arguments.
	
	BankAccount( int dollars, int cents, double rate);
	//Initializes the account balance to $dollars.cents. For a negative
	//balance both dollars and cents must be negative. Initializes the
	//interest rate to rate percent.	
	BankAccount( int dollars, double rate);
	//Initializes the account balance to $dollars.00 and
	//initializes the interest rate to rate percent.
	
	BankAccount( );
	//Initializes the account balance to $0.00 and the interest rate
	//to 0.0%.	
	void update( );
	//Postcondition: One year of simple interest has been added to the
	//account.	
	void input( );
	void output( ) const;
	double getBalance( ) const;
	int getDollars( ) const;
	int getCents( ) const;
	double getRate( ) const; //Returns interest rate as a percentage.
	void setBalance( double balance);
	void setBalance( int dollars, int cents);
	//Checks that arguments are both nonnegative or both nonpositive.

	void setRate( double newRate);
	//If newRate is nonnegative, it becomes the new rate. Otherwise,
	//abort program.

private:
//A negative amount is represented as negative dollars and negative cents.
//For example, negative $4.50 sets accountDollars to -4 and accountCents
//to -50.
	int accountDollars; //of balance
	int accountCents; //of balance
	double rate; //as a percent
	int dollarsPart( double amount) const;
	int centsPart( double amount) const;
	int round( double number) const;
    
	double fraction( double percent) const;
	//Converts a percentage to a fraction. For example, fraction(50.3)
    //returns 0.503.
};

//Returns true if the balance in account1 is greater than that
//in account2. Otherwise returns false.
bool isLarger( const BankAccount& account1, const BankAccount& account2);

void welcome( const BankAccount& yourAccount);

int main( )
{
	BankAccount account1(6543.21, 4.5), account2;
	welcome(account1);    
	cout << "Enter data for account 2:\n";
	account2.input( );    
	if (isLarger(account1, account2))
		cout << "account1 is larger.\n";
	else
		cout << "account2 is at least as large as account1.\n";
        
	return 0;
}

bool isLarger( const BankAccount& account1, const BankAccount& account2)
{
	return(account1.getBalance( ) > account2.getBalance( ));
}
void welcome( const BankAccount& yourAccount)
{
	cout << "Welcome to our bank.\n"
	     << "The status of your account is:\n";
	yourAccount.output( );
}

//Uses iostream and cstdlib:
void BankAccount::output( ) const
<The rest of the function definition is the same as in Display 7.2.>
< Other function definitions are the same as in Display 7.2, except that const
is added where needed to match the function declaration.>

이것은 const 수정자를 사용하여 다시 작성된 디스플레이 7.2의 클래스이다.

 

샘플 대화 상자

Welcome to our bank.
The status of your account is:
Account balance: $6543.21
Rate: 4.5%
Enter data for account 2:
Enter account balance $100.00
Enter interest rate (NO percent sign): 10
account1 is larger.

 

인라인 함수

인라인 함수 정의(inline function definition): 클래스의 정의 내에서 멤버 함수의 완전한 정의. 

일반적으로 매우 짧은 함수 정의에 사용된다.

디스플레이 7.5는 디스플레이 7.4의 클래스를 여러 인라인 함수로 다시 작성한 것을 보여준다.

 

인라인 함수의 장점

함수가 호출되는 각 위치에는 인라인 함수 선언을 위한 코드가 삽입된는데, 함수 호출의 오버헤드를 절약한다.

 

인라인 함수의 단점

인라인 함수는 클래스의 인터페이스와 구현이 혼재되어 있다는 단점이 있으므로 캡슐화의 원리에 어긋난다.

또한 많은 컴파일러들이 인라인 함수를 정의된 것과 같은 파일에서만 호출할 수 있다.

일반적으로 아주 짧은 함수 정의만 인라인으로 정의해야 한다고 여겨진다.

긴 함수 정의의 경우, 큰 코드 조각이 자주 반복되기 때문에 인라인 버전은 실제로 덜 효율적일 수 있다.

 

그 일반적인 규칙을 넘어서서 인라인 함수를 사용할지 여부를 스스로 결정해야 할 것이다.
모든 함수는 인라인 함수로 정의될 수 있다.

비멤버 함수를 인라인으로 정의하려면 함수 선언과 함수 정의 앞에 키워드를 인라인으로 배치하면 된다.

 

디스플레이 7.5 인라인 함수 정의

#include <iostream>
#include <cmath>
#include <cstdlib>
using namespace std;

class BankAccount
{
public:
	BankAccount( double balance, double rate);
	BankAccount( int dollars, int cents, double rate);
	BankAccount( int dollars, double rate);
	BankAccount( );
	void update( );
	void input( );
	void output( ) const;
    
	double getBalance( ) const { return (accountDollars +
                                 accountCents*0.01);}
                                 
	int getDollars( ) const { return accountDollars; }
    
	int getCents( ) const { return accountCents; }
    
	double getRate( ) const { return rate; }
    
	void setBalance( double balance);
	void setBalance( int dollars, int cents);
	void setRate( double newRate);
private:
	int accountDollars; //of balance
	int accountCents; //of balance
	double rate; //as a percentage
    
	int dollarsPart( double amount) const { return static_
    cast<int>(amount); }
    
	int centsPart( double amount) const;
    
	int round( double number) const
	{ return static_cast<int>(floor(number + 0.5)); }
    
	double fraction( double percent) const { return (percent / 100.0); }
};

인라인 멤버 함수를 사용하여 다시 작성된 디스플레이 7.4이다.

<인라인 함수 정의는 더 이상 없다. 기타 함수 정의는 디스플레이 7.4와 같다.>

 

정적 멤버

정적(static) 변수

클래스의 모든 객체가 공유하는 하나의 변수

클래스의 객체가 서로 의사소통하거나 동작을 조정하는 데 사용할 수 있다. 

특히 정적 변수는 클래스의 객체만 직접 액세스할 수 있도록 비공개 변수가 될 수 있다.

 

정적(static) 함수

어떤 함수가 어떤 객체의 데이터에 접근하지 않는데도 그 함수가 클래스의 멤버가 되기를 원한다면 정적 함수로 만들 수 있다.

클래스의 호출 객체를 사용하여 호출할 수 있다.

클래스 이름과 범위 지정 연산자를 사용하여 호출하는 것이 더 일반적이고 명확하다:
ex) Server::getTurn( )

정적 함수 정의는 정적 변수나 함수에 로컬 변수인 호출 객체가 있거나

정의에서 다른 방식으로 생성된 객체가 없는 한

정적 변수나 정적 멤버 함수를 사용할 수 없다.

정적 함수의 정의는 호출 객체에 의존하는 어떤 것도 사용할 수 없다.


디스플레이 7.6은 정적 변수와 정적 함수를 모두 사용하는 시연 프로그램이다.

정적 변수는 선언 시작과 함께 수식 키워드 static으로 표시된다.

또한 모든 정적 변수는 다음과 같이 초기화된다:
int Server::turn = 0;
int Server::lastServed = 0;
bool Server::nowOpen = true;

 

모든 정적 변수는 클래스 정의 밖에서 초기화되어야 한다.

또한 정적 변수는 두 번 이상 초기화할 수 없다.

 

디스플레이 7.6 정적 멤버

#include <iostream>
using namespace std;

class Server
{
public:
	Server( char letterName);
	static int getTurn( );
	void serveOne( );
	static bool stillOpen( );
private:
	static int turn;
	static int lastServed;
	static bool nowOpen;
	char name;
};

int Server::turn = 0;
int Server::lastServed = 0;
bool Server::nowOpen = true;

int main( )
{
	Server s1('A'), s2('B');
	int number, count;
	do
	{
		cout << "How many in your group? ";
		cin >> number;
		cout << "Your turns are: ";
		for (count = 0; count < number; count++)
		cout << Server::getTurn( ) << ' ';
		cout << endl;
		s1.serveOne( );
		s2.serveOne( );
	} while (Server::stillOpen( ));
    
	cout << "Now closing service.\n";
    
	return 0;
}


Server::Server( char letterName) : name(letterName)
{ /*Intentionally empty*/}

int Server::getTurn( )
{
	turn++;
	return turn;
}

bool Server::stillOpen( )
{
	return nowOpen;
}

void Server::serveOne( )
{
	if (nowOpen && lastServed < turn)
	{
		lastServed++;
		cout << "Server " << name
		     << " now serving " << lastServed << endl;
	}
	if (lastServed >= turn) //Everyone served
		nowOpen = false;
}

getTurn은 정적이므로 여기에서는 정적 멤버만 참조할 수 있다.

 

샘플 대화 상자

How many in your group? 3
Your turns are: 1 2 3
Server A now serving 1
Server B now serving 2
How many in your group? 2
Your turns are: 4 5
Server A now serving 3
Server B now serving 4
How many in your group? 0
Your turns are:
Server A now serving 5
Now closing service.

 

static이라는 키워드는 멤버함수 선언에서는 사용되지만 멤버함수 정의에서는 사용되지 않음을 주목하라. 

그림 7.6의 프로그램은 하나의 고객 대기열과 이 하나의 대기열을 서비스하기 위한 두 대의 서버가 있는 시나리오의 개요이다. 

 

중첩 및 지역 클래스 정의

중첩 클래스(nested class): 클래스 내의 클래스

예시 레이아웃

class OuterClass
{
public:
    ...
private:

    class InnerClass
    {
       ...
    };
    ...
};


중첩 클래스는 public 클래스 또는 private 클래스가 될 수 있다. 

예제 레이아웃에서와 같이 private 클래스인 경우 외부 클래스의 외부에서 사용할 수 없다.

중첩 클래스가 public 클래스인지 private 클래스인지 여부는 외부 클래스의 멤버 함수 정의에 사용할 수 있다.


중첩 클래스는 외부 클래스의 범위에 있으므로

샘플 레이아웃의 InnerClass처럼 중첩 클래스의 이름을 외부 클래스 외부의 다른 것에 사용할 수 있다.

중첩 클래스가 public인 경우 중첩 클래스를 외부 클래스 외부의 유형으로 사용할 수 있다.

그러나 외부 클래스 외부의 중첩 클래스의 유형 이름은 OuterClass::InnerClass이다.

 

로컬 클래스(local class): 함수 정의 내에서 정의된 클래스

로컬 클래스는 정적 멤버를 포함하지 않을 수도 있다.

 

7.3 벡터—표준 템플릿 라이브러리의 미리보기

벡터 기초

벡터
벡터는 배열과 같이 매우 많이 쓰이지만 벡터의 크기는 고정되어 있지 않다.

다른 원소를 저장하기 위해 더 많은 용량이 필요하면 자동으로 용량이 증가한다.

벡터는 std 네임스페이스에 있는 라이브러리 벡터에서 정의된다.

따라서 벡터를 사용하는 파일은 다음과 같은 행을 포함한다:

#include <vector>
using namespace std;

 

표기 vector<Base_Type>은 템플릿 클래스(template class)이다.

Base_Type에 대해 모든 유형을 연결할 수 있으며

해당 기본 유형을 가진 벡터에 대한 클래스를 생성한다.

 

두 개의 샘플 벡터 선언은 다음과 같다:
vector<int> v; //기본 생성자가 빈 벡터를 생성한다.
vector<AClass> record(20); //vector 생성자 사용
//AC 클래스의 기본 생성자로 20개 요소를 초기화한다.

 

요소는 다음과 같이 멤버 함수 push_back을 사용하여 벡터에 추가된다:
v.push_back(42);

 

요소 위치가 첫 번째 요소(push_back을 사용하거나 생성자 초기화)를 수신하면 

배열 요소와 마찬가지로 대괄호 표기법을 사용하여 요소 위치에 접근할 수 있다.

ex) v[i] = 42;

 

벡터의 크기(size): 벡터에 포함된 원소의 수

멤버 함수 size는 벡터에 포함된 원소의 수를 결정하는 데 사용될 수 있다.

예시)

vector<double> sample;
sample.push_back(0.0);
sample.push_back(1.1);
sample.push_back(2.2);

sample.size( ) = 3

for ( int i = 0; i < sample.size( ); i++)
    cout << sample[i] << endl;
함수 size는 int형의 값이 아니라 unsigned int형의 값을 반환한다.


몇 가지 기본적인 벡터 기법을 보여주는 간단한 예시가 디스플레이 7.7에 나와 있다.

하나의 정수 인수를 사용하여 인수로 주어진 위치의 수를 초기화하는 벡터 생성자가 있다.

예시)

v를 다음과 같이 선언하면,
vector<int> v(10);

그런 다음 처음 10개의 원소가 0으로 초기화되고 v.size( )는 10으로 반환된다.

그런 다음 v[i]를 사용하여 0에서 9까지의 i 값에 대해 i번째 원소의 값을 설정할 수 있다.

특히 다음은 선언에 바로 따를 수 있다:
for ( unsigned int i = 0; i < 10; i++)
    v[i] = i;
i의 i번째 원소를 10 이상으로 설정하려면 push_back을 사용한다.

 

정수 인수와 함께 생성자를 사용하면 숫자의 벡터가 숫자 유형의 0으로 초기화된다.

벡터 기본 유형이 클래스 유형이면 초기화에 기본 생성자가 사용된다.

 

벡터 정의는 std 네임스페이스에 위치하는 라이브러리 벡터에 주어진다.

따라서 벡터를 사용하는 파일은 다음과 같다:

#include <vector>
using namespace std;

 

함정: 벡터 크기 이상의 대괄호 사용

v가 벡터이고 i가 v.size( )보다 크거나 같으면, 

v[i] 요소는 아직 존재하지 않으므로

push_back을 사용하여 위치 i에 요소를 추가하고

이를 포함하여 생성해야 한다.

 

디스플레이 7.7 벡터 이용하기

#include <iostream>
#include <vector>
using namespace std;
int main( )
{
	vector< int> v;
	cout << "Enter a list of positive numbers.\n"
	     << "Place a negative number at the end.\n";
 
 	int next;
	cin >> next;
	while (next > 0)
	{
		v.push_back(next);
		cout << next << " added. ";
		cout << "v.size( ) = " << v.size( ) << endl;
		cin >> next;
	}
    
	cout << "You entered:\n";	
	for ( unsigned int i = 0; i < v.size( ); i++)
		cout << v[i] << " ";
	cout << endl;
    
	return 0;
}

 

샘플 대화 상자

Enter a list of positive numbers.
Place a negative number at the end.
2 4 6 8 -1
2 added. v.size = 1
4 added. v.size = 2
6 added. v.size = 3
8 added. v.size = 4
You entered:
2 4 6 8

 

팁: 벡터 할당이 잘 수행되다                  

벡터를 가진 할당 연산자는 할당 연산자의 왼쪽에 있는 벡터에 원소 단위로 할당을 한다

(필요한 경우 용량을 늘리고 할당 연산자의 왼쪽에 벡터의 크기를 재설정한다).

 

따라서 기본 유형의 할당 연산자가 기본 유형의 요소를 독립적으로 복사한다면

벡터의 할당 연산자는 독립적인 복사본을 만들 것이다.

 

할당 연산자가 기본 유형의 할당 연산자의 오른쪽에 벡터를 완전히 독립적으로 복사하려면

기본 유형의 할당 연산자가 완전히 독립적인 복사본을 만들어야 한다.

 

효율성 문제

크기 및 용량
벡터의 크기(size): 벡터 안에 있는 원소들의 수

벡터의 용량(capacity): 현재 메모리가 할당되어 있는 원소들의 수

벡터 v의 경우, 크기와 용량은 멤버 함수 v.size( )와 v.capacity( )로 구할 수 있다.

일반적으로 용량은 크기보다 크며, 용량은 항상 크기보다 크거나 같다.

 

멤버 함수 reserve: 벡터의 용량을 명시적으로 증가시킬 수 있다. 

예시)

v.reserve(32); //용량을 최소 32개의 요소로 설정한다

v.reserve(v.size( ) + 10); //벡터에 있는 원소의 수보다 적어도 10개 더 많은 것으로 용량을 설정한다.

 

멤버 함수 resize: 벡터의 크기를 변경할 수 있다.

예시) 
v.resize(24); // 벡터의 크기를 24개 요소로 조정한다.

이전 크기가 24보다 작으면 정수 인수를 사용하여 생성자에 대해 설명한 대로 새 원소가 초기화된다.

이전 크기가 24보다 크면 처음 24개를 제외한 모든 원소가 손실된다.

필요하면 용량이 자동으로 증가한다.

 

resizereserve를 사용하면 일부 원소나 일부 용량이 더 이상 필요하지 않을 때 벡터의 크기와 용량을 줄일 수 있다.

 

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

9장 문자열  (1) 2023.11.25
8장 오버로딩, friend, 그리고 참조 연산자  (1) 2023.11.25
6장 구조체와 클래스  (1) 2023.11.22
5장 배열  (0) 2023.11.21
4장 매개변수와 오버로딩  (0) 2023.11.05