ArrayList로 구현한 스택을 통해 Operator overloading 및 몇 가지 문법을 정리해볼 것이다.
C++에서는 자바의 출력문 System.out.println() 혹은 C의 printf()와 달리 ostream의 '<<'를 이용하여 출력한다.
고작 두개의 기호로 출력문의 역할을 하는것이 되게 생소하게 느껴진다.
C++에만 존재하는 특이한 문법인데(확실하지 않음), +, - 같은 연산기호를 통해서 함수 호출을 쉽게 하려는 의도로 만든것 같다. 하지만 자바같은데서 쓰지않는 것을 보아하니 여러가지 부작용이 있을수 있는 문법이라 생각된다.
각설하고, Operator overloading을 어떤식으로 사용 가능한가 하면, Stack이 있을때 값을 넣고 빼는 작업을 우리는 일반적으로 push(x), pop()으로 한다. 하지만 Operator overloading을 이용한 함수 호출 방식은 Stack s가 있을때
s + 10; //s.push(10)
s + 20; //s.push(20)
--s; //s.pop()
이런 식으로 값을 넣었다 뺐다 할 수 있다.
+ 처럼 기호가 하나인 경우 unary operator라 하고, -- 같이 두개의 연산자를 가진 경우는 binary operator 라고 한다.
편리해 보이지만 스택 객체를 변수처럼 사용하는 형태가 낯설면서도 코딩시에 헷갈릴 요소가 많아 보인다.
그래도 저런 방식으로 push, pop을 해보는 코드와 최종적으로 '<<'를 사용하여 스택의 내용을 모두 출력하는 toString()에 해당하는 함수를 만드는 코드들을 작성해 보도록 하겠다.
그 전에 앞서 몇 가지 개념을 정리하고 가겠다.
C나 C++에서는 함수 호출시 클래스를 넘겨줄 때, 항상 Reference type 「&」으로 넘겨주거나 포인터 「*」로 넘겨주는 것이 관습이다. 이유는 destructor에 대한 문제 때문이다.
함수 호출시 call by value로 값을 넘겨주게 되면, Actual parameter가 참조하는 값을 Formal parameter도 같이 사용하고나서 함수 종료시 그것을 destructor를 호출하여 값을 날려버리기 때문이다. 이것은 Segmentation fault를 유발한다. (운영체제에서 사용중인 메모리 영역에 다른 내용을 덮어 쓰려하는 것 등과 같은 것이 Segmentation fault)
Actual parameter는 함수를 호출할때 사용하는 실제로 쓰는 값을 가진 parameter이고, Formal parameter는 함수를 정의할 때와 함수 안에서 사용되는 parameter이다.
그리하여 주소를 통해 해당 값에 접근하는 역참조(Dereferencing) 방식을 함수 호출하는데 있어서 사용해야 한다.
Dereferencing에 대해 조금 더 보자면
int x = 10;
int *px;
px = &x;
이런식으로 변수들이 선언되어 있을 때, "x", "&x", "px", "*px"를 각각 출력한다면 결과가 어떻게 나올 것인가?
바로 아래와 같을 것이다.
x → 10
&x → x의 주소값
px → x의 주소값
*px → 10
즉, *px가 x를 역참조(Dereferencing)한 값이 되는 것이다.
int x = 10;
int *px;
px = &x;
*px = 20;
여기서 이제 *px에 20을 넣는다면, x와 *px의 출력값들은 어떻게 될까?
x → 20
*px → 20
이렇게 된다. 즉 변수 명은 다르지만 같은 주소의 메모리를 다루게 되는데 이것을 alias라고 하기도 한다. 이런 작업들의 목적은 결론적으로 garbage 처리를 수월하게 하기 위함이다.
이쯤하고 다시 Operator overloading에 대한 것들을 이어 나가겠다.
바로 코드를 보면서 어떤식인지 살펴보겠다.
#include <stdio.h>
#include <iostream>
using namespace std;
#include "Stack.h"
void operator+(Stack &s, int x)
{
s.top++;
s.s[s.top] = x;
}
int operator--(Stack &s)
{
s.top--;
return s.s[s.top+1];
}
void doSomething(Stack &s) {
int x = --s;
x = --s;
printf("%d\n", x);
s + 100;
s + 200;
}
void doSomething(Stack *s) {
int x = s->pop();
printf("%d\n", x);
s->push(100);
s->push(200);
}
void main()
{
cout << "hello world\n";
cout << 10;
operator<<(cout,"HELLO WORLD\n");
Stack a;
a + 10;
a.push(20);
operator++(a, 30);
// doSomething(a);
doSomething(&a);
cout << a;
Stack b;
b.push(100);
Stack *p = new Stack();
p->push(50);
Stack::sayHello();
}
C++의 생소한 연산 << 같은 것들은 변수 type + operator<< 과 같은 식으로 함수를 정의할 수 있는데, +, -- 작업을 보겠다. Stack 객체의 주소값을 넘겨 받고 스택의 push와 pop에 해당하는 일을 하도록 정의하면 된다.
이렇게 메서드를 정의하면 다음 방식들로 push를 실행할 수 있게 되는 것이다.
Operator overloading 방식을 알아봤으니 다음은 Stack의 데이터들을 모두 출력해주는 자바의 toString()에 해당하는 객체를 만들어볼 것이다.
우선 Stack의 헤더 파일이다.
#ifndef _STACK_H
#define _STACK_H
#include <iostream>
using namespace std;
#define INIT_SIZE (10)
class Stack {
static int nStack;
int *s;
int top;
int sz;
void stackEmptyError();
public:
Stack();
~Stack();
void push(int x);
//void operator+(int x);
int pop();
//int operator--();
int peek();
void print();
static void sayHello();
friend void operator+(Stack &s, int x);
friend int operator--(Stack &s);
friend ostream& operator<<(ostream& o, Stack &s);
};
#endif
operator+와 operator--같은 함수들 앞에 friend라는 이상한 키워드가 붙어있는데, 이는 Stack에서 정의한 함수가 아니더라도 우리의 Stack Data member 등을 참조해서 써도 된다고 허용해주는 키워드이다. 마치 '너는 친구니까 내꺼 써도 괜찮다'라는 느낌이다.
마찬가지로
이 메서드도 Stack 내의 메서드가 아니므로 friend를 붙여준다.
다음으론 Stack.cpp 파일이다.
#include "Stack.h"
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int Stack::nStack = 0;
ostream& operator<<(ostream& o, Stack &s)
{
o << "top = " << s.top << endl;
for (int i = 0; i < s.sz; i++) {
o << s.s[i] << " ";
}
o << endl;
return o;
}
void Stack::sayHello()
{
cout << "n = " << nStack;
}
Stack::Stack()
{
nStack++;
top = -1;
sz = INIT_SIZE;
s = new int[sz];
for (int i = 0; i < sz; i++) s[i] = 0;
}
Stack::~Stack()
{
//printf("Hello\n");
if (s) delete []s;
}
void Stack::print()
{
printf("top = %d\n", top);
for (int i = 0; i < sz; i++) {
printf("%d ", s[i]);
}
printf("\n");
}
void Stack::push(int x)
{
if (top >= sz-1) {
int *tmp = new int[sz*2];
for (int i = 0; i < sz; i++) {
tmp[i] = s[i];
}
for (int i = sz; i < 2*sz; i++) tmp[i] = 0;
delete []s;
s = tmp;
sz *= 2;
}
top++;
s[top] = x;
}
/*void Stack::operator+(int x)
{
if (top >= sz-1) {
int *tmp = new int[sz*2];
for (int i = 0; i < sz; i++) {
tmp[i] = s[i];
}
for (int i = sz; i < 2*sz; i++) tmp[i] = 0;
delete []s;
s = tmp;
sz *= 2;
}
top++;
s[top] = x;
}*/
int Stack::pop()
{
if (top == -1) stackEmptyError();
top--;
return s[top+1];
}
/*int Stack::operator--()
{
if (top == -1) stackEmptyError();
top--;
return s[top+1];
}*/
int Stack::peek()
{
if (top == -1) stackEmptyError();
return s[top];
}
void Stack::stackEmptyError()
{
printf("Stack Empty!!!\n");
exit(-1);
}
라이브러리의 ostream과 cout등을 사용하기 위해서는 include가 필요하다
using namespace std를 정의하는 이유는 cout을 쓸 때마다 std::cout 이런식으로 항상 써야되는데, 이런 scope resolution operator '::'를 cout 쓸 때마다 사용하지 않기 위함이다.
Stack 내용 출력 메서드이다. ostream& o를 parameter로 받아서 o에 출력 내용을 집어넣고 return 시킬 것이다.
<< 을 여러번 사용함으로써 원하는 내용을 연달아 붙여서 출력할 수 있다.
endl은 줄바꿈을 의미한다.
스택 내용을 돌면서 o에 값들을 붙이면 모든 값들이 출력 될 것이다.
메인 함수에서 cout << s (s는 스택) 와 같은 식으로 출력하면 잘 출력되어 나올 것이다.
'Data Structure' 카테고리의 다른 글
C++ LinkedList 만들기 (version 1) (0) | 2023.03.24 |
---|---|
C LinkedList 만들기 (version 1) (0) | 2023.03.23 |
C++ Stack 만들기 (version.3.3 - LinkedList Stack) (0) | 2023.03.19 |
C++ Stack 만들기 (version.3.2 - LinkedList Stack) (0) | 2023.03.18 |
C++ Queue 만들기 (version.4 - LinkedList Queue) (0) | 2023.03.18 |
댓글