01. Input/Output
C | C++ | |
Input | scanf | cin |
Output | printf | cout |
Header | stdio.h | iostream |
Speed | > |
- C++의 endl은 \n보다 속도가 느리다. 그 이유는, endl의 경우 buffer를 지워주는 flush 과정이 존재하기 때문이다.
ios_base::sync_with_stdio(bool sync);
- C++의 경우, 기본적으로 C와 표준 스트림이 연결되어 있다. 이 과정에서 속도가 느려지는 현상이 생긴다. 다만, 위와 같은 코드를 이용하여 속도를 늘리는 방법 보단, scanf와 printf를 사용하는 편이 속도 상승에 유리하다.
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
- 위 코드를 사용하면, 동기화를 해제할 수 있다.
- 동기화 해제시 주의할 점은, 해제한 뒤 stdio와 iostream을 사용하면 순서를 보장하지 않기 때문에 에러가 발생할 수 있다.
02. Overloading
- 함수 호출 시 전달되는 인자를 통해서 호출하고자 하는 함수가 구분이 가능하기 때문에 작동 가능한 방법으로, C언어에서는 지원하지 않는 기능이다.
void student()
{
}
void student(int number)
{
}
void student(int number, string name)
{
}
- C++의 경우 호출할 함수를 찾을 때, (함수의 이름, 매개변수의 선언)을 보고 탐색한다.
void student()
{
}
int student()
{
}
- 다만, 위와 같이 반환형이 다른 경우를 기준으로 탐색하는 것은 불가능하다. 왜냐하면 C++가 함수를 구분하는 기준이 될 수 없기 때문이다.
03. Default Value
- C++의 함수는 디폴트 값을 가질 수 있다.
void student(int = 19860221, string = "Zelda")
{
}
- 디폴트 값은 다음과 같은 규칙을 가진다.
- 선언 부분에만 표현한다.
- 부분적으로 디폴트 값 설정이 가능하다.
- 단, 오른쪽부터 채워져 있어야 한다.
- C++의 매개변수 호출은 왼쪽부터 하기 때문이다.
- 단, 오른쪽부터 채워져 있어야 한다.
04. Inline Function
- C언어의 매크로 함수와 유사하다. 매크로 함수의 장점은 일반적인 함수에 비해서 실행속도에 이점이 있다는 것이지만, 정의하기 복잡하다. 이때, 일반 함수처럼 정의가 가능하며 실행속도에 이점이 있는 함수가 인라인 함수이다.
// 매크로 함수
#define SQUARE(X) ((X)*(X))
// 인라인 함수
inline int SQUARE(int x)
{
return x*x;
}
- 매크로 함수의 경우, 자료형에 의존적이지 않은 함수가 되어, 데이터 손실이 발생하지 않는다.
- 인라인 함수의 경우 자료형에 의존적이어서 데이터 손실이 발생할 수 있다. 다만, C++의 템플릿(T)을 사용하면, 이러한 단점을 보안할 수 있다.
05. Namespace
- 동일한 이름의 서로 다른 기능의 함수를 사용하기 위해, 즉 이름 충돌 방지를 위한 해결책
namespace NAME
{
// namespace inside
SpecialFunction()
{
.
.
.
}
namespace NAME_SUB_ONE
{
int num = 5;
}
}
.
.
.
int main(void)
{
NAME::SpecialFunction();
cout << NAME::NAME_SUB_ONE::num << std::endl;
return 0;
}
- Namespace안에 Namespace를 호출하는 것 또한 가능하며, 사용법은 위 예시와 같다.
- 만일, 그때그때 Namespace를 선언하기 귀찮다면, using keyword를 사용할 수 있다.
- Namespace에 별칭을 부여한느 것 또한 가능하다. 가령 위 함수에서 namespce nso = NAME::NAME_SUB_ONE; 와 같은 형식으로 선언하면 된다.
- 만일 지역변수의 이름과 전역변수의 이름이 같다면, 범위지정 연산자(::)를 통해 해결할 수 있다.
int val = 100 // global variable
int SimpleFunc(void)
{
int val = 20; // local variable
val += 3; // local variable +3
}
.
.
.
int val = 100 // global variable
int SimpleFunc(void)
{
int val = 20; // local variable
val += 3; // local variable +3
::val += 7 // global variable +7
}
06. bool
- C언어에서는 지원하지 않는 bool 타입의 자료형을 C++에서는 지원한다.
- true == 1, false == 0
07. Reference
포인터 | 참조자 | |
포인터의 효과를 내는가? | O | O |
사용하기 쉬운가? | X | O |
포인터를 모르는 사람도 이해할 수 있는가? |
X | O |
- 참조자는 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름이다.
- C++에서는 참조자와 변수를 구분해서 이야기한다.
#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;
}
VAL: 3047
REF: 3047
VAL: 0012FF60
REF: 0012FF60
- 즉, 참조자는 변수에 별칭(Alias)을 붙여주는 것이다.
int &num2 = num1;
- 위와 같은 코드는 num2는 num1의 별칭이 된다.
- 또한, 참조자의 수에는 제한이 없으며, 참조자를 대상으로 참조자를 선언할 수 있다.
int num = 2759;
int &num2 = num1;
int &num3 = num1;
int &num4 = num1;
------------------
int num = 2759
int &num2 = num1;
int &num3 = num2;
int &num4 = num3;
- 참조자의 선언 가능범위
- 참조자는 변수에 대해서만 선언이 가능하고, 선언됨과 동시에 누군가를 참조해야한다.
- 위와 같은 변수의 범위에는 배열 또한 포함된다.
- 참조자는 NULL 초기화가 불가하다.
int &ref = 20; (X)
int &ref; (X)
int &ref = NULL; (X)
.
.
.
int arr[3] = {1,3,5};
int &ref = arr[0]; (O)
08. Reference & Function
- Call-by-value의 경우, 함수 외부에서 선언된 변수에 접근이 불가능하다. 그래서 보통 함수 외부에서 선언된 변수에 접근하기 위해서는 Call-by-reference 기반의 함수를 사용한다.
- Call-by-reference의 경우, Call-by-address로 불리기도 한다.
int * SimpleFunc01(int * ptr)
{
return ptr+1;
}
int * SimpleFunc02(int * ptr)
{
if(ptr == NULL)
return NULL;
*ptr=20;
return ptr;
}
- Simplefunc01의 경우, Call-by-value라 해야 옳다. 이는, 함수의 연산 주체가 '값(value)'이기 때문이다. 다만 그 값이 주소 값일 뿐이다.
- 반대로 Simpefucn02의 경우, 주소 값을 이용, 함수 외부에 선언된 변수를 '참조(reference)'하였으므로, Call-by-reference이다. 즉, Call-by-reference는 "주소 값을 전달 받아, 함수 외부에 선언된 변수에 접근하는 형태의 함수호출'을 말한다. 쉽게 말하자면, 주소 값이 외부 변수의 참조도구로 사용되는 함수의 호출이 Call-by-reference이다.
- 이러한 참조는 참조자를 이용해서도 가능하다.
void SwapByRef(int &ref1, int &ref2)
{
int temp = ref1;
ref1 = ref2;
ref2 = temp;
// Call-by-reference
}
- 이 과정에서, 앞선 참조자의 규칙인 '선언과 동시에 초기화 되어야 한다.'가 충족되지 않았다고 생각할 수 있다. 하지만, parameter의 경우, 함수가 호출되어야 초기화가 진행되기 때문에 함수 호출 시 초기화 값이 전해지면서 문제가 생기지 않는다.
- 다만 이러한 활용이 오히려 단점이 될 수 있다. 다음 코드를 보자.
int num = 24;
HappyFunc(num);
cout << num << endl;
- C언어의 경우 위의 코드는 당연하게도 24가 출력된다. 하지만, C++의 경우 다음과 같은 이유로 출력을 단정지을 수 없다.
void HappyFunc(int prm) {...}
void HappyFunc(int &ref) {...}
- 만일, 첫 번째 줄이 호출된다면 24가 출력할 것이고, 두 번째 줄이 호출된다면 그 값을 단정할 수 없다.
- 이러한 단점을 보안하기 위해, const 키워드가 사용된다.
void HappyFucn(const int &ref) {...}
- 이렇게 선언할 경우, ref에 값을 저장하려고 시도하는 순간 컴파일 에러가 발생한다.
반환형이 참조형인 경우도 존재한다.
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);
num1++;
num2++;
cout << "num1: " << num1 << endl;
cout << "num2: " << num2 << endl;
return 0;
}
num1: 4
num2: 4
#include <iostream>
using namespace std;
int& RefRetFuncOne(int &ref)
{
ref++;
return ref;
}
int main(void)
{
int num1 = 1;
int num2 = RefRetFuncOne(num1);
num1++;
num2+=100;
cout << "num1: " << num1 << endl;
cout << "num2: " << num2 << endl;
return 0;
}
num1: 3
num2: 102
- 참조형으로 반환된 값을 참조자에 저장하면, 참조의 관계가 하나 더 추가된다.
- 2번째 코드에서 실행은 다음과 같다.
int num1 = 1;
int &ref = num1;
int num2 = ref;
- 즉, 처음에 num1을 선언한 뒤, 선언한 num1을 RefRetFuncOne함수의 int &ref선언을 통해 num에는 ref라는 별칭이 생겼다. 이 선언 과정을 진행한 뒤, 함수 내에서 ref의 값을 1 추가하고, num1의 값을 1 추가한다. 이렇게 ref의 값이 2가 되었을 때, 2가 된 값을 num2가 가진다. 그 뒤, num1에 1을 추가하고, num2에 100을 더한다. 그렇게 나온 값이 num1 : 3, num2: 102이다.
#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 += 2;
cout << "num1: " << num1 << endl;
cout << "num2: " << num2 << endl;
return 0;
}
num1: 3
num2: 102
- 위 코드가 앞선 코드와 출력값은 같지만 차이점은 다음과 같다.
- 먼저 앞선 코드를 보면
- int num2 = RefRetFuncOne(num1); (O)
- int &num2 = RefRetFuncOne(num1); (O)
- 이 가능하다. 하지만, 반환형이 기본 자료형으로 선언된 위 코드의 경우
- int num2 = RefRetFuncTwo(num1); (O)
- int &num2 = RefRetFuncTwo(num1); (X)
- 반환 값은 반드시 변수에 저장해야 한다. 즉, 반환 값은 상수나 다름없다.
- 잘못된 참조의 반환 또한 존재한다. 다음의 코드를 보자
int& RetuRefFunc(int n)
{
int num = 20;
num += n;
return num;
}
- 만일, int &ref = RetuRefFunc(10); 의 형태로 위 함수를 호출하면, 다음과 같이 작동할 것이다.
- ref라는 참조자가 num을 참조한다.
- 참조된 num이라는 지역변수는 함수의 코드를 수행한 뒤 반환된다.
- 이때, 지역변수 num은 소멸된다.
- 따라서 지역 변수를 참조형으로 반환하는 일은 권장되지 않는다.
09. Dynamic Allocation
C | C++ | |
allocate | malloc | new |
delete | free | delete |
header | stdlib.h | - |
- C언어의 경우, 할당할 대상의 정보를 반드시 바이트 크기단위로 전달해야 한다. 또한 반환형이 void형 포인터이기 때문에 적절한 형 변환을 거쳐야 할 필요가 있다.
- 하지만, C++에서 제공하는 new와 delete의 경우 이러한 불편함을 제거한다.
C | C++ | |
allocate | int * ptr = (int*)malloc(sizeof(int)*4) | int * ptr = new int; |
delete | free(ptr) | delete ptr |
- 객체의 생성에 malloc와 free는 권장되지 않는다. 이는, new와 malloc 함수의 동작방식의 차이가 있기 때문인데, new의 경우 C++에 의해 기본적으로 제공되지만, malloc의 경우 별도의 라이브러리가 요구된다. 또한 new의 경우, 생성자를 자동호출해준다.
- C++의 경우, new 연산자를 이용해서 할당된 메모리 공간도 변수로 간주한다. 즉, 참조자의 선언이 가능하도록 하고 있다. 이러한 특징은 참조자의 선언을 통해 포인터 연산 없이 힙 영역에 접근이 가능하다는 점을 알려준다.
int *ptr = new int;
int &ref = *ptr;
ref = 20;
cout << *ptr << endl;
'Study > C++' 카테고리의 다른 글
[C++] OOP; Object-Oriented Programming (0) | 2023.10.06 |
---|---|
[C++] Structure (0) | 2023.10.06 |
[C++] Class & Object (0) | 2023.10.06 |
[C++] Call by value / Call by reference (0) | 2023.10.06 |
[C++] Const (0) | 2023.10.06 |