서론
별다른 부제가 생각나질 않는다. 나중에는 부제를 안 달고 글을 발행하지 않을까 하는 생각이 든다.
인트라넷이 맛 갔을 때 한글 번역본을 보고 과제를 진행한 다음 바뀐 게 있는지 확인하고 평가를 받았는데, 알고 보니 clang++이 아닌 c++ 컴파일러를 사용해야 하는 이슈가 있었다. 다음 과제부터는 고쳐서 제출할 예정이다.
ex00
다음과 같이 동작하는 프로그램을 만들어라. 최대한 C++답게 만들도록 노력할 것
$>./megaphone "shhhhh... I think the students are asleep..."
SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
$>./megaphone Damnit " ! " "Sorry students, I thought this thing was off."
DAMNIT ! SORRY STUDENTS, I THOUGHT THIS THING WAS OFF.
$>./megaphone
* LOUD AND UNBEARABLE FEEDBACK NOISE *
$>
모든 알파벳을 대문자로 전환해서 출력하며, 인자가 없을 때 지정된 문자열을 출력 + 인자가 여러 개로 들어오면 하나의 문자열인 것처럼 출력해야 하는 문제.
#include <iostream>
int main(int ac, char *av[])
{
if (ac == 1)
{
std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *";
}
else
{
for (int i = 1; i < ac; i++)
{
for (int j = 0; av[i][j]; j++)
{
std::cout << static_cast<char>(std::toupper(av[i][j]));
}
}
}
std::cout << std::endl;
return (0);
}
인자의 개수가 1일 때와 나머지를 분리해서 for 문과 toupper 함수를 이용하여 만들었다.
ex01
전화번호부와 연락처 이렇게 2개의 클래스를 구현해야 한다.
- 전화번호부
- 연락처의 배열
- 8개까지 보관할 수 있으며 9번째 연락처가 들어오면 가장 오래된 것부터 대체됨
- 동적 할당을 허용하지 않음
- 연락처
- 전화번호부 연락처
전화번호부와 연락처는 반드시 클래스에서 인스턴스화 해서 사용해야 함. 클래스의 구성은 자유롭지만, 클래스 안에서만 사용할 것은 private, 밖에서 사용할 것은 public으로 구분할 것.
프로그램을 시작하면 전화번호부는 비어있고, 유저는 프롬프트에 세 가지 명령어 중 하나를 입력할 수 있다. 프로그램은 오로지 ADD, SEARCH, EXIT의 명령어만 받는다.
- ADD : 새로운 연락처를 저장한다.
- 사용자가 이 명령어를 입력하면 한 번에 한 필드씩 입력하라는 메시지가 표시된다.
- ‘first name’, ‘last name’, ‘nickname’, ‘phone number’, ‘darkest secret’ 순서로 입력받으며, 빈 채로 저장할 수 없다.
- SEARCH : 특정 연락처 표시
- 명령어를 입력받으면 저장된 연락처들을 ‘index’, ‘first name’, ‘last name’, ‘nickname’ 순으로 표시한다. 각 열의 너비는 10자이며 파이프 문자로 구분한다. 텍스트는 오른쪽 정렬되어있어야 하며, 텍스트가 열보다 길면 잘리고, 마지막 표시 가능한 문자는.으로 대체된다.
- 위의 정보를 보여준 다음, 사용자에게 인덱스를 다시 입력받는다. 인덱스가 범위를 벗어나거나 잘못된 경우 관련된 동작을 적절하게 정의한다. 제대로 입력받은 경우 연락처 정보를 한 줄에 하나씩 표시한다.
- EXIT : 프로그램이 종료되고 연락처가 소실됨.
이외의 다른 입력은 전부 무시되며, 명령어가 실행된 경우 계속 프롬프트 상태가 지속되어야 한다.
#include <iostream>
int main(void)
{
std::string cmd;
while (true)
{
std::cout << "please enter your command : (ADD, SEARCH, EXIT)" << std::endl;
std::cin >> cmd;
if (cmd == "ADD")
{
}
else if (cmd == "SEARCH")
{
}
else if (cmd == "EXIT")
{
break ;
}
else
{
std::cout << "sorry, command not found" << std::endl;
}
}
return (0);
}
프롬프트를 일단 만들어준다. 다만 클래스의 입장에서 생각을 해보면 프롬프트가 전화번호부의 역할인지, 아니면 프로그램의 역할인지 의견이 나뉠 수는 있을 거라고 생각한다. 서브젝트가 전화번호부를 만드는 거라고 생각하면 전자이고 전화번호부를 다루는 프로그램을 만든다고 생각하면 후자일 텐데, 생각하기 나름이니 구현이 편한 쪽으로 구현하면 된다.
class Contact
{
private:
std::string f_name;
std::string l_name;
std::string n_name;
std::string p_number;
std::string secret;
};
#include "Contact.hpp"
class PhoneBook
{
private:
Contact contacts[8];
int index;
};
클래스의 변수들을 우선 만들어준다. 그다음 기능을 하나씩 생각하면서 구현해준다.
void Contact::AddContect(void)
{
std::cout << "His(her) first name is..." << std::endl;
std::cin >> this->f_name;
std::cout << "His(her) last name is..." << std::endl;
std::cin >> this->l_name;
std::cout << "His(her) nickname is..." << std::endl;
std::cin >> this->n_name;
std::cout << "His(her) phone number is..." << std::endl;
std::cin >> this->p_number;
std::cout << "His(her) darkest secret is..." << std::endl;
std::cin >> this->secret;
std::cout << "Save his(her) Contact." << std::endl;
}
void PhoneBook::AddContact(void)
{
this->contacts[index % 8].AddContect();
this->index++;
}
ADD는 어렵지 않게 구현할 수 있다. index를 계속 1씩 추가시켜주면서 오래된 값을 바꾸는 걸 생각하고 좋아했는데, 이후에 예외처리가 조금 난해했다. 그냥 가장 오래된 값을 저장하는 변수 하나를 만들어둬도 될 것 같다.
std::string Contact::getShortStr(std::string str)
{
if (static_cast<int>(str.size()) > 10)
return (str.substr(0, 9) + ".");
else
return (std::string(10 - static_cast<int>(str.size()), ' ') + str);
}
void Contact::PrintShortContact(void)
{
std::cout << "|" << getShortStr(this->f_name) \\
<< "|" << getShortStr(this->l_name) \\
<< "|" << getShortStr(this->n_name) \\
<< "|" << std::endl;
}
void Contact::PrintAllContact(void)
{
std::cout << "first name : " << this->f_name << std::endl;
std::cout << "last name : " << this->l_name << std::endl;
std::cout << "nickname : " << this->n_name << std::endl;
std::cout << "phone number : " << this->p_number << std::endl;
std::cout << "darkest secret : " << this->secret << std::endl;
}
void PhoneBook::PrintContact(void)
{
int i;
int number;
i = 0;
if (this->index == 0)
std::cout << "Your PhoneBook is empty." << std::endl;
else
{
std::cout << " index" << "|" \\
<< "first name" << "|" \\
<< " last name" << "|" \\
<< " nickname" << "|" << std::endl;
while (i < this->index && i < 8)
{
std::cout << " " << i + 1;
this->contacts[i].PrintShortContact();
i++;
}
std::cout << "Please enter the number you want" << std::endl;
std::cin >> number;
if (std::cin.fail())
{
std::cin.clear();
std::cin.ignore(255, '\\n');
std::cout << "Wrong input" << std::endl;
return ;
}
number = number - 1;
if (number <= 7 && number >= 0 && number < this->index)
contacts[number].PrintAllContect();
else
std::cout << "Out of range" << std::endl;
}
}
index가 0인 경우는 당연히 예외 처리해주고 0이 아닌 경우 우선 첫 줄에 헤더를 입력해준다.
그다음 contacts [index]를 순회하면서 해당 연락처의 짧은 버전을 출력해준다. 출력해줄 때의 문자열은 서브젝트에서 요구하는대로 가공한다.
그런 다음 유저에게 index를 입력받아 해당 연락처의 상세 내용을 보여줘야 하는데, 숫자가 아닌 값이 들어오는 경우에 대비해서 예외처리를 해줘야 한다. 그렇지 않으면 문자를 입력했을 때 계속 출력이 돌게 된다.
https://computer-science-student.tistory.com/45
출력해서 유저에게 보여줄 때 index를 +1 해주고 값을 가져올 때 -1을 하면 조금 더 자연스러워 보이지만 꼭 필요한 작업은 아닌 것 같다.
그런 다음 입력받은 값이 index에 있으면 해당 연락처의 상세 내용을 출력한다.
그 외에 서브젝트에는 그 외의 입력은 무시하라고 되어있지만 소문자를 입력받을 수 있도록 만드는 정도는 괜찮을 것 같다. 평가받으려니까 생각보다 불편하다는 걸 알게 되었음.
ex02
갑작스럽게 영작 문제가 나온다. 대충 넘기고 필요한 부분만 얘기하면 Account.hpp와 texts.cpp 파일을 이용해서 로그파일과 동일한 출력물을 내뱉은 프로그램을 만드는데 필요한 Account.cpp를 만들면 된다.
환경에 따라(ex. m1 맥) 처음에 tests.cpp에서 에러가 발생할 수 있다.
https://en.cppreference.com/w/cpp/utility/functional/mem_fun_ref
해당 함수가 c++17에서 사라졌기 때문인데 대체 함수인 std::mem_fn를 사용해주자
[19920104_091532] index:0;amount:42;created
[19920104_091532] index:1;amount:54;created
[19920104_091532] index:2;amount:957;created
[19920104_091532] index:3;amount:432;created
[19920104_091532] index:4;amount:1234;created
[19920104_091532] index:5;amount:0;created
[19920104_091532] index:6;amount:754;created
[19920104_091532] index:7;amount:16576;created
[19920104_091532] accounts:8;total:20049;deposits:0;withdrawals:0
[19920104_091532] index:0;amount:42;deposits:0;withdrawals:0
[19920104_091532] index:1;amount:54;deposits:0;withdrawals:0
[19920104_091532] index:2;amount:957;deposits:0;withdrawals:0
[19920104_091532] index:3;amount:432;deposits:0;withdrawals:0
[19920104_091532] index:4;amount:1234;deposits:0;withdrawals:0
[19920104_091532] index:5;amount:0;deposits:0;withdrawals:0
[19920104_091532] index:6;amount:754;deposits:0;withdrawals:0
[19920104_091532] index:7;amount:16576;deposits:0;withdrawals:0
[19920104_091532] index:0;p_amount:42;deposit:5;amount:47;nb_deposits:1
[19920104_091532] index:1;p_amount:54;deposit:765;amount:819;nb_deposits:1
[19920104_091532] index:2;p_amount:957;deposit:564;amount:1521;nb_deposits:1
[19920104_091532] index:3;p_amount:432;deposit:2;amount:434;nb_deposits:1
[19920104_091532] index:4;p_amount:1234;deposit:87;amount:1321;nb_deposits:1
[19920104_091532] index:5;p_amount:0;deposit:23;amount:23;nb_deposits:1
[19920104_091532] index:6;p_amount:754;deposit:9;amount:763;nb_deposits:1
[19920104_091532] index:7;p_amount:16576;deposit:20;amount:16596;nb_deposits:1
[19920104_091532] accounts:8;total:21524;deposits:8;withdrawals:0
[19920104_091532] index:0;amount:47;deposits:1;withdrawals:0
[19920104_091532] index:1;amount:819;deposits:1;withdrawals:0
[19920104_091532] index:2;amount:1521;deposits:1;withdrawals:0
[19920104_091532] index:3;amount:434;deposits:1;withdrawals:0
[19920104_091532] index:4;amount:1321;deposits:1;withdrawals:0
[19920104_091532] index:5;amount:23;deposits:1;withdrawals:0
[19920104_091532] index:6;amount:763;deposits:1;withdrawals:0
[19920104_091532] index:7;amount:16596;deposits:1;withdrawals:0
[19920104_091532] index:0;p_amount:47;withdrawal:refused
[19920104_091532] index:1;p_amount:819;withdrawal:34;amount:785;nb_withdrawals:1
[19920104_091532] index:2;p_amount:1521;withdrawal:657;amount:864;nb_withdrawals:1
[19920104_091532] index:3;p_amount:434;withdrawal:4;amount:430;nb_withdrawals:1
[19920104_091532] index:4;p_amount:1321;withdrawal:76;amount:1245;nb_withdrawals:1
[19920104_091532] index:5;p_amount:23;withdrawal:refused
[19920104_091532] index:6;p_amount:763;withdrawal:657;amount:106;nb_withdrawals:1
[19920104_091532] index:7;p_amount:16596;withdrawal:7654;amount:8942;nb_withdrawals:1
[19920104_091532] accounts:8;total:12442;deposits:8;withdrawals:6
[19920104_091532] index:0;amount:47;deposits:1;withdrawals:0
[19920104_091532] index:1;amount:785;deposits:1;withdrawals:1
[19920104_091532] index:2;amount:864;deposits:1;withdrawals:1
[19920104_091532] index:3;amount:430;deposits:1;withdrawals:1
[19920104_091532] index:4;amount:1245;deposits:1;withdrawals:1
[19920104_091532] index:5;amount:23;deposits:1;withdrawals:0
[19920104_091532] index:6;amount:106;deposits:1;withdrawals:1
[19920104_091532] index:7;amount:8942;deposits:1;withdrawals:1
[19920104_091532] index:0;amount:47;closed
[19920104_091532] index:1;amount:785;closed
[19920104_091532] index:2;amount:864;closed
[19920104_091532] index:3;amount:430;closed
[19920104_091532] index:4;amount:1245;closed
[19920104_091532] index:5;amount:23;closed
[19920104_091532] index:6;amount:106;closed
[19920104_091532] index:7;amount:8942;closed
처음에 보면 이게 뭔가 싶고 정신이 아득해지지만 곰곰이 보면 크게 어려운 로그는 아니다.
첫 부분 오른쪽의 created라는 말을 보면 해당 클래스가 생겨났다(다른 말로 하면 계좌를 열었다)는걸 알 수 있다. 왼쪽부터 타임스탬프, 인덱스, 액수 하는 걸 알 수 있으며, 맨 마지막에 전체 계좌의 정보를 모아서 출력한다.
그다음엔 각 계좌의 현재 정보를 출력한 다음, 예금을 입금하면서 정보를 보여주고, 마찬가지로 출금할 때의 정보를 보여준 다음, 전체 정보를 출력하고 클래스를 소멸시킨다(계좌를 닫는다)
이걸 보고 test.cpp를 보면 내용이 하나씩 이해가 된다.
// ************************************************************************** //
// //
// tests.cpp for GlobalBanksters United //
// Created on : Thu Nov 20 23:45:02 1989 //
// Last update : Wed Jan 04 09:23:52 1992 //
// Made by : Brad "Buddy" McLane <bm@gbu.com> //
// //
// ************************************************************************** //
#include <vector>
#include <algorithm>
#include <functional>
#include "Account.hpp"
int main( void ) {
typedef std::vector<Account::t> accounts_t;
typedef std::vector<int> ints_t;
typedef std::pair<accounts_t::iterator, ints_t::iterator> acc_int_t;
int const amounts[] = { 42, 54, 957, 432, 1234, 0, 754, 16576 };
size_t const amounts_size( sizeof(amounts) / sizeof(int) );
accounts_t accounts( amounts, amounts + amounts_size );
accounts_t::iterator acc_begin = accounts.begin();
accounts_t::iterator acc_end = accounts.end();
int const d[] = { 5, 765, 564, 2, 87, 23, 9, 20 };
size_t const d_size( sizeof(d) / sizeof(int) );
ints_t deposits( d, d + d_size );
ints_t::iterator dep_begin = deposits.begin();
ints_t::iterator dep_end = deposits.end();
int const w[] = { 321, 34, 657, 4, 76, 275, 657, 7654 };
size_t const w_size( sizeof(w) / sizeof(int) );
ints_t withdrawals( w, w + w_size );
ints_t::iterator wit_begin = withdrawals.begin();
ints_t::iterator wit_end = withdrawals.end();
Account::displayAccountsInfos();
std::for_each( acc_begin, acc_end, std::mem_fn( &Account::displayStatus ) );
for ( acc_int_t it( acc_begin, dep_begin );
it.first != acc_end && it.second != dep_end;
++(it.first), ++(it.second) ) {
(*(it.first)).makeDeposit( *(it.second) );
}
Account::displayAccountsInfos();
std::for_each( acc_begin, acc_end, std::mem_fn( &Account::displayStatus ) );
for ( acc_int_t it( acc_begin, wit_begin );
it.first != acc_end && it.second != wit_end;
++(it.first), ++(it.second) ) {
(*(it.first)).makeWithdrawal( *(it.second) );
}
Account::displayAccountsInfos();
std::for_each( acc_begin, acc_end, std::mem_fn( &Account::displayStatus ) );
return 0;
}
// ************************************************************************** //
// vim: set ts=4 sw=4 tw=80 noexpandtab: //
// -*- indent-tabs-mode:t; -*-
// -*- mode: c++-mode; -*-
// -*- fill-column: 75; comment-column: 75; -*-
// ************************************************************************** //
컨테이너에 대한 부분은 이후에 다시 사용하면서 공부할 기회가 있으니 자세한 건 넘기고 벡터라고 하는 자료형(자료구조)이 있고, 거기에 첫 부분과 마지막 부분, 그리고 함수를 인자로 하여 for_each문을 사용하면 내용물 전체에 함수를 사용할 수 있다고만 알면 된다. 과제가 벡터를 전혀 몰라도 진행할 순 있지만 눈으로는 익혀두자.
// ************************************************************************** //
// //
// Account.hpp for GlobalBanksters United //
// Created on : Thu Nov 20 19:43:15 1989 //
// Last update : Wed Jan 04 14:54:06 1992 //
// Made by : Brad "Buddy" McLane <bm@gbu.com> //
// //
// ************************************************************************** //
#pragma once
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__
// ************************************************************************** //
// Account Class //
// ************************************************************************** //
class Account {
public:
typedef Account t;
static int getNbAccounts( void );
static int getTotalAmount( void );
static int getNbDeposits( void );
static int getNbWithdrawals( void );
static void displayAccountsInfos( void );
Account( int initial_deposit );
~Account( void );
void makeDeposit( int deposit );
bool makeWithdrawal( int withdrawal );
int checkAmount( void ) const;
void displayStatus( void ) const;
private:
static int _nbAccounts;
static int _totalAmount;
static int _totalNbDeposits;
static int _totalNbWithdrawals;
static void _displayTimestamp( void );
int _accountIndex;
int _amount;
int _nbDeposits;
int _nbWithdrawals;
Account( void );
};
// ************************************************************************** //
// vim: set ts=4 sw=4 tw=80 noexpandtab: //
// -*- indent-tabs-mode:t; -*-
// -*- mode: c++-mode; -*-
// -*- fill-column: 75; comment-column: 75; -*-
// ************************************************************************** //
#endif /* __ACCOUNT_H__ */
내용을 대충 이해했으니 타임스탬프 기능은 전체 다 사용되니 가장 먼저 구현하고… 그다음에 하나씩 구현하면 될 것 같다.
void Account::_displayTimestamp( void )
{
time_t ts;
char buf[16];
time(&ts);
strftime(buf, sizeof(buf), "%Y%m%d_%H%M%S", localtime(&ts));
std::cout << "[" << buf << "]";
}
타임스탬프를 저장한 다음 적절하게 변환해서 출력한다.
Account::Account( int initial_deposit )
{
_accountIndex = _nbAccounts;
_amount = initial_deposit;
_nbDeposits = 0;
_nbWithdrawals = 0;
_nbAccounts++;
_totalAmount = _totalAmount + initial_deposit;
_displayTimestamp();
std::cout << " index:" << _accountIndex \\
<< ";amount:" << _amount \\
<< ";created" << std::endl;
}
생성자는 hpp의 내용을 보면서 하나씩 알맞게 지정해준 다음, 필요한 내용을 출력한다.
int Account::getNbAccounts( void )
{
return (_nbAccounts);
}
int Account::getTotalAmount( void )
{
return (_totalAmount);
}
int Account::getNbDeposits( void )
{
return (_totalNbDeposits);
}
int Account::getNbWithdrawals( void )
{
return (_totalNbWithdrawals);
}
void Account::displayAccountsInfos(void)
{
_displayTimestamp();
std::cout << " accounts:" << getNbAccounts()\\
<< ";total:" << getTotalAmount()\\
<< ";deposits:" << getNbDeposits()\\
<< ";withdrawals:" << getNbWithdrawals() << std::endl;
}
private 변수들을 가져올 수 있도록 get 함수들을 만들어주고 그걸 이용해서 static 변수들을 출력하는 함수를 만든다.
void Account::displayStatus(void) const
{
_displayTimestamp();
std::cout << " index:" << _accountIndex \\
<< ";amount:" << _amount \\
<< ";deposits:" << _nbDeposits \\
<< ";withdrawals:" << _nbWithdrawals << std::endl;
}
void Account::makeDeposit(int deposit)
{
_amount = _amount + deposit;
_totalAmount = _totalAmount + deposit;
_nbDeposits++;
_totalNbDeposits++;
_displayTimestamp();
std::cout << " index:" << _accountIndex \\
<< ";p_amount:" << _amount - deposit \\
<< ";deposit:" << deposit \\
<< ";amount:" << _amount \\
<< ";nb_deposits:" << _nbDeposits << std::endl;
}
타임스탬프를 출력한 다음 변수들을 가져와서 정확히 로그처럼 만들어준다
int Account::checkAmount( void ) const
{
if (_amount < 0)
return (0);
return (1);
}
bool Account::makeWithdrawal(int withdrawal)
{
bool result;
_amount = _amount - withdrawal;
_displayTimestamp();
std::cout << " index:" << _accountIndex \\
<< ";p_amount:" << _amount + withdrawal;
if (checkAmount())
{
result = true;
_totalAmount = _totalAmount - withdrawal;
_nbWithdrawals++;
_totalNbWithdrawals++;
std::cout << ";withdrawal:" << withdrawal \\
<< ";amount:" << _amount \\
<< ";nb_withdrawals:" << _nbWithdrawals;
}
else
{
result = false;
_amount = _amount + withdrawal;
std::cout << ";withdrawal:refused";
}
std::cout << std::endl;
return (result);
}
hpp의 checkAmount 함수가 뭘까 고민을 했는데 아마 이 함수에서 사용하는 게 맞을 것 같다는 생각이 들었다. 왜 자료형이 int인지는 지금도 모르겠다. 이게 정확하다면 bool로 줬어야 하는 게 아닐까?
출금은 입금보다 기능이 조금 추가되는데, 출금했을 때 돈이 0보다 작은 경우 (당연히) 출금을 refuse 해야 한다. 그래서 checkAmount 함수를 이용하여 해당 상황을 체크하고 해당 상황에 맞춰서 기능을 구현하였다.
Account::~Account( void )
{
_nbAccounts--;
_totalAmount = _totalAmount - _amount;
_totalNbDeposits = _totalNbDeposits - _nbDeposits;
_totalNbWithdrawals = _totalNbWithdrawals - _nbWithdrawals;
_displayTimestamp();
std::cout << " index:" << _accountIndex \\
<< ";amount:" << _amount \\
<< ";closed" << std::endl;
}
소멸자에서는 그냥 출력만 하는 게 아니라 static 변수들과 관련된 값들을 전부 처리해주면서 소멸시킨다.
여기까지 하고 다 완성했다고 생각하면서 컴파일을 했을 때 Undefined symbols for architecture arm64: 관련 에러가 발생한다면 cpp 파일 상단에 아래 내용을 입력해서 static변수를 따로 알려줘야 한다.
int Account::_nbAccounts;
int Account::_totalAmount;
int Account::_totalNbDeposits;
int Account::_totalNbWithdrawals;
https://stackoverflow.com/questions/9282354/static-variable-link-error
'프로그래밍' 카테고리의 다른 글
[42서울] CPP Module 02 - 고정 소수점 클래스 만들기 (0) | 2022.08.18 |
---|---|
[42서울] CPP Module 01 - 클래스와 레퍼런스 (0) | 2022.08.10 |
[42서울] cub3D는 아름다워(Cub 3d è bella) (0) | 2022.07.23 |
[42서울] 넷프랙티스(netpractice). 놀랄 만큼 쉽고 믿기 힘들 만큼 재미있는 과제. (0) | 2022.06.27 |
[42서울] 미니쉘(minishell) 파싱, 그 후회와 참회의 기록. (0) | 2022.06.19 |