CPP Module 00

wonbpark·2023년 5월 5일
0

CPP Modules

목록 보기
1/2
post-thumbnail

CPP 과제의 시작

공통 지침

  1. 컴파일
    • c++을 사용하여 컴파일하며, -Wall -Wextra -Werror 플래그를 추가해야 합니다.
    • -std=c++98 플래그를 추가했을 떄 컴파일이 정상적으로 이루어져야 합니다.
  2. 형식 및 클래스 이름 명명
    • Class 이름은 UpperCamelCase 형식을 지켜야 합니다.
      예시)
      ClassName.hpp/ClassName.cpp ...
    • 따로 명시되어있지 않는 한, 모든 output 메시지는 new-line 으로 끝나야 하며, standard output 으로 출력되어야 합니다.
    • Goodbye Norminette!
      강제되는 코드 스타일은 더이상 없지만, 가독성 있는 코드 스타일링을 유지하세요.
  3. 허용/금지 사항
    • Standard library에 있는 모든 것은 사용 가능하되, 최대한 "C++ 스러운" 함수들을 사용하세요.
    • 다른 외부 라이브러리는 사용 불가합니다. (C++11, Boost 등)
    • printf(), alloc(), free() 는 사용이 금지됩니다.
    • 따로 명시되어있지 않는 한, namespace <ns_name> 및 friend 키워드는 사용이 금지됩니다.
    • STL의 사용은 Module 08, 09 에서만 사용이 가능합니다.
  4. 구현 시 주의사항
    • (당연히) memory leak 은 없어야 합니다.
    • Module 02~09 에서는 Orthodox Canonical Form을 지켜 클래스를 구현해야 합니다.
    • 클래스의 헤더파일에 함수 구현 시 0점
    • 구현 한 헤더파일은 독립적으로 쓰일 수 있어야 합니다. 의존성을 지키되, 중복 include 를 방지하세요.
  5. 기타
    • 코드 분할 / 테스트 케이스 작성 등을 위해 추가로 파일을 얼마든지 제출 할 수 있습니다.
    • 각 exercise 마다 있는 예시들은 가이드라인에 명시되지 않는 요구사항을 포함 할 수 있습니다.
    • RTFM!!

ex00: Megaphone

다음과 같이 동작하는 프로그램을 작성하십시오.

$>./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 *
$>

구현:

char ft_toupper(char c) {
    if (c >= 'a' && c <= 'z') {
        return c - ('a' - 65);
    } else {
        return c;
    }
}

int main(int argc, char* argv[]) {
    if (argc == 1) {
        std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl;
        return 0;
    }
    for (int i = 1; i < argc; i++) {
    	for (size_t j = 0; j < strlen(argv[i]); ++j) {
        	argv[i][j] = ft_toupper(argv[i][j]);
		}
        std::cout << argv[i];
	}
	std::cout << std::endl;
    return 0;
}
  • 인자로 들어오는 string의 모든 알파벳을 대문자로 전환하여 출력
  • 인자가 여러개 들어오면 하나의 문자열인 것처럼 출력
  • 인자가 없을 시 지정된 문구 출력

  • 특별히 어려운 구현사항이 없다. 대문자 변환 시 표준 라이브러리의 toupper 함수를 사용해도 되지만, C 과제의 버릇대로 직접 구현해서 써버렸다.

ex01: My Awesome Phonebook

간단한 전화번호부처럼 작동하는 프로그램을 작성하십시오.

  • 두개의 클래스를 구현해야 한다.

    1. PhoneBook
    - 연락처(contact)의 배열
    - 최대 8개의 연락처를 저장할 수 있으며, 9번째 추가 시 가장 오래된 것 부터 대체됨.
    - 동적할당은 금지
    2. Contact
    - PhoneBook 의 연락처.

  • 전화번호부와 연락처는 반드시 클래스에서 인스턴스화해서 사용해야 한다. 클래스의 구현은 자유지만, 클래스 내부에서만 사용할 것은 private, 외부에서 사용할 것은 public으로 구현해야한다.
  • 프로그램 시작 시 전화번호부는 비어있는 상태여야 하며, 유저는 프롬프트를 통해 ADD, SEARCH, EXIT 중 한가지 명령어를 입력 할 수 있다.

  1. ADD - 신규 연락처를 추가
    • 명령어 실행 시 유저는 한번에 하나의 field를 입력하게끔 프롬프트를 받는다. 입력이 전부 완료되면 연락처를 전화번호부에 추가한다.
    • fields: first name, last name, nicknanme, phone number, darkest secret
      필드는 비어있으면 안된다.
  2. SEARCH - 특정 연락처 정보 검색 및 출력
    • 저장된 모든 연락처의 index, first name, last name, nickname을 4개의 열로 나누어 표시한다.
    • 각 열의 너비는 10자이며, 파이프 기호 ('|') 로 구분된다.
      텍스트는 오른쪽 정렬.
      10자를 넘어가면 truncate 하고 마지막 글자를 점 ('.') 으로 대체해야 한다.
    • 유저에게 한번 더 index를 입력받는다.
      잘못되었거나 범위를 벗어난 입력 시 적절한 처리를 해야한다.
      유효한 index일 경우 한줄에 하나씩 해당 연락처의 모든 field를 출력한다.
  3. EXIT
    • 프로그램이 종료되며 모든 연락처가 소실된다.
  4. 이외의 모든 명령어는 무시하며, 프롬프트 상태가 유지되어야 한다.

PhoneBook.hpp

#ifndef PHONEBOOK_HPP
# define PHONEBOOK_HPP

#include "Contact.hpp"
#include <iomanip>

class PhoneBook{
    public:
        PhoneBook();
        ~PhoneBook();

        void    add_contact();
        void    search_contact();
    private:
        Contact contacts[8];
        int     index;
        int     n_contacts;
};

#endif
  • 처음으로 클래스를 구현하는 예제. 과제 지침상 헤더에는 프로토타입만, 구현은 .cpp 파일에 따로 해야한다.

  • Public / Private 키워드?
    - 접근 지정자(Access specifier)로, 말 그대로 클래스 멤버들을 접근할 수 있는 범위를 지정해주는 키워드.
    - Public 멤버는 클래스 인스턴스를 가지는 / 가르키는 어떤 코드에서든 접근이 가능하다.
    - Private 멤버는 클래스 내부 및 friend끼리만 접근 할 수 있다.
    - 일반적으로 public 멤버는 클래스의 인터페이스(전반적인 틀)을 구성하는 속성을 정의할 때 사용되고, private은 클래스의 작동 방식을 정하는 멤버들에 붙는다 (멤버 함수 등).

  • PhoneBook(...), ~PhoneBook()
    - 각각 생성자(constructor)와 소멸자(destructor).
    - 함수의 형태를 띄고 있지만 반환값과 매개변수가 없다.
    - 생성자의 경우는 매개변수가 없는 생성자를 default 생성자라고 부르며, 매개변수의 개수와 타입을 다양하게 지정하여 추가로 정의할 수도 있다.
    - 생성자는 클래스 인스턴스 생성시 호출되며, 멤버 변수의 초기화를 담당.
    - 소멸자는 클래스 소멸시(메모리 반환 시) 호출되며, 따로 delete 해주지 않는 한 일반적으로 소멸 단계는 프로그램 종료 시다. 보통 동적으로 할당한 오브젝트들을 해제(delete)하는 단계.
    - 사실 직접 구현하지 않아도 컴파일러가 알아서 생성자와 소멸자를 만들어준다. 하지만 의도치 않는 동작을 막기 위해 직접 구현하는 것이 대체로 좋은 방법이다.

PhoneBook.cpp

#include "PhoneBook.hpp"

PhoneBook::PhoneBook(){
    index = 0;
    n_contacts = 0;
}

PhoneBook::~PhoneBook() {}

void    PhoneBook::add_contact(){
    Contact     contact;
    std::string input;

    if (index == 8)
        index = 0;

    std::cout << "Enter a first name" << std::endl;
    std::getline(std::cin, input);
    contact.set_first_name(input);
    
    std::cout << "Enter a last name" << std::endl;
    std::getline(std::cin, input);
    contact.set_last_name(input);

    std::cout << "Enter a nickname" << std::endl;
    std::getline(std::cin, input);
    contact.set_nickname(input);

    std::cout << "Enter a phone number" << std::endl;
    std::getline(std::cin, input);
    contact.set_phone_number(input);

    std::cout << "Enter a darkest secret" << std::endl;
    std::getline(std::cin, input);
    contact.set_darkest_secret(input);
        
    if (std::cin.eof()){
        std::cout << "Input cancelled" << std::endl;
        return ;
    }
    contacts[index] = contact;
    index++;
    if (n_contacts < 8)
        n_contacts++;
}

void    print_cut_str(std::string str){
    if (str.length() < 11)
        std::cout << "|" << std::setw(10) << str;
    else
        std::cout << "|" << str.substr(0, 9) + ".";
}

void    PhoneBook::search_contact() {
    int idx;

    if (n_contacts == 0){
        std::cout << "The phonebook is currently empty." << std::endl;
        return ;
    }

    for (int i = 0; i < n_contacts; i++){
        std::cout << std::setw(10) << i + 1;
        print_cut_str(contacts[i].get_first_name());
        print_cut_str(contacts[i].get_last_name());
        print_cut_str(contacts[i].get_nickname());
        std::cout << "|" << std::endl;
    }

    std::cout << "\nEnter an index of the entry to display" << std::endl;
    std::cin >> idx;
    if (std::cin.eof()){
        std::cout << "Input cancelled" << std::endl;
        return ;
    }
    if (std::cin.fail() || idx < 1 || idx > n_contacts){
        std::cout << "Error: invalid contact index" << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        return ;
    }
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    std::cout << "-------------------------" << std::endl;
    std::cout << "First name: " << contacts[idx - 1].get_first_name() << std::endl;
    std::cout << "Last name: " << contacts[idx - 1].get_last_name() << std::endl;
    std::cout << "Nickname: " << contacts[idx - 1].get_nickname() << std::endl;
    std::cout << "Phone Number: " << contacts[idx - 1].get_phone_number() << std::endl;
    std::cout << "Darkest secret: " << contacts[idx - 1].get_darkest_secret() << std::endl;
    std::cout << "-------------------------" << std::endl;
}
  • ADD와 SEARCH 기능을 add_contact / search_contact로 구현.
  • 뭔가 엄청나게 해놓은 것 같지만 유저 프롬프트 및 적절한 출력 메시지, SEARCH 시 요구 형식에 맞게 출력해주는 구현 내용이 전부이다.

  • getline(istream& is, string& str);
    - 지정 instream으로 문자열을 newline까지 입력받는다.
    유저 입력을 받기 위해 사용.
    std::cin >> input;

          위 처럼 받아도 되지만,
          이렇게 하면 공백으로 나눠진 입력을 여러개의 입력으로 받아버린다.

  • setw(int n);
    - 이후 출력의 너비를 n으로 지정해준다(n만큼의 공간을 확보하고 뒤에서부터 출력하는 방식). 사용하기 위해 <iomanip> 헤더를 include 했다.

  • 중간중간 std::cin 에러처리
    - main() 의 프롬프트에서 명령을 받고, 각 기능에 내부적으로도 프롬프트가 있다. 내부에서도 Ctrl+D 등으로 프롬프트 종료 시 적절한 처리를 해줘야 한다.
    • cin.clear(); : cin의 에러 플래그를 비워준다. 안그러면 켜져있는 채로 메인으로 다시 돌아간다.
    • cin.ignore(streamsize n, int delim); : cin 버퍼를 n 만큼, 또는 delim을 만날때까지 비워준다. 코드에서 크기는 저장되어있는 input stream 입력의 최대 크기로 지정했다.

Contact.hpp

#ifndef CONTACT_HPP
# define CONTACT_HPP

class Contact {
	public:
		Contact();
		~Contact();

    	std::string	get_first_name() const;
		std::string	get_last_name() const;
		std::string	get_nickname() const;
		std::string	get_phone_number() const;
		std::string	get_darkest_secret() const;

		void		set_first_name(std::string str);
		void		set_last_name(std::string str);
		void		set_nickname(std::string str);
		void		set_phone_number(std::string str);
		void		set_darkest_secret(std::string str);

	private:
		std::string first_name;
		std::string last_name;
		std::string nickname;
		std::string phone_number;
		std::string	darkest_secret;
};

#endif
  • Contact 클래스는 별다른 내용은 없지만, gettersetter에 대해 정리하기 위해 가져와봤다.

  • getter / setter란?
std::string	Contact::get_first_name() const{
	return (this->first_name);
}

void	Contact::set_first_name(std::string str){
	this->first_name = str;
}
  • private 멤버들은 기본적으로 클래스 외부에서 접근 할 수 없기 때문에 gettersetter 를 사용해서 가져오거나 설정해줘야한다.
    애초에 외부에서 접근을 금지하는 키워드를 써놓고, 굳이굳이 함수까지 만들어가며 가져온다는 말에는 다소 모순이 있지만, private 멤버들을 가지고 있는 같은 클래스의 멤버 함수를 사용해서 접근한다는 점, 어느정도 통제된 인터페이스 내에서만 할 수 있다는 점, 바로 접근이 안되고 함수를 통해 접근함으로써 예외처리를 할 수 있다는 점 등에서 최소한의 안정성이 보장된다.
  • this 키워드
    현재 인스턴스를 가리키는 포인터로, 위의 예시에서는 'first_name'이 반드시 해당 클래스 오브젝트의 멤버 변수임을 명시해준다. 해당 과제에서는 무방하지만 같은 이름을 가지는 다른 변수와의 충돌 방지 목적으로 쓰일 때가 많고, 전체적으로 가독성을 높여주기도 한다.
  • 참고로 private 멤버들을 지정하는 일을 캡슐화(encapsulation)이라고 부른다. 어찌됐든 민감한 정보를 보호하는 개념이니 gettersetter는 꼭 필요할 때 만 쓰도록 하자.

main.cpp

#include "PhoneBook.hpp"

int main(void){
    PhoneBook   pb;
    std::string cmd;

    while(true){
        std::cout << "Enter a command: [ADD / SEARCH / EXIT]" << std::endl;
        std::getline(std::cin, cmd);
        if (cmd == "EXIT" || std::cin.eof()){
            exit(0);
        }
        else if (cmd == "ADD"){
            pb.add_contact();
        }
        else if (cmd == "SEARCH"){
            pb.search_contact();
        }
        else
            std::cout << "Error: Invalid command" << std::endl;
        if (std::cin.eof()){
            clearerr(stdin);
            std::cin.clear();
        }
    }
    return (0);
}
  • 요구사항에 맞게 프롬프트를 구현했다.

  • 별다른 특이사항이 없다. 눈여겨 볼 만한 문법이라면 클래스 멤버함수를 쓰고싶을때는 객체를 선언하고, 마치 구조체처럼 도트연산자('.')으로 접근하면 된다.
  • 개인적으로 신기했던 점은 C에서는 불가능했던 문자열끼리 비교법인데, C++에서는 비교연산자로 비교가 가능하다 (cmd == "EXIT").
profile
42 Seoul Cadet _ 6기

0개의 댓글