본문 바로가기

프로그래밍

[42서울] CPP Module 01 - 클래스와 레퍼런스

서론

cpp 과제는 과제보다 평가가 두배는 더 힘든 느낌이다. 평가받아야 하는 횟수 자체도 압도적으로 많은데, 카뎃 대부분은 C++을 모르다 보니 설명을 하려면 굉장히 먼데부터 설명해야 한다. 

 

본론으로 들어가면, cpp module 01 과제는 제목에서처럼 클래스와 레퍼런스를 사용해보는걸 중점으로 하고 있다. 객체 지향 언어의 기본이자, C++의 꽃이라고 할 수 있는 부분이기 때문에 깊게 공부할수록 좋다.

 

 

ex00

Zombie 클래스를 만든다. 디버깅, 평가 등을 위해 소멸자에 메시지를 넣는다.

 

Zombie 클래스에는 본인의 이름과 BraiiiiiiinnnzzzZ... 를 출력하는 announce 기능이 있다.

 

Zombie가 적절할 때 소멸되도록 만들고, 그에 따라 스택 영역과 힙 영역에 할당하도록 한다.

 

Zombie를 만들어서 announce 하는 함수를 만든다.

 

Zombie를 만들어서 반환하는 함수를 만든다.

 

스택 영역, 힙 영역은 기존 과제에서 직접 말한 적은 없지만 모든 카뎃들은 기본적으로 몸에 익히고 있는 내용이다. 스택은 지역변수이고(전역 변수는 거의 못쓰니까) 힙은 C에서 malloc 해서 사용하는 동적 메모리이다. 

 

힙은 느리며 메모리 릭이 없도록 잘 관리해야 하지만 필요한 만큼 메모리를 할당할 수 있고, 스택은 빠르지만 너무 큰 메모리를 할당할 수 없으며 스택 범위를 넘기면 바로 프로그램이 터진다는 것 정도는 당연히 알 테니 설명하지 않겠다.

 

#ifndef ZOMBIE_HPP
# define ZOMBIE_HPP

# include <iostream>

class Zombie
{
	private:
		std::string	name;
	public:
		Zombie(std::string name);
		~Zombie(void);
		void	announce(void);
};

Zombie	*newZombie(std::string name);
void	randomChump(std::string name);

#endif

헤더에서는 생성자와 소멸자 그리고 announce를 넣어주고 함수 두 개는 따로 빼준다.

 

#include "Zombie.hpp"

Zombie::Zombie(std::string name)
{
	this->name = name;
}

Zombie::~Zombie(void)
{
	std::cout << this->name << " is dead" << std::endl;
}

void	Zombie::announce(void)
{
	std::cout << this->name <<  " BraiiiiiiinnnzzzZ..." << std::endl;
}

생성자에 출력을 반드시 넣을 필요는 없어서 뺐는데, 평가받을 때 출력이 들어가 있는 게 편하지 않나 하는 생각이 들었다.

 

#include "Zombie.hpp"

void	randomChump(std::string name)
{
	Zombie tmp(name);
	tmp.announce();
}

#include "Zombie.hpp"

Zombie	*newZombie(std::string name)
{
	Zombie *result = new Zombie(name);
	return (result);
}

두 함수의 차이점은 스택에 선언해서 announce만 하고 바로 소멸돼도 되는가, 혹은 힙에 선언해서 재사용할 수 있게 반환하는가에 중점을 두고 이해하면 된다.

 

#include "Zombie.hpp"

int	main(void)
{
	Zombie stack("stack1");
	Zombie *heap = newZombie("heap");

	randomChump("stack2");
	stack.announce();
	heap->announce();
	delete heap;
	return (0);
}

평가받을 때 설명하기 쉽도록 메인 문을 만드는데서 조금 고민했는데, stack2가 announce 하고 바로 소멸되고, heap 이 소멸된 다음, main이 종료될 때 stack1이 소멸되는 걸 보여주기 위해 이렇게 배치하였다.

 

 

ex01

좀비 떼를 만드는 함수를 만들어야 한다. 프로토 타입은 아래와 같다.

 

Zombie* zombieHorde( int N, std::string name );

 

반드시 단일 할당을 해야 하며(즉, 할당을 따로 해서 만들면서 00의 newZombie를 이용해 이름을 부여하는 방식은 안됨) 첫 번째 포인터를 반환해야 한다.

 

각 좀비에 대해 announce를 호출하고 삭제해야 한다

 

#ifndef ZOMBIE_HPP
# define ZOMBIE_HPP

# include <iostream>
# include <sstream>

class Zombie
{
	private:
		std::string	name;
	public:
		Zombie(void);
		Zombie(std::string name);
		~Zombie(void);
		void	announce(void);
		void	setName(std::string name);
};

Zombie* zombieHorde( int N, std::string name );

#endif
#include "Zombie.hpp"

Zombie::Zombie(void)
{
	
}

Zombie::Zombie(std::string name)
{
	this->name = name;
}

Zombie::~Zombie(void)
{
	std::cout << this->name << " is dead" << std::endl;
}

void	Zombie::announce(void)
{
	std::cout << this->name <<  " BraiiiiiiinnnzzzZ..." << std::endl;
}

void	Zombie::setName(std::string name)
{
	this->name = name;
}

이전 과제의 클래스를 재사용하되 사용하지 않을 기능들을 없애고, name을 넣지 않았을 때의 생성자와, 만들어진 이후에 이름을 변경할 수 있게 setName 함수를 만들어준다.

 

#include "Zombie.hpp"

Zombie* zombieHorde( int N, std::string name )
{
	Zombie *horde = new Zombie[N];
	std::stringstream	ss;

	for (int i = 0; i < N; i++)
	{
		ss.str(std::string());
		ss << i;
		horde[i].setName(name + ss.str()); 
	}
	return (horde);
}

이름 뒤에 숫자로 구별해주기 위해 stringstream 기능을 사용하였다.

 

https://psychoria.tistory.com/708

 

C++ int를 string으로 변경하는 방법

C++의 string 클래스는 int나 float, double 같은 타입을 변환하는 메소드를 가지고 있지 않습니다. 다만 표준인 stringstream이나 boost의 lexical_cast를 활용해서 변환이 가능합니다. 이 방법 외에도 C++11에서.

psychoria.tistory.com

to_string을 쓰면 편할 텐데 c++11 표준 함수여서 여기에서는 사용하지 않았다.

 

#include "Zombie.hpp"

int	main(void)
{
	Zombie	*horde;

	horde = zombieHorde(10, "zombie");
	for (int i = 0; i < 10; i++)
	{
		horde[i].announce();
	}
	delete [] horde;
	return (0);
}

과제 요구사항대로 main문을 만들면 끝.

 

ex02

string을 만든 다음 그것의 포인터와 레퍼런스를 만든다.

 

각각의 메모리 주소를 출력해주고, 각각의 value를 출력해주면 끝.

 

심플하지만, 평가 때 이게 끝입니다! 하고 넘어가면 평가자가 이상하게 쳐다보겠죠?

 

#include <iostream>

int	main(void)
{
	std::string	str = "HI THIS IS BRAIN";
	std::string	*stringPTR = &str;
	std::string	&stringREF = str;

	std::cout << "Print memory address" << std::endl;
	std::cout << "str address       : " << &str << std::endl;
	std::cout << "stringPRT address : " << stringPTR << std::endl;
	std::cout << "stringREF address : " << &stringREF << std::endl;

	std::cout << std::endl;
	std::cout << "Print value of string" << std::endl;
	std::cout << "str value       : " << str << std::endl;
	std::cout << "stringPRT value : " << *stringPTR << std::endl;
	std::cout << "stringREF value : " << stringREF << std::endl;

	return (0);
}

포인트와 레퍼런스에 대한 구별과 이해를 묻는 과제이다.

 

포인터는... 기존에 C에서 신나게 사용하던 포인터와 동일하게 변수의 주소를 저장하는 변수다.

 

레퍼런스는 내부 동작은 거의 동일하지만, 포인터를 사용하면서 겪을 수 있는 에러를 줄이고 조금 더 쉽게 사용할 수 있도록 만든 포인터의 추상화이다. 따라서 사용할 수 있다면 참조자를 사용하고, 어쩔 수 없을 때에만 포인터를 사용하도록 권장된다.

 

거의 동일하다고 한 부분을 먼저 얘기하자면 포인터는 그 주소를 어떻게 사용하든지 간에 메모리를 사용하지만, 레퍼런스는 굳이 주소를 저장하지 않아도 되는 상황이라면 메모리를 사용하지 않는다. 이 과제에서 컴파일러의 세부 동작까지 생각해야 할 일은 없으니 여기서는 일단 동일하다고 생각하고 넘어가자

 

내부 동작이 동일하다면 어떤 에러를 줄이고 어떻게 쉽게 사용할 수 있도록 만들었느냐가 중요한데, 가장 큰 건 레퍼런스는 무조건 선언과 동시에 초기화를 해야 하며, 가리킬 대상을 변경할 수도 없고, (따라서 당연히) NULL을 할당할 수도 없다.

 

따라서 초기화하지 않은 값이나 NULL을 참조해서 생기는 에러들을 전부 회피할 수 있으며, 포인터처럼 주소 값의 증감 연산이 안되기 때문에 애먼 값을 참조해서 생기는 에러들 또한 피할 수 있다. 

 

쉽게 사용할 수 있도록 만든 점은 우선 위의 이유로 사용할 때에는 함수의 인자가 포인터라면 NULL인지를 참조해서 예외처리를 먼저 해야겠지만, 레퍼런스에서는 그런 예외처리가 불필요하다.

 

다른 하나는 해당 주소가 아닌 값을 부를 때 다른 단항 연산자 없이 그냥 해당 변수를 호출하면 값이 불러와진다는 점이다. 위의 코드를 보면 알 수 있지만 주소를 넘겨야 할 때 &를 붙이고 값을 넘겨야 할 때 아무것도 붙이지 않고 넘긴다. 따라서 미리 정해두면 마치 일반 변수를 사용하듯이 주소를 이용할 수 있다.

 

#include <iostream>

int& return_ref(int &a)
{
        return (a);
}

int main(void)
{
        int a = 0;
        int &a_ref = a;

        std::cout << return_ref(a) << std::endl;
}

예시를 들어보면 함수의 인자나 반환 자료형에 미리 레퍼런스라고 선언을 해두면 그냥 변수를 넘기듯이 넘기고 받아오는 걸 알 수 있다.

 

  

https://ssinyoung.tistory.com/16

 

12. [C / C++] 포인터와 레퍼런스의 차이

1. 포인터 (pointer) 포인터는 메모리의 주소를 가지고 있는 변수이다. 주소 값을 통한 메모리 접근을 한다. (간접 참조) 2. 레퍼런스 (reference) 레퍼런스 = 참조자. ( C++ 문법 ) 참조자는 자신이 참조하

ssinyoung.tistory.com

https://woo-dev.tistory.com/43

 

[C++] 포인터와 레퍼런스(참조)의 차이를 이해해보자

C++에는 포인터(Pointer)와 레퍼런스(Reference)라는 개념이 있다. 포인터는 C 에도 있었던 개념이며 레퍼런스는 C++ 에서 등장한 개념이다. 언뜻 보면 용도가 비슷한데 정확히 어떤 차이점이 있는지,

woo-dev.tistory.com

https://gracefulprograming.tistory.com/11

 

[C++] 포인터(Pointer)와 레퍼런스(Reference : 참조자)의 차이

안녕하세요 피터입니다. 오늘은 C언어를 배운 후 C++을 공부하는데 있어서 굉장히 헷갈리는 개념인 포인터와 레퍼런스의 차이에 대해서 설명드리겠습니다. 개요 C++ 프로그래밍을 시작하면 레퍼

gracefulprograming.tistory.com

 

ex03

int main()
{
	{
		Weapon club = Weapon("crude spiked club");
		HumanA bob("Bob", club);
		bob.attack();
		club.setType("some other type of club");
		bob.attack();
	}
	{
		Weapon club = Weapon("crude spiked club");
		HumanB jim("Jim");
		jim.setWeapon(club);
		jim.attack();
		club.setType("some other type of club");
		jim.attack();
	}
	return 0;
}

위와 같은 메인 문이 주어지고, 위의 내용이 의도대로 잘 출력되도록 클래스들을 구성해야 한다.

 

Weapon 클래스는 string 변수 type을 가지고 getType 함수와 setType함수를 만들어준다.

 

HumanA와 HumanB는 name과 Weapon을 가지고 있는 건 동일하지만, A는 생성될 때 Weapon을 반드시 소유해야 하고, B는 반드시 소유할 필요는 없다.

 

과제를 시작하기 전에 ex02에서 배운 걸 토대로 두 Human의 Weapon이 포인터여야 할지, 아니면 레퍼런스여야 할지를 고민한 다음에 시작하도록 하자.

 

저 메인 문에서 가장 중요한 점은 club이 setType가 된 이후에 Human이 setWeapon을 하지 않더라도 해당 내용이 반영이 되어야 한다는 것이다. 따라서 Human의 weapon은 둘 다 포인터 또는 레퍼런스를 반드시 사용해야 한다.

 

HumanA의 경우 생성자에서 club 인자를 받을 때 포인터가 아닌 레퍼런스 또는 값으로 받은 것을 알 수 있다. 하지만 club의 setType이 변경된걸 바로 적용하려면 당연히 레퍼런스일 것이다.

 

HumanB의 경우 생성자에서 인자를 받지 않으므로 NULL을 허용하지 않는 레퍼런스가 아닐 것이다. 값은 위와 같은 이유로 아닐 것이라고 생각해보면, 변수는 포인터이며 setWeapon의 인자는 레퍼런스라고 생각할 수 있다.

 

#ifndef WEAPON_HPP
# define WEAPON_HPP

# include <iostream>

class Weapon
{
private:
	std::string type;
public:
	Weapon(std::string type);
	~Weapon(void);
	const std::string &getType(void) const;
	void setType(const std::string type);
};

#endif


#include "Weapon.hpp"

Weapon::Weapon(std::string type)
{
	this->type = type;
}

Weapon::~Weapon(void)
{

}

const std::string &Weapon::getType(void) const
{
	return (this->type);
}

void Weapon::setType(const std::string type)
{
	this->type = type;	
}

 

Weapon 클래스는 어렵지 않게 만들 수 있다. getType 마지막의 const는 클래스 안의 값이 변경되지 않음을 보장한다.

 

#ifndef HUMANA_HPP
# define HUMANA_HPP

# include <iostream>
# include "Weapon.hpp"

class HumanA {
 private:
	std::string name;
	Weapon &weapon;
 public:
	HumanA(std::string name, Weapon &weapon);
	~HumanA();
	void attack(void) const;
};

#endif


#include "HumanA.hpp"

HumanA::HumanA(std::string name, Weapon &weapon) : name(name), weapon(weapon)
{
}

HumanA::~HumanA() 
{
}

void	HumanA::attack(void) const
{
	std::cout << this->name << " attacks with his " << this->weapon.getType() << std::endl;
}

위에서 말한 것처럼 HumanA의 weapon은 레퍼런스로 구성을 해준다. 그런데 레퍼런스(그리고 상수)는 반드시 생성하자마자 초기화를 해줘야 한다는 제약을 클래스에서 해결하는 방법이 초기화 리스트이다. 생성자 뒤에 : name(name), weapon(weapon)를 넣어서 객체의 생성과 동시에 초기화를 해도 록 해준다.

 

https://pandas-are-bears.tistory.com/16

 

[C++] 생성자와 초기화 리스트 (Initializer List)

C++에서 생성자는 어떤 구조체 또는 클래스 객체의 생성 시 자동으로 호출되는 함수이다. 따라서 생성자에서는 흔히 초기화에 필요한 동작을 수행하게 되는데, 이때 멤버 변수를 초기화하는 데

pandas-are-bears.tistory.com

https://blockdmask.tistory.com/510

 

[C++] 멤버 초기화 리스트 (member initializer lists)

안녕하세요. BlockDMask 입니다. 오늘은 C++ 멤버 초기화 리스트 라는 주제로 이야기를 해보려합니다. <목차> 1. 멤버 초기화 리스트란? 2. 멤버 초기화 리스트를 꼭 사용해야하는 경우 1. C++ member initia

blockdmask.tistory.com

 

#ifndef HUMANB_HPP
# define HUMANB_HPP

# include <iostream>
# include "Weapon.hpp"

class HumanB {
 private:
	std::string name;
	Weapon *weapon;
 public:
	HumanB(std::string name);
	~HumanB();
	void setWeapon(Weapon &weapon);
	void attack(void) const;
};

#endif


#include "HumanB.hpp"

HumanB::HumanB(std::string name) 
{
	this->name = name;
}

HumanB::~HumanB() 
{
}

void	HumanB::setWeapon(Weapon &weapon)
{
	this->weapon = &weapon;
}

void	HumanB::attack(void) const
{
	std::cout << this->name << " attacks with his " << this->weapon->getType() << std::endl;
}

HumanB는 Weapon변수를 레퍼런스가 아닌 포인터로 가져온다는 것을 제외하면 크게 다르지 않다.

 

 

ex04

3개의 인자를 지닌 프로그램을 만든다. 파일명과 s1, s2.

 

파일명에서 지닌 파일을 연 다음 파일명. replace에 파일 안의 s1을 s2로 변경한 내용을 저장한다.

 

std::string의 replace는 사용할 수 없으며, C의 파일 관련 함수들은 사용할 수 없다.

 

당연히 모든 에러는 적절하게 처리되어야 한다.

 

#include <iostream>
#include <fstream>

int	main(int ac, char *av[])
{
	std::ifstream ifs;
	std::ofstream ofs;
	std::string outfile;
	std::string contents;
	std::string s1;
	std::string s2;
	int s1_len;
	int s2_len;

	if (ac != 4)
	{
		std::cout << "argc is not 4" << std::endl; 
		return (1);
	}
	s1 = av[2];
	s2 = av[3];
	s1_len = s1.length();
	s2_len = s2.length();
	if (std::strlen(av[1]) == 0 || s1_len == 0 || s2_len == 0)
	{
		std::cout << "argv length is 0" << std::endl; 
		return (1);
	}

	ifs.open(av[1]);
	if (ifs.fail())
	{
		std::cout << "sorry, can't open " << av[1] << std::endl;
		return (1); 
	}

	outfile = av[1];
	outfile.append(".replace"); 
	ofs.open(outfile);
	if (ofs.fail())
	{
		std::cout << "sorry, can't open " << outfile << std::endl;
		return (1); 
	}

	while (true)
	{
		std::getline(ifs, contents);
		
		size_t pos = 0;
		while (true)
		{
			pos = contents.find(s1, pos);
			if (pos == std::string::npos)
			{
				break ;
			}
			contents.erase(pos, s1_len);
			contents.insert(pos, s2);
			pos += s2_len;
		}
		ofs << contents;
		if (ifs.eof())
			break ;
		ofs << std::endl;	
	}
	
	return (0);
}

생각나는 에러들을 나열해보면 argc가 4가 아닐 때, 파일명, in file(argv [1]) 을 열지 못했을 때, out file(. replace )을 열지 못했을 때 정도이다. 코드를 보면 알겠지만 에러 처리는 크게 다르지 않다.

 

파일의 입출력을 담당하는 클래스가 따로 있어서 해당 인스턴스를 이용하면 에러 처리도 할 수 있다.

 

반복문에서 기존에 만든 gnl을 가져 올 필요 없이, size_t getline(ifstream, string) 함수를 사용하면 필요한 파일의 내용을 한 줄씩 가져올 수 있다.

 

find 함수의 반환 값을 이용하여 종료 시 반복문을 break 하고, 아닐 경우 글자의 해당 부분을 erase 하고 insert 하면 바로 해당 줄을 바꿀 수 있다.

 

그렇게 해서 나온 contents를 ofstream에 넣으면서 종료 시 break를 하고 아닐 때 한 줄씩 추가하도록 만들면 끝.

 

https://www.techiedelight.com/ko/replace-all-occurrences-of-a-substring-in-string-in-cpp/

 

C++에서 문자열의 모든 부분 문자열을 교체합니다.

이 게시물은 C++에서 문자열의 모든 부분 문자열을 바꾸는 방법에 대해 설명합니다. 1. 사용 string::find C++의 문자열에 있는 모든 부분 문자열을 대체하는 내장 함수는 없습니다. 문자열에서 부분

www.techiedelight.com

 

 

ex05

원래 이름은 Harl이 아닌 Karen이었으나 아무래도 좋은 뜻이 아닌지라 바뀌었습니다.

private 함수로 아래 함수들을 만든다.

  • void debug( void );
  • void info( void );
  • void warning( void );
  • void error( void );

그다음 public함수로 아래 함수를 만든다

  • void complain( std::string level );

멤버 함수에 대한 포인터를 사용하고, if elseif else 범벅을 회피하여 DEBUG INFO WARNING ERROR를 구별해서 출력하도록 구현하면 된다.

 

#ifndef HARL_HPP
# define HARL_HPP

# include <iostream>

class Harl {
 private:
	void debug(void);
	void info(void);
	void warning(void);
	void error(void);
 public:
	Harl(void);
	~Harl();
	void complain(std::string level);
};

#endif


#include "Harl.hpp"

Harl::Harl(void) 
{
}

Harl::~Harl() 
{
}

void	Harl::debug(void)
{
	std::cout << "I love to get extra bacon for my 7XL-double-cheese-triple-pickle-special-ketchup burger. I just love it!" << std::endl;
}

void	Harl::info(void)
{
	std::cout << "I cannot believe adding extra bacon cost more money. You don’t put enough! If you did I would not have to ask for it!" << std::endl;
}

void	Harl::warning(void)
{
	std::cout << "I think I deserve to have some extra bacon for free. I’ve been coming here for years and you just started working here last month." << std::endl;
}

void	Harl::error(void)
{
	std::cout << "This is unacceptable, I want to speak to the manager now." << std::endl;
}

void Harl::complain(std::string level) 
{
	std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
	void (Harl::*f[4])(void) = {&Harl::debug, &Harl::info, &Harl::warning, &Harl::error};

	for (int i = 0; i < 4; i++)
	{
		if (levels[i] == level) 
		   	(this->*f[i])();
  }
}

생성자와 소멸자에서 특별히 할 일은 없으니 넘기고 private 함수들도 출력만 구현해주면 되니 별로 어렵지는 않다.

 

string의 배열과 함수 포인터의 배열을 만들어서 각 배열의 요소를 동일하게 맞춘 다음 for문을 불러서 level이 배열의 요소와 일치하는지를 확인하도록 구현하였다.

 

#include "Harl.hpp"

int	main(void)
{
	Harl h;

	h.complain("DEBUG");
	h.complain("INFO");
	h.complain("WARNING");
	h.complain("ERROR");
	return (0);
}

메인은 그냥 한 번씩 불러오고 끝.

 

 

ex06

위에서 구현한 Harl과 비슷하지만, 두 가지 요소가 다른 프로그램을 만들어야 한다.

  1. 중요하지 않은 말(키워드가 아닌 문자열)을 걸러내서 [ Probably complaining about insignificant problems ] 출력
  2. 키워드를 받았을 때 해당 키워드와 해당 키워드 이상의 모든 정보를 출력

이를 구현하기 위해서 switch를 사용하면 편리하다...라고 힌트를 주길래 다른 방식으로 구현해도 되나 보다 했더니 평가할 때 보니 switch문을 사용해야 한다고 적혀있었다.

 

#include "Harl.hpp"

Harl::Harl(void) 
{
}

Harl::~Harl() 
{
}

void	Harl::debug(void)
{
	std::cout << "[ DEBUG ]" << std::endl;
	std::cout << "I love to get extra bacon for my 7XL-double-cheese-triple-pickle-special-ketchup burger. I just love it!\n" << std::endl;
}

void	Harl::info(void)
{
	std::cout << "[ INFO ]" << std::endl;
	std::cout << "I cannot believe adding extra bacon cost more money. You don’t put enough! If you did I would not have to ask for it!\n" << std::endl;
}

void	Harl::warning(void)
{
	std::cout << "[ WARNING ]" << std::endl;
	std::cout << "I think I deserve to have some extra bacon for free. I’ve been coming here for years and you just started working here last month.\n" << std::endl;
}

void	Harl::error(void)
{
	std::cout << "[ ERROR ]" << std::endl;
	std::cout << "This is unacceptable, I want to speak to the manager now." << std::endl;
}

void Harl::complain(std::string level) 
{
	std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};

	int i;
	for (i = 0; i < 4; i++)
	{
		if (levels[i] == level) 
		   	break ;
	}

	switch (i)
	{
		case 0:
			this->debug();
		case 1:
			this->info();
		case 2:
			this->warning();
		case 3:
		{
			this->error();
			break ;
		}	
			
		default:
			std::cout << "[ Probably complaining about insignificant problems ]" << std::endl;
	}
}

헤더는 05와 동일하니 생략하고 이전에 함수 포인터를 결정하던 for문에서 index의 숫자를 결정한다.

 

그다음 switch case문에서 break를 사용하지 않고 함수를 실행하면 해당 레벨과 그 이상의 레벨을 전부 출력할 수 있다.

 

#include "Harl.hpp"

int	main(int ac, char *av[])
{
	if (ac != 2)
	{
		std::cout << "argc error" << std::endl;
		return (1);
	}
	
	Harl h;

	h.complain(av[1]);
	return (0);
}

메인 문도 살짝 고쳐주면 끝.

 

https://boycoding.tistory.com/186

 

C++ 06.02 - switch 문 (switch statement)

06.02 - switch 문 (switch statement) 많은 if-else 문을 연결할 수 있지만, 이것은 가독성이 떨어져 읽기 어렵다. 아래 프로그램을 보자. #include enum Colors { COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_G..

boycoding.tistory.com