printf 와 scanf 대신해서 cout, cin을 사용할 수 있습니다. 서식지정을 안 해줘도 되고, 값을 입력 받을 때 포인터와 연관된 & 연산자를 붙일 필요도 없으니 말입니다. 실행중인 프로그램의 메모리 공간. 데이터는 전역변수가 저장되는 영역입니다. 스택은 지역변수 및 매개변수가 저장되는 영역입니다. 힙은 malloc 함수호출에 의해 프로그램이 실행되는 과정에서 동적으로 할당이 이뤄지는 영역입니다. malloc & free 는 malloc 함수호출에 의해 할당된 메모리 공간은 free 함수호출을 통해서 소멸하지 않으면 해체되지 않습니다. 키워드 const의 의미 const int num = 10; 은 변수 num을 상수화한다는 뜻입니다. const int * ptr1 = &val1; 포인터 ptr1 을 이용해서 val1의 값을 변경할 수 없음을 나타냅니다. int * const ptr2 = &val2; 포인터 ptr2가 상수화 됨을 의미합니다. const int * ptr3 = &val3; 은 포인터 ptr3은 상수화 되었으며, ptr3을 이용해서 val1의 값을 변경할 수 없음을 의미합니다.
새로운 자료형 bool
키워드 true 와 false를 이해하는 것이 우선입니다.
#include <iostream> using namespace std; int main(void) { int num = 10; int i = 0; cout << "true : " << true << endl; cout << "false : " << false << endl; while (true) { cout << i++ << ' '; if (i > num) break; } cout << endl; cout << "sizeof 1: " << sizeof(1) << endl; cout << "sizeof 0: " << sizeof(0) << endl; cout << "sizeof ture: " << sizeof(true) << endl; cout << "sizeof false: " << sizeof(false) << endl; return 0; } |
키워드 ture 와 false 를 콘솔에 출력했을 때의 출력내용을 확인하기 위한 문장입니다. C언어에서는 무한루프를 형성하기 위해서 일반적으로 숫자1을 사용합니다. C++에서도 숫자1을 사용할 수 있지만, true를 대신 사용할 수도 있습니다. 상수 1과 상수 0의 데이터 크기는 4입니다. 참과 거짓을 의미하는 데이터 true와 false의 크기를 확인하기 위한 문장입니다. 1이므로 true 와 false를 사용하는게 효율적입니다. 이 둘은 참과 거짓을 표현하기 위한 1바이트 크기의 데이터일 뿐입니다. true 와 false는 그 자체를 참과 거짓을 나타내는 목적으로정의된 데이터로 인식하는 것이 바람직합니다.
true와 false는 그 자체로 참과 거짓을 의미하는 데이터이므로, 이들 데이터의 저장을 위한 자료형이별도로 정의되어 있는 것은 당연합니다.
#include <iostream> using namespace std; bool IsPositive(int num) { if (num <= 0) return false; else return true; } int main(void) { bool isPos; int num; cout << "Input number: "; cin >> num; isPos = IsPositive(num); if (isPos) cout << "Postive number" << endl; else cout << "Negative number" << endl; return 0; } |
bool도 기본자료형이기 때문에 함수의 반환형으로 선언이 가능합니다. bool형 변수를 선언해서 함수 IsPositive가 반환되는 bool형 데이터를 저장하고 있습니다.
참조자의 이해
변수는 할당된 메모리 공간에 붙여진 이름이다. 그리고 그 이름을 통해서 해당 메모리 공간에 접근이 가능합니다. 할당된 하나의 메모리 공간에 둘 이상의 이름을 부여할 수는 없을까? & 연산자는 변수의 주소 값을 반환하는 연산자입니다. 하지만 위의 문장에서 보이듯이 & 연산자는 전혀 다른 의미로도 사용이 됩니다. 새로 선언되는 변수의 이름 앞에 오면, 이는 참조자의 선언을 뜻하게 됩니다. 참조자는 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름입니다.
#include <iostream> using namespace std; int main(void) { int num1 = 1020; int &num2 = num1; num2 = 3047; cout << "VAL :" << num1 << endl; cout << "REF: " << num2 << endl; cout << "VAL: " << &num1 << endl; cout << "REF: " << &num2 << endl; return 0; } |
num1에 대한 참조자 num2를 선언하였습니다. 따라서 이후로는 num1 과 num2가 동일한 메모리 공간을 참조하게 됩니다. 참조자는 변수를 대상으로만 선언이 가능합니다. 일단 선언이 되고 나면 변수와 차이가 없습니다. & 연산자를 통해서 주소 값을 반환 받을 수도 있고, 함수 내에서 선언된 지역적 참조자는 지역변수와 마찬가지로 함수를 빠져나가면 소멸이 됩니다. 참조자는 별칭입니다. 변수에 별명(별칭)을 하나 붙여주는 것입니다. num1이 변수의 이름이라면, num2는 num1의 별명이라는 뜻입니다. 참조자의 수에는 제한이 없습니다. 또한 참조자를 대상으로 참조자를 선언할 수도 있습니다. 참조자는 변수에 대해서만 선인 가능합니다. 선언됨과 동시에 누군가를 참조해야 합니다. 상수를 대상으로 참조자를 선언할 수 없습니다. 또한 미리 참조자를 선언했다가, 후에 누군가를 참조하는 것은 불가능합니다. 참조자는 무조건 선언과 동시에 변수를 참조하도록 해야 합니다. 변수의 범위에는 배열요소도 포함됩니다.
#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]; cout << ref1 << endl; cout << ref2 << endl; cout << ref3 << endl; return 0; } |
배열이 아니라, 배열오소는 변수로 간주되며 참조자의 선언이 가능합니다. 포인터 변수도 변수이기 때문에 참조자의 선언이 가능합니다.
#include <iostream> using namespace std; int main(void) { int num = 12; int *ptr = # int **dptr = &ptr; int &ref = num; int *(&pref) = ptr; int **(&dpref) = dptr; cout << ref << endl; cout << *pref << endl; cout << **dpref << endl; return 0; } |
포인터 변수의 참조자 선언도 & 연산자를 하나 더 추가하는 형태로 진행이 됩니다. pref는 포인터 변수 ptr의 참조자이므로, 변수 num에 저장된 값이 출력됩니다. dpref는 포인터 변수 dptr의 참조자이므로, 변수 num에 저장된 값이 출력됩니다.
참조자와 함수
값을 인자로 전달하는 함수의 호출방식과 주소 값을 인자로 전달하는 함수의 호출방식이 있습니다. 주소 값을 이용하여 외부에 선언된 변수에 접근하는 callbyreference. 주소 값을 전달 받아서, 함수 외부에 선언된 변수에 접근하는 형태의 함수호출. 주소 값이 외부 변수의 참조도구로 사용되는 함수의 호출을 뜻합니다.
참조자를 이용한 callbyreference. 함수호출 시 전달되는 인자로 초기화를 하겠다는 의미입니다.
#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; cout << "val2: " << val2 << endl; return 0; } |
두 참조자 ref1, ref2에 저장된 값의 교환과정입니다. 이 교환 과정은 main 함수에 선언된 변수 val1 과 val2의 교환으로 이루어집니다. 매개변수로 참조자가 선언되었으므로, 참조의 대상이 될 변수를 인자로 전달하면 됩니다.
포인터는 잘못 사용할 확률이 높고, 참조자의 활용이 상대적으로 포인터의 활용보다 쉽기 때문에, 참조자 기반의 함수정의가 더 좋은 선택이라고 생각할 수 있습니다. 코드를 분석하는 과정에 있다면, 함수의 호출문장만 보고도 함수의 특성을 어느 정도 판단할 수 있어야 합니다. 그러나 참조자를 사용하는 경우, 함수의 원형을 확인해야 하고, 확인결과 참조자가 매개변수의 선인에 와있다면, 함수의 몸체까지 문장 단위로 확인을 해서 참조자를 통한 값의 변경이 일어나는지를 확인해야 합니다. 참조자 ref 에 const 선언이 추가 된다면 함수 내에서 참조자 ref 를 이용한 값이 변경은 하지 않겠다라는 뜻입니다. 함수 내에서, 참조자를 통한 값의 변경을 진행하지 않을 경우, 참조자를 const로 선언해서, 함수의 원형만 봐도 값의 변경이 이뤄지지 않음을 알 수 있게 합니다.
반환형이 참조형인 경우
함수의 반환형에도 참조형이 선언될 수 있다.
#include <iostream> using namespace std; int& RefRetFuncOne(int &ref) { ref++; return ref; } int main(void) { int num1 = 1; int &num2 = RefRetFuncOne(num1); num1++; num2++; cout << "num1: " << num1 << endl; cout << "num2: " << num2 << endl; return 0; } |
참조형으로 반환된 값을 참조자에 저장하면, 참조의 관계가 하나 더 추가됩니다.참조형으로 반환이 되지만, 이렇듯 참조자가 아닌 일반변수를 선언해서 반환 값을 저장할 수 있습니다.
#include <iostream> using namespace std; int RefRetFuncTwo(int &ref) { ref++; return ref; } int main(void) { int num1 = 1; int num2 = RefRetFuncTwo(num1); num1 += 1; num2 += 100; cout << "num1: " << num1 << endl; cout << "num2: " << num2 << endl; return 0; } |
참조자를 반환하지만, 반환형이 기본자료형 int 이기 때문에 참조자가 참조하는 변수의 값이 반환됩니다. 변수에 저장된 값이 반환됩니다. const 참조자는 다음과 같이 상수도 참조가 가능합니다. const int &ref = 50; 지금까지 참조자는 변수만 참조가 가능하독 설명했는데, 아닙니다. const 선언에 의해서 만들어진 변수를 가리켜 상수화된 변수라고 합니다. 본래 상수가 아니라 변수를 상수화시킨 것으로 정의하고 있기 때문입니다.
malloc & free 를 대신하는 new & delete
힙 영역의 특성을 이해하고, 힙의 메모리 할당 및 소멸에 필요한 함수가 malloc 과 free 임을 알고 있다고 가정하고 설명하도록 하겠습니다. 길이 정보를 인자로 받아서, 해당 길이의 문자열 저장이 가능한 배열을 생성하고, 그 배열의 주소 값을 반환하는 함수를 정의해보도록 하겠습니다.
#pragma warning(disable:4996) #include <iostream> #include <string.h> #include <stdlib.h> using namespace std; char * MakeStrAdr(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++에서 C언어의 헤더파일을 추가하는 것도 가능합니다. 문자열 저장을 위한 배열을 힙 영역에 할당하고 있습니다. 힙에 할당된 메모리 공간을 소멸하고 있습니다. 할당할 대상의 정보를 무조건 바이트 크기 단위로 전달해야 한다는 단점과 반환형이 void형 포인터 이기 때문에 적절한 형 변환을 거쳐야 한다는 단점이 있습니다. new는 malloc 함수를 대신하는 키워드이고, delete는 free 함수를 대신하는 키워드입니다. int형 변수의 할당시 int * ptr1 = new int; double형 변수의 할당 double * ptr2 = new double; 길이가 3인 int형 배열의 할당 int * arr1= new int [3]; 길이가 7인 double형 배열의 할당 double * arr2 = new double[7]; 키워드 new의 오른편에, 할당할 대상의 정보를 직접 명시하고 있습니다. 앞서 할당한 int 형 변수의 소멸 delete ptr1; double 형 변수의 소멸 delete ptr2; int형 배열의 소멸 delete []arr1; double형 배열의 소멸 delete []arr2;
#pragma warning(disable:4996) #include <iostream> #include <string.h> using namespace std; char * MakeStrAdr(int len) { // char * str = new char[len]; return str; } int main(void) { char * str = MakeStrAdr(20); strcpy(str, "I am so happy"); cout << str << endl; delete []str; return 0; } |
단순 비교를 하더라도 new를 이용한 동적할당이 훨씬 간결함을 알 수 있습니다. 배열의 형태로 할당된 메모리 공간의 해제를 보이고 있습니다.
객체이 생성에서는 반드시 new & delete
#include <iostream> #include <string.h> using namespace std; class Simple { public: Simple() { cout << "I'm simple constructor~" << endl; } }; int main(void) { cout << "case 1: "; Simple * sp1 = new Simple; cout << "case 2: "; Simple * sp2 = (Simple*)malloc(sizeof(Simple) * 1); cout << endl << "end of main" << endl; delete sp1; free(sp2); return 0; } |
Simple 이라는 자료형의 변수를 하나 할당하는 문장입니다. 동작방식에서 차이가 있음을 알 수 있습니다. new 와 malloc 함수의 동작 방식에는 차이가 있습니다. 참조자의 선언은 상수가 아닌 변수를 대상으로만 가능함을 알고 있을 것입니다. 참조자의 선언을 통해서, 포인터 연산 없이 힙 영역에 접근했다는 사실에 주목할 필요가 있습니다.
C++ 에서 C언어의 표준함수 호출하기
C언어의 라이브러리에는 매우 다양한 유형의 함수들이 정의되어 있습니다. 이러한 함수들은 C++의 표준 라이브러리에도 포함되어 있습니다. 따라서 어렵지 않게 사용이 가능합니다. 헤더파일의 확장자인 .h를 생략하고 앞에 c를 붙이면 C언어에 대응하는 C++의 헤더파일 이름이 됩니다.
#include <cmath> #include <cstdio> #include <cstring> using namespace std; int main(void) { char str1[] = "Result"; char str2[30]; strcpy(str2, str1); printf("%s: %f\n", str1, sin(0.14)); printf("%s: %f\n", str2, abs(-1.25)); return 0; } |
C의 표준에 정의된 함수들 조차 이름공간 std 안에 선언이 되어 있어서 이 문장이 삽입되어야 합니다. C언어의 표준함수를 호출하고 있습니다. 모든 표준함수들이 이름공간 std 내에 선언되어 있습니다. C++ 에서는 함수 오버로딩이 가능하기 때문에 자료형에 따라서 함수의 이름을 달리해서 정의하지 않고, 보다 사용하기 편하도록 함수를 오버로딩 해 놓은 것입니다.