학습목표
- 절차식 프로그래밍과 객체 지향 프로그래밍
- 클래스의 개념
- 클래스의 정의와 구현
public
,private
클래스에 접근하기- 클래스 데이터 멤버
- 클래스 메소드(클래스 함수 멤버)
- 클래스 객체의 생성과 사용
- 클래스 생성자와 파괴자
const
멤버 함수this
포인터- 객체 배열의 생성
- 추상 데이터형(ADT)
10.1 절차식 프로그래밍과 객체 지향 프로그래밍
절차적 접근 방식을 사용할 경우
- 프로그래머는 처리해야 할 절차에 우선적으로 초점을 맞춤
- 이후 데이터를 표현할 방법을 찾음
OOP 접근 방식을 사용할 경우
- 프로그래머는 데이터에 우선적으로 초점을 맞춤
- 객체를 서술하는데 필요한 데이터 및 사용자가 그 데이터를 다루는 방법에 대해 고려함
- 사용자가 이해하는 방식대로 객체에 초점을 맞추고, 인터페이스를 설계함
- 인터페이스와 데이터 저장 형태를 구현하고 새로운 설계를 사용하는 프로그램을 완성함
10.2 추상화와 클래스
C++에서는 클래스가 추상화 인터페이스를 구현하는 사용자 정의 데이터형임
인터페이스 : 두 시스템간의 상호 작용을 위한 공통된 프레임워크
- 사용자의 의도를 컴퓨터에 저장되어있는 특정 정보로 변환하는 것을 담당함
- 클래스와 사용자 사이에는
public
인터페이스가 존재 public
은 클래스를 사용하는 프로그램이며 상호 작용 시스템은 클래스 객체, 인터페이스는 클래스의 작성자가 제공한 메서드들로 구성string
객체의size()
메소드가 사용자와 클래스 객체 사이의public
인터페이스의 예시임
데이터형이란 무엇인가?
기본형을 서술할 때는 여러가지가 결정됨
- 데이터 객체에 필요한 메모리의 크기를 결정
- 메모리에 있는 비트들을 어떻게 해석할 것인지 결정 (
long
과float
는 동일한 비트 수를 갖지만 수치 해석이 달라짐) - 데이터 객체를 사용하여 수행할 수 있는 연산이나 메소드를 결정
내장된 기본 데이터형의 경우엔 동작에 관한 정보들이 컴파일러에 내장되어있음
반면 사용자 정의 데이터형의 경우 프로그래머가 정보들을 직접 제공해야함
C++의 클래스
클래스(class) : 추상화를 사용자 정의 데이터형으로 변환해주는 수단
데이터 표현과 데이터를 조작하는 메소드들을 하나의 패키지 안에 결합함
일반적으로 클래스 서술은 두 부분으로 이루어짐
- 클래스 선언(class declarartion) : 데이터 멤버와
public
인터페이스, 멤버 함수(메소드)를 이용하여 데이터 표현을 서술
즉, 클래스의 개요 제공 - 클래스 메소드 정의(class method definitions) : 클래스 멤버 함수가 어떻게 구현되는지 서술
즉, 세부 사항 제공
전형적으로 C+11 프로그래머들은 인터페이스를 헤더 파일의 클래스 선언 안에 위치시키고, 구현부분은 소스 코드 파일의 클래스 함수들을 위한 코드 폼 안에 위치시킴
// stock00.h - version 00
#ifndef STOCK00_H_
#define STOCK00_H_
#include <string>
class Stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
void acquire(const std::string & co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
#endif
Stock
을 새로운 클래스의 데이터형 이름으로 선언했으므로 Stock
형의 변수인 객체 또는 인스턴스를 선언할 수 있음
멤버 함수는 원형으로만 표현하고 나중에 완전한 정의를 할 수도 있고, 혹은 set_tot()
과 같이 그 자리에서 정의될 수도 있음
특정 클래스의 객체를 사용하는 프로그램은 public
부분에 직접 접근할 수 있음
단, private
멤버는 public
멤버 함수 또는 프렌드 함수를 통해서만 접근할 수 있음
- 즉,
public
멤버 함수는 객체의private
멤버와 프로그램 사이의 인터페이스를 제공함 - 이처럼 프로그램이 데이터에 직접 접근하지 못하게 차단되는 것을 데이터 은닉(data hiding)이라고 하고, 이를 통해 데이터의 무결성을 보호함
- 데이터 은닉시 데이터 표현에 대해 클래스 사용자가 신경쓰지 않을 수 있음
- 따라서 추후에 데이터 표현 및 멤버 함수의 세부 사항 구현 등에서 좀 더 나은 방법을 발견시 프로그램 인터페이스를 변경하지 않고도 세부 구현을 변경할 수 있으므로 프로그램의 유지관리가 더 쉬움
- 일반적으로 데이터 항목들은
private
, 멤버 함수들은public
부분에 선언하지만public
인터페이스를 구성하지 않는 세부적인 구현을 처리하는 함수는private
멤버 함수로 지정하기도 함
public
인터페이스는 설계의 추상화를 나타냄
세부적인 구현들을 따로 결합하여 추상화와 분리하는 것을 캡슐화(encapsulation)이라고 함
- 데이터 은닉은 캡슐화의 한 예가 됨
- 클래스 함수들의 정의를 클래스 선언과 독립된 파일에 넣는 것도 캡슐화의 일종
C에서도 OOP적인 접근이 가능하나, C++를 사용하면 더 나은 OOP를 구현할 수 있음
- 함수 원형과 데이터 표현을 분리해두지 않고 하나의 클래스 선언에 넣음으로써, 하나의 클래스 선언에 데이터 표현과 함수 원형을 통합함
- 데이터 표현을
private
로 만들어 권한이 주어진 함수만 해당 데이터에 접근할 수 있도록 접근 조건을 강화
클래스 객체의 디폴트 접근 제어가 private
이기 때문에 private
키워드는 생략 가능함
C++에서는 클래스가 가지고있는 특징들이 구조체까지 확장되었으나, 구조체에 대한 디폴트 접근 제어는 public
임
일반적으로 클래스 서술을 구현하는데는 클래스를, 순수한 데이터 객체를 정의하는 데에는 구조체를 사용함
클래스 멤버 함수의 구현
멤버 함수의 정의는 일반 함수와 같이 하나의 함수 머리와 함수 몸체로 이루어짐
- 단, 멤버 함수 정의시 어느 클래스에 속해있는지 나타내기 위해 사용 범위 결정 연산자
::
를 사용해야함 - 클래스 메소드는 해당 클래스의
private
부분에만 접근할 수 있음 - 멤버 함수는 같은 클래스에 속해있다면 사용 범위 결정 연산자 없이도 메소드를 맘대로 사용할 수 있음
// stock00.cpp - version 00
#include <iostream>
#include "stock00.h"
void Stock::acquire(const std::string & co, long n, double pr)
{
company = co;
if (n < 0)
{
std::cout << "주식 수는 음수가 될 수 없으므로, "
<< company << " shares를 0으로 설정합니다.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
void Stock::buy(long num, double price)
{
if (num < 0)
{
std::cout << "매입 주식 수는 음수가 될 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if (num < 0)
{
cout << "매입 주식 수는 음수가 될 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else if (num > shares)
{
cout << "보유 주식보다 많은 주식을 매도할 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show()
{
std::cout << "회사명 : " << company
<< " 주식 수 : " << shares << '\n'
<< " 주가 : $" << share_val
<< " 주식 총 가치 : $" << total_val << '\n';
}
데이터는 private
으로 만들고 public
함수에게만 접근을 허용하여 데이터가 사용되는 방법을 제어할 수 있도록 함 set_tot()
을 private
멤버 함수로 만듦으로써 타이핑 수고 및 코드 공간을 절약할 수 있고, 항상 정확하게 같은 계산이 수행됨과 동시에 계산의 수정이 필요할시 한군데만 수정하면 되도록 함
클래스 선언 안에 정의를 가지고있는 모든 함수는 자동으로 인라인 함수가 됨
클래스 선언의 외부에 멤버 함수를 정의하면서도 inline
제한자를 사용하여 인라인 함수로 만들 수도 있음
인라인 함수는 함수가 사용되는 각각의 파일 안에서 정의되어야 함
객체 생성시 클래스 변수를 사용함
Stock a, b;
의 형식으로 사용, 객체 a와 b를 생성a.show();
,b.show();
의 형식으로 멤버 함수를 호출- 각각의 객체는 자체의 클래스 멤버들을 위한 저장 공간을 가지지만, 같은 클래스에 속하는 모든 객체들은 동일한 클래스 메소드 집합을 공유함
클래스 사용하기
// usestock0.cpp
#include <iostream>
#include "stock00.h"
int main()
{
Stock fluffy_the_cat;
fluffy_the_cat.acquire("NanoSmart", 20, 12.50);
fluffy_the_cat.show();
fluffy_the_cat.buy(15, 18.125);
fluffy_the_cat.show();
fluffy_the_cat.sell(400, 20.00);
fluffy_the_cat.show();
fluffy_the_cat.buy(300000, 40.125);
fluffy_the_cat.show();
fluffy_the_cat.sell(300000, 0.125);
fluffy_the_cat.show();
return 0;
}
프로그램 설계를 클라이언트-서버 모델로 적용
- 클라이언트는 클래스를 사용하는 프로그램
- 서버는 그것을 필요로하는 프로그램들이 사용할 수 있는 리소스이며 클래스 메소드를 포함한 클래스 선언이 서버를 구성
- 클라이언트는
public
으로 정의된 인터페이스를 통해서만 서버를 사용하기 때문에 클라이언트는 인터페이스만 이해하면 됨 - 서버는 서버가 인터페이스에 따라 신뢰성있고 정확하게 수행되는지만 확인하면 됨
- 서버 설계자가 클래스 설계에 가하는 변경은 인터페이스가 아닌 세부 구현이여만 하며, 이를 통해 클라이언트와 서버의 기능을 프로그래머들이 서로 독립적으로 개선할 수 있음
실행상의 변경
ostream
클래스에는 양식을 컨트롤하는 멤버 함수가 존재함
std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
를 사용시cout
개체 안에 플래그를 세워 고정 소수점 표기를 사용함- 고정 소수점 표기인 상태에서
std::cout.precision(n);
을 사용시 소수점 이하 n자리수까지 표기됨 - 표기 양식의 컨트롤을 메소드 내에서 할 수 있으나, 메소드에 대한 변경 사항을 구현할 때에는 클라이언트 프로그램의 다른 부분에 대해 영향을 주지 않아야 함
- 따라서 아래와 같이 세팅 구문을 위한 리턴 값을 사용하여 구현할 수 있음
std::streamsize prec =
std::cout.precision(3); // 이전 값 저장
...
std::cout.precision(prec); // 과거 값 리턴
std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed); // 원본 플래그 저장
...
std::cout.setf(orig, std::ios_base::floatfield); // 저장된 값 리셋
10.3 클래스 생성자와 파괴자
클래스 객체의 데이터들은 private
접근 제어를 가지고있어 직접 접근할 수 없기 때문에 일반적인 방법으로는 초기화 할 수 없음
따라서 객체를 초기화하기 위해서는 적당한 멤버 함수를 고안해야함
객체가 생성될 때 자동으로 초기화하기 위해 C++에는 새로운 객체를 생성하고 그 객체의 데이터 멤버에 값을 대입해주는 클래스 생성자(class constructor)라는 특별한 멤버 함수를 제공함
- 생성자의 이름은 클래스와 같음
- 생성자는 리턴값이 없더라도
void
형 등의 데이터형을 선언하지 않음
생성자의 선언과 정의
Stock(const string & co, long n = 0, double pr = 0.0);
의 형태로 생성자의 원형을 선언
- 위와 같이 디폴트 매개변수를 사용 가능
- 리턴형이 없음
- 원형은 클래스 선언의
public
부분에 들어감클래스 멤버 이름을 생성자의 매개변수로 사용할 수 없음
혼동을 피하기 위해 일반적으로는m_
접두사 또는_
접미사를 사용하여 데이터 멤버 이름을 나타냄 - 생성자의 정의는 이전에 사용했던
acquire()
함수와 형태가 같음
단, 생성자의 경우에는 객체 선언시 프로그램이 자동으로 호출함
생성자 사용하기
생성자는 Stock food = Stock("World Cabbage", 250, 1.25);
과 같이 명시적으로 호출할 수 있음 Stock food("World Cabbage", 250, 1.25);
처럼 암시적으로 호출할 수도 있음
클래스의 객체가 생성될 때마다 클래스 생성자를 사용하므로 new
를 사용할 때도 마찬가지로 생성자가 사용됨
생성자가 객체를 만들기 전까지 객체는 존재하지 않으므로, 생성자 호출에 객체를 사용할 수 없음
디폴트 생성자
디폴트 생성자(default constructor)는 명시적인 초기화 값을 제공하지 않을 때 객체를 생성하는데 사용됨
- 즉,
Stock fluffy_the_cat;
과 같이 클래스 객체를 선언할 때는 자동으로 디폴트 생성자의 암시적 버전이 제공됨 - 이 때 디폴트 생성자는 매개변수를 갖지 않으므로 선언에 아무런 값도 나타나지 않음
단, 사용자가 생성자를 이미 정의했다면 디폴트 생성자가 제공되지 않음
- 따라서 생성자를 이미 정의하였음에도 명시적 초기화 없이 객체를 생성하고싶다면 사용자가 직접 자신의 디폴트 생성자를 정의해야 함
Stock(const string & co = "Error", int n = 0, double pr = 0.0);
과 같이 기존의 생성자에 있는 모든 매개변수에 디폴트 값을 제공- 또는 함수 오버로딩을 사용하여
Stock();
과 같이 매개변수가 없는 또 하나의 생성자를 정의하며, 두가지 방법중 한가지만 사용 가능함
파괴자
프로그램은 객체의 수명이 끝나는 시점에서 파괴자(destructor)라는 별칭을 가진 멤버 함수를 자동으로 호출함
생성자가 new
를 사용하여 메모리를 대입했다면 파괴자는 delete
를 사용하여 대입된 메모리를 해제해야함 Stock
과 같이 new
를 사용하지 않은 생성자는 실제로 수행해야 할 작업이 없기 때문에 아무 행동도 하지 않는 암시적인 파괴자를 컴파일러가 생성할 수 있음
파괴자는 클래스 이름 앞에 ~
를 붙인 형태를 가지고있음
- 파괴자도 생성자와 마찬가지로 리턴값을 가질 수 없고, 선언된 데이터형을 갖지 않음
- 단, 생성자와는 달리 파괴자는 매개변수 또한 가질 수 없음
일반적으로 사용자가 코드에 명시적으로 파괴자를 호출해서는 안되며, 컴파일러가 자동으로 파괴자를 호출함
- 자동 기억 공간의 클래스 객체 생성시 해당 객체가 정의된 코드 블록을 벗어날 때 파괴자가 호출됨
new
를 사용한 객체 생성시 메모리 해제를 위해delete
를 사용할 때 파괴자가 호출됨- 임시적인 객체 생성시 객체 사용을 마쳤을 때 파괴자가 호출됨
- 사용자가 파괴자를 제공하지 않는다면 컴파일러가 디폴트 파괴자를 선언함
Stock 클래스 개선하기
// stock10.h
#ifndef STOCK10_H_
#define STOCK10_H_
#include <string>
class Stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
Stock();
Stock(const std::string & co, long n = 0, double pr = 0.0);
~Stock();
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
#endif
원래의 클래스 선언에 생성자와 파괴자 함수를 위한 원형을 추가함
생성자를 추가하였기에 역할이 겹치는 acquire()
함수가 제거됨
// stock10.cpp
#include <iostream>
#include "stock10.h"
Stock::Stock()
{
std::cout << "디폴트 생성자가 호출되었습니다. \n";
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::Stock(const std::string & co, long n, double pr)
{
std::cout << co << "를 사용하는 생성자가 호출되었습니다.\n";
company = co;
if (n < 0)
{
std::cerr << "주식 수는 음수가 될 수 없으므로, "
<< company << " shares를 0으로 설정합니다.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
Stock::~Stock()
{
std::cout << "안녕, " << company << "!\n";
}
void Stock::buy(long num, double price)
{
if (num < 0)
{
std::cout << "매입 주식 수는 음수가 될 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if (num < 0)
{
cout << "매입 주식 수는 음수가 될 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else if (num > shares)
{
cout << "보유 주식보다 많은 주식을 매도할 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show()
{
using std::cout;
using std::ios_base;
ios_base::fmtflags orig =
cout.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize prec = cout.precision(3);
std::cout << "회사명 : " << company
<< " 주식 수 : " << shares << '\n'
<< " 주가 : $" << share_val;
cout.precision(2);
cout << " 주식 총 가치 : $" << total_val << '\n';
cout.setf(orig, ios_base::floatfield);
cout.precision(prec);
}
생성자와 파괴자 메소드의 정의를 추가하고, show()
함수에 양식을 컨트롤하는 기법을 추가함
// usestock1.cpp
#include <iostream>
#include "stock10.h"
int main()
{
{
using std::cout;
cout << "생성자를 사용하여 새로운 객체들을 생성한다.\n";
Stock stock1("NanoSmart", 12, 20.0);
stock1.show();
Stock stock2 = Stock("Boffo Objects", 2, 2.0);
stock2.show();
cout << "stock1을 stock2에 대입한다.\n";
stock2 = stock1;
cout << "stock1과 stock2를 출력한다.\n";
stock1.show();
stock2.show();
cout << "생성자를 사용하여 새로운 객체들을 재생성한다.\n";
stock1 = Stock("Nifty Foods", 10, 50.0);
cout << "갱신된 stock1 : \n";
stock1.show();
cout << "프로그램을 종료합니다.\n";
}
return 0;
}
컴파일러가 객체를 생성하고 초기화할 때, 임시객체가 생성되지 않을 수도 있고 생성자에 대한 호출이 임시 객체를 먼저 생성한 후 객체를 복사하고 임시 객체를 버리면서 파괴자를 호출할 수도 있음
이미 존재하는 객체에 새로운 값을 대입하기 위해 생성자 사용시에는 임시 객체가 생성된 후 폐기처분됨
하나의 객체를 같은 형의 다른 객체에 대입할 수 있으며, 이 때 소스 객체 데이터 멤버들의 각 내용을 타깃 객체에 해당하는 데이터 멤버에 복사함
객체의 값을 초기화로도 설정할 수 있고 대입으로도 설정할 수 있다면 일반적으로 초기화를 사용하는 것이 효율적임
Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
Stock jock {"Sport Age Storage, Inc"};
Stock temp {};
C++11의 중괄호를 이용한 리스트 초기화를 클래스에도 사용할 수 있음 hot_tip
과 jock
은 Stock::Stock(const std::string & co, long n = 0, double pr = 0.0);
형태의 생성자, temp
는 디폴트 생성자를 이용하여 생성됨
객체를 const
로 선언시, 메소드에는 앞에 const
로 지시할 수 없기 때문에 함수 괄호 뒤에 const
키워드를 넣는 문법을 사용함
즉, 멤버 함수의 선언은 void show() const;
의 형식으로, 멤버 함수의 정의는 void Stock::show() const
의 형식으로 하고 이렇게 정의된 클래스 함수를 const
멤버 함수라고 함
호출 객체를 변경하면 안되는 클래스 메소드들은 const
로 만들어야함
Bozo(int age);
Bozo dribble = Bozo(44);
Bozo roon(66);
Bozo tubby = 32;
생성자의 매개변수가 하나일 경우, 생성자는 매개변수와 동일한 데이터형을 가진 하나의 값으로 객체를 초기화할 경우에 호출됨
특히 마지막 경우처럼 대입문을 사용하여 객체를 값으로 초기화할 수 있으나, C++11에서는 이를 금지할 수 있음
10.4 객체 들여다보기, this 포인터
하나의 메소드가 두 개의 객체를 동시에 처리할 필요가 종종 생김
- 이는 두 번째 객체를 참조 매개변수로 전달하고, 객체에 대한 참조를 리턴하는 것으로 해결할 수 있음
const Stock & topval(const Stock & s) const;
의 형식으로 사용- 괄호 안의
const
는 명시적으로 접근된 객체s
를 변경하지 않겠다는 의미 - 맨 뒤의
const
는 암시적으로 접근된 객체를 변경하지 않겠다는 의미
- 괄호 안의
// stock20.h
#ifndef STOCK20_H_
#define STOCK20_H_
#include <string>
class Stock
{
private :
std::string company;
int shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
Stock();
Stock(const std::string & co, long n = 0, double pr = 0.0);
~Stock();
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show() const;
const Stock & topval(const Stock & s) const;
};
#endif
// stock20.cpp
#include <iostream>
#include "stock20.h"
Stock::Stock()
{
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::Stock(const std::string & co, long n, double pr)
{
company = co;
if (n < 0)
{
std::cerr << "주식 수는 음수가 될 수 없으므로, "
<< company << " shares를 0으로 설정합니다.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
Stock::~Stock()
{
}
void Stock::buy(long num, double price)
{
if (num < 0)
{
std::cout << "매입 주식 수는 음수가 될 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if (num < 0)
{
cout << "매입 주식 수는 음수가 될 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else if (num > shares)
{
cout << "보유 주식보다 많은 주식을 매도할 수 없으므로, "
<< "거래가 취소되었습니다.\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show() const
{
using std::cout;
using std::ios_base;
ios_base::fmtflags orig =
cout.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize prec = cout.precision(3);
std::cout << "회사명 : " << company
<< " 주식 수 : " << shares << '\n'
<< " 주가 : $" << share_val;
cout.precision(2);
cout << " 주식 총 가치 : $" << total_val << '\n';
cout.setf(orig, ios_base::floatfield);
cout.precision(prec);
}
const Stock & Stock::topval(const Stock & s) const
{
if (s.total_val > total_val)
return s;
else
return *this;
}
명시적으로 접근된 객체의 데이터는 명확한 이름으로 리턴할 수 있으나, 메소드를 호출하는데 사용된 객체는 부를 이름이 지정되어있지 않음
따라서 이 때 this
포인터를 사용함
this
포인터는 멤버 함수를 호출하는데 사용된 객체를 지시함this
는 기본적으로 해당 메소드에 숨은 매개변수로 전달되며, 일반적으로 모든 클래스 메소드들은 자신을 호출하는 객체의 주소로 설정되어있는 하나의this
포인터를 가짐this
자체는 객체의 주소이기 때문에 객체를 리턴하려면 참조 연산자*
를 붙여야함
10.5 객체 배열
// usestock2.cpp
#include <iostream>
#include "stock20.h"
const int STKS = 4;
int main()
{
Stock stocks[STKS] = {
Stock("NanoSmart", 12, 20.0),
Stock("Boffo Objects", 200, 2.0),
Stock("Monolithic Obelisks", 130, 3.25),
Stock("Fleep Enterprises", 60, 6.5)
};
std::cout << "보유 주식 리스트 : \n";
int st;
for (st = 0; st < STKS; st++)
stocks[st].show();
const Stock * top = &stocks[0];
for (st = 1; st< STKS; st++)
top = &top->topval(stocks[st]);
std::cout << "\n최고 가치의 주식 : \n";
top->show();
return 0;
}
객체 배열은 표준 데이터형의 배열을 선언하는 것과 동일한 방법으로 선언
생성자를 사용하여 배열 원소들을 초기화시 각각의 원소에 대해 생성자를 호출해야함
10.6 클래스 사용 범위
클래스 사용 범위는 클래스 안에서 정의되는 이름들에 적용됨
클래스 사용 범위를 갖는 것들은 클래스 바깥에는 알려지지 않음
- 따라서 클래스 멤버들을 서로 다른 클래스에 같은 이름으로 선언해도 충돌하지 않음
- 바깥 세계에서 클래스 멤버들에 직접 접근할 수 없기 때문에 객체를 사용해야함
- 멤버 함수들을 정의할 때에는 사용 범위 결정 연산자를 사용해야함
- 생성자 이름은 클래스 이름과 같으므로 호출될 때 바로 인식됨
- 클래스 멤버 이름 사용시 상황에 따라
.
/->
/::
를 사용해야함
클래스 사용 범위 함수
클래스 내에 직접적으로 상수를 선언할 수 없음
class Bakery
{
private :
enum {Months1 = 12};
static const int Months2 = 12;
double consts1[Months1];
double consts2[Months2];
}
클래스 내에 열거체를 선언하여 상수와 동일한 효과를 낼 수 있음
열거체를 선언하는 것은 클래스 데이터 멤버를 생성하지 않음
이 때의 열거체는 열거체 변수 생성이 아닌 단순히 기호 상수를 생성하기 위한 것이기 때문에 열거체 태그를 제공할 필요가 없음
또는 static
키워드를 사용하여 클래스 안에 상수를 정의할 수 있음
이렇게 정의된 상수는 객체 안이 아니라 다른 정적 변수들과 함께 저장되기 때문에 모든 객체들이 하나의 상수를 공유할 수 있음
단, 정수값과 열거값을 가지는 정적 상수만 위 두가지 경우를 적용할 수 있으나 C++11에서는 이러한 제한이 제거됨
범위가 정해진 열거(C++11)
두 개의 다른 정의로부터 온 열거자는 충돌한다는 문제점이 있었기 때문에, C++11에서는 열거자가 클래스 범위를 갖게 함으로써 문제를 해결함
enum class egg {Small, Medium, Large, Jumbo};
enum class t_shirt {Small, Medium, Large, Xlarge};
egg choice = egg::Large;
t_shirt Floyd = t_shirt::Large;
일반적인 열거는 int
형 변수에 대한 대입이나 비교 표현같이 일부 상황에 대해 자동적으로 int
형 변환이 이루어지지만, 범위가 지정된 열거의 경우 int
형으로의 암시적 전환이 이루어지지 않음
단, 필요한 경우에는 int Frodo = int(t_shirt::Small);
의 형태와 같이 명시적 형 변환이 가능함
C++11에서 범위가 정해진 열거의 내재적 형태는 자동적으로 int
가 되며, enum class : short pizza {Small, Medium, Large, XLarge};
와 다른 기본 변수형을 지정할 수 있음
단, 기본 변수형은 정수형이 되어야만함
범위가 정해지지 않은 열거에 대해서도 위와같은 구문을 사용할 수 있으며, 사용자가 형태를 선택하지 않을 경우 컴파일러가 임의로 선택함
10.7 추상화 데이터형
추상화 데이터형(abstract data type, ADT) : 언어나 시스템의 세부적인 것들을 따지지 않고, 데이터형을 일반적인 형식으로 서술
// stack.h
#ifndef STACK_H_
#define STACK_H_
typedef unsigned long Item;
class Stack
{
private:
enum {MAX = 10};
Item items[MAX];
int top;
public:
Stack();
bool isempty() const;
bool isfull() const;
bool push(const Item & item);
bool pop(Item & item);
};
#endif
// stack.cpp
#include "stack.h"
Stack::Stack()
{
top = 0;
}
bool Stack::isempty() const
{
return top == 0;
}
bool Stack::isfull() const
{
return top == MAX;
}
bool Stack::push(const Item & item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
bool Stack::pop(Item & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
// stacker.cpp
#include <iostream>
#include <cctype>
#include "stack.h"
int main()
{
using namespace std;
Stack st;
char ch;
unsigned long po;
cout << "주문서를 추가하려면 A, 주문서를 처리하려면 P, \n"
<< "종료하려면 Q를 입력하십시오.\n";
while (cin >> ch && toupper(ch) != 'Q')
{
while (cin.get() != '\n')
continue;
if (!isalpha(ch))
{
cout << '\a';
continue;
}
switch (ch)
{
case 'A':
case 'a': cout << "추가할 주문서의 번호를 입력하십시오 : ";
cin >> po;
if (st.isfull())
cout << "스택이 가득 차 있습니다.\n";
else
st.push(po);
break;
case 'P':
case 'p': if (st.isempty())
cout << "스택이 비어있습니다.\n";
else {
st.pop(po);
cout << "#" << po << " 주문서를 처리했습니다.\n";
}
break;
}
cout << "주문서를 추가하려면 A, 주문서를 처리하려면 P,\n"
<< "종료하려면 Q를 입력하십시오.\n";
}
cout << "프로그램을 종료합니다.\n";
return 0;
}
public
멤버 함수들로 스택 연산을 나타내는 인터페이스를 제공하는 클래스 선언과 서술을 대응시킴
스택 데이터의 저장은 private
의 데이터 멤버들이 담당함
연습문제
- 추상화 인터페이스를 구현하는 사용자 정의 데이터형
- 추상화 : 클래스 메소드의
public
인터페이스를 통해 클래스 객체들을 대상으로 수행할 수 있는 연산들을 서술
데이터 은닉 : 프로그램이private
에 선언된 데이터에 직접 접근하지 못하고public
의 멤버 함수들을 통해서만 데이터에 접근할 수 있음
캡슐화 : 데이터 표현과 메소드 코드와 같은 세부 구현을 사용자가 보지 못하게 은닉시킴 - 클래스와 객체의 관계는 표준 데이터형과 그 데이터형의 변수 사이의 관계와 같음
- 클래스의 모든 객체들은 하나의 멤버 함수 집합을 공유하지만, 각 객체마다 자신만의 고유 데이터 집합을 저장할 공간을 따로따로 대입받음
더불어 일반적으로 멤버 함수들은public
, 데이터 멤버들은private
임 -
class Acount { private: std::string name; std::string acount_num; double balance; public: Acount(const std::string & client, const std::string & num, double bal = 0.0); void show(void) const; void deposit(double cash); void withdraw(double cash); };
- 생성자는 클래스 객체가 생성될 때 호출되고, 파괴자는 객체의 수명이 끝나는 시점에서 호출됨
-
Acount::Acount(const std::string & client, const std::string & num, double bal); { name = client; acount_num = num; balance = bal; }
- 명시적인 초기화 값을 제공하지 않을 때(매개변수가 없거나, 모든 매개변수에 디폴트값을 사용하는 경우) 객체를 생성하는데 사용되는 생성자
초기화를 수행하는 생성자가 이미 정의되어있더라도 초기화하지 않고 객체들을 선언할 수 있으며, 객체들의 배열을 선언할 수 있게 해줌 -
// stock3.h #ifndef STOCK3_H_ #define STOCK3_H_ #include <string> class Stock { private: std::string company; int shares; double share_val; double total_val; void set_tot() { total_val = shares * share_val; } public: Stock(); Stock(const std::string & co, long n = 0, double pr = 0.0); ~Stock(); void buy(long num, double price); void sell(long num, double price); void update(double price); void show() const; const Stock & topval(const Stock & s) const; string co_name() const { return company; } int numshares() const { return shares; } double shareval() const { return share_val; } double totalval() const { return total_val; } }; #endif
this
는 클래스 멤버 함수를 호출하는데 사용된 객체의 주소를 나타내는 포인터이며,*this
는 객체 자체를 나타냄
'개인공부 > C++ 기초플러스' 카테고리의 다른 글
C++ Primer 11 (0) | 2023.02.19 |
---|---|
C++ Primer 10 Exercise (0) | 2023.02.19 |
C++ Primer 09 Exercise (0) | 2023.02.19 |
C++ Primer 09 (0) | 2023.02.19 |
C++ Primer 08 Exercise (0) | 2023.02.19 |
댓글