SH1R0_HACKER

Chapter02 C언어 기반의 C++ 2 본문

Programming Language/C++

Chapter02 C언어 기반의 C++ 2

SH1R0_HACKER 2020. 10. 18. 15:23

 

Part 01 C++로의 전환

 

Chapter02 C언어 기반의 C++ 2

02-2 새로운 자료형 bool

- true와 false는 그 자체로 참과 거짓을 의미하는 데이터이기 때문에, 이들 데이터의 저장을 위한 자료형이 별도로 정의되어 있는 것은 당연하다. true와 false를 가리켜 bool형 데이터라 한다. 그리고 bool은 int, double과 마찬가지로 기본자료형의 하나이기 때문에 다음과 같이 bool형 변수를 선언하는 것이 가능하다.

#include <iostream>
using namespace std;

bool IsPositive(int num) // bool도 기본자료형이기 때문에 함수의 반환형으로도 선언이 가능하다.
{
	if (num <= 0)
		return false;
	else
		return true;
}

int main(void)
{
	bool isPos; // bool형 변수(isPos)를 선언
	int num;
	cout << "Input number: ";
	cin >> num;

	isPos = IsPositive(num); // 함수 IsPositive가 반환하는 bool형 데이터를 저장
	if (isPos)
		cout << "Positive number" << endl;
	else
		cout << "Negative number" << endl;
	return 0;
}

02-3 참조자(Reference)의 이해

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

- 이미 선언된 변수의 앞에 & 연산자가 오면 주소값을 반환을 명령하는 뜻이 되지만, 새로 선언되는 변수의 이름 앞에 등장하면, 이는 참조자의 선언을 뜻하는 게 된다.

- 참조자는 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름이다. 변수에 대해서만 선언이 가능하고, 선언됨과 동시에 누군가를 참조해야만 한다. NULL로 초기화하는 것도 불가능하다.

#include <iostream>
using namespace std;

int main(void)
{
	int num1 = 1020;
	int &num2 = num1; // num1에 대한 참조자 num2를 선언
	//num1과 num2가 동일한 메모리 공간을 참조하게 된다.

	num2 = 3047;
	cout << "VAL : " << num1 << endl; // VAL : 3047 출력
	cout << "REF : " << num2 << endl; // REF : 3047 출력

	//둘 다 같은 주소 값 출력
	cout << "VAL : " << &num1 << endl;
	cout << "REF : " << &num2 << endl;
	return 0;
}

변수의 범위에는 배열요소도 포함이 된다. 배열요소는 변수로 간주되어 참조자의 선언이 가능하다. 아래는 예제이다.

#include <iostream>
// using namespace std;

int main(void)
{
	int arr[3] = { 1, 3, 5 };
	int& ref1 = arr[0];
	int& ref2 = arr[1];
	int& ref3 = arr[2];

	std::cout << ref1 << std::endl; // 1 출력
	std::cout << ref2 << std::endl; // 3 출력
	std::cout << ref3 << std::endl; // 5 출력
	return 0;
}

포인터 변수도 변수이기 때문에 참조자의 선언이 가능하다.

#include <iostream>
using namespace std;

int main(void)
{
	int num = 12;
	int *ptr = &num; // 포인터 ptr은 변수 num의 주소값을 가리킨다.
	int **dptr = &ptr; // 이중 포인터 dptr은 포인터 ptr을 가리킨다.

	int &ref = num; // 변수 num의 별명은 ref이다.
	int *(&pref) = ptr; // ptr의 별명은 pref이다.
	int **(&dpref) = dptr; // dptr의 별명은 dpref이다.
	// 포인터 변수의 참조자 선언도 & 연산자를 하나 더 추가하는 형태로 진행이 된다.

	cout << ref << endl; // 12 출력
	cout << *pref << endl; // 12 출력
	cout << **dpref << endl; // 12 출력
	return 0;
}

 


02-4 참조자(Reference)와 함수

- 매개변수로 선언된 참조자 ref1과 ref2는 main 함수에서 선언된 변수 val1과 val2의 또 다른 이름이 된다.

- 아래의 코드는 참조자를 이용한 Call-by-reference

#include <iostream>
using namespace std;

void SwapByRef2(int& ref1, int& ref2)
{
	int temp = ref1;
	ref1 = ref2;
	ref2 = temp;
}

int main(void)
{
	int val1 = 10;
	int val2 = 20;

	SwapByRef2(val1, val2);
	cout << "val1 : " << val1 << endl; // 20 출력
	cout << "val2 : " << val2 << endl; // 10 출력
	return 0;
}

 

p.80 문제 02-1의 3번 문제 한번 더 풀어보기

 

 

< 반환형이 참조형(Reference Type)인 경우 >

1. RefReturnOne.cpp

- 14행을 보면 RefRetFuncOne 함수가 참조자를 반환했고, 이를 다시 참조자에 저장하고 있다.

즉, num 1이라는 변수에 대한 참조자는 총 2개가 된다. (&ref, &num2) 그런데 여기서 함수 RefRetFuncOne의 매개변수로 선언된 참조자 ref는 지역변수와 동일한 성격을 갖는다. RefRetFuncOne이 반환을 하면, 참조자 ref는 소멸된다. (참조자가 참조하는 변수는 소멸되지 않는다.)

 

출력값은 다음과 같다.

num1: 4

num2: 4

 

2. RefReturnTwo.cpp

- 14행을 보면 num2가 새로운 변수로 선언되었다. 즉, num1과 num2는 완전히 별개의 변수라는 것이다. 이렇게 RefReturnOne.cpp와 같이 참조자를 반환한 것을 참조자뿐만 아니라 일반변수를 선언해서 반환값을 저장할 수 있다.

 

출력값은 다음과 같다.

num1 : 3

num2 : 102

 

3. RefReturnThree

- RefRetFuncTwo 함수의 반환형이 기본자료형 int로 되어있다. 이 경우는 참조자를 반환하되, 반환형은 기본자료형인 경우이다. 실행결과는 RefReturnTwo.cpp와 차이가 없다.

 

하지만 다음의 차이가 있다.

반환형이 참조형인 RefRetFuncOne 함수는 반환값을 일반 변수, 참조형 두 가지 형태로 저장할 수 있다.

반환형이 기본자료형으로 선언된 RefRetFuncTwo 함수의 반환값은 반드시 변수에 저장해야한다. 반환값은 상수나 다름없기 때문이다.

 

여기서 복습을 하자면 참조자는 변수에 대해서만 선언이 가능하고, 선언됨과 동시에 누군가를 참조해야만 한다.

다음의 선언은 모두 유효하지 않다.

int &ref=20; (X) // 참조자는 변수에 대해서만 선언가능 int &ref; (X) // 미리 참조자를 선언했다가, 후에 누군가를 참조하는 것은 불가능 int &ref=NULL; (X) //NULL로 초기화하는 것도 불가능하다.

 

< 잘못된 참조의 반환 >

- 다음의 코드와 같이 지역변수를 참조형으로 반환하는 일은 없어야 한다.

#include <iostream>
using namespace std;

int& RetuRefFunc(int n) //지역변수 num에 저장된 값을 반환하지 않고, num을 참조의 형태로 반환하고 있다.
{
	int num = 20;
	num += n;
	return num;
} // 함수가 반환되면 지역변수 num은 소멸이 된다.

int main(void)
{
	int& ref = RetuRefFunc(10); // 지역변수 num에 ref라는 또 하나의 이름이 붙게 된다.
    // 아직 소멸되지 않았지만 곧 소멸될 찌꺼기의 형태로 남아있는 데이터를 참조해서 출력결과를 보이기도 한다.
	cout << "ref: " << ref << endl;
	return 0;
}

02-05 malloc & free를 대신하는 new & delete

#include <iostream>
#include <string.h>
using namespace std;

char* MakeStrAdr(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;
}

 

문제 02-3 [구조체에 대한 new & delete 연산]

 

- 먼저 C언어에서 배운 구조체에 대해 복습을 해보자.

우리는 지금까지 동일한 종류의 데이터를 하나로 묶기 위해 배열을 사용하였다. 그러나 만약 서로 다른 종류의 데이터들을 하나로 묶어야 된다면 어떻게 할 것인가? 이러한 경우에 사용할 수 있는 방법이 구조체(structure)이다.

 

- 구조체의 선언

구조체 태그(structure tag) : 변수 X, 구조체와 구조체를 구별하기 위해 붙여지는 이름

구조체 멤버(structure member) : 변수 O, 어떠한 자료형도 변수의 구조체의 멤버가 될 수 있다.

#include <iostream>
using namespace std;

struct student // student는 구조체 태그(structure tag)
{
	int number; // 학번
	char name[10]; // 이름
	double grade; // 학점

	// number, name, grade는 구조체 멤버(structure member)
} s1; // s1은 student의 변수이다.

int main(void)
{
	s1.number = 18; // student 구조체 변수 s1의 멤버 변수인 number에 18을 대입한다.
	strcpy(s1.name, "PARK"); //멤버가 문자열이면 strcpy()를 사용하여 대입한다.
	cout << "학점 입력 : ";
	cin >> s1.grade;
	
	cout << "학번 : " << s1.number << endl;
	cout << "이름 : " << s1.name << endl;
	cout << "학점 : " << s1.grade << endl;
	return 0;
}

 

- 이제 문제를 풀어보자

 

Q. 구조체에 대한 복습을 겸할 수 있는 문제를 제시하겠다. 2차원 평면상에서 좌표를 표현할 수 있는 구조체를 다음과 같이 정의하였다.

typedef struct __Point
{
	int xpos;
	int ypos;
} Point;

 

위의 구조체를 기반으로 두 점의 합을 계산하는 함수를 다음의 형태로 정의하고(덧셈결과는 함수의 반환을 통해서 얻게 한다),

Point& PntAdder(const Point &p1, const Point &p2);

 

임의의 두 점을 선언하여, 위 함수를 이용한 덧셈연산을 진행하는 main 함수를 정의해보자. 단, 구조체 Point 관련 변수의 선언은 무조건 new 연산자를 이용해서 진행해야 하며, 할당된 메모리 공간의 소멸도 철저해야 한다. 참고로 이 문제의 해결을 위해서는 다음 두 질문에 답을 할 수 있어야 한다.

▶ 동적할당 한 변수를 함수의 참조형 매개변수의 인자로 어떻게 전달해야 하는가?

▶ 함수 내에 선언된 변수를 참조형으로 반환하려면 해당 변수는 어떻게 선언해야 하는가?

 

아래는 해답이다.

#include <iostream>
using namespace std;

typedef struct Point{ // 구조체 정의
	int x; // 구조체 멤버 1
	int y; // 구조체 멤버 2
} P; // 구조체 변수 선언

P& PntAdder(const P& p1, const P& p2) // 두 점의 합을 계산하는 함수
{
	P* pptr = new P;
	pptr->x = p1.x + p2.x;
	pptr->y = p1.y + p2.y;
	return *pptr;
}

int main(void)
{
	P * pptr1 = new P; // 구조체 포인터 선언, 메모리 할당
	//typedef를 선언하지 않았다면 struct를 붙여야한다 : struct Point* pptr1 = new struct Point;

	// 화살표 연산자로 구조체 멤버에 접근하여 값 할당
	pptr1->x=3; // 참조 연산자를 이용하는 방법 : (*pptr1).x=3;
	pptr1->y=30;

	P* pptr2 = new P;
	pptr2->x = 70;
	pptr2->y = 7;

	P& pref = PntAdder(*pptr1, *pptr2);
	cout << "[" << pref.x << ", " << pref.y << "]" << endl; // [73, 37] 출력


	delete pptr1;
	delete pptr2;
	delete& pref;
	return 0;
}