본문 바로가기
프로그래밍/C || CPP

[C++ 정복하자] C에서 C++로 가기 위한 도약. [2]

by _BlankSpace 2017. 5. 17.

[C++ 정복하자] 를 위한 두 번째 정리 포스팅입니다. 이 글은 제가 공부한 내용을 토대로 정리하는 글이므로, 원하시는 내용이나

보시는 분보다 낮은 실력일수도 있습니다. 부족하다고 생각하시는 점들은 댓글로 남겨주시면 최대한 보완하는 데 최선을 다하겠습니다.


목차로 돌아가기


 1. bool 이란?

C++에서는  C에서 존재하지 않는 자료형이 추가됩니다. 그것은 바로 bool 입니다.

C에서 듣지도 보지도 않았는 데, 익숙하시다구요? 그건, 아무래도 JAVA를 접하셨기 때문이 아닐까 생각이 드네요.

이러한 bool 형은 은근히 가독성도 있기 때문에 C에서도 구조체로 설정하여, 사용하시는 분도 있습니다.

그럼 bool에서 사용하는 키워드 true와 false 를 이해해보겠습니다.


보통 C 에서 bool 처럼 사용할 때, 다음과 같이 표현합니다.

#define FALSE    0

#define TRUE     1

전산학을 공부하신 분이라면 0은 거짓, 1은 참이라는 개념이 확립되어 있을 것입니다. 간단히, 참과 거짓을 표현하는 자료형이라고 생각하시면 되겠네요.

이 내용은 쉽죠? 그러므로 간단한 예제로 마무리하겠습니다.


BoolEx.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
 
int main(void)
{
    bool data = false;
    if (data == false) {
        std::cout << "data = " << data << std::endl;
        data = true;
    }   
    if (data == true) {
        std::cout << "data = " << data << std::endl;
    }   
    return 0;
}
cs


결과:

 >> data = 0

 >> data = 1

코드에서는 가독성 있게 bool 자료형은 true와 false 키워드로 표현할 수 있습니다. 

하지만, 출력해보면 숫자 0과 1로 표현됩니다. 그러므로, 6열이나 10열에서 false, true 키워드 대신에 0과 1로 비교도 가능하겠죠?

아! 그리고, bool 자료형은 '참'과 '거짓'을 표현하기 위한 1바이트 크기의 데이터입니다. (혹시나, 시험에 나올지도!)

이상 bool 형이었습니다. !


 2. 참조자(Reference)란?  

변수는 할당된 메모리 공간에 붙여진 별명 또는 이름입니다. 이러한 이름을 통해서 우리는 해당 메모리 공간에 접근할 수 있습니다.

근데 이러한 공간은 무조건 하나의 입구로만 가야할까요? 다시 말하자면, 한 메모리 공간에 하나의 변수로만 접근할 수 있어야 하나라는 말이에요. 이러한 점을 궁금하셨던 분이 계셨을까 모르겠네요.

눈치채셨겠지만, 참조자란 이러한 기능을 하는 C++의 추가 기능입니다.

정리하자면, 할당된 하나의 메모리 공간에 두 가지 이상의 이름을 부여하여, 접근할 수 있도록 만드는 것이라고 할 수 있겠습니다.


다음 그림을 참고하여, 설명하겠습니다.

왼쪽의 그림은 n1 변수에 105 값이 들어가 있습니다. 그럼 오른쪽은 어떨까요?

오른쪽은 105라는 값이 들어간 메모리 공간에 n1, n2, n3로 접근할 수 있음을 뜻합니다. 셋 중 어느 것을 선택하더라도 105 값에 접근할 수 있다는 뜻이죠.

그럼, 오른쪽의 참조자를 코드로 표현해보겠습니다.


int n1 = 105;

int &n2 = n1;    // n1에 대한 참조자 n2를 선언.

int &n3 = n2;    // n2에 대한 참조자 n3를 선언.

위의 코드를 보시면 & 기호가 있습니다. 보통 C를 공부하신 분이라면 &를 보셨을 때, and 연산자? 또는 주소 값을 출력하고자 할 때나 사용했는 데, 저게 뭐지? 라고 생각하실 수 있습니다.

C++에서 참조자를 나타낼 때는 변수 앞에 & 기호를 이용하여 참조자라는 것을 나타냅니다.

그럼 위의 코드를 바탕으로 간단하게 값을 출력하는 예제를 확인하여, 정말 같은 값을 출력하는 지 확인해보겠습니다.


ReferenceEx.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
 
int main(void)
{
    int n1 = 105;
    int &n2 = n1; 
    int &n3 = n2; 
    std::cout << "n1 : " << n1 << std::endl;
    std::cout << "n2 : " << n2 << std::endl;
    std::cout << "n3 : " << n3 << std::endl;
    return 0;
}
cs


결과:

 >> n1 : 105

 >> n2 : 105

 >> n3 : 105

직접 테스트 해보시면, 다음과 같이 같은 값을 참조한다는 것을 확인하실 수 있습니다.

참조자의 수는 제한이 없다고 합니다. (직접 테스트해봤냐구요? 아뇨.. 그러지는 않았습니다만.. 참고 내용에서는 그렇다고하네요..)


 3-1. 참조자를 이용한 Call by value & Call by reference

C언어를 공부하신 분이라면 Call by value & Call by reference 에 대해서 들어보신 분이 많으실 것입니다. 물론 모르시는 분도 계실 수 있구요. 그래서 간단히 설명하자면..

- Call by value : 값을 인자로 전달하는 함수의 호출 방식을 말함.

- Call by reference : 주소 값을 인자로 전달하는 함수의 호출 방식을 말함.

다음의 간단한 예제로 설명을 덧붙이겠습니다.


CallByValueEx.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
 
void byValue(int num1, int num2)
{
    int tmp = num1;
    num1 = num2;
    num2 = tmp;
}
 
void byRefer(int* num1, int* num2)
{
    int tmp = *num1;
    *num1 = *num2;
    *num2 = tmp;
}
 
int main(void)
{
    int num1 = 10
    int num2 = 20
    std::cout << "num1 : " << num1 << " num2 : " << num2 << std::endl;
    byValue(num1, num2);
    std::cout << "num1 : " << num1 << " num2 : " << num2 << std::endl;
    byRefer(&num1, &num2);
    std::cout << "num1 : " << num1 << " num2 : " << num2 << std::endl;
    return 0;
}
cs


결과:

 >> num1 : 10 num2 : 20

 >> num1 : 10 num2 : 20

 >> num1 : 20 num2 : 10 

byValue 함수는 단순히 값을 인자로 전달하였습니다. 그리고 byRefer 함수는 주소를 인자로 전달하였습니다. 값과 주소의 차이인데 결과는 다르게 나왔습니다. 왜 그럴까요?

먼저, 함수의 인자는 지역변수와 비슷하다고 생각하시면 될 것 같습니다. byValue는 값을 인자로 받았기 때문에 그 값을 새로운 변수에 대입한 것입니다. 그러므로 함수 내에서 값 끼리 swap한다고 하더라도 함수가 끝나면 결국 main에서는 아무런 효과가 없는 것입니다.

그에 반해서, byRefer는 인자로 주소를 받았습니다. 그리고, 그 주소가 가리키는 메모리 공간에 들어있는 값을 직접 바꾸고 있습니다. 그러므로 함수 내에서 값을 바꾸는 행동을 하였을 때, main에서도 값이 바뀐 효과를 볼 수 있는 것입니다.

(최대한 쉽게 설명하고자 글을 풀어 쓰려고 해봤는 데, 이해가 되시려는 지는 모르겠습니다.. 부족하시다면 댓글 남겨주시기 바랍니다.)


그럼, 참조자를 설명하는 곳에서 이 두 가지를 왜 설명했을까요?

그 이유는 참조자를 이용해서도 Call-by-reference 방법의 함수 호출을 진행할 수 있기 때문입니다.

다음 예제를 참고하여, 설명을 이어가겠습니다.


byRefbyRefecence.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
 
void byRefer(int& num1, int& num2)
{
    int tmp = num1;
    num1 = num2;
    num2 = tmp;
}
 
int main(void)
{
    int num1 = 10
    int num2 = 20
    std::cout << "num 1 : " << num1 << " num 2 : " << num2 << std::endl;
    byRefer(num1, num2);
    std::cout << "num 1 : " << num1 << " num 2 : " << num2 << std::endl;
    return 0;
}
cs


결과:

 >> num 1 : 10 num 2 : 20

 >> num 1 : 20 num 2 : 10

위의 예제보다 더 간단해 보이지 않으신가요? 보통 C를 어려워하시는 이유가 포인터 때문아닌가 싶습니다. 포인터 때문에 포기하는 분들도 여럿 봤구요 ㅋㅋ

그에 반에, 참조자를 이용하여 간단하게 값을 swap하는 프로그램을 구현하였습니다.

이 프로그램은 어떻게 값이 변경될 수 있었을 까요? 분명 main에서 byRefer 함수를 호출할 때, 값을 인자로 보냈는 데 말입니다.

분명히 & 기호에 힌트가 있을 텐데.. 뭘까요..? 그건, &를 붙임으로써, num1, num2를 가리키는 참조자가 선언된 것으로 생각하시면 되겠습니다.

다시 말하자면, num1, num2에 접근하는 방법이 두 가지가 되었다는 겁니다. 따라서 byRefer에서는 둘 중 한가지의 방법으로 num1, num2의 메모리 공간을 접근하여, 값을 변경한 거에요.

그러므로, main에서도 변경된 값으로 출력할 수 있었습니다.


 3-2. 반환형이 참조일 경우.

참조자는 함수의 인자로만 사용하는 건 아닙니다. 함수의 반환형으로도 사용할 수 있습니다.

다음 예제를 바탕으로 설명을 진행하겠습니다.


ReturnRefer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
 
int& ReternRefer1(int num)
{
    num+=2;
    return num;
}
 
int& ReternRefer2(int& num)
{
    num+=2;
    return num;
}
 
int main(void)
{
    int num = 0;
    std::cout << "num : " << num << std::endl;
    num = ReternRefer1(num);
    std::cout << "num : " << num << std::endl;
    num = ReternRefer2(num);
    std::cout << "num : " << num << std::endl;
    return 0;
}
cs


결과:

 >> num : 0

 >> error

 >> num : 2

위의 코드를 돌려보시면, 제대로 실행이 안되는 결과를 보실 수 있습니다. 왜그럴까요?

먼저, ReternRefer1와 ReternRefer2의 차이를 아셔야 합니다. 먼저, ReternRefer1은 인자를 값으로 받았습니다. 이후, 2를 더해서 참조자를 반환합니다.

이것은 문제가 발생합니다. ReternRefer1 안에서 일어나는 일을 설명하겠습니다.

1. num이라는 지역 변수를 하나 생성합니다.

2. num에 2를 더합니다.

3. num 지역 변수를 가르키는 참조자를 반환합니다.

응? 지역 변수는 함수가 끝나면 사라지는 특성을 가진다는 것은 다 아실 텐데요. 그럼 그것을 가리키는 참조자는 어떻게 될까요? 당연히 문제가 발생하겠죠? 참조자가 가리키는 원본이 사라지니, 참조자도 문제가 발생하는 것입니다.

그러므로, ReternRefer2 처럼 인자를 참조자로 받아서, 그것을 가리키는 참조자를 하나 더 생성하는 참조자로 반환하는 방법은 문제가 발생하지 않는 것입니다.


참조자는 정말 유용한 기능이니, 꼭 이해하셔서 잘 사용하셨으면 좋겠습니다!


 4. new & delete 란? 

우리는 C 에서 주구장창 malloc으로 메모리를 할당하고, free로 메모리를 해제했었습니다. 그런데 이 두 가지를 이용한 메모리 제어는 다음의 두 가지 불편한 점이 있습니다.

- 할당할 대상의 정보를 무조건 바이트 크기 단위로 전달해야 합니다.

- 반환형이 void형 포인터이므로 형 변환이 필요합니다.

어느정도 공감하시나요? 이러한 불편한 점을 new와 delete 키워드를 사용하면, 해결할 수 있습니다.

포인터 공부는 열심히 하고 오셨으리라 생각하고, 바로 예제로 들어가겠습니다.


NewDeleteEx1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
 
using namespace std;
 
int main(void)
{
    int* arr = new int[3];
    cin >> arr[0>> arr[1>> arr[2];
    cout << arr[0<< " " << arr[1<< " " << arr[2];
    delete[] arr;
    return 0;
}
cs


결과:

 >> 1 5 8

 >> 1 5 8


NewDeleteEx2.cpp 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
 
using namespace std;
 
class Exam
{
public:
    Exam();
};
 
Exam::Exam()
{
    cout << "Exam 입니다." << endl;
}
 
int main(void)
{
    Exam* ex1 = new Exam();
    Exam* ex2 = new Exam();
    delete ex1;
    delete ex2;
    return 0;
}
cs


결과:

 >> Exam 입니다.

 >> Exam 입니다.

굳이, 예제를 2가지로 작성한 이유는 일반적인 데이터 자료형일 때와, 객체일 때를 구분하여 보여드리고자 함이었습니다.

확실히, malloc과 free보단 간단하게 사용할 수 있어보이지 않으신가요?


메모리 제어에서 가장 중요한 것은 메모리 할당보다는 메모리 해제라고 생각합니다. 가랑비에 옷 젖는다고 결국 사소한 메모리 누수가 엄청난 결과를 초래하니깐요.


이상, C에서 C++로 가기 위한 도약. [2] 을 마치겠습니다. 다음부터는 본격적으로 C++ 내용으로 들어가도록 하겠습니다. 읽어주셔서 감사합니다.


참고 자료 : 윤성우의 열혈 C++ 프로그래밍

구글링


목차로 돌아가기


제 글이 도움이 되셨거나 공감이 되시는 부분이 있으셨다면, 밑에 있는 공감 버튼 한 번씩 꾸욱 눌러주시면 감사하겠습니다.


공감 버튼은 저에게 큰 도움이 됩니다. 감사합니다.

댓글