주의 : 이 글은 C의 기초문법에 대해 상세하게 다루지 않습니다. (즉 C언어에서 배울 수 있는 기초내용은 생략합니다)
※ 명품 C++ Programming 의 책을 참고하여 개인적으로 정리한 글입니다.
이 글의 목적은 해당 책의 내용을 인용하여 더 쉽게 이해하고자 정리하고, 더 쉬운 예제를 통해 이해하는 것을 목표로 하고 있습니다.
명품 C++ Programming의 예제문제와 실습문제가 정말 좋으므로, 깊게 공부하고 싶다면 책을 구매하는 것을 추천드립니다.
책의 저작권 등등 각종 권한은 출판사와 지은이/옮긴이에 있습니다.
- 출판사: (주)생능 출판사
- 지음: 황기태
템플릿이란 무엇인가?
그냥 왜 필요하지? 이거에 초점을 맞추면 가장 쉬운 내용이다. 우리가 지금까지 함수의 중복, 오버라이딩 등등을 배웠다.
다음의 코드를 보자.
#include <iostream>
using namespace std;
class cal {
public:
int sum(int a, int b) {
return a + b;
}
};
int main() {
cal c;
cout << c.sum(1, 2);
}
정수의 1+2를 계산해서 출력하는 프로그램이다. 여기서 소수점도 계산을 하고 싶어요. 라고 한다면, 우리는 함수의 중복을 이용해서 매개변수를 다르게한 메소드를 만들어낸다.
#include <iostream>
using namespace std;
class cal {
public:
int sum(int a, int b) {
return a + b;
}
double sum(double a, double b) {
return a + b;
}
};
int main() {
cal c;
cout << c.sum(1, 2);
cout << c.sum(1.1, 2.2);
}
그러면 문자열을 서로 더하는 건 어떻게 구현할건가요? 그러면 객체와 객체를 더하는 건 어떻게 구현할 건가요?
그럴때마다 함수를 중복시켜서 양을 늘려가면.... 클래스에 같은 내용의 메소드가 엄청나게 많아진다.
그럴때 등장하는 개념이 템플릿이라는 놈이다. 자바에서는 제네릭(일반적인, generic) 이라는 용어도 등장하는데, C++에서도 등장한다.
그래서 위와 같은 코드를 다음처럼 만들 수 있다.
#include <iostream>
using namespace std;
class cal {
public:
template <class T> // 혹은 template <typename T>
T sum(T a, T b) {
return a + b;
}
};
int main() {
cal c;
cout << c.sum(1, 2);
cout << c.sum(1.1, 2.2);
}
그래서 template<class T> 라는 부분이 제네릭 타입이라고 부르는 놈이된다. 컴파일러는 어떻게 이를 처리하게 되는 걸까?
이 과정을 구체화라고 한다. 구체화에서는 컴파일러가 template를 보고 구체적인 코드를 만들어낸다. 그래서 지금 바로 위에 있는 코드를 컴파일러가 보고 우리가 처음에 언급했던 코드로 찍내내게 되는 것이다.
그리고 한 가지 유의 사항이 있다. 위의 코드에서는 다음과 같은 것은 안된다.
int main() {
cal c;
cout << c.sum(1, 2.1);
}
우리가 타입을 하나만 지정을 했기 때문에 이런 문제가 발생하며, 이 문제를 해결하는 방법은 다음과 같다.
#include <iostream>
using namespace std;
class cal {
public:
template <class T, class A> // 혹은 template <typename T>
T sum(T a, A b) {
return a + b;
}
};
int main() {
cal c;
cout << c.sum(1, 2.1);
}
뭐 제네릭타입을 하나 더 선언해주면 된다. 쉽지..
그래서 이러한 템플릿을 사용하는 것의 장단점은 무엇일까?
1. 함수 코드의 재사용성을 가능하게 하여 소프트웨어 생산성과 유연성을 높인다.
2. 컴파일러에 따라서 템를릿이 지원이 안될 수 있어 포팅에 취약하다
3. 템플릿에 뜨는 오류메시지가 빈약해 디버깅에 어려움이 있다.
여튼 이런 방식의 프로그래밍 기법을 제네릭 프로그래밍이라고 부른다.
응용에 대한 이야기는 넘어가도록 하고 다음 쳅터로 넘어가보자
제네릭 클래스란 무엇인가?
템플렛을 통해 제네릭 클래스라는 것도 만들 수 있다. 여튼 앞에서 메소드를 제네릭 함수(?)로 만드는 것과 다르게 그냥 클래스를 제네릭 클래스로 만든다고 생각하는게 편하다.
간단하게 클래스를 모두 template로 선언하면된다. 다음을 보도록 하자.
#include <iostream>
using namespace std;
template <class T>
class cal {
public:
T sum(T a, T b) {
return a + b;
}
};
int main() {
cal<int> c;
cout << c.sum(1, 2);
}
이를 main함수에서 사용하는건, 정말 간단하게 우리가 벡터를 쓰던 상황을 생각하면 편하다. 다음의 코드를 한 번 살펴보면
#include <iostream>
#include<vector>
using namespace std;
int main() {
vector<int> v;
}
우리가 vector를 사용할때 이런식으로 구현을 했었다. 그러니까 결국에 vector라는 놈도 제네릭클레스라고 볼 수도 있다.
그래서 이러한 코드로 stack을 구현시켜본 코드는 다음과 같다. stack에서는 데이터를 넣고(push)와 빼는 (pop)으로 구성이 되어 있다. 다음의 코드를 한 번 살펴보자.
#include<iostream>
using namespace std;
#define STACK_SIZE 100
template <class T>
class stack {
T stack_value[STACK_SIZE];
int top;
public:
stack() {
top = 0;
}
bool empty_stack() {
if (top > 0)
return true;
else
return false;
}
bool full_stack() {
if (top == 99)
return false;
else
return true;
}
void push(T str) {
if (full_stack() == true)
stack_value[top++] = str;
else {
cout << "스텍이 꽉찼습니다.";
}
}
T pop() {
T str;
if (empty_stack() == true)
return stack_value[(top--) - 1];
else {
cout << "주의 : 빈 스텍입니다 " << endl;
return 0;
}
}
T peek() {
if (empty_stack() == false)
cout << "주의 : 빈 스텍입니다 " << endl;
return stack_value[top - 1];
}
void print_stack_value() {
cout << "STACK [";
for (int i = 0; i < top; i++)
cout << stack_value[i] << ",";
cout << "] \n";
}
};
int main() {
stack<int> sta;
int insert_str;
cout << "stack 입니다~" << endl;
while (true) {
sta.print_stack_value();
cout << "작업을 선택해주세요. >> 1. push 2.pop 3.peek 조회 4. 종료";
int sel;
cin >> sel;
switch (sel) {
case 1:
cout << "push >> ";
cin >> insert_str;
sta.push(insert_str);
break;
case 2:
cout << "pop >> " << sta.pop() << endl;
break;
case 3:
cout << "peak = " << sta.peek() << endl;
break;
case 4:
cout << "종료합니다.";
return 0;
break;
}
}
}
그냥 아 T가 어떻게 쓰이고 있구나만 이해하면 된다. 실제로 제네릭 클래스가 어떻게 생겨먹었는지만 느껴보면 충분하다고 생각한다.
C++ 표준 템플릿 라이브러리 (STL)
아마 기초 C++이 끝나면 위의 내용을 주제로 실무 C++ 카테고리가 만들어질 예정이다. 거기서 진행되는 내용이 STL에 관한 내용이다. morden C++ 혹은 STL 에 관한 내용으로 백과사전 정도의 책이 나온다고 한다.
STL에는 3가지로 분류가 될 수 있다.
1. 컨테이너 (container) 템플릿 클래스 : 데이터를 저장하고 검색하기 위해 담아두는 자료구조로 (list, vector, map. set 등이 있다.)
2. iterator - 컨테이너 원소에 대한 포인터 : 굉장히 중요한 개념 반복자라고 불리는 것으로, 컨테이너 원소에 대한 순회 접근을 하기 위해 만들어진 컨테이너 원소에 대한 포인터이다.
3. 알고리즘-템플릿함수 : 여러가지 알고리즘을 담담하는 놈들이다.
STL 컨테이너의 종류
<vector> vector : 가변 크기의 배열을 일반화한 클래스
<deque> deque : 앞 뒤 모두 입력 가능한 큐 클래스
<list> list : 빠른 삽입/삭제가 가능한 리스트 클래스
<set> set : 정렬된 순서로 값을 저장하는 집합 클래스, 값은 유일
<map> map : (key, value> 쌍을 저장하는 맵 클래스
<stack> stack : 스택을 일반화한 클래스
<queue> queue : 큐를 일반화한 클래스
STL iterator의 종류
iterator : literator++ 하게 되면 다음 원소로 전진 하며 read/write 가능
const_iterator : literator++ 하게 되면 다음 원소로 전진 하며 read 가능
reverse_iterator : literator++ 하게 되면 지난 원소로 후진 하며 read/write 가능
const_reverse_iterator : literator++ 하게 되면 지난 원소로 후진 하며 read 가능
STL 알고리즘 함수들
copy, merge, random, rotate, equal, min, remove, search, find, move, replace, sort, max, partition, reverse, swap 가 있음
각 STL 컨테이너에 대한 내용은 나중에 크게 다룰 생각이라 여기서는 생략하겠습니다.
vector나 map 그런거 다음 내용부터는 vector에 대한 내용을 생략하고 진행합니다.
iterator
원소에 대한 포인터로 이를 모르면 안된다. 정말 중요한 내용으로 나중에 코딩테스트와 관련해서 꼭 알고있어야하는 내용이다. 가장 쉽게 생각하면 텍스트 파일을 열고 키보드의 방향키로 여기저기 왔다 갔다하면서 글자를 추가하고 삭제하고 하는 역할을 하는 것이라 생각하면된다.
이에 대한 내용도 다른 글에서 상세히 다루도록 하겠습니다.
C++ auto
파이썬을 알고오는 것과 모르고 오는 것에서 차이가 나는 부분이다.
파이썬에서는 다른 언어와 다르게 변수 타입을 지정하지 않아도 된다.
그래서 다음처럼 선언하게 된다.
intiger_num = 1
float_num = 1.
str_sen = " "
다음처럼 선언하면 파이썬은 알아서 알아먹는다. 이것과는 다르게 아예 지정을 안해줘도 일단 자신이 보기에 맞는 타입과 매칭시키기도 한다. 그래서 어떤 타입인지 가끔식 확인해줘야하는 번거로움이 있는데 (print(type(str_sen)) 이런느낌) 이런 기능을 C++에서도 사용할 수 있게 해주는 기능이 auto라고 생각하면된다. (간단하게 파이썬 처럼 코드 짜게 해주는 친구로 이해해도 될까? 싶지만 이렇게 생각해도 될 듯하다)
그래서 처음 배우면 이게 뭐하는 짓인가? 싶기도 할텐데, 다들 편하게 살자고 하는거지
#include <iostream>
#include<vector>
using namespace std;
int main() {
auto num = 1;
auto num2 = 1.1;
}
다음처럼 int형 1이 되거나 double형 1.1이 되거나 알아서 된다. 그래서 간단하게 이야기하면 데이터 타입을 컴파일러 니가 알아서 판단해라... 하고 맡기는 느낌이다.
그리고 STL에서 다음처럼 코드를 간단하게 작성이 가능하다.
int main() {
vector<int> v = { 1, 2, 3, 4, 5 };
vector<int>::iterator it; // 이부분이 너무 귀찮다.
for (it=v.begin(); it != v.end(); it++)
cout << *it << endl;
}
다음처럼 작성해도 돌아간다.
#include <iostream>
#include<vector>
using namespace std;
int main() {
vector<int> v = { 1, 2, 3, 4, 5 };
for (auto it=v.begin(); it != v.end(); it++)
cout << *it << endl;
}
람다식
이름없는 익명 함수는 람다식, 람다함수로 불리는데 다음과 같은 구조를 가지고 있습니다.
[캡처리스트] (매개변수리스트) -> 리턴타입{함수바디}
그래서 다음처럼 코드를 작성할 수 있습니다.
#include <iostream>
#include<vector>
using namespace std;
int main() {
[](int x, int y) {cout << "합은 " << x + y; } (2, 3);
}
근데, 뭔가 이해가 어렵고 왜쓰는지 모르겠으면, 이것만 이해하면 됩니다.
C++은 결국 사람이 만들었고, auto나 람다를 만든 애들이 얼마나 귀찮으면 이런짓까지 했을까? 이렇게 생각하고 그냥 받아들이면 됩니다.어려우라고 만든게 아니라 귀찮으니까 만들었다 라고 생각하면 그냥 프로그래밍 세상은 거의 이해가 가능합니다.
그래서 이를 auto와 함께 작성이 가능한데
#include <iostream>
#include<vector>
using namespace std;
int main() {
auto sum = [](int x, int y) {cout << "합은 " << x + y; };
sum(1, 2);
}
이런식으로 가능합니다. auto까지는 뭐 어찌되었든 사용한다 치지만, 저렇게 람다식을 써서까지 코드를 줄일 필요가 있을까? 이런 생각을 주로합니다.
파이썬에서는 이런 방식을 원라인 커멘드라고 부릅니다.
호불호가 극명하게 갈립니다. 쓸데없는 내용을 줄여도 되니까 좋다 vs 코드의가독성이 개판난다. 등등
들은 썰로는 파이썬 하는 사람은 원라인 커멘드로 짧게 짜는걸 좋아하고 다른 언어사람들은 최대한 가독성을 살려서 쓰는걸 좋아한다고 하는데, 저는 아직 초보라 모르겠습니다.
여튼 이 람다식과 벡터를 사용하는 코드를 한 번 봅시다. for_each라는 놈이 있는데
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v = { 1, 2, 3, 4, 5 };
for_each(v.begin(), v.end(), [](int n) {cout << n << " "; });
}
람다식을 넣어서 함수를 따로 정의하지 않고 쓰는게 가능하다고 합니다. 지금의 경우는 오히려 가독서이 더 좋아졌다고 볼 수 있습니다. 뭐 여러모로 알고있으면 편리한 기능입니다.
저러한 경우에는 정말 쓰는게 좋을 수도 있죠. 그래서 파이썬에서도 한줄에 포문이랑 출력까지 다 넣거나 if문까지 넣는 방식의 코드 작성방법도 있습니다.
이 부분은 솔직히 개념으로 일단 알아두고 나중에 코테문제를 다풀고, 고인물 아저씨들의 문제를 구경하면 자신들의 실력을 뿜내기 위해 람다, 오토, namespace 등등 넣어서 멋져보이게 적어놓은 코드들이 있습니다.
거기서 얻어갈 정보들을 몇개씩 가져오면서 공부하면 됩니다. 진짜 이부분은 남의코드를 많이 보는 것 말고는 와닿게 하는 방법이 없습니다.
이번에 생략되었던 STL 관련 이야기는 다음에 실무 C++에서 자세히 다루도록 하겠습니다.
명품 C++ 책에는 다음 장으로 입출력과 파일입출력에 대한 이야기가 등장합니다. 개인적인 생각으로 이부분은 나중에 필요할때 찾아보면 되는 부분이기도 하고, C언어의 기초 느낌이기도 하고, 당장 알고리즘 문제풀이나 객체지향적 설계와 관련해서는 중요한게 없다고 판단하여 건너뛰고 바로 예외 처리로 넘어가도록 하겠습니다.
참고 문헌
Scott Douglas Meyers(2015). Effective C++. Protec Media(프로텍 미디어)
Robert C. Martin(2021).UML 실전에서는 이것만 쓴다(UML for Java Programmers). 인사이트
황기태(2021). 명품 C++ Programming. (주)생능출판사
'ㅇ 공부#언어 > (C++)기초' 카테고리의 다른 글
(기초 C++) 11장. 예외처리 (0) | 2023.03.07 |
---|---|
(기초 C++) 9장. C++의 가상 함수와 추상 클래스 (0) | 2023.03.06 |
(기초 C++) 8장. C++에서의 클래스 상속 (0) | 2023.03.01 |
(기초 C++) 7장. C++에서의 프랜드와 연산자 중복 (0) | 2023.03.01 |
(기초 C++) 6장. C++에서의 함수 중복과 static 멤버 (0) | 2023.02.26 |