read 메소드는 파일입력스트림 fin을 매개변수로 받고 파일의 음료 데이터를 차례로 필드에 입력한다.
price를 int로 바꾸기 위해 double 값으로 읽은 후 1000을 더한다.
print 메소드에서는 필드를 차례로 출력한다.
검색 기능에서 숫자인 경우 price와 비교하기 위해 스트링이 숫자인가를 검사하는 isdigit()를 만들었다. 스트링의 한 글자씩 검사하기 위해 c_str()을 이용해서 char*로 바꾼다. 이 때 스트링은 immutable이므로 스트링의 char* 값은 변경될 수 없는 const char*로 되어 있다. 그러므로 cstr도 const char*로 해야 지정해서 받을 수 있다.
matches 메소드는 kwd 값을 차례로 필드와 비교하면서 code나 size와 같은가, 이름에 키워드가 포함되는가, 숫자이면서 가격보다 큰가를 검사한다. 스트링이 같은가 비교는 == 연산자를 이용할 수 있다. kwd나 code는 string의 값모델 변수이고 스트링 클래스에서 값으로 비교하는 == 연산자를 제공한다. 한편 포함되는가 검사는 find 함수를 이용할 수 있고 이 함수는 해당 문자열이 있으면 그 인덱스를 돌려주고 없으면 -1을 돌려준다. 마지막으로 숫자인 경우 stoi는 스트링을 int로 바꿔주는 함수다.
(3) main.cpp
#include "Drink.h"
void readMenu(string filename);
void printMenu();
void search();
Drink* list[100];
int cnt;
void main() {
readMenu("menu.txt");
printMenu();
search();
}
void readMenu(string filename) {
ifstream fin("menu.txt");
while (!fin.eof()) {
list[cnt] = new Drink();
list[cnt]->read(fin);
cnt++;
}
}
void printMenu() {
for (int i = 0; i < cnt; i++)
list[i]->print();
}
void search() {
string kwd;
int* match_cnt = new int[cnt];
int kwd_cnt = 0;
while (1) {
for (int i = 0; i < cnt; i++)
match_cnt[i] = 0;
kwd_cnt = 0;
cout << ">> ";
cin >> kwd;
if (kwd == "end") break;
while (kwd != "end") {
for (int i = 0; i < cnt; i++)
if (list[i]->matches(kwd))
match_cnt[i]++;
kwd_cnt++;
cin >> kwd;
}
for (int i = 0; i < cnt; i++)
if (match_cnt[i] == kwd_cnt)
list[i]->print();
}
}
Drink.h를 인클루드한다.
main이 사용할 함수들의 전방선언
메뉴를 저장하기 위한 자료구조를 선언한다. 메뉴의 음료를 차례로 저장할 list 배열과 음료 개수 cnt를 선언했다. list 배열은 음료를 저장하는데, 음료의 개수를 미리 알지 못하므로 충분히 크게 선언하고 대신 개수 변수를 가지고 실제 입력된 음료의 개수를 저장한다.
readMenu는 파일을 열어 메뉴의 음료 전체를 읽어들이는 함수고 printMenu는 메뉴에서 읽은 음료를 출력한다.
search 함수는 여러 개의 키워드를 받아 모든 키워드에 일치하는 음료만 출력한다. 여기서 사용하는 match_cnt 배열은 각 음료에 대해 키워드 중에 몇개가 매치되었는가를 나타내는 배열이다. 이것은 처음에 모두 0으로 설정되고 해당 음료가 각 키워드에 매치될 때마다 1씩 증가한다. 한편 kwd_cnt는 여러 개의 키워드를 매치하기 위해 들어온 키워드의 수를 가진다.
바깥 while 루프는 ">> " 뒤에 바로 end가 나올 때까지 키워드 매치를 반복한다. 매번 반복할 때마다 match_cnt 배열과 kwd_cnt 변수를 0으로 리셋해 준다.
안쪽 while 루프는 여러 개의 키워드를 한개씩 음료 list 배열에서 비교하는데, i번째 음료가 매치가 되면 match_cnt[i]를 증가시킨다.
다음 키워드를 읽고 kwd_cnt를 증가시킨다.
end로 키워드가 끝나면 음료 중에서 match_cnt가 키워드의 개수와 같은 것을 출력한다. 키워드에 모두 매치된 음료를 찾는 방법이고 하나라도 매치된 것을 모두 찾는다면 0보다 큰 것을 출력하면 될 것이다. 또한 모두 매치된 것을 먼저 출력해 주고 그 다음으로 일부만 매치된 것도 출력할까요? 하고 물어보고 kwd_cnt-1인 것을 출력해 주는 방법도 가능하다.
여러 개의 키워드를 매치하기 위해 bool 배열을 사용하는 방법도 있고 또는 아예 여러 개의 키워드를 Drink에게 줘서 모두 매치되는지를 물어보는 matches 메소드를 만들어도 된다. c++은 함수 오버로딩을 허용하므로 string 을 매개변수로 받는 matches와 별개로 string*로 배열을 매개변수로 받는 matches 메소드를 추가할 수도 있다.
Drink 클래스는 클래스 이름에 대문자를 사용했으나 C++에서는 이름에 대문자를 거의 사용하지 않고 클래스 이름도 소문자로 시작한다. string 클래스가 그런 예다. 대신 여러 단어를 연결한 이름은 단어 사이에 밑줄(_)을 넣는다. 이것을 자바의 CamelStyle에 대비하여 snake_style이라고 부르기도 한다.
각 음료의 match_cnt를 배열로 하지 않고 Drink 클래스의 필드로 하는 것은 어떨까? 이것은 사실 검색에서 필요한 정보이므로 음료 자체와는 좀 거리가 있다. 검색하는 쪽에서 생성해서 사용하는 배열이 더 적합하다. 가능하다면 객체의 필드는 꼭 필요한 것만 넣는 것이 좋다. 왜냐하면 모든 객체가 생성될 때마다 그 필드에 대해 메모리가 잡혀야 되고 그 값이 의미있는 값으로 지정되어야 하므로 꼭 필요한 것이 아니라면 필드에 추가하지 않는 것이 좋다. 한편 salesCnt 같은 경우는 그 음료가 몇개가 팔렸느냐를 가지는 필드인데, 이것은 음료에 관련된 정보라고 볼 수 있고 프로그램 수행 과정에서 전체적으로 사용되어야 하므로 필드에 넣었다.
다음 포스트에서는 상품 클래스 이외에 주문을 받아 처리하는 Store 클래스를 추가해서 자바 프로그램에 대응하는 C++ 프로그램을 작성하는 예제를 살펴본다.