C++

Chapter 02 C언어 기반의 C++2

kdi6316 2025. 1. 3. 20:31

Chapter 02 C언어 기반의 C++2

 

목차

 

1. C언어 복습 확인문제

2. 새로운 자료형 bool

3. 참조자(Reference)의 이해

4. 참조자(Refernece)와 함수

5. malloc & free를 대신하는 new & delete

 

C언어 복습 확인문제

[문제 1] 키워드 const의 의미

키워드 const는 어떠한 의미를 갖는가? 다음의 문장들을 대상으로 이를 설명해보자

---------------------------------------------------------------------------------------------------------------------------------------------------------------

const int num=10; => 변수 num을 상수화

const int* ptr=&val1; => 포인터 ptr1을 이용해서 val1의 값을 변경할 수 없음 / 포인터 변수가 가리키는 값 상수화

int* const ptr2 = &val2; => 포인터 ptr2가 상수화됨 / 포인터 변수 자체를 상수화

const int* const ptr3 = &val3; => 포인터 ptr3가 상수화 되었으며, ptr3를 이용해서  val3의 값을 변경할수 없음

 

[문제 2] 실행중인 프로그램의 메모리 공간

실행중인 프로그램은 운영체제로부터 메모리 공간을 할당 받는데, 이는 크게 데이터,스택,힙 영역으로 나뉜다. 각각의 영역에는 어떠한 형태의 변수가 할당되는지 설명해보자. 특히 C언어의  malloc과 free함수와 관련해서도 설명해보자

---------------------------------------------------------------------------------------------------------------------------------------------------------------

데이터 -> 전역변수가 저장되는 영역

스택 -> 지역변수 및 매개변수가 저장되는 영역

힙 -> malloc 함수 호출에 의해 프로그램이 실행되는 과정에서 동적으로 할당이 이뤄지는 영역

malloc & free malloc 함수호출에 의해 할당된 메모리 공간은 free 함수호출을 통해서 소멸하지 않으면 해제되지 않는다.

 

[문제 3] Call-by-value vs Call-by-reference

함수의 호출형태는 크게 '값에 의한 호출(Call-by-value)'과 '참조에 의한 호출(Call-by-reference)'로 나뉜다. 이둘을 나누는 기준이 무엇인지, 두 int형 변수의 값을 교환하는 Swap 함수를 예로 들어가면서 설명해보자.

void SwapByValue(int num,int num2)
{
	int temp = num1;
    num1= num2;
    num2= temp;
    //Call-by-value
}

void SwapByRef(int *ptr1,int *ptr2)
{
	int temp =*ptr1;
    *ptr1=*ptr2;
    *ptr2 = temp;
    //Call-by-reference
}

 

 

새로운 자료형 bool

bool형은 C언어에는 존재하지 않고, C++에만 존재하는 자료형이었다. 그러나 C의 최근 표준에서는 bool을 기본자료형에 추가하였다.

 

'참'을 의미하는 true와 '거짓'을 의미하는 false

C와 C++ 모두, 정수 0은 '거짓'을 의미하는 숫자로, 그리고 0이 아닌 모든 정수는 '참'을 의미하는 숫자로 정의하고 있다.

true는 1이 아니며,false역시 0이 아니다. 이둘은 '참'과 '거짓'을 표현하기 위한 1바이트 크기의 데이터일 뿐이다. 다만, true와 false가 정의되기 이전에는 참을 표현하기 위해서 숫자 1을,거짓을 표현하기 위해서 숫자 0을 사용했기때문에 이둘을 출력하거나 정수의 형태로 형 변환하는 경우에 각각 1과0으로 변환되도록 정의되어 있을 뿐이다.

 

자료형 bool

true와 false는 그 자체로 참과 거짓을 의미하는 데이터이기 때문에, 이들 데이터의 저장을 위한 자료형이 별도로 정의 되어 있는데 그것을 bool형 데이터 라고 한다

ex) bool isTrueOne  = true;

 

참조자(Reference)의 이해

'참조자'라는 것은 성격상 포인터와 비유가 되기 쉽다. 그러나 참조자는 포인터를 모르는 사람도 이해할 수 있는 개념이다. 

 

참조자(Reference)의 이해

"변수란 할당된 메모리 공간에 붙여진 이름이다. 그리고 그 이름을 통해서 해당 메모리 공간에 접근이 가능하다."

그렇다면 할단된 하나의 메모리 공간에 둘 이상의 이름을 부여할 수는 없을까?

 

ex) int num1 = 2010; 

위의 변수 선언을 통해서 2010으로 초기화 된 메모리 공간에 num1이라는 이름이 붙게된다

그런데 이 상황에서 다음의 문장을 실행하면,num1이라는 이름이 붙어있는 메모리 공간에는 num2라는 이름이 하나 더 붙게 된다.

int &num2 = num1;

&연산자는 변수의 주소 값을 반환하는 연산자이기 때문이다. 하지만 위의 문장에서 보이듯이 &연산자는 전혀 다른 의미로도 사용이 된다. 이미 선언된 변수의 앞에 이 연산자가 오면 주소 값의 반환을 명령하는 뜻이 되지만, 새로 선언되는 변수의 이름 앞에 등장하면, 이는 참조자의 선언을 뜻하는 게 된다.

 

int *ptr = &num1; // 변수 num1의 주소 값을 반환해서 포인터 ptr에 저장해라

int &num2 = num1; // 변수 num1에 대한 참조자 num2를 선언해라

 

결과적으로 num1이라 이름 붙어있는 num2라는 이름이 하나 더 붙은 꼴이된다.

이는 사실상 변수로 봐도 무리는 없고 기능과 연산의 결과가 변수와 동일하다. 허나 C++에서는 참조자와 변수를 구분해서 이야기한다.

 

결론적으로 참조자는 자신이 참조하는 변수를 대신할 수 있는 도 하나의 이름인 것이다.

#include <iostream>
using namespace std;

int main(void)
{
	int num1 = 1020;
    int &num2 = num1; // num1에 대한 참조자 num2를 선언하였다. 따라서 이후로는 num1과 num2가 동일한 메모리 공간을 참조하게 된다.
    
    num2 = 3047;
    cout <<"VAL: "<<num1<<endl; //동일한 출력값이 출력되면 동일한 메모리 공간을 참조함을 증명하는 셈이다
    cout <<"REF: "<<num2<<endl;
    
    cout <<"VAL: "<<&num1<<endl; //num1과num2의 주소 값을 출력하게 하였다.
    cout <<"REF: "<<&num2<<endl;
   
   	return 0; 
}

실행을 해본 결과 VAL과 REF값이 동일하게 나왔으면 주소값도 같다고 나왔다.

 

변수와 참조자는 선언의 방식에 있어서 확실한 차이를 보인다. 참조자는 변수를 대상으로만 선언이 가능하기 때문이다. 하지만 일단 선언이 되고 나면, 변수와 차이가 없다. 위 예제에서 알수있듯이 &연산자를 이용해서 주소 값을 반환 받을 수도 있고, 함수 내에서 선언된 지역적(local) 참조자는 지역변수와 마찬가지로 함수를 빠져나가면 소멸이 된다.

 

참조자의 수에는 제한이 없으며, 참조자를 대상으로도 참조자를 선언할 수 있다.

int num1 = 2759;

int &num2=num1;

int &num3=num1;

int &num4=num1;

 

따라서 위의 문장들을 순서대로 실행하면, 하느의 메모리 공간에 num1,num2,num3,num4라는 이름을 붙인 꼴이 된다

 

참조자를 대상으로 참조자를 선언하는 것도 가능하다. 즉, 다음의 문장들을 순서대로 실행해도 결과는 같다

int num1 = 2759;

int &num2=num1;

int &num3=num2;

int &num4=num3;

 

참조자의 선언 가능범위

참조자는 변수에 대해서만 선언이 가능하고, 선엄됨과 동시에 누군가를 참조해야만 한다,즉 다음과 같은 선언은 유효하지 않다

int &ref = 20;

또한 미리 참조자를 선언했다가, 후에 누군가를 참조하는 것은 불가능하며, 참조의 대상을 바꾸는 것도 불가능하다.

배열의 경우 배열의 요소는 변수로 간주되어 참조자의 선언이 가능하다,또한 포인터 변수도 변수이기 때문에 참조자의 선언이 가능하다.

 

참조자(Reference)와 함수

 

Call-by-value & Call-by-reference

 

call-by-value - 값을 인자로 전달하는 함수의 호출방식

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

 

이 중에서, Call-by-value 기반의 함수는 다음과 같이 정의된 함수를 의미한다.

int Adder(int num1,int num2)

{

      return num1+num2;

}

위 함수는 두 개의 정수를 인자로 요구하고 있다. 따라서 Call-by-value 기반의 함수이다. 그런데 Call-by-value의 형태로 정의된 함수의 내부에서는, 함수외부에 선언된 변수에 접근이 불가능하다. 따라서 두 변수에 저장된 값을 서로 바꿔서 저장할 목적으로 다음과 같이 함수를 정의하면 원하는 결과를 얻을수없다.

void SwapByValue(int num1,int num2)
{
	int temp=num1;
    num1=num2;
    num2=temp;
    //Call-by-Value
}

위함수로 num1과 num2의 값을 바꾸려할때 생각처럼 바뀌지 않는다 이때 필요한것이 Call-by-reference기반의 함수이다

void SwapByRef(int* ptr,int* ptr2)
{
	int temp = *ptr1;
    *ptr1=*ptr2;
    *ptr2=temp;
    //Call-by-reference
}

위 함수에서는 두 개의 주소값을 받아서,그 주소 값이 참조하는 영역에 저장된 값을 직접 변경하고 있다

 

참조자를 이용한 Call-by-reference

C++에서는 참조자를 기반으로도 Call-by-reference의 함수호출을 진행할 수 있다. 

ex)

void SwapByRef2(int &ref1,int &ref2)
{
	int temp = ref1;
    ref1=ref2;
    ref2=temp;
    //Call-by-reference
}

 

참조자를 이용한 Call-by-reference의 황당함과 const참조자

포인터는 잘못 사용할 확률이 높고, 참조자의 활용이 상대적으로 포인터의 활용보다 쉽기 때문에, 참조자 기반의 함수 정의가 더 좋은 선택이라고 생각할 수 있다. 그러나 참조자 기반의 함수정의에 좋은 점만 있는것은 아니다. 이와 관련해서 아래의 코드를 보자

int num = 24;
HappyFunc(num);
cout << num << endl;

C언어의 관점에서는 100% 24가 출력된다. 그러나 C++에서는 얼마가 출력될지 알 수 없다.

코드를 분석하는 과정에 있다면, 함수의 호출문장만 보고도 함수의 특성을 어느 정도 판단할수 있어야하는데 참조자를 사용하는 경우, 함수의 원형을 확인해야하고, 확연결과 참조자가 매개변수의 선언에 와있다면, 함수의 몸체까지 문장 단위로 확인을 해서 참조자를 통한 값의 변경이 일어나는지 확인해야한다.

그러나 const 키워드를 이용하면, 이러한 단점을 어느 정도는 극복할수 있다

ex)void HappyFun(const int &ref){}

참자조 ref에 const선언이 추가 되었다. 이는 다음의 의미를 지닌다

"함수 HappyFun 내에서 참조자 ref를 이용한 값의 변경은 하지 않겠다"

이를 통해 함수 내에서, 참조자를 통한 값의 변경을 진행하지 않을 경우, 참조자를 const로 선언해서,함수의 원형만 봐도 값의 변경이 이뤄지지 않을을 알수 있게 할수있다.

 

반환형이 참조형(reference Type)인 경우

함수의 반환형에도 참조형이 선언될 수 있다. 다음이 가장 대표적인 경우다

int& RefRetFuncOne(int &ref)
{
	ref++;
    return ref;
}

또다른 경우도 있다

int RefRetFuncTwo(int &ref)
{
	ref++;
    return ref;
}

이 경우에는 반환하는건 참조자 이지만 반환형은 참조형이 아니다

예제를 통해서 한번더 확인해 보도록 하겠다

#include <iostream>
using namespace std;

int& RefRetFuncOne(int &ref)
{
	ref++;
    return ref;
}

int main(void)
{
	int num1=1;
    int &num2=RefRetFuncOne(num1); // RefRetFuncOne 함수가 참조자를 반환했고, 이를다시 참조자에 저장하고있다
    
    num1++; //변수 num1과 참조자 num2의 값을 1씩 증가시키고 있다
    num2++;
    cout << "num1: " << num1 << endl; //변수num1과 참조자 num2의 관계를 확인하기 위한 출력이다
    cout << "num2: " << num2 << endl;
	return 0;
}

위 코드에서 볼수 있듯이 참조형으로 반환된 값을 참조자에 저장하면, 참조의 관계가 하나 더 추가된다.

 

Const 참조자를 이용한 상수의 참조

int num=20+30

과 같은 코드가 있다고 생각해보자 여기서 20,30의 경우 프로그램상에서 표현되는 숫자(리터럴 상수)이다. 이들은 다음의 특징을 가진다.

"임시적으로 존재하는 값이다. 다음 행으로 넘어가면 존재하지 않는 상수다."

즉 메모리 공간에 저장은 되지만 재 참조가 가능한 값은 아니며 다음 행으로 넘어가면 소멸된다.

허나 const참조자를 사용하면 이러한 일이 가능하게 된다

const int &ref=30;

이는 숫자 30이 메모리 공간에 계속 남아있을 때에나 성립이 가능한 문장이다. 그래서 C++에서는 위의 문장이 성립할 수 있도록, const 참조자를 이용해서 상수를 참조할 때 '임시변수'라는 것을 만든다. 그리고 이 장소에 상수 30을 저장하고선 참조자가 이를 참조하게끔 한다.

"왜 임시변수라는 잘 와 닿지도 않는 개념까지 끌여들여서 상수의 참조가 가능하게 했는가?"

다음과 같은 함수를 한번 봐보자

int Adder(const int &num1,const int &num2)
{
	return num1+num2;
}

위와 같이 정의된 함수에 인자의 전달을 목적으로 변수를 선언한다는 것은 매우 번거로운 일이 아닐 수 없다. 그러나 임시변수의 생성을 통한 const 참조자의 상수참조를 허용함으로써,위의 함수는 다음과 같이 매우 간단히 호출이 가능해졌다.

cout << Adder(3,4)<<endl;

 

malloc & free를 대신하는 new & delete 

new & delete

길이정보를 인자로 받아서, 해당 길이의 문자열 저장이 가능한 배열을 생성하고, 그 배열의 주소 값을 반환하는 함수를 정의해보자.

#include <iostream>
#include <string.h> // C++에서 C언어의 헤더파일을 추가하는 것도 가능하다.
#include <stdlib.h>
using namespace std;

char * MakeStr(int len)
{
	char * str=(char*)malloc(sizeof(char)*len); // 문자열 저장을 위한 배열을 힙 영역에 할당하고있다
    return str;
}

int main(void)
{
	char * str=MakeStrAdr(20);
    strcpy(str,"I am so happy~");
    cout << str << endl;
    free(str); // 힙에 할당된 메모리 공간을 소멸하고 있다.
    return 0;
}

위 예제는 C언어에서의 동적할당을 보이기 위한 것이다. 그런데 이 방법에는 다음의 두 가지 불편사항이 따른다.

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

-반환형이 void 포인터이기 때문에 적절한 형 변환을 거쳐야한다.

 

그런데 C++에서 제공하는 키워드 new와 delete를 사용하면 이러한 불편한 점이 사라진다. new는 malloc함수를 대신하는 키워드이고,delete는 free 함수를 대신하는 키워드이다

-int형 변수의 할당 int * ptr=new int;

-double형 변수의 할당 double * ptr2=new double;

-길이가 3인 int형 배열의 할당 int * arr1= new int[3];

-길이가 7인 double형 배열의 할당 double * arr=new double[7];

 

free함수를 대신하는 키워드 delete

앞서 할당한 int형 변수의 소멸 delete ptr1;

앞서 할당한 double형 변수의 소멸 delete ptr2;

앞서 할당한 int형 배열의 소멸 delete []arr;

앞서 할당한 double형 배열의 소멸 delete []arr2;

 

#include <iostream>
#include <string.h> // C++에서 C언어의 헤더파일을 추가하는 것도 가능하다.
using namespace std;

char * MakeStr(int len)
{
	//char * str=(char*)malloc(sizeof(char)*len); // 문자열 저장을 위한 배열을 힙 영역에 할당하고있다
    char * str = new char[len];
    return str;
}

int main(void)
{
	char * str=MakeStrAdr(20);
    strcpy(str,"I am so happy~");
    cout << str << endl;
    //free(str); // 힙에 할당된 메모리 공간을 소멸하고 있다.
    delete []str;
    return 0;
}

 

힙에 할단된 변수에 포인터를 사용하지 않고 접근하기

참조자의 선언은 상수가 아닌 변수를 대상으로만 가능함을 알고 있을 것이다(const 참조자가 아닌 경우)

그렇다면 new 연산자를 이용해서 할당된  메모리 공간에도 참조자의 선언이 가능할까? 정의에 따르면, 변수의 자격을 갖추기 위해서는 메모리 공간이 할당되고, 그 공간을 의미하는 이름이 존재해야 하지만,C++에서는 new 연산자를 이용해서 할당된 메모리 공간도 변수로 간주하여, 참조자의 선언이 가능하도록 하고 있다. 따라서 다음과 같은 문장의 구성이 가능하다.

 

int *ptr=new int;
int &ref=*ptr; //힙 영역에 할당된 변수에 대한 참조자 선언
ref=20;
cout<<*ptr<<endl; // 출력결과는 20;

흔히 사용되는 문장은 아니지만, 참조자의 선언을 통해서, 포인터 연산 없이 힙 영역에 접근할수 있다는걸 확인했다.