삽질의 현장/- C++

#029_시(c)시(c)해서 C++?!_C++의 모든 것! 최종 정리 (1)

shovelman 2015. 7. 28. 14:30

안녕하세요 삽잡이 입니다...


이번 시간에는 그동안 알아오던 (저혼자...) C++에 대해서 정리할 시간을 가져보려고 합니다...

물론... 실습 위주가 아닌 용어위주로 말이죠... 하하...

사전에 C++에 대해 살펴보신 경험이 있는데, 가볍게 한번 훑고 지나가고 싶다 하시는 분들이

읽으시기에 적당한 글이 될 것 같습니다.

조금은 긴 글이 될 수 있겠군요... 바로 시작하겠습니다.



C++은 C언어에 그 외 기능들이 +(plus) 된 언어입니다.

해당 언어는 C언어에 비해 보다 신뢰성이 있는 언어로써, 

객체 중심으로 프로그래밍을 할 수 있는 객체 지향 언어(OOP) 중 하나입니다.


물론, C++ 이후에 나온 Java, C#에 비하면 신뢰성이 보다 떨어질 수 있으나, 

C언어와 비교했을 때 보다 신뢰성을 추구하며,

개발자에게 신뢰성 있는 코드를 제안하도록 오류를 던져주는 좋은 녀석이죠...


OOP의 핵심이자, 공통적인 특징으로 크게 세가지를 소개할 수 있습니다.

바로 캡슐화, 상속, 다형성인데요.... 잠시 후에 하나 하나 소개하도록 하겠습니다.



자... C++에서 새로 등장하는 녀석들이 몇몇 있는데

전부는 아니지만 기억에 남는 녀석들에 대해 소개를 하겠습니다.


1.

우선 C언어에서는 참과 거짓을 0이외의 숫자와 0으로 구분을 했는데요,

C++에서는 보다 가독성 있는 true false의 값을 가진 bool형식을 제공해줍니다.


2.

또한 레퍼런스 변수가 나타납니다.

다른 변수에 할당된 메모리를 참조하는 변수를 바로 레퍼런스 변수라고 부릅니다.

단, 변수 선언과 동시에 초기화 되는 조건을 가진 변수이지요...


int sapzaep = 0;

int &sap = sapzape;


이와 같이 사용할 수 있습니다.


주의해야 할 점은, 선언과 동시에 초기화 하지 않는 &연산자가 붙은 변수는

기존에 사용하던 것과 동일하게 됩니다. '&sap'은 sap의 주소를 나타내는 것과 같이 말입니다.


3.

다음으로는 오버로딩 즉, 함수 중복 정의에 대해서 알아보도록 하겠습니다.

함수 중복 정의란, 말 그대로 함수를 중복 정의할 수 있도록 C++에서 제공하고 있는 기능입니다.

기존 C언어와 같은 경우 시그니처가 다른 함수일 지라도 함수명이 동일할 시, 중복을 허용하지 않았습니다.


C++의 경우에는 함수의 시그니처가 다르다면 함수명이 같아도 중복으로 정의할 수 있게 되었죠...

이런 기능이 허용된 이유는, C++ 컴파일러의 경우에는 컴파일 과정에서 

함수명이 같을 지라도 고유한 함수명으로 변경을 해주기 때문입니다.


아래와 같이 크게 두가지의 경우에는 함수 중복 정의를 할 수 없습니다.

- 리턴 형식만 다른 경우

- 입력 매개 변수 리스트의 형식이 같을 경우


4.

C++에서는 함수 호출 시 입력 매개 변수의 값을 디폴트 값으로 지정해줄 수 있는 기능 또한 가지고 있습니다.

예를 통해 살펴보시죠...


1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
 
int Add(int a, int b = 10)
{
    return a + b;
}
 
void main()
{
    cout << Add(10<< endl;
}
 
 
 
cs


이와 같이 Add() 함수의 시그니처에는 입력 매개 변수로 두가지의 int 형 변수를 요구하지만,

int b라는 매개변수를 살펴보시면, 10으로 디폴트 값 지정을 해주었기 때문에

인자를 하나만 넘기게 된다면, 알아서 int b에는 10이 저장되게 됩니다.


5. 

다른 많은 언어들도 같겠지만, C++ 언어에서는 다른 라이브러리들을 불러와 사용하는 경우가 많습니다.

이에 많은 라이브러리들을 include 시키다 보면, 동일한 이름의 메소드를 호출 하는 경우들이 종종 있습니다.

하지만, 컴파일러의 입장에서 어느 라이브러리의 메소드인지 구분할 수가 없어 오류를 발생시키죠...


이에 C++에서는 'namespace'라는 기능을 제공하게 됩니다.

A라는 라이브러이와 B라는 라이브러리 둘 모두 Sap() 이라는 메소드를 제공해준다고 하면,

아래와 같이 사용할 수 있게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
 
 
using namespace A;
void main()
{
    Sap(); // A 라이브러리 내의 메소드 호출
    B::Sap(); // B 라이브러리 내의 메소드 호출
}
 
 
 
 
cs


6.

자... 이제 OOP의 삼대장중 첫번째 대장인 캡슐화에 대해서 알아보도록 하겠습니다.

캡슐화는 간략하게 설명드리자면, 여러개의 멤버를 하나의 형식으로 묶는 기능이라고 말씀드릴 수 있겠습니다.


그렇다면 이런 질문을 할 수 있습니다.

'C에서 제공하는 구조체와 다를게 없지 않습니까?'

맞습니다. 하지만 차이가 조금 있죠....

우선 C가 제공하는 구조체의 경우 멤버 변수만을 묶어 하나의 형식으로 만들 수 있습니다. 

하지만, C++에서 제공하는 캡슐화의 경우 멤버 변수와 메소드 즉, 함수 또한 같이 묶을 수 있게 됩니다.

C에서 구조체를 사용할 때는 'struct'를 사용했듯이 C++에서는 'class'를 통해 캡슐화를 할 수 있습니다.


즉, c++에서는 행위 까지 캡슐화할 수 있게 된 것으로 

캡슐화에 대한 문법이 강화 되었다는 것을 확인할 수 있습니다.


확실하게 정리하도록 해봅시다.

C언어에서 사용자 정의 형식을 만들 때에 구조체를 가지고 정의를 했다면,

C++에서는 여전히 구조체를 가지고 있지만, 캡슐화를 할 때에 class를 사용하고 있다는 것입니다.


다음으로 중요한 기능이 C++에서 추가가 되며 캡슐화의 빛을 더더욱 밝혀줍니다. 바로, '접근 지정자' 입니다...

C언어의 경우 모든 멤버에 접근할 수 있었습니다. 

하지만, C++에서의 경우 기본적으로 해당된 클래스에서만 접근을 할 수 있도록 

private 라는 접근 지정자로 막혀져 있습니다.


여기서 잠깐 접근 지정자에 종류에 대하여 소개해드리고자 합니다.

우선 위에서 설명했듯이, 해당된 형식에서만 접근이 가능한 'private',

모든 곳에서 접근이 가능한 'public', 추후 배울 상속된 클래스까지 접근이 가능한 'protected'로 나누어 집니다.


자... 캡슐화되어있는 class가 동적으로 메모리에 할당 되는 순간 (인스턴스화)

객체가 생성되게 되고, private 된 멤버 변수 및 메소드의 경우 

해당 객체 내에서만 접근이 가능하게 되는 것이죠.


이와 같이 접근 지정자를 통해 접근에 대한 보다 엄격한 기준을 제시함으로써

우리는 내부의 중요한 멤버 변수 및 메소드에 접근을 하지 못하게 할 수 있습니다.

이를 바로 '정보 은닉'이라고 부르며, 보다 높은 신뢰성을 보장하게 됩니다.


방금 위의 말과 중복되는 말이지만, 이를 통해서 C언어와의 차이점을 확인할 수 있었습니다.

접근 지정자를 통해 가시성 설정을 할 수 있다는 차이점과, 

이 특징을 통해서 정보 은닉(음폐)를 통한 데이터의 신뢰성을 높일 수 있다는 차이점 말입니다.


7.

C언에서는 'malloc' 을 사용했고, 'free' 를 통해 동적 메모리 해제가 가능했습니다.

위에서 잠깐 언급했듯이, class를 동적으로 메모리에 할당 하는 것을 객체의 인스턴스화라고 하는데요...

어렵게 생각할 것 없이... 죄송합니다... 이미 말이 어려웠죠... 허허..

아무튼... 우리가 캡슐화 한 class는 아직 죽어있는 객체라고 생각하시면 됩니다.

하지만, 'new' 연산자를 통해 동적으로 메모리를 할당한다면 이는 살아 있는 객체가 생성되는 것입니다.


이와 같이 살아있는 객체를 만드는 과정에서 C++에서는 초기화를 진행합니다.

그 초기화를 바로 생성자를 통해 진행하게 되는 것입니다.


정확하게 말씀을 드린다면, new 라는 연산자는

요청한 형식의 객체를 위해 메모리가 할당 을 하도록 한 뒤에 객체 생성을 위한 몇몇 작업이 수행합니다

그 다음에 생성자 (생성자 메소드)를 통해 class 내의 멤버 변수들이 초기화 되고

생성된 객체의 메모리가 반환 되는 것입니다.


이 생성자의 경우에는 아무것도 명시하지 않는다면 기본적으로 디폴트 생성자가 생성이 됩니다.

하지만 우리가 생성자를 만들었다면, 반드시 그 생성자의 형식에 맞도록 객체를 생성해줘야합니다.


예시를 보여드리며 생성자에 대한 설명을 마무리 지으려고 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
 
class Sapzape
{
    string name;
    int age;
    int power;
public:
    Sapzape();
    Sapzape(string name, int age, int power);
};
 
 
 
cs


다음과 같이 클래스명과 동일한 함수를 바로 생성자라고 부릅니다.

생성자 또한 중복 정의가 가능하며, 

중복 정의시 객체를 생성할 때에 맞는 인자 리스트에 따라 호출 되는 생성자가 결정됩니다.


8. 

생성하는 기능이 있다면, 반드시 소멸하는 기능 또한 존재해야 합니다. 이를 소멸자라고 부릅니다.

소멸자 또한 굉장히 중요합니다. 소멸을 제대로 하지 않는다면, 

메모리가 남게되어 메모리 낭비가 발생하게 됩니다.

delete 연산자를 사용하여 동적으로 할당된 객체의 메모리를 소멸시킵니다.


9.

보통 생성자의 경우 인자로 변수 혹은 직접 적인 데이터를 입력하지만,

복사 생성자는 인자로 객체를 전달하는 것입니다.

즉, 자기와 같은 형식을 입력 인자로 받는 것을 복사 생성자라고 정의할 수 있습니다.

디폴트 복사 생성자를 은 똑같은 메모리를 복사 수행하는 기능을 제공합니다.


추가적으로 여러 서적에서 '얕은 복사'와 '깊은 복사'에 대한 설명을 하고 있는데,

이는 이전 C언어를 배울 때 Call-By-Value와 Call-By-Reference를 비교하는 것과 같이

생각을 해보시길 바랍니다... (맞는 비유일지... 하하...)


9.5

사용자가 정의하지 않을 때에 컴파일러가 자동으로 디폴트 값을 제공해주는 기능으로는

크게 4가지가 있습니다.


- 디폴트 기본 생성자

- 디폴트 복사 생성자

- 디폴트 소멸자

- 디폴트 대입 연산자


디폴트 대입연산자는 메모리 복사 역할을 해주는 기능을 하고 있습니다.

사용자가 정의한 형식들은 대입 연산자 외에 다른 연산자를 사용하게 되면 컴파일 오류가 발생하게 됩니다. 

대입 연산자 같은 경우에는 개발자가 정의하지 않아도 사용이 가능하죠... 

따라서 대입 연산자 이외의 다른 연산자들은 중복정의를 해줘야합니다.

이와 같은 연산자 중복정의는 아래에서 다시 설명하도록 하겠습니다.


10.

C++ 에서는 C에서 전역변수를 선언하듯이,

static 키워드를 명시하여 static 멤버 즉, 정적 멤버를 선언 할 수 있습니다.

하나의 class로 여러 개의 객체를 생성할 수 있는데,

이 때마다 새로운 메모리가 할당될지라도, static 멤버의 경우에는 오직 하나의 메모리만 할당이 됩니다.

정적 멤버에 접근하고자 할 때에는 반드시 'class 명'과 '스코프 연산자(::)'를 통해 접근해야 합니다.


주의해야할 점은 소스파일에 반드시 선언을 해줘야 한다는 것입니다.

개념적으로 접근하자면,

나머지 멤버 필드들 같은 경우 객체가 생성될 때 메모리가 생성 되기에 상관이 없지만,

정적 메모리 (static 변수) 같은 경우에는 프로그램이 시작되면서 메모리가 생성되기 때문에

이를 표현해주기 위해서 클래스 외부에 선언을 해줘야 한다는 것입니다.


11.

static 멤버 변수의 경우 하나의 class에 하나의 메모리만을 가지는 변수라면,

객체마다 메모리가 생성되는 변수이지만, 값을 변경할 수 없는 const 변수 즉, 상수화 멤버 변수가 있습니다.


이를 통해 C++에서 제공하는 멤버에 대한 종류를 두 가지의 방법으로 나눌 수 있음을 확인하게 됬습니다.

- 정적 / 비 정적

- 상수 / 비 상수


const 멤버 즉, 상수화 멤버의 경우 생성자에서 초기화 할 때 

반드시 이니셜라이저 (:) 를 통해 초기화를 진행해줘야합니다. 반드시 입니다.

왜냐하면 생성시에 초기화를 해주기 위해서 입니다.

괄호 안에 const 멤버를 초기화 시키려고 해도, 값이 변하지 않는 상수이기때문에

이는 컴파일 오류를 발생시키게 됩니다.


예를 들자면


1
2
3
4
5
6
7
8
9
10
11
12
13
 
 
class Sapzape
{
    string name;
    int age;
    const int power;
    public:
    Sapzape(string name, int age, int power);
};
 
 
 
cs


이와 같은 경우 const 변수인 power 멤버 변수는 반드시 이니셜라이즈를 통해 초기화 해야합니다.


1
2
3
4
5
6
7
8
9
 
 
Sapzape::Sapzape(string name, int age, int power): power(power)
{
    //this->power = power //상수화 되어 있는 멤버이기 때문에 대입을 할 수 없다. 
}
 
 
 
cs


이처럼 초기화를 진행해야합니다.


여담으로 말씀드리자면, '왜 그렇게 써야하냐'... 이런 생각을 하는 삽잡이와 같은 사용자들을 위해

추후 개발된 Java, C# 언어에서는 해당 초기화를 가능하도록 만들어줬습니다...


11.5

메소드명 뒤에 const가 붙게 되면, 해당 메소드 내에서 객체의 멤버 필드 값을 변경할 수 없게 됩니다.

좀 더 깊게 생각을 해보도록 하겠습니다.

이와 같이 const 키워드를 붙인 메소드를 제공함으로 아래와 같은 혼란 스러운 부분이 생깁니다.


const 메소드에서 const 메소드를 호출하는 것은 문제가 되지 않지만

const 메소드가 아닌 다른 메소드로 호출하는 것은 불가능 합니다.


왜냐하면, const 메소드가 아닌 다른 메소드라는 것은 

아주 조금이라도 변경될 가능성이 있다고 판단하기 때문에 

아무리 값을 변경하지 않는다고 해도 컴파일러는 시그니처를 보고 판단을 하기 때문에 오류를 발생하게 됩니다.


12.

this는 나 자신을 가리키는 정적 멤버 입니다. 이 this는 언제나 객체 형식에 들어있습니다.

즉, 우리는 this를 class 내에 선언하지 않더라도 자동으로 사용할 수 있게 됩니다. 

바로 컴파일러 덕분이죠...

this의 경우 접근 지정이 private로 설정 되어 있기 때문에 코드 상에 노출 되지는 않습니다.


쉽게 생각하면, this는 해당 클래스의 주소를 담게 되어

this-> 와 같이 주소를 통해 멤버 필드에 접근할 수 있게 됩니다.

this 멤버를 사용하게 되면 같은 이름의 멤버 변수와 매개 변수를 구분할 수 있습니다.



한 개시글에 모든 글을 올려보려고 했으나... 상당히 길군요...

이미 저 징글징글한 글자만 봐도 충분히 가독성이 떨어질만 한데...

에라 모르겠다 싶어 반으로 잘랐습니다.


다음시간에 뵙겠습니다.

이상 삽잡이였습니다~!