IT공부/IT서적

[윤성우 열혈 C++프로그래밍] Part4. 객체지향의 완성 - 2

shine94 2024. 9. 24. 15:01

** C++ 표준 라이브러리, string 클래스

 : 문자열의 처리를 목적으로 정의된 클래스

#include <iostream>
#include <string>
using namespace std;

int main(void)
{
	string str1 = "I like ";
	string str2 = "string class";
	string str3 = str1 + str2;

	cout << str1 << endl;
	cout << str2 << endl;
	cout << str3 << endl;

	str1 += str2;
	
	cout << "str1 += str2 : " << str1 << endl;

	if (str1 == str3)
		cout << "동일한 문자열!" << endl;
	else
		cout << "동일하지 않은 문자열!" << endl;

	string str4;
	cout << "문자열 입력: ";
	cin >> str4;
	cout << "입력한 문자열 : " << str4 << endl;

	// 위의 예시를 통해
	// 1. + 연산자의 오버로딩
	// 2. << 연산자 오버로딩
	// 3. += 연산자의 오버로딩
	// 4. >> 연산자 오버로딩

	return 0;
}

 

** string 클래스를 대체하는 String 클래스 만들기

1) 분석

   ① 문자열을 인자로 전달받는 생성자 정의

   ② 생성자, 소멸자, 복사 생성자, 대입 연산자의 정의

   ③ 결합된 문자열로 초기화된 객체를 반환하는 + 연산자의 오버로딩

   ④ 문자열을 덧붙이는 += 연산자의 오버로딩

   ⑤ 내용 비교를 진행하는 == 연산자의 오버로딩

   ⑥ 콘솔입출력이 가능하도록 << , >> 연산자의 오버로딩

 

2) 만들기

#include <iostream>
#include <cstring>
using namespace std;

class String
{
private:
	int len;
	char* str;
public:
	String()
	{
		len = 0;
		str = NULL;
	}
	String(const char* s)
	{
		len = strlen(s) + 1;
		str = new char[len];
		strcpy_s(str, len, s);
	}
	String(const String& s)
	{
		len = s.len;
		str = new char[len];
		strcpy_s(str, len, s.str);
	}

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

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

		len = s.len;
		str = new char[len];
		strcpy_s(str, len, s.str);
		return *this;
	}
	String& operator+=(const String& s)
	{
		len += (s.len - 1);
		char* tempstr = new char[len];
		strcpy_s(tempstr, len, str);
		strcat_s(tempstr, len, s.str);

		if (str != NULL)
			delete[] str;
		str = tempstr;
		return *this;
	}
	bool operator==(const String& s)
	{
		return strcmp(str, s.str) == 0;
	}
	String operator+(const String& s)
	{
		int idxSize = len + s.len - 1;
		char* tempstr = new char[idxSize];
		strcpy_s(tempstr, idxSize, str);
		strcat_s(tempstr, idxSize, s.str);

		String temp(tempstr);	// String temp = tempstr;
		delete[] tempstr;
		return temp;
	}

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

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

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

int main(void)
{
	String str1 = "I like ";
	String str2 = "string class";
	String str3 = str1 + str2;

	cout << str1 << endl;
	cout << str2 << endl;
	cout << str3 << endl;

	str1 += str2;

	cout << "str1 += str2 : " << str1 << endl;

	if (str1 == str3)
		cout << "동일한 문자열!" << endl;
	else
		cout << "동일하지 않은 문자열!" << endl;

	String str4;
	cout << "문자열 입력: ";
	cin >> str4;
	cout << "입력한 문자열 : " << str4 << endl;

	String str5 = "apple";
	if (str4 == str5)
		cout << "동일한 문자열!" << endl;
	else
		cout << "동일하지 않은 문자열!" << endl;
	
	return 0;
}

 

** 템플릿(template)

1) 함수 템플릿, 템플릿 함수

2) 클래스 템플릿, 템플릿 클래스

 

** 함수 템플릿(function template)

 : 함수 템플릿은 함수를 만듦

   함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야 함

   ㄴ 컴파일러가 함수의 호출문장을 보면서 필요한 함수를 만듦

   ㄴ 그러나 한번 함수가 만들어지면 만들어진 함수를 호출할뿐 새로 만들지 않음

   ㄴ 즉, 함수는 자료형당 하나만 만듦

   ㄴ 이 방식은 컴파일 속도가 감소하나 이는 컴파일 속도이지 실행속도가 아니란 점을 잘 인지하고

        컴파일 하는데 시간을 아껴야 하는 상황인지 아닌지 잘 판단해서 사용 권장

   ㄴ 템플릿 함수를 호출할 때, 자료형 정보 생략 가능

 

   (예) 함수의 기능 : 덧셈

          대상 자료형 : 결정되어 있지 않음

template <typename T>
T Add(T num1, T num2)
{
	return num1 + num2;
}

 

** typename을 대신해서 class를 사용할 수 있음

template <class T>	// template <typename T>과 동일

 

** 템블릿 함수(template function)

 : 함수 템플릿을 기반으로 컴파일러가 만들어 내는 함수들을 일컫는 말

   템플릿 함수는 컴파일러에 의해서 생성된 함수이기 때문에 생성된 함수(generated function)라고 부르기도 함

 

** 둘 이상의 형(Type)에 대해 템플릿 선언

#include <iostream>
using namespace std;

template <class T1, class T2>
void ShowData(double num)
{
	cout << (T1)num << ", " << (T2)num << endl;
}

int main(void)
{
	ShowData<char, int>(65);
	ShowData<char, int>(67);
	ShowData<char, double>(68.9);
	ShowData<short, double>(69.2);
	ShowData<short, double>(70.4);

	return 0;
}

 

** 함수 템플릿의 특수화(specialization of function template)

 : 상황에 따라서 템플릿 함수의 구성방법에 예외를 둘 필요가 있을 때 사용

 

▼ 문자열을 대상으로 호출할 때, 단순히 주소 값의 비교결과가 반환되는 상황

#include <iostream>
using namespace std;

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

int main(void)
{
	cout << Max(11, 15) << endl;
	cout << Max('T', 'Q') << endl;
	cout << Max(3.5, 7.5) << endl;
	cout << Max("Simple", "Bast") << endl;

	return 0;
}

 

▼ 문자열을 대상으로 호출할 때,

    ① const로 넘겨받으면 → 사전편찬 순서

    ② const가 아니면 → 길이 비교

#include <iostream>
using namespace std;

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

template<>
char* Max(char* a, char* b)
{
	return strlen(a) > strlen(b) ? a : b;
}

template<>
const char* Max(const char* a, const char* b)
{
	return strcmp(a, b) > 0 ? a : b;
}

int main(void)
{
	cout << Max(11, 15) << endl;
	cout << Max('T', 'Q') << endl;
	cout << Max(3.5, 7.5) << endl;
	cout << Max("Simple", "BastNameGood") << endl;

	char str1[] = "Simple";
	char str2[] = "BastNameGood";
	cout << Max(str1, str2) << endl;

	return 0;
}

 

** 함수를 템플릿 정의했듯이 클래스도 템플릿 정의가 가능하며 이를 클래스 템플릿(class template)이라고 함

   이를 기반으로 컴파일러가 만들어 내는 클래스를 가리켜 템플릿 클래스(template class)라고 함

   ㄴ 템플릿 클래스는 자료형의 정보를 생략할 수 없음

#include <iostream>
using namespace std;

template <typename T>
class Point
{
private:
	T xpos, ypos;
public:
	Point(T x = 0, T y = 0) : xpos(x), ypos(y)
	{ }
	void ShowPosition() const
	{
		cout << '[' << xpos << ", " << ypos << ']' << endl;
	}
};

int main(void)
{
	Point<int> pos1(3, 4);
	pos1.ShowPosition();

	Point<double> pos2(2.4, 3.6);
	pos2.ShowPosition();

	Point<char> pos3('X', 'Y');
	pos3.ShowPosition();

	return 0;
}

 

** 위의 클래스 템플릿을 바탕으로 클래스 템플릿의 선언과 정의 분리

★ 위의 코드는 왜 에러? 파일을 나눌때 고려해야 하는 부분을 고려하지 못했기 때문

 : 컴파일은 파일 단위로 이뤄짐

   Main.cpp가 컴파일될 때, 컴파일러는 템플릿 클래스를 생성해야 함

   이때, PointTemplate.h와 PointTemplate.cpp에 정보 필요 → 에러 발생

   [해결책1] 헤더파일에 템플릿 생성자와 멤버 함수의 정의를 모두 넣는다

   [해결책2] main에 #include문 추가

 

▼ 해결책1

 

▼ 해결책2

 

** 배열 클래스의 템플릿화

▼ ArrayTemplate.h → 배열 클래스의 템플릿화 대상

#ifndef __ARRAY_TEMPLATE_H_
#define __ARRAY_TEMPLATE_H_

#include <iostream>
#include <cstdlib>
using namespace std;

template <typename T>
class BoundCheckArray
{
private:
	T* arr;
	int arrlen;

	BoundCheckArray(const BoundCheckArray& arr) {}
	BoundCheckArray& operator=(const BoundCheckArray& arr) {}

public:
	BoundCheckArray(int len);
	T& operator[](int idx);
	T operator[](int idx) const;
	int GetArrLen() const;
	~BoundCheckArray();
};

template <typename T>
BoundCheckArray<T>::BoundCheckArray(int len) : arrlen(len)
{
	arr = new T[len];
}

template <typename T>
T& BoundCheckArray<T>::operator[](int idx) 
{
	if (idx < 0 || idx >= arrlen)
	{
		cout << "Array index out of bound exception" << endl;
		exit(1);
	}
	return arr[idx];
}

template <typename T>
T BoundCheckArray<T>::operator[](int idx) const
{
	if (idx < 0 || idx >= arrlen)
	{
		cout << "Array index out of bound exception" << endl;
		exit(1);
	}
	return arr[idx];
}

template <typename T>
int BoundCheckArray<T>::GetArrLen() const
{
	return arrlen;
}

template <typename T>
BoundCheckArray<T>::~BoundCheckArray()
{
	delete[] arr;
}
#endif

 

▼ Point.h → 객체 생성을 위해서 Point 선언

#ifndef __POINT_H_
#define __POINT_H_

#include <iostream>
using namespace std;

class Point
{
private:
	int xpos, ypos;
public:
	Point(int x = 0, int y = 0);
	friend ostream& operator<<(ostream& os, const Point& pos);
};

#endif // !__POINT_H_

 

▼ Point.cpp → 객체 생성을 위해서 Point 정의

#include <iostream>
#include "Point.h"
using namespace std;

Point::Point(int x, int y) : xpos(x), ypos(y) { }

ostream& operator<<(ostream& os, const Point& pos)
{
	os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
	return os;
}

 

▼ Main.cpp

#include <iostream>
#include "ArrayTemplate.h"
#include "Point.h"
using namespace std;

int main(void)
{
	// int형 정수 저장
	BoundCheckArray<int> iArr(5);

	for (int i = 0; i < 5; i++)
		iArr[i] = (i + 1) * 11;

	for (int i = 0; i < 5; i++)
		cout << iArr[i] << endl;

	cout << endl;

	// Point 객체 저장
	BoundCheckArray<Point> oArr(3);
	oArr[0] = Point(3, 4);
	oArr[1] = Point(5, 6);
	oArr[2] = Point(7, 8);

	for (int i = 0; i < oArr.GetArrLen(); i++)
		cout << oArr[i];

	cout << endl;

	// Point 객체의 주소 저장
	BoundCheckArray<Point*> pArr(3);
	pArr[0] = new Point(3, 4);
	pArr[1] = new Point(5, 6);
	pArr[2] = new Point(7, 8);

	for (int i = 0; i < oArr.GetArrLen(); i++)
		cout << *(pArr[i]);

	return 0;
}

 

** 특정 템플릿 클래스의 객체를 인자로 받는 일반함수의 정의와 friend 선언 가능

 #include <iostream>
using namespace std;

template <typename T>
class Point
{
private:
	T xpos, ypos;
public:
	Point(T x = 0, T y = 0) : xpos(x), ypos(y)
	{ }

	void ShowPosition() const
	{
		cout << '[' << xpos << ", " << ypos << ']' << endl;
	}

	friend Point<int> operator+(const Point<int>& pos1, const Point<int>& pos2);
	friend ostream& operator<<(ostream& os, const Point<int>& pos);
};

Point<int> operator+(const Point<int>& pos1, const Point<int>& pos2)
{
	return Point<int>(pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos);
}

ostream& operator<<(ostream& os, const Point<int>& pos)
{
	os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
	return os;
}


int main(void)
{
	Point<int> pos1(2, 4);
	Point<int> pos2(4, 8);
	Point<int> pos3 = pos1 + pos2;
	
	cout << pos1 << pos2 << pos3;
	
	return 0;
}

 

** 클래스 템플릿의 특수화(class template specialization)

 : 특정 자료형을 기반으로 생성된 객체에 대해, 구분이 되는 다른 행동 양식을 적용하기 위함

 

▼ 클래스 템플릿

template <typename T>
class SoSimple
{
public:
	T SimpleFunc(T num) { ... }
}

 

▼ 클래스 템플릿의 특수화

template <>
class SoSimple<int>
{
public:
	int SimpleFunc(int num) { ... }
}

 

** 클래스 템플릿의 부분 특수화

▼ 클래스 템플릿

template <typename T1, typename T2>
class MySimple { ... }

 

▼ 클래스 템플릿의 특수화

template <>
class MySimple<char, int> { ... }

 

▼ 클래스 템플릿의 부분 특수화

template <typename T1>
class MySimple<T1, int> { ... }

 

** 템플릿 인자

 : 템플릿을 정의할 때, 결정되지 않은 자료형을 의미하는 용도로 사용

   T 또는 T1, T2와 같은 문자를 가리켜 템플릿 매개변수라고 함

 

1) 템플릿 매개변수는 변수의 선언이 올 수 있음

template <typename T, int len>
class SimpleArray
{
private:
	T arr[len];
public:
	T& operator[](int idx)
	{
		return arr[idx];
	}
};

 

2) 템플릿 매개변수는 디폴트 값 지정도 가능

template <typename T=int, int len=0>
class SimpleArray
{
private:
	T arr[len];
public:
	T& operator[](int idx)
	{
		return arr[idx];
	}
};

 

** 템플릿과 static

1) 함수 템플릿과 static 지역변수

 : 컴파일러에 의해서 만들어진 템플릿 함수 별로 static 지역변수가 유지됨

2) 클래스 템플릿과 static 멤버변수

 : 변수가 선언된 클래스의 객체간 공유가 가능한 변수

   static 멤버변수는 초기화와 특수화가 가능

 

▼ 클래스 템플릿의 static 멤버변수 초기화

template <typename T>
T SimpleStaticMen<T>::mem = 0;

 

▼ 클래스 템플릿의 static 멤버변수 초기화와 특수화

template <>
long SimpleStaticMen<long>::mem = 5;

 

** 템플릿 관련 정의에는 template<typename T> 또는 template<>와 같은 선언을 뒤서,

   템플릿의 일부 또는 전부를 정의하고 있다는 사실을 컴파일러에게 알려야 함

   ㄴ [정의 부분에 T가 존재하면] T에 대한 설명을 위해서 <typename T>의 형태

   ㄴ [정의 부분에 T가 존재하지 않으면] <> 형태로 간단하게 선언하면 된다

 

** 예외처리(Exception Handling)

 : 프로그램 실행 도중에 발생하는 예외적인 상황을 처리하기 위한 문법

 

** 예외처리 메커니즘

   try : 예외를 발견

   catch : 예외를 잡음

   throw : 예외를 던짐

 

** try 블록 내에서 예외가 발생하면, catch 블록을 실행하고 나서, catch 블록의 이후가 실행됨

 

** 예외의 전달(= 예외 던지기)

 : 예외가 처리되지 않으면,

   예외가 발생한 함수를 호출한 영역으로 예외 데이터(더불어 예외처리에 대한 책임까지)가 전달

   이러한 특성은 예외가 발생한 위치와 예외를 처리해야 하는 위치가 달라야 하는 경우에 유용

 

▼ 예외 던지기에 적합한 예시

    예외상황이 발생한 위치와 예외상황을 처리해야 하는 위치가 다름

#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;

int StoI(char* str)
{
	int len = strlen(str);
	int num = 0;

	for (int i = 0; i < len; i++)
	{
		if (str[i] < '0' || str[i] > '9')
			throw str[i];
		num += (int)(pow((double)10, (len - 1) - i) * (str[i] + (7 - '7')));
	}
	return num;
}

int main(void)
{
	char str1[100];
	char str2[100];

	while (1)
	{
		cout << "두 개의 숫자 입력: ";
		cin >> str1 >> str2;

		try 
		{
			cout << str1 << " + " << str2 << " = " << StoI(str1) + StoI(str2) << endl;
			break;
		}
		catch (char ch)
		{
			cout << "문자 " << ch << "가 입력되었습니다." << endl;
			cout << "재입력 진행합니다." << endl << endl;
		}
	}
	cout << "프로그램을 종료합니다." << endl;
	return 0;
}

 

** 스택 풀기(Stack Unwinding)

 : 예외가 처리되지 않아서, 함수를 호출한 영역으로 예외 데이터가 전달되는 현상

 

** 예외 데이터의 자료형과 catch의 매개변수 형이 일치해야 함

   만약 아래와 같이 자료형 불일치하면 예외는 처리되지 않음(catch 블록으로 값이 전달되지 않음)

   따라서 함수를 호출한 영역으로 예외 데이터는 전달

int SimpleFunc(void)
{
	...
	try
	{
		if ( ... )
		{
			throw -1;	// int형 예외 데이터 발생
		}
	}
	catch(char expn)		// char형 예외 데이터를 전달
	{
		...
	}
}

 

** 함수를 정의할 때에는 함수 내에서 발생 가능한 예외의 종류를 명시해주는 것이 좋음

int ThrowFunc(int num) throw (int, char)
{
	...
}

 

** unexpected 함수

 : 함수의 선언에 명시되지 않은 예외가 전달된 경우 unexpected라는 이름의 함수 호출

   이 함수의 기본 기능은 프로그램 종료임

   때문에 명시되지 않은 예외가 전달될 경우 프로그램이 종료됨

 

** 예외 클래스와 예외 객체

 : 예외발생을 알리는데 사용되는 객체를 가리켜 '예외객체'라 함

   예외객체의 생성을 위해 정의된 클래스를 '예외 클래스'라 함

#include <iostream>
#include <cstring>
using namespace std;

class DepositException
{
private:
	int reqDep;		// 요청 입금액
public:
	DepositException(int money) : reqDep(money)
	{}

	void ShowExceptionReson()
	{
		cout << "[예외 메시지: " << reqDep << "는 입금불가]" << endl;
	}
};

class Account
{
private:
	char accNum[50];	// 계좌번호
	int balance;		// 잔고
public:
	Account(const char* acc, int money) : balance(money)
	{
		strcpy_s(accNum, strlen(acc) + 1, acc);
	}

	void Deposit(int money) throw (DepositException)
	{
		if (money < 0)
		{
			DepositException expn(money);
			throw expn;
		}
		cout << "money: " << money << endl;
	}
};

int main(void)
{
	Account myAcc("1234-5678", 5000);

	try
	{
		myAcc.Deposit(1000);
		myAcc.Deposit(-5);
	}
	catch (DepositException& expn)
	{
		expn.ShowExceptionReson();
	}

	return 0;
}

 

 

** 예외 클래스도 상속의 관계로 구성 가능

class AccountException
{
public:
	virtual void ShowExceptionReson() = 0;	// 순수 가상 함수
};

class DepositException : public AccountException
{
private:
	int reqDep;
public:
	DepositException(int money) : reqDep(money)
	{}

	void ShowExceptionReson()
	{
		cout << "[예외 메시지: " << reqDep << "는 입금불가]" << endl;
	}
};

 

** new 연산을 이용하여 메모리 공간 할당에 실패하면 bad_alloc이라는 예외 발생

 

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

try
{
	...
}
catch (...)		// ... 은 전달되는 모든 예외를 다 받아주겠다는 선언
{
	...
}

 

** C++에서의 형변환

1) static_cast : A타입에서 B 타입으로

2) const_cast : const의 성향을 삭제하는 형 변환

3) dynamic_cast : 상속관계에서의 안전한 형 변환

4) reinterpret_cast : 상관없는 자료형으로 형 변환

 

 

** 해당 글은 윤성우의 열혈 C++ 프로그래밍 도서를 읽고 정리한 글입니다.

   https://product.kyobobook.co.kr/detail/S000001589147

 

윤성우의 열혈 C++ 프로그래밍 | 윤성우 - 교보문고

윤성우의 열혈 C++ 프로그래밍 | 『C++ 프로그래밍』은 C언어를 이해하고 있다는 가정하에서 집필된 C++ 기본서로서, 초보자에게 적절한 설명과 예제를 통해서 C++ 프로그래밍에 대해 설명한다. 더

product.kyobobook.co.kr