struct MerchandiseTable { CatalogEntry tab[10]; void init(); char const *getname(char const *code); int getprice(char const *code); void MerchandiseTable::init() { strcpy(tab[0].code, "m01"); strcpy(tab[0].name, "soda"); tab[0].price = 1000; strcpy(tab[1].code, "m02"); strcpy(tab[1].name, "icecream"); tab[1].price = 700; strcpy(tab[2].code, "m04"); strcpy(tab[2].name, "chocolate"); tab[2].price = 2000; strcpy(tab[3].code, ""); char const * MerchandiseTable::getname(char const *code) { for (int i = 0; strlen(tab[i].code)!= 0; i++) if (_stricmp(tab[i].code, code) == 0) return tab[i].name; int MerchandiseTable::getprice(char const *code) { for (int i = 0; strlen(tab[i].code)!= 0; i++) if (_stricmp(tab[i].code, code) == 0) return tab[i].price; void main() { MerchandiseTable mtab; char code[10]; char const *name; int price; int total = 0; wrong code wrong code wrong code mtab.init(); total amount = 4400-28 -
while (1) { cout << " "; cin >> code; if (_stricmp(code, "quit") ==0) break; if (name = mtab.getname(code)) { total += price = mtab.getprice(code); cout << code << ' t' << name << ' t' << price << endl; else cout << "wrong code" << endl; cout << endl << "total amount = " << total << endl; 다음코드는위의 Shop 예제에서상품정보가 catalog.txt 라는파일에들어있는것으로가정하여프로그램을수정한것이다. 이는 MerchandiseTable 클래스의 init() 함수에반영되어있다. 이프로그램은또한판매물품정보를끝에모아서영수증형태와유사하게출력하는데, 이를위해 ostrstream 객체를통한스트링출력을이용한다. ( 수정된부분은적색으로표시 ) Merchandise Table #include <iostream> #include <fstream> #include <strstream> using namespace std; #include <string.h> struct CatalogEntry { char code[10]; char name[20]; int price; struct MerchandiseTable { CatalogEntry tab[10]; m01 m02 m03 wrong code m04 quit RECEIPT total amount = 3700 void init(char const * filename); char const *getname(char const *code); int getprice(char const *code); - 29 -
void MerchandiseTable::init(char const * filename) { ifstream file(filename); int i; for (i = 0; file >> tab[i].code; i++) file >> tab[i].name >> tab[i].price; tab[i].code[0] = ' 0'; <catalog.txt> 파일 cout << "Merchandise Table" << endl << endl; for (i = 0; tab[i].code[0]!= ' 0'; i++) cout << tab[i].code << ' t' << tab[i].name << ' t' << tab[i].price << endl; cout << endl; char const * MerchandiseTable::getname(char const *code) { for (int i = 0; strlen(tab[i].code)!= 0; i++) if (_stricmp(tab[i].code, code) == 0) return tab[i].name; int MerchandiseTable::getprice(char const *code) { for (int i = 0; strlen(tab[i].code)!= 0; i++) if (_stricmp(tab[i].code, code) == 0) return tab[i].price; void main() { MerchandiseTable mtab; char code[10]; char const *name; int price; int total = 0; char receipt[1000]; ostrstream strout(receipt, 1000); mtab.init("catalog.txt"); while (1) { cout << " "; cin >> code; if (_stricmp(code, "quit") ==0) break; if (name = mtab.getname(code)) { total += price = mtab.getprice(code); strout << code << ' t' << name << ' t' << price << endl; else cerr << "wrong code" << endl; - 30 -
strout << ends; cout << endl << "RECEIPT" << endl << endl << receipt; cout << endl << "total amount = " << total << endl; 2.2 정보은닉 (data hiding) 과클래스 일반적으로클래스에서정의되는데이터멤버에대해해당클래스를이용하는프로그램에서직접접근할필요는없으며, 그러한접근은바람직하지않을수있다. 예를들면, 일련의데이터항목리스트를포함하는객체가있으며그리스트가배열로구현되어있다고하자. 그러한객체안의리스트에새로운항목을추가하기위해직접그배열에접근할수있을경우, 원래클래스설계자의의도에어긋나는리스트가만들어질수있다. 예를들어, 데이터항목에포함된어떤속성값들의합을클래스안의데이터멤버를통해표현하고있는데, 그와같은사항은모르면서배열에데이터항목만추가함으로써객체의일관성을해칠수도있는것이다. 이러한접근을방지하기위한기법이정보은닉이며, 데이터멤버에대한직접적인접근은금지하면서데이터를이용하기위해클래스안에정의되어있는멤버함수를이용하도록한다. 정보은닉은 C++ 나자바와같은객체지향언어들에서일반적으로지원되는프로그래밍개념의하나이다. 정보은닉의또다른효과는클래스에서은닉되어있는부분을변경하더라도클래스를이용하는프로그램에영향을미치지않는다는것이다. 예를들어원래는고정된크기의배열로구현되어있었지만, 수용할수있는데이터항목수에유연성을두기위해포인터로선언한후실행중에크기를지정할수도있고보다복잡한데이터형식을사용할수도있다. 물론이러한데이터멤버구현에대한변경은클래스멤버함수구현도함께변경하게될것이다. 그러나이러한변경은클래스내부에국한되며, 일반적으로해당클래스를이용하는다른부분에는영향을미치지않는다. 정보은닉대상은데이터멤버에국한되지는않으며, 클래스밖에서호출될필요가없는멤버함수도해당된다. 이러한함수는클래스내부에서만사용될것이다. C++ 에는정보은닉과관련하여사용되는키워드로가시성 (visibility) 수식어 private, public, protected 등이있다. 이러한키워드들은 struct 정의안에나타날수있다. "public:" 이나타나면그이후의 struct 필드나멤버함수들은어디에서나접근가능하다. "private:" 이나타나면그이후의 struct 필드나멤버함수들은그 struct 안에서만접근할수있다. 즉, 그 struct의멤버함수에서만접근가능하다. struct의멤버함수나데이터멤버는별도지정이없을경우 public 가시성을갖는다. class의멤버함수나데이터멤버는별도지정이없을경우 private 가시성을갖는다. C++ 에서 class와 struct는기본적으로같다. 유일한차이는바로위에기술된디폴트가시성이다. ( 이후클래스에대한설명은 class와 struct에공히적용된다.) - 31 -
protected 가시성을갖는멤버들은현재의클래스와현재클래스를상속받아정의되는모든클래스에서접근가능하다. (2.7절상속참고 ) 일반적으로데이터멤버들은 private 가시성을갖게되며, 클래스외부에서호출하여사용될멤버함수들은 public 가시성을갖게된다. 아래코드에서는가시성표현을포함하여 person 클래스를정의하고있다. ( 아래코드가 class이든 struct이든아무차이가없음을유의하라.) class person { private: char name [80], address [80]; public: char const *getname (void); char const *getaddress (void); void setname (char const *n); void setaddress (char const *a); void print (void); 이경우아래의마지막문장에서사용되는 x.name 은가시성에위배되는오류이다. person x; x.setname("frank"); strcpy(x.name, "Knarf"); // ok, setname() is public // error, name is private Shop 예제 3 앞의가게예제에서여러명의고객에대한판매를처리할수있도록확장한다. 각고객의장바구니를표현하는 Basket 클래스가추가되어있다. 장바구니에서선택한상품들의코드는 char * 배열로표현된다. 그리고장바구니안의상품개수는정수 n으로표현한다. 멤버함수 genbasket() 은임의의개수의코드들을생성한다. 임의의코드를생성하기위해멤버함수 gencode() 가사용된다. process() 함수는장바구니안의상품들을처리하여영수증을출력한다. 멤버함수 add(char const *code) 는파라미터로전달되어온 code를장바구니에추가한다. MerchandiseTable에서상품종류의개수는정수 n으로나타낸다. 즉, sentinel 값 대신 n을사용하여테이블크기를나타낸다. 이에맞추어멤버함수들의구현이조정되어있다. - 32 -
#include <iostream> #include <fstream> using namespace std; #include <string.h> #include <stdlib.h> // for rand() #include <ctype.h> // for tolower() struct CatalogEntry { char code[10]; char name[20]; int price; class MerchandiseTable { CatalogEntry tab[10]; int n; void add(char const *, char const *, int); public: void init(char const * filename); void print(); char const *getname(char const *code); int getprice(char const *code); void MerchandiseTable::add(char const *code, char const *name, int price) { if (n < 10) { strncpy(tab[n].code, code, 10); strncpy(tab[n].name, name, 20); tab[n].price = price; n++; void MerchandiseTable::init(char const * filename) { ifstream file(filename); char code[10], name[20]; int price; n = 0; while (file >> code) { file >> name >> price; add(code, name, price); - 33 -
void MerchandiseTable::print() { cout << "Merchandise Table (size: " << n << ")" << endl << endl; for (int i = 0; i < n; i++) cout << tab[i].code << ' t' << tab[i].name << ' t' << tab[i].price << endl; cout << endl; char const * MerchandiseTable::getname(char const *code) { for (int i = 0; i < n; i++) if (_stricmp(tab[i].code, code) == 0) return tab[i].name; int MerchandiseTable::getprice(char const *code) { for (int i = 0; i < n; i++) if (_stricmp(tab[i].code, code) == 0) return tab[i].price; class Basket { char const *items[10]; int n; char const *gencode(); public: void add(char const *); void genbasket(); void process(merchandisetable&); char const *Basket::gencode() { switch(rand() % 3) { case 0: return "m01"; case 1: return "m02"; case 2: return "m04"; void Basket::add(char const *code) { if (n < 10) { items[n] = code; n++; - 34 -
void Basket::genbasket() { n = 0; for (int m = rand() % 10 + 1; m > 0; m--) add(gencode()); void Basket::process(MerchandiseTable& mtab) { int price; int total = 0; cout << endl << "RECEIPT" << endl << endl; for (int i = 0; i < n; i++) { total += price = mtab.getprice(items[i]); cout << items[i] << ' t' << mtab.getname(items[i]) << ' t' << price << endl; cout << endl << "total amount = " << total << endl; void main() { MerchandiseTable mtab; char reply[10]; mtab.init("catalog.txt"); mtab.print(); do { Basket b; b.genbasket(); b.process(mtab); cout << "Continue(Yes/No)? "; cin >> reply; while (tolower(reply[0])!= 'n'); cout << endl << "Closing NOW" << endl; < 실행결과 > Merchandise Table (size: 3) - 35 -
RECEIPT total amount = 2700 Continue(Yes/No)? RECEIPT total amount = 2000 Continue(Yes/No)? RECEIPT total amount = 6700 Continue(Yes/No)? Closing NOW 2.3 생성자 (constructor) 와소멸자 (destructor) C++ 클래스에는두가지의특별한함수가포함될수있으며, 이들은생성자와소멸자이다. 생성자 생성자함수는소속클래스와동일한이름을갖도록규정되어있다. 생성자에는결과값타입을표시하지않으며, 심지어 void조차도사용할수없다. 파라미터를갖지않는생성자는기본생성자 (default constructor) 라고불린다. 생성자는파라미터들을가질수도있다. 클래스의인스턴스, 즉, 객체가생성될때그클래스의생성자가호출된다. 생성자의일반적인역할은데이터멤버들에대한초기화와객체표현에필요한메모리할당등의작업이다. 복소수예제 1-36 -
#include <iostream> using namespace std; class Complex { double re, im; public: Complex(); Complex(double, double); Complex add(complex); Complex subtract(complex); void print(); // default constructor // 2nd constructor Complex::Complex(): re(0), im(0) { // Complex::Complex() { re = 0; im = 0; 와기능적으로동일함 // 즉, re 와 im 의값이 0 으로초기화된다는점에서는동일하다. // 그러나실행시간등에서앞의표현이유리할수있다. Complex::Complex(double r, double i): re(r), im(i) { Complex Complex::add(Complex b) { Complex c; c.re = re + b.re; c.im = im + b.im; return c; Complex Complex::subtract(Complex b) { return Complex(re - b.re, im - b.im); void Complex::print() { cout << re; if (im < 0) cout << " - " << (-im); else cout << " + " << im; cout << " i n"; void main() { Complex a(5, 3); Complex b(4, 7); Complex c; c = a.add(b); c.print(); a.subtract(b).print(); 9 + 10 i 1-4 i - 37 -
클래스에어떠한생성자도정의해주지않을경우, C++ 컴파일러가기본생성자를만들어준다. 컴파일러가제공하는기본생성자는부모클래스가있을경우부모클래스생성자를호출하고데이터멤버중다른클래스객체들이있을경우이들을위한생성자를호출해준다. (2.7절상속참고 ) 소멸자 소멸자함수는해당객체가더이상존재하지않게될때호출된다는점에서생성자의정반대이다. 소멸자함수이름은클래스이름앞에 ~(tilde) 기호를붙여표시한다. 소멸자는리턴값도없지만파라미터도갖지않는다. 아래예제코드를살펴보자. #include <iostream> using namespace std; #include <string.h> class Test { public: Test(); Test(char const *name); ~Test(); private: char *n; // constructor with no argument // constructor with an argument // destructor // name field char *strdup(char *s) { return strcpy(new char[strlen(s) + 1], s); Test::Test() { n = strdup("without name"); cout << "Test object without name created n"; Test::Test(char const *name) { n = strdup(name); cout << "Test object " << n << " created n"; - 38 -
Test::~Test() { cout << "Test object " << n << " destroyed n"; delete n; Test 클래스의객체들에이름을부여함으로써이객체들의생성과소멸을관찰할수있다. 생성자함수를위한인수는변수명다음에오는괄호안에표시함을유의하라. Test globaltest("global"); void func() { Test functest("func"); int main() { Test maintest("main"); func(); 위의프로그램의실행결과는아래와같다. 즉, 전역변수 globaltest, main() 함수의지역변수 maintest, func() 함수의지역변수 functest 등이차례로생성되며, 소멸순서는생성순서의역순이다. Test object global created Test object main created Test object func created Test object func destroyed Test object main destroyed Test object global destroyed 2.4 friend 함수와 friend 클래스 데이터멤버들에 public 가시성을부여하는것은일반적으로위험한일로간주된다. 그러나 private으로선언된데이터멤버들에제한적인외부접근을허용할필요가있는경우가있다. 이러한경우그클래스에서특정클래스나전역함수에대해 friend라고선언할수있다. friend로선언된클래스의멤버함수나 friend로선언된전역함수에서는해당클래스의 private 멤버들에자유롭게접근할수있다. 아래의예를살펴보자. ( 코드길이를줄이기위해모든함수는 inline 방식으로표현되어있다.) - 39 -