10.3~10.10
10.3 함수에 객체 전달
지금까지 primitve type, array, string을 인수로 함수에 전달하는 법을 배웠는데 객체 type도 함수로 전달할 수 있습니다. 객체는 call of value, call of reference 모두 가능하지만 참조에 의한 객체 전달이 더욱 효율적입니다.
call of value 예제
리스트 10.3 ▼
#include <iostream>
#include "Circle.h" // 9장의 Circle.h
using namespace std;
void printCircle(Circle c) {
cout << "The area of the circle of " << c.getRadius() << " is "
<< c.getArea() << endl;
}
int main()
{
Circle myCircle(5.0);
printCircle(myCircle);
return 0;
}
함수 내 객체 c는 myCircle 객체와 독립적인 객체입니다.
//그럼 Circle c는 빈 객체임? 비효율적이지 않나?
call of reference 예제
리스트 10.4 ▼
#include <iostream>
#include "Circle.h" // 9장의 Circle.h
using namespace std;
void printCircle(Circle& c) {
cout << "The area of the circle of " << c.getRadius() << " is "
<< c.getArea() << endl;
}
int main()
{
Circle myCircle(5.0);
printCircle(myCircle);
return 0;
}
myCircle 객체의 참조가 printCircle함수에 전달되고 함수 내 객체 c는 main함수에 있는 myCircle객체의 별명(alias)가 됩니다. c와 myCircle은 종속적입니다.
check point)
10.8) 객체를 함수에 전달하기 위해 참조에 의한 전달이 효율적인 이유는 무엇인가?
값에 의한 전달은 새로운 객체를 만들고 거기에 객체를 대입해야 하기 때문이다. 또, 값을 직접 변경하지 않고 복사해온 값만 변경할 수 있기 때문에 return값이나 포인터로 전달해주어야 한다.
10.9) 다음 코드의 출력은 무엇인가?
#include <iostream>
using namespace std;
class Count {
public:
int count;
Count(int c) {
count = c;
}
Count() {
count = 0;
}
};
void increment(Count c, int times) {
c.count++;
times++;
}
int main()
{
Count myCount;
int times = 0;
for (int i = 0; i < 100; i++) {
increment(myCount, times);
}
cout << "myCount.count is " << myCount.count << endl;
cout << "times is " << times;
return 0;
}
10.10) 만일 체크 포인트 10.9에서 14번째 줄 코드가 다음과 같이 변경되는 경우, 출력은 무엇인가?
void increment(Count& c, int times)
10.11) 만일 체크 포인트 10.9에서 14번째 줄 코드 가 다음과 같이 변경되는 경우, 출력은 무엇인가?
void increment(Count &c, int& times)
10.12) 만일 체크 포인트 10.9에서 14번째 줄 코드를 다음과 같이 변경할 수 있는가?
void increment(const Count &c, int times)
없다. 15번째 줄 c.count++;에서 c는 const 개체를 통해 액세스되고 있으므로 수정할 수 없다고 출력된다.
10.4 객체의 배열
기본 자료형(primitive type) 배열이나 문자열의 생성에 대해 배웠는데 객체도 배열을 생성할 수 있습니다. 객체의 배열은 인수없는 생성자로 초기화할 경우, Circle circleArray1[10];으로 선언할 수 있고 인수 있는 생성자로 초기화하고 싶을 경우에는 Circle circleArray2[3] = {Circle(3), Circle(4), Circle(5)};으로 초기화할 수 있습니다. 요소 하나당 하나의 객체가 선언됩니다. circleArray2[0].getRadius()는 1을 반환합니다.
리스트 10.5
// 클래스 배열도 매개변수로 전달되면 주소로 전달되나?
#include <iostream>
#include <iomanip>
#include "Circle.h"
using namespace std;
//원의 면적 더하기
double sum(Circle circleArray[], int size) {
//sum 초기화
double sum = 0;
//면적을 sum에 더하기
for (int i = 0; i < size; i++) {
sum += circleArray[i].getArea();
}
return sum;
}
//원의 배열과 전체 면적 출력
void printCircleArray(Circle circleArray[], int size) {
cout << setw(35) << left << "Radius" << setw(8) << "Area" << endl;
for (int i = 0; i < size; i++) {
cout << setw(35) << left << circleArray[i].getRadius()
<< setw(8) << circleArray[i].getArea() << endl;
}
cout << "-------------------------------------" << endl;
//결과 및 계산 출력
cout << setw(35) << left << "The total area of circles is"
<< setw(8) << sum(circleArray, size) << endl;
}
int main()
{
const int SIZE = 10;
//radius 1을 갖는 객체 생성
Circle circleArray[SIZE];
for (int i = 0; i < SIZE; i++) {
circleArray[i].setRadius(i + 1);
}
printCircleArray(circleArray, SIZE);
return 0;
}
checkpoint)
10.13) 10개의 string 객체의 배열 선언은 어떻게 하는가?
string arry[10];
10.14) 다음 코드의 출력은 무엇인가?
int main()
{
string cities[] = {"Atlanta", "Dallas", "Sacannah"};
cout << cities[0] << endl;
cout << cities[1] << endl;
return
}
Atlanta
Dallas
10.5 인스턴스 멤버와 정적 멤버
클래스 내에서 사용되는 데이터 필드를 intance data field라고 합니다. 인스턴스 변수는 같은 클래스의 객체들 사이에 공유되지 않고 각각 특정 객체에 제한적입니다.
만약 클래스의 모든 인스턴스가 데이터를 공유하도록 하려면 클래스 변수(class variable)로 알려진 정적 변수(static variable)을 사용하면 됩니다. static int 변수이름; 으로 사용되고 private에 선언해도 정상적으로 동작합니다. 한 객체가 정적 변수의 값을 변경하면 같은 클래스의 모든 객체가 영향을 받습니다.
또, 정적함수도 있습니다. 정적함수는 static int 변수이름(인수); 형식으로 쓰이고 헤더 파일과 구현파일이 나뉜 정상적인 파일 이라면 구현 파일에서는 int 변수이름(인수){}로 씁니다.
정적 함수를 사용할 때는 Circle::getNumberOfObjects() 나 circle1. getNumberOfObjects()를 사용할 수 있습니다. 하지만 정적함수를 사용했다는 것이 눈에 바로 들어오므로 Circle::getNumberOfObjects()을 쓰는 것이 좋습니다.
//Circle.h
#ifndef Circle_H
#define Circle_H
class Circle
{
public:
Circle();
Circle(double);
double getArea();
double getRadius();
void setRadius(double newRadius);
static int getNumberOfObjects();
private:
double radius;
static int numberOfObjects;
};
#endif
//Circle.cpp
#include "Circle.h"
double PI = 3.141592;
int Circle::numberOfObjects = 0;
Circle::Circle() {
radius = 1;
numberOfObjects++;
}
Circle::Circle(double newRadius) {
radius = newRadius;
numberOfObjects++;
}
double Circle::getArea() {
return radius * radius * PI;
}
double Circle::getRadius() {
return radius;
}
void Circle::setRadius(double newRadius) {
radius = newRadius;
}
int Circle::getNumberOfObjects() {
return numberOfObjects;
}
//main파일
#include <iostream>
#include <iomanip>
#include "Circle.h"
using namespace std;
int main()
{
cout << "Number of circle objects created: "
<< Circle::getNumberOfObjects() << endl;
Circle circle1;
cout << "The area of the circle of radius "
<< circle1.getRadius() << " is " << circle1.getArea() << endl;
cout << "Number of circle objects created: "
<< Circle::getNumberOfObjects() << endl;
Circle circle2(5.0);
cout << "The area of the circle of radius "
<< circle2.getRadius() << " is " << circle2.getArea() << endl;
cout << "Number of circle objects created: "
<< Circle::getNumberOfObjects() << endl;
circle1.setRadius(3.3);
cout << "The area of the circle of radius "
<< circle1.getRadius() << " is " << circle1.getArea() << endl;
cout << "Number of circle objects created: "
<< Circle::getNumberOfObjects() << endl;
return 0;
}
checkpoint)
10.15) 데이터 필드와 함수는 인스턴스 또는 정적으로 선언될 수 있다. 그것을 결정하는 기준은 무엇인가?
특정 인스턴스에 대해 독립적인 경우 정적 변수나 함수로 선언할 수 있습니다.
10.16) 정적 데이터 필드 초기화는 어디에서 하는가?
헤더 파일에서
10.17) 함수 f()는 클래스 c에서 정적으로 정의되어 있고, c는 C클래스의 객체다. c.f()나 C::f(),c::f()를 호출할 수 있는가?
모두 가능하다.
10.6) 상수 멤버 함수
멤버 함수가 객체 내의 데이터 필드를 변경하지 못하도록 상수 멤버함수로 지정할 수 있습니다. class의 인터페이스파일과 구현파일 모두 뒤에 const를 붙여주면 됩니다. 오직 인스턴스 멤버함수만 상수 함수로 정의될 수 있습니다. 함수 내에서 데이터 필드 값이 실수로 변경된다면 컴파일 오류가 발생할 것입니다.
방어적 프로그래밍(defensive programming)을 위해 인스턴스 get함수는 항상 상수 멤버 함수로 정의해야 합니다.
//circle.h
#ifndef Circle_H
#define Circle_H
class Circle
{
public:
Circle();
Circle(double);
double getArea() const;
double getRadius() const;
void setRadius(double newRadius);
static int getNumberOfObjects();
private:
double radius;
static int numberOfObjects;
};
#endif
//Circle.cpp
double Circle::getArea() const {
return radius * radius * PI;
}
double Circle::getRadius() const {
return radius;
}
만약 함수가 매개변수로 전달된 객체를 const로 받는다면, 그 함수 안에서 사용되는 인스턴스멤버함수를 상수로 정의해야 합니다. 하지 않으면 컴파일 오류가 발생합니다.
//main파일
#include <iostream>
#include <iomanip>
#include "Circle.h"
using namespace std;
void printCircle(const Circle& c) {
cout << "The area of the circle of " << c.getRadius()
<< " is " << c.getArea() << endl;
}
int main()
{
Circle circle1;
printCircle(circle1);
return 0;
}
checkpoint)
10.18) "오직 인스턴스 멤버 함수만 상수 함수로 정의 할 수 있다."는 참인가 거짓인가?
참
10.19) 다음 클래스 정의에서 잘못된 부분은 무엇인가?
class Count
{
public:
int count;
Count(int c){
count = c;
}
Count(){
count =0;
}
int getCount() const
{
return count;
}
void incrementCount() const
{
count++;
}
}
void incrementCount() const{count++;}에서 멤버 변수를 변경한다.
10.20 다음 코드에서 잘못된 부분은 무엇인가?
#include <iostream>
using namespace std;
class A
{
public:
A();
double getNumber();
private:
double number;
};
A ::A()
{
number = 1;
}
double A::getNumber ()
{
return number;
}
void printA(const A& a){
cout<<"The number is" << a.getNumber() << endl;
}
int main(){
A myObject;
printA(myObject);
return 0;
}
printA가 전달된 A객체를 변경하지 않기 때문에 const A& a로 객체를 받았습니다. 이때, printA안에서 객체를 이용하는 모든 멤버 함수는 const로 설정되어야 합니다. 선언부와 구현부 모두 const로 설정해야 합니다.
옳게 바꾼 코드 ▼
#include <iostream>
using namespace std;
class A
{
public:
A();
double getNumber() const;
private:
double number;
};
A::A()
{
number = 1;
}
double A::getNumber() const
{
return number;
}
void printA(const A& a) {
cout << "The number is" << a.getNumber() << endl;
}
int main() {
A myObject;
printA(myObject);
return 0;
}
10.7) 객체에서의 고려사항
객체를 사용함으로써 소프트웨어의 재사용성을 계산할 수 있습니다.
BMI 계산 프로그램 ▼
//main.cpp
#include <iostream>
#include "BMI.h"
using namespace std;
int main() {
BMI bmi1("John Doe", 18, 145, 70);
cout << "The BMI for" << bmi1.getName() << " is " << bmi1.getBMI() << " "
<< bmi1.getStatus() << endl;
BMI bmi2("Susan King", 215, 70);
cout << "The BMI for" << bmi2.getName() << " is " << bmi2.getBMI() << " "
<< bmi2.getStatus() << endl;
return 0;
}
//BMI.h
#include <iostream>
#include <string>
using namespace std;
#ifndef BMI_H
#define BMI_H
class BMI {
public:
BMI(string newName, int newAge, double newWeight, double newHeight);
BMI(string newName, double newWeight, double newHeight);
double getBMI() const;
string getStatus() const;
string getName() const;
int getAge() const;
double getWeight() const;
double getHeight() const;
private:
string name;
int age;
double weight;
double height;
};
#endif
//BMI.cpp
#include "BMI.h"
BMI::BMI(string newName, int newAge, double newWeight, double newHeight){
name = newName;
age = newAge;
weight = newWeight;
height = newHeight;
}
BMI::BMI(string newName, double newWeight, double newHeight){
name = newName;
weight = newWeight;
height = newHeight;
}
double BMI:: getBMI() const {
const double KILOGRAM_PER_POUND = 0.45359237;
const double METERS_PER_INCH = 0.0254;
double bmi = weight * KILOGRAM_PER_POUND / ((height * METERS_PER_INCH) * (height * METERS_PER_INCH));
return bmi;
}
string BMI:: getStatus() const {
double bmi = getBMI();
if (bmi < 18.5)
return "Underweight";
else if (bmi < 25)
return "Normal";
else if (bmi < 30)
return "Overweight";
else
return "Obese";
}
string BMI::getName() const { return name; }
int BMI::getAge() const { return age; }
double BMI::getWeight() const { return weight; }
double BMI::getHeight() const { return height; }
checkpoint)
10.21) 다음 코드의 출력은 무엇인가?
#include <iostream>
#include "BMI.h"
using namespace std;
int main() {
string name("John Doe");
BMI bmi1(name, 18, 145, 70);
name[0] = 'P';
cout << "name from bmil.getName() is " << bmi1.getName() << endl;
cout << "name is " << name << endl;
return 0;
}
name from bmil.getName() is John Doe
name is Pohn Doe
10.22) 다음 코드에서 main함수에 있는 a.s와 b.k의 출력은 무엇인가?
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A() {
s = "John";
}
string s;
};
class B {
public:
B() {
k = 4;
}
int k;
};
int main() {
A a;
cout << a.s << endl;
B b;
cout << b.k << endl;
return 0;
}
John
4
10.23) 다음 코드에서 잘못된 부분은 무엇인가?
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A() {}
string s("abc");
};
int main() {
A a;
cout << a.s << endl;
return 0;
}
string s("abc")
클래스 안에서는 초기화가 불가능하다. 클래스를 선언함과 동시에 초기화하고 싶다면 클래스 선언과 동시에 호출되는 생성자를 이용하는 것이 좋다.
10.24) 다음 코드에서 잘못된 부분은 무엇인가?
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A() {}
string s;
};
int main() {
A a;
cout << a.s << endl;
return 0;
}
a.s
s가 초기화되지 않았습니다. a.s를 사용하여 아무것도 불러올 수 없습니다.
10.8) 객체 합성(composition)
위에서 정의한 BMI클래스는 string 데이터 필드를 포함합니다. BMI 클래스와 string 사이의 관계를 composition(합성) 관계라고 합니다.
합성은 집합(aggregation) 관계 중 특수한 경우를 말합니다. aggregation 모델은 has-a 관계이고 UML 다이어그램에서는 다이아몬드로 나타냅니다. 객체를 소유하고 있는 객체(owner object)를 aggregating 객체라고 하고 소유당하는 객체를 aggregated 객체라고 합니다. 여기 각 학생(student)은 한개의 이름(name)과 한개의 주소(address)만 가질 수 있고 각가의 주소는 3명의 학생에게 공유되는 예제가 있습니다. name과 address는 aggregated 객체이고 student는 aggregating 객체입니다. name과 student는 두 객체 간에 서로 소유관계이기 때문에 composition(합성) 관계라고도 할 수 있습니다.
관계에 포함된 각 클래스는 다중성(multiplicity)를 지정할 수 있습니다. 다중성은 클래스의 많은 객체가 관계에 어떻게 포함되는 지를 나타내는 수 또는 간격(interval)이라고 할 수 있습니다.
집합은 같은 클래스의 객페 사이에서도 표현될 수 있습니다. a person has a supervisor 예제에서
class Person{
private:
Person supervisor; //데이터 유형이 클래스 자신
}
한명의 사람(person)이 여러 명의 관리자(supervisor)를 포함하고 싶다면 배열을 사용하면 됩니다.
class Person{
private:
Person supervisor[10]; //데이터 유형이 클래스 자신
}
checkpoint)
10.25) 객체 합성이란 무엇인가?
두 객체 간에 서로 소유 관계(has a)인 것을 말한다.
10.26) 집합과 합성의 차이점은 무엇인가?
좁은 의미의 합성은 집합의 일종으로 두 객체 간에 서로 소유관계인 것을 말한다.
10.27) 집합과 합성의 UML 표기법은 어떻게 되는가?
has a
10.28) 집합과 합성 모두를 합성이라고 하는 이유는 무엇일까?
둘 모두 유사한 방법으로 클래스를 사용하여 표현된다.
10.9) 예제: StackOfIntegers 클래스
//main파일
#include <iostream>
#include "StackOfIntegers.h"
using namespace std;
int main() {
StackOfIntegers stack;
for (int i = 0; i < 10; i++) {
stack.push(i);
}
while (!stack.isEmpty()) {
cout << stack.pop() << " ";
}
return 0;
}
//StackOfIntegers.cpp
#include "StackOfIntegers.h"
StackOfIntegers::StackOfIntegers() {
//for문으로 배열 0으로 초기화하는 것도 괜찮음
size = 0;
}
bool StackOfIntegers::isEmpty() const {
if (size == 0)return true;
else return false;
}
int StackOfIntegers::peek() const {
return elements[size - 1];
}
void StackOfIntegers::push(int value) {
elements[size] = value;
size++;
}
int StackOfIntegers::pop() {
return elements[--size];
}
int StackOfIntegers::getsize() const{
return size;
}
//StackOfIntegers.h
#pragma once
class StackOfIntegers {
public:
StackOfIntegers();
bool isEmpty() const;
int peek() const;
void push(int value);
int pop();
int getsize() const;
private:
int elements[100];
int size;
};
checkpoint)
10.29) 스택이 생성될 때 elements 배열의 초기값은 무엇인가?
쓰레기값으로 아무렇게나 설정된다.
10.30) 스택이 생성될 때 변수 size의 값은 무엇인가?
생성자에 의해 0으로 초기화된다.
10.10) 클래스 설계 지침
1)결합성(cohesion)
클래스는 단일 요소를 설명하고 클래스의 모든 동작은 분명한 목적을 제공해야 합니다. 너무 많은 동작을 수행하거나 너무 많은 정보를 담고 있는 단일 요소는 여러 개의 클래스로 분리하는 것이 좋습니다.예를 들어 학생과 교수에 대한 정보를 하나의 클래스로 만드는 것보다 각각의 클래스(총 2개)로 만드는 것이 권장됩니다.
2)일관성(Consistency)
표준 프로그래밍 형식과 이름 명명 형식을 따릅니다.
유사 동작을 수행하는 함수는 일관성있게 이름을 짓습니다. 함수 오버로딩을 사용하는 것도 좋습니다.
일반적으로 기본 인스턴스를 구성하기 위해 public에 인수없는 생성자를 일관성있게 사용합니다. (자동으로 만들어주기도 함)
3) 캡슐화(encapsulation)
클라이언트로부터의 직접 접근에서 데이터를 숨기기 위해 클래스를 사용합니다.(information hiding)
private 변경자로 변수를 숨기고 데이터 필드를 읽을 수 있는 get함수와 데이터 필드를 설정하는 set함수를 설정합니다. 클라이언트가 사용하지 않을 함수는 클래스에서 숨겨야 합니다.
4) 명확성(clarity)
다른 데이터 필드로부터 유도될 수 있는 데이터 필드는 선언하지 않아야 합니다. 예를 들어 birthdate와 age를 같이 선언하면 안됩니다.
5) 완성도(completeness)
많은 사용자들이 사용할 수 있도록 설계해야 하고 폭넓게 응용될 수 있도록 사용자 정의도 제공해야 합니다.
6) 정적으로 선언해야 하는 인스턴스
특정 인스턴스에 종속되지 않는 변수나 함수는 정적으로 선언되어야 합니다.
정적 변수나 함수는 인스턴스 함수로부터 호출될 수 있지만 인스턴스 변수와 함수는 정적 함수로부터 호출될 수 없습니다.
10.31) 클래스의 설계 지침을 설명하라.
위의 것들