[C++/Syntax] 예외 처리와 형변환

Try … catch

try {} 구문 안의 내용중 예외(Exception)이 발생한다면, catch구문안의 내용을 실행하는 문법이다.
이 예외라는것은 런타임 에러와 아주 조금이나마 유사하다고 볼수있을것이다.
컴파일 중엔 에러가 발생하지 않는 에러에 대해서 대처할 수 잇기 때문이다.


Syntax

try{
   Some Code ...
} catch (exception_type val) {
   Exception controlling ...
}

위와 같은 구문이며 if else if 처럼 catch를 중첩해서 쓸 수 도있다.

 

Throw

예외를 발생 시키는 키워드 이다.
만약 try 구문 안에서 throw가 이루어진다면 catch로 throw된 에러가 전달 된다.

try{
    ...
    throw expction;
    ...
} catch (type expc){
    ...
}

위처럼 원하는 예외를 발생 시켜 catch구분으로 넘어가게 한다.
만약 다른 try 구문안에 존재하는 함수 내부에서 throw가 발생했을때 만약 그안에 catch구문이 없다면, catch를 만날때 가지 함수 밖(?)으로 나가게된다.
그 과정에서 쌓여있던 (stack)이 풀리게(free)된다. 이를 스택 풀기(Stack unwinding)이라고 하는 것 같다.

++ 예외(throw)의 경우 함수 밖으로 나가게 될때 해당 함수의 반환(return)타입에 국한되지 않고 함수를 빠져나가게되며, 에러를 전달한다.
물론 이를 제한하기 위한 방법이 아래와 같은 방법으로 존재한다.

int func(int num) throw(int){...}
위와 같이 함수를 선언하게 되면 (int)형으로만 예외 발생을 제한하며,
만약 throw ( ) 와같이 안에 아무것도 들어있지 않다면, 어떠한 예외도 전달하지 않는 단 뜻이다. 
이때 호출되는 함수는 unexpected()이다. 이 함수가 호출되면 프로그램을 종료하는 함수를 호출해 프로그램을 강제 종료 시킨다.

 

예외 클래스

예외를 발생 시킬 때 type에 따라 받을 수 있다고 했는데, class를 통해서도 받을 수 있다.
이를 예외 클래스라고 하는데, 일반 클래스와 다른게 거의 없고 throw를 통해 throw CLASS var(param)이렇게 하게되면 해당 클래스로된 예외가 전달 되게 되며,
해당 클래스를 catch하는 구문이 나올때 까지 전달하게 된다.

다만, 클래서에서 상속에 대해선 조심해야되는데, 만약 예외 클래스A를 상속한 예외 클래스 B, C 가있다면,
이를 catch할 때 A를 먼저 catch하게 되면 B C예외가 발생하더라도 A클래스를 catch하는 catch문에 잡히게 되므로 주의를 요한다.

 

모든 예외를 처리하는 catch 블록

어떠한 예외가 발생하더라도 모든 자료형에 대해서 catch하는 try-catch구문이 있다.
try { } catch (...) { }이 구문에서 ( … )는 모든 예외를 받겠단 뜻이다.

 


C++에서의 형변환

C에선 형변환을 위해선 (int)var 같은 형식으로 작성하였는데, C++에선 오래된 형변환으로 지원만할 뿐 C++에서 사용되는 형 변환방법이 따로 존재한다.

  • static_cast
    : A 타입에서 B타입으로 
  • const_cast
    : const 성향을 삭제
  • dynamic_cast
    : 상속 관계에서의 안전한 형변환
  • reinterpret_cast
    : 상관없는 자료형으로 변환

이들을 사용하기 위해선 ~~_cast<바꿀 type>var 이렇게 사용하면 된다. 

C++ 문법정리 끝.

[C++/Syntax] Template (in Class)

클래스에서의 탬플릿

템플릿은 함수뿐만아니라 클레스에서도 사용이가능하다.
대부분의 경우 선언, 작동방식, 특수화의 경우는 함수와 유사하다.

동일한 부분에 대해선 짧게 넘어가고, 클래스와 템플릿에 대해서 설명하지 못한 부분에대해서 추가적으로 이야기해본다.


Syntax

template <typename T>
class TEST{
private:
    T num;
public:
    TEST(T x=0);
    void show();

함수와 사용방법은 동일하다. template을 선언하고 <typename T>를 기준으로 T로 설정하고 싶은 변수들에게 T를 부여하면된다(..?)

당연하겠지만 TEST를 변수로 갖는 TESTER 클래스가 있다면, T에 TEST 클래스가 들어갈 수 있게된다. TESTER<TEST<int>>() 와 같은 형식으로..

또한 함수처럼 클래스를 다음과 같이 정의하여 특수화를 할 수 있다.
template<>
class TEST<int> { ... }

이렇게 정의한다면, TEST<int>에 대해선 일반 템플릿을 사용하지 않고 사용자가 선언한 클래스에서 불러오게된다.

++ 부분특수화가 가능하다.
template <class T1>
class TEST<T1, int> {...}
위와같은 방식으로 하게되면 < ? , int>인 템플릿 클래스는 모두 위 클래스를 불러온다.

템플릿에게도 인자를 부여할 수 있는데 이를 템플릿 메개변수라고 한다.
template <class T, int len>
class TEST {...}

이를 호출할땐 TEST<int, 5> test; 와 같이 정의 할 수 있다.

또한 템플릿 매개변수는 디폴트 값 설정도 가능하다.
template <class T, int len=10>
class TEST {...}

호출할때 따로 len값을 전달하지 않으면 10이 기본적으로 들어가게된다.

 

템플릿과 static 변수

static은 같은 템플릿 함수 내부에서 값을 공유 하는데, 이는 템플릿의 type에 따라 각각 작동한다.
예를들어, TEST<int>()에서의 static값을 TEST<long>()에서의 static값에 영향을 주지 못한다.

클래스 또한 유사한 방식으로 작동한다.
템플릿 클래스 별로 static 변수를 공유하게된다.
즉, class<int>와 class<int> 의 static변수는 서로 공유되지만, class<int>와 class<long>의 static변수는 다르게 작동한다.

 

[C++/Syntax] Template (in Func)

템플릿(Template)

템플릿이란 ‘틀’이란 뜻 그대로 틀만을 짜주고, 이후 컴파일 과정에서 컴파일러가 알아서 해주는 도구이다.
우린 틀만을 만들어주면 컴파일러가 완성된 코드를 만들어 실행하게 하는 방식으로 하여금 코드의 크기를 줄이고 반복된 코드를 줄인다.


Syntax

선언

template <typename T> 혹은 <class T>
T test(T num1, T num2){
    return num1+num2;
}

위 예시를 보면 타입명 대신 T가 들어간 것을 볼 수 있다. 
해당 템플릿으로 선언된 함수를 호출 하는 코드는 아래와 같다.

test<int>(1,2); test<double>(1.1,2.2);

위 처럼 test<타입>(..)과 같이 호출하게 되면 T 대신 ‘타입’이 들어가게 되면서 호출된다.
혹은 test(1.1, 2) 이렇게 호출하더라도 컴파일러가 알아서 입력된 데이터가 손실 되지 않는 자료형으로 바꿔주기도 한다.
즉, test(1.1, 2)를 호출한다면 test<dobule>(1.1, 2)가 호출 된다는 뜻이다.

둘 이상의 type의 경우

선언은 

template <typename T1, typename T2> 혹은 <class T1, class T2>
T test(T1 num1, T2 num2){
    return (T1)num1+(T2)num2;
}

위와 같이 선언하며, 호출시에는 

test<int, int>(1, 2); test<int, double>(1, 2.2);

위처럼 각각의 형을 정의해 준다.


대략적인 동작원리

템플릿은 컴파일러가 선언된 템플릿을 기반으로 호출되는 각각의 자료형 마다 함수를 새로 만든다.

즉, test<int>()를 호출하게되면,
int test(int num1, int num2) ...가 컴파일시 생성된다는 뜻이다.

이는 컴파일 과정에서 시간이 걸리지만 실행에 있어서는 문제가 되지 않는다.
참고로 이렇게 템플릿으로 생성된 함수를 ‘템플릿 함수’라고하며, 함수의 템플릿을 ‘함수 템플릿’이라고 한다.

또한 사용자가 int test(..)를 정의해놓앗다면 컴파일러는 따로 템플릿 함수를 생성하지 않고 정의된 함수를 사용한다.


함수 템플릿의 특수화

앞서 말한것 중에서 자용자가 int test(…)와 같이 미리 정의한것이 있다면 해당 함수를 불러온다고 이야기 했다.
이는 일정 자료형에 대해서 사용자가 다른 동작을 원하는 경우 따로 코드를 작성해 사용할 수 있다는 뜻이다.

위 내용을 종합해만든 아래 코드를 보자.

template <typename T1, class T2>
void print(T1 num1, T2 num2);

template <>
void print<int, double>(int num1, double num2);

int main() {
	print(1, 2);
	print(1, 2.3);
	return 0;
}

template <typename T1, class T2>
void print(T1 num1, T2 num2) {
	std::cout << num1 << " " << num2 << std::endl;
}

template <>
void print<int, double>(int num1, double num2) {
	std::cout << num1 << " dobule:" << num2 << std::endl;
}

템플릿 함수를 선언한 후에,
template<> 아래에선 특별한 경우 (int, double)가 들어올 경우 두 값사이에 ‘double:’ 을 넣도록 하고 있다.
이처럼 몇몇 경우에 대해서 사용자가 따로 특수화를 시켜 다른 동작을 하게 하는 것 또한 가능하다.


파일을 나누어 저장 할 경우 주의사항

컴파일러는 파일별로 컴파일 한다는 것을 알고있다.
하지만 템플릿은 컴파일러가 템플릿을 보고 각각의 형에 따라 함수를 만든다.

즉, 헤더파일에 템플릿를 선언만 해놓고 cpp에서 해당 템플릿을 정의할 경우 헤더따로, cpp 파일 다로 컴파일하기에 에러가 발생한다.
이럴경우 헤더 뿐만아니라 ‘ #include “~~.cpp” ‘와 같이 템플릿이 정의된 cpp파일을 include 해야한다.

[C++/Practice] String Class의 구현

Hand made String Class

지금까지 공부한 내용을 바탕으로 C++의 String 클래스를 직접 만들어 본다.
String Class의 기능들은 다음과 같다.

  1. 선언 방식은 String(), String(char*), String(String)이 있다.
  2. ‘==’ 연산은 두 문자열을 비교한다.
  3. ‘+’ 연산은 두 문자열을 연결한 String class를 반환한다.
  4. ‘+=’ 연산은 기존 String class에 이어 붙여 준다.
  5. ‘<<‘ 연산은 문자열을 출력(out)한다.
  6. ‘>>’ 연산은 문자열을 입력(in)받는다.

위와 같은 기능을 하는 String class를 직접 만들어본다.


String.h Class Header

#ifndef CUS_STR
#define CUS_STR
#include <cstring>
#include <iostream>

class String {
private:
	int len;
	char *str;
public:
	String();
	String(const char *s);
	String(const String &s);
	~String();
	String&	operator=	(const String& s);
	String&	operator+=	(const String& s);
	bool	operator==	(const String& s);
	String	operator+	(const String& s);

	friend std::ostream& operator<< (std::ostream& os, const String& s);
	friend std::istream& operator>> (std::istream& is, String& s);
};

#endif // !CUS_STR

String.cpp Class code

#include "Stringh.h"

String::String() {
	len = 0;
	str = NULL;
}

String::String(const char *s) {
	len = strlen(s) + 1;
	str = new char[len];
	strcpy(str, s);
}

String::String(const String& s) {
	len = s.len;
	str = new char[len];
	strcpy(str, s.str);
}

String::~String() {
	if (str != NULL)
		delete[]str;
}

String& String::operator= (const String& s) {
	if (str != NULL)
		delete[]str;

	len = s.len;
	str = new char[len];
	strcpy(str, s.str);
	return *this;
}

String& String::operator+= (const String& s) {
	len += s.len;
	char *tmpstr = new char[len];
	strcpy(tmpstr, str);
	strcat(tmpstr, s.str);

	if (str != NULL)
		delete[]str;
	str = tmpstr;
	return *this;
}

bool String::operator==	(const String& s) {
	return strcmp(str, s.str) ? false : true;
}

String String::operator+ (const String& s) {
	char *tmpstr = new char[len + s.len - 1];
	strcpy(tmpstr, str);
	strcat(tmpstr, s.str);

	String tmp(tmpstr);
	delete[]tmpstr;
	return tmp;
}

std::ostream& operator<< (std::ostream& os, const String& s) {
	os << s.str;
	return os;
}

std::istream& operator>> (std::istream& is, String& s) {
	char str[100];
	is >> str;
	s = String(str);
	return is;
}

Main.c

#include "Stringh.h"
int main()
{
	String str1 = "Str 1";
	String str2 = "Str 1";
	String str3 = str1 + str2;

	std::cout << str1 << std::endl;
	std::cout << str2 << std::endl;
	std::cout << str3 << std::endl;

	str1 += str2;
	if (str1 == str3)
		std::cout << "동일!" << std::endl;
	else
		std::cout << "다름!" << std::endl;

	String str4;
	std::cout << "문자열 입력: ";
	std::cin >> str4;
	std::cout << "입력된 문자열: ";
	std::cout << str4 << std::endl;
	return 0;
}

 

[C++/Syntax] 연산자 오버로딩

Operation Overloading

C++에선 연산자를 호출할 때 a+b를 그대로진행하는 것이 아닌 a.operator+(b)와 같은 함수형식으로 연산을 진행한다.
즉, +, /, -, *, =, += 과 같은 연산자는 함수오버로딩을 지원해, 클래스마다 연산방식을 제어 해 줄 수 있다.


Syntax

문법은 '반환형' operator'연산자'(피연산변수){ ... }이다.
예를 들어 ‘+’ 연산자를 오버로딩 할 경우

class Test2 {
    ...
    Test2 operator+(const Test2 &ref) {
        Test2 t(Test2num + ref.Test2num);
        return t;
    }
}

위 처럼 class 내부에서 할 수 있으며, Test2형 클래스를 ‘+’ 연산을 할 경우 Test2::operator+( ... ) 함수를 호출하게 된다.

또한 전역함수로 연산자 오버로딩을 할 경우 frind가 적절히 사용되는 몇 안되는 경중 하나인데,

friend Test2 operator+(const Test2 &ref, const Test2 &ref1);
...
Test2 operator+(const Test2 &ref, const Test2 &ref1) {
    return ref.Test2num + ref2.Test2num;
}

Test2 클래스 끼리 + 연산을 할 때 내부 operator+함수가 아닌 전역 함수인 operator+( 피연산자1 , 피연사자2 )를 호출하게 된다.


단항 연산자의 오버로딩

단항 연산자 또한 오버로딩이 가능하다. 
다만, 특정 형태에 대해서 전역 함수형태로 오버로딩된 연산자는 동작이 다르게 되는데 이에 대해서도 알아보자.

class Optest {
private:	int num;
public:	Optest(int num) : num(num) {}	void Show() { std::cout << num << " "; }
	Optest& operator++() {
		num += 1;
		return *this;
	}
	friend Optest& operator--(Optest &ref);
};

Optest& operator--(Optest &ref) {
	ref.num -= 1;
	return ref;
}

int main(){
	Optest op(10);
	++op;	op.Show();
	--op;	op.Show();

	++(++op);	op.Show();
	--(--op);	op.Show();
	return 0;
}

++의 경우 return값이 *this 이다. 이를 연산하는 과정은
++(++op) > ++(op.operator++(10)) > ++ (op의 참조값); > (op의 참조값).operator++(  )
위와 같이 진행된다. 계산이 완료되고 op의 참조값이 반환되면서 일반적인 ++ 연산이 가능한것이다.

–의 경우엔 return값이 ref 이다. 전달받은 잠조자를 return한다. 이를 연산하는 과정은
–(–op) > –(operator–(op)) >> –(op의 참조값) > operator(op의 참조값)
위처럼 진행된다.

전위 증가와 후위 증가

C++에선 전위 증가와 후위증가를 오버로딩 하기위해서, 키워드 int를 사용해 구분하고 있다.
++num은 operator++()
num++는 operator++(int)


교환 법칙의 구현

‘*’ 연산자의 경우엔 교환 법칙이 성립하는데 이를 기존의 operator* 하나론 교환 법칙이 성립되지 않게된다.
이를 해결하기 위해 operator*를 추가해 한번 더 오버로딩 해주면 된다.

class Optest {
private:
	int num;
public:
	Optest(int num) :num(num) {}
	int operator*(int num) {
		return this->num*num;
	}
	friend int operator*(int num, Optest& ref);
};

int operator*(int num, Optest& ref) {
	return ref.num*num;
}

int main(){
	Optest op(10);
	std::cout << 3*op << " " << op*3;
	return 0;
}

 

++”<<“, “>>” 오버로딩

우리가 std::cout에 사용하는 “<<” 혹은 “>>”또한 연산자이기에 오버로딩이 가능하다.
즉, 클래스가 출력 혹인 입력을 받을 때에 일정하게 출력할 수 있다.

+++ [ ], new, delete, new[], delete[], () 오버로딩

각각의 사항들은 쓸 때 정리한다.

[C++/Syntax] 상속 etc…

클래스 내 함수

클래스 내의 함수는 실제로 클래스 안에 있을까?

이 물음에 대한 답변은 함수 포인터에 대한 개념이 필요한데,
각각의 클래스 안엔 함수를 가르키는 포인터가 존재해 메모리에 상주하는 함수를 불러오는 것이다.

하지만 이는 C에서 struct를 통해 구현 가능한 것이다. 
즉, C++은 동작 방식이 다른데 이 방식이 바로 “가상함수”때문이다.

가상함수는 가상함수 테이블을 통해 동작이 이루어지는데,
key엔 클래스,함수 정보가 담겨있고 key가 가르키는 value엔 함수를 가르키는 포인터가 담겨 있다.

이 테이블에서 함수 포인터를 통해 함수를 불러오기에 C에 비해 속도가 떨어지지만, 
그 차이가 미미하고 이를 통해 얻을 수 있는 이득이 크기에 사용된다.

 

다중상속

“일반적인 경우에서 다중상속은 다양한 문제를 동반한다. 따라서 가급적 사용해선 안된다. 하지만 예외적으로 매우 제한적인 사용까지 부정할 필욘없다”

쓰지 말란다.

문법은 아래와 같다.

class son : public dady, public mom { ... }

즉 dady 클래스와 mom 클래스를 다중으로 상속하고 있다.
이때 모호성이 발생 할 수 있는데, 만약 dady와 mom이 person에서 상속받는다면 누구의 person속성을 받아야 할지 모호해진다는 것이다.
이를 죽음의 다이야몬드라고 한다. 자세한건 나무위키를 보자

[C++/Syntax] 상속2

상속의 성질과 포인터

상속은 다양한 성질을 지니고 있다.
그 다양한 성질과 포인터가 만나게 되면 더욱 다양한 성질을 띄게 되는데,
이는 C++에서의 객체를 사용함에 있어서 다양한 방식을 제공하게 해준다.


객체 상속과 포인터

class Test {
private:
	int Testnum;
public:
	Test(int num) {
		this->Testnum = num;
	}
	void test() { std::cout << "test in Test" << std::endl;}
};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num)
		: Test(num)
	{
		this->Test2num = num;
	}
	void test() { std::cout << "test in Test2" << std::endl;}
};

int main(){
	Test *test = new Test2(10);
	test->test();
	return 0;
}

위 코드의 main 함수에 Test *test = new Test2(10);을 보자 분명히 Test 형 포인터 인데 Test2를 넣고 있다.
이말인 즉슨, 부모클래스형 포인터는 자식 클래스의 포인터를 담을 수 있다는 뜻이다.

근데 이게 동작이 굉장히 신기한데,  main의 test->test();함수를 실행 시키면, Test의 test()함수 결과인 “test in Test”가 출력된다.
즉 같은 함수가 있을 땐 자료형의 함수를 불러온다는 뜻이다.

참고로, 만약
int main(){
    Test2 *test2 = new Test2(10);
    Test *test = test2;
    test2->test();
    test->test();
    return 0;
}

해당 코드를 실행하면
“test in Test2”
“test in Test”가 출력된다. 즉, 포인터의 자료형에 따라 불러올 함수가 결정 된다는 거다.

하지만 이는 자식 클래스에서 오버라이딩한 이유가 없어지게 되며, 문제가 있다고 볼 수 있다.
이를 해결하기 위해 도입하는 개념이 바로 Virtual Function, 가상함수이다.

 

가상함수 Virtual Function

가상함수의 선언은 virtual키워드 선언을 통해서 이뤄진다.
이후 오버라이딩을 하게 되면 자료형의 관계없이 오버라이딩 된 함수가 사용된다.

class Test {
private:
	int Testnum;
public:
	Test(int num) {
		this->Testnum = num;
	}
	virtual void test() { std::cout << "test in Test" << std::endl; }

};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num)
		: Test(num)
	{
		this->Test2num = num;
	}
	void test() { std::cout << "test in Test2" << std::endl; }
};

int main(){
	Test2 *test2 = new Test2(10);
	Test *test = test2;
	test2->test();
	test->test();
	return 0;
}

Test 클래스에 test() 함수 앞에 virtual 선언이 되어 있다.
즉, 이후 상속 받은 Test2에서 해당 함수를 오버라이트할 경우 자료형의 관계없이 Test2의 test()를 불러오란 뜻이다.

 

가상 함수와 소멸자

class Test {
private:
	int Testnum;
public:
	Test(int num) {	this->Testnum = num; }
	virtual void test() { std::cout << "test in Test" << std::endl; }

	~Test() {
		std::cout << "delete Test" << std::endl;
	}
};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num) : Test(num){ this->Test2num = num; }
	virtual void test() { std::cout << "test in Test2" << std::endl; }

	~Test2(){
		std::cout << "delete Test2" << std::endl;
	}
};

int main(){
	Test *test = new Test2(10);
	delete test;
	return 0;
}

위 코드를 실행하면 “delete Test” 만 뜨게 되며 ~Test() 소멸자만 뜨게 된다.

즉, 클래스에서 소멸자의 호출 또한 “자료형”의 소멸자만이 호출된다.
이는 메모리 leak이 일어 날 수 있기에 이 또한 virtual 키워드를 통해 해결 할 수 있다.

virtual ~Test() { ... }와 같이 바꿔주면,
“delete Test2
delete Test” 라는 결과가 뜨며, ~Test2()와 ~Test()가 순서대로 소멸 하는 것을 볼 수 있다.

 

++ 순수 가상함수

클래스 중에서 객체 생성을 목적으로 정의되지 않는 클래스도 있다고한다.
이때 virtual void test() = 0;이렇게 정의하게 되면, 
Test test = new Test(1)와 같이 객체를 생성하려고 하면 컴파일 에러가 발생한다.

class Test {
private:
	int Testnum;
public:
	Test(int num) {
		this->Testnum = num;
	}
	virtual void test() = 0;
};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num)
		: Test(num)
	{
		this->Test2num = num;
	}
	void test() { std::cout << "test in Test2" << std::endl; }
};

int main(){
	Test2 *test2 = new Test2(10);
	Test *test = test2;
	test2->test();
	test->test();
	return 0;
}

Test 클래스의 virtual void test() = 0;은 new Test(1)과 같은 Test 객체를 생성할시 컴파일 에러를 출력한다.

즉, Test 클래스는 상속을 위한 클래스이며,
객체로 생성되어 사용되어선 안되고,
test()함수는 상속 받은 클래스에서 오버라이트 해야 된다는 의미다.

 

+++ 참조(Reference)

포인터 뿐만이 아니라 참조자 또한 같은 동작을 한다.

int main(){
	Test2 test2(10);
	Test &test = test2;
	test.test();
	return 0;
}

단순히 “->”가 “.”으로 선언 방식에서 살짝의 차이를 제외하곤 포인터와 동일한 동작을 한다.

[C++/Syntax] 상속

상속 (inheritance)

객체지향의 꽃이라고도 불리는 상속은 상위 클레스의 함수, 변수 등을 반복해서 작성하지 않고 재사용을 위해서 만들어 졋다.
friend를 써도 될거 같은데, friend는 private변수까지 모두 다 사용이 가능해지는데, 이는 접근제어가 깨지게 된다.
이를 보안하기 위해 나온게 상속은 아니지만, 현재로써 내가 이해한 가장 큰 필요성인 것 같다.


문법 Syntax

class Test {
private:
	int num;
public:
	Test(num){
		this->num = num;
	}
	void test() {
		// code
	}
};
class Test2 : public Test {
private:
	int num;
public:
	Test2(num)
		:Test(num)
	{
		this->num = num;
	}
	void test2() {
		// code
	}
};

Test2 클래스는  Test 클래스를 public으로 상속 받고 있다.
이를 public 상속이라 하며 Test2 형으로 선언된 class 변수는 .test()와 test2() 모두 사용이 가능하다.

하지만 private로 선언된 변수라든가, 함수는 직접 적으로 접근하지 못한다.
그래서 상속받은 자식 클레스에 한에 접근이 가능하도록 하는 권한이 protected가 있다.

또한 16-17번 줄을 보면,
부모(상위) 클래스의 생성자가 존재한다면, 자식(하위) 클래스에서 초기화 해주어야 한다.

 

Private, Protected, Public 상속

상속을 할 때 class class_name : public other_class와 같은 방식으로 상속을 해주었는데,
이때 public, protected, private 선언에 따라 상속 받을 수 있는 권한이 달라지게 된다.

class Test {
private:
	void func_private() {...}
protected:
	void func_protected() {...}
public:
	void func_public() {...}
};

위와 같은 Test 클래스가 있다고 했을때 Test를 어떤 권한을 가지고 상속 받느냐에 따라 접근 권한이 달라진다.
가장먼저,

class pubTest : public Test {}란 클레스가 있을때,  pubTest 클래스는 Test 클래스의 func_public() 밖에 쓰지 못한다.
즉 public 권한을 가진 변수/함수 등 밖에 접근하지 못한다.

class proTest : protected Test {} 란 클래스는 public 변수/함수와 protected 변수/함수 등 가지 접근이 가능하다.
또한 상속 되면서 Test (부모)클래스의 public 변수/함수 등도 protected로 변하면서 외부에서 접근이 불가능하다!

마지막으로 class priTest : private Test {}는 private에 있는 변수/함수엔 접근하지 못한다.
다만 무모 클래스의 Protected 와 Public 변수/함수는 private로 변하게 되면서 이를 다시 상속 받은 변수에서도 사용할 수 없게 된다!