** 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++ 프로그래밍 도서를 읽고 정리한 글입니다.
'IT공부 > IT서적' 카테고리의 다른 글
[뇌를 자극하는 윈도우즈 시스템 프로그래밍] 2장. 아스키코드 vs 유니코드 (0) | 2024.12.21 |
---|---|
[뇌를 자극하는 윈도우즈 시스템 프로그래밍] 1장. 컴퓨터 구조 - 1 (0) | 2024.12.20 |
[윤성우 열혈 C++프로그래밍] Part4. 객체지향의 완성 - 1 (0) | 2024.09.21 |
[윤성우 열혈 C++프로그래밍] Part3. 객체지향의 전개 (0) | 2024.09.14 |
[윤성우 열혈 C++프로그래밍] Part2. 객체지향의 도입 (0) | 2024.08.15 |