본문 바로가기

Study/C++

[C++] Difference of C & C++

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); 의 형태로 위 함수를 호출하면, 다음과 같이 작동할 것이다.
    1. ref라는 참조자가 num을 참조한다.
    2. 참조된 num이라는 지역변수는 함수의 코드를 수행한 뒤 반환된다.
    3. 이때, 지역변수 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