class가 만들어지기 전, C언어에서는 어떤 방식으로 구현을 했을까?

바로 struct를 이용한 방식이다.

이런 struct 구조체와 class에 대한 차이점은 다음과 같다.

- 구조체: struct 내부에 선언된 변수들은 public과 비슷하게 어디서든 접근이 허용된다.

- 클래스: class 내부에 선언된 변수 및 함수에 대한 접근과 관련한 접근제어 지시자가 필요하며 default값은 private이다.

 

 

※ 접근제어 지시자

- private: 클래스 내 정의된 변수 및 함수에서의 접근만 허용

- public:  어디서든 접근이 허용됨

- protected: 상속관계에서 유도클래스에서의 접근만 허용

 

※ 멤버 변수와 멤버함수

- 멤버변수: class를 구성하는 변수로 주로 private에 선언된다.

- 멤버함수: class를 구성하는 함수로 주로 public에 선언된다.

 

 

※ Class

- 클래스 선언: Car.h과 같은 헤더파일처럼 클래스가 구성하는 외형적인 틀을 보여준다.

- 클래스 정의: Car.cpp 처럼 멤버함수의 정의와 소스파일을 저장, 컴파일 되도록 한다.

 

 

※ 객체지향프로그래밍(OOP):

현실에 존재하는 사물, 대상과 연관된 행동을 실체화 하기 위한 프로그래밍

- 객체: class 타입의 변수라 생각, 클래스 기반 2가지 객체생성 방법이 있는데 아래와 같다.

ClassName objName;  // 일반적인 변수 선언 방식
ClassName *ptrObj = new ClassName;  // 동적할당 방식

이때, 동적할당 방식은 반드시 마지막에 delete ptrObj; 로 할당한 객체를 없애 memory leak을 방지해 줘야 한다.

 

 

- 생성자: 일종의 객체 멤버변수를 초기화하는 함수로 아래와 같은 성질이 있다.

1. 클래스 이름과 함수의 이름이 동일

2. 반환형 선언이 없고 실제로도 반환하지 않음

3. 생성자는 객체 생성 시 딱 한번만 호출

4. 멤버변수를 초기화 할 필요없이 생성자를 이용해 객체를 생성과 동시에 초기화 가능.

 

 

 

※ 객체생성과 함수원형선언

이때, 매개변수가 선언되어 있지 않은 생성자의 경우, Simple s0();과 같은 선언은 불가능하다.

(객체생성문인지 함수 원형 생성인지 구분 불가.)

Simple s0();  --> 함수 원형 선언

Simple s0;    --> 객체 생성 선언

 

 

 

 

 

※ OOP_정보 은닉

1. 멤버변수를 private으로 선언

2. 해당 변수에 접근하는 함수를 별도로 정의

3. 안전한 형태의 멤버변수의 접근을 유도 하는 것.

 

§ access함수: GetX, SetX처럼 멤버변수를 private으로 선언, 클래스 외부에서 멤버변수 접근을 목적으로 정의하는 함수

 

※ OOP_캡슐화(encapsulation)

- 큰 목적을 위해 둘 이상의 기능이 모여 목적을 달성하는 것으로 캡슐화가 무너지면 클래스 상호관계가 복잡해진다.

 

 

※ Member Initializer List

멤버변수로 선언된 객체의 생성자 호출 및 객체와 멤버의 초기화에 사용

이니셜라이저의 장점

 - 초기화 대상을 명확히 인식 가능

 - 성능상의 이점

 - 선언과 동시에 초기화 되는 Binary code 생성

 - const 멤버변수도 Initializer로 초기화 가능

 

※ default 생성자: 생성자를 정의하지 않은 클래스에는 default constructor가 자동삽입된다.

 

※ 객체 배열 생성: Add arr[10] 또는 Add *add = new Add[10]; 형태로 생성

- 객체 배열선언에도 생성자가 호출되며 호출할 생성자를 별도로 명시하는 것은 불가능

- 따라서 Add() {...}와 같은 생성자가 반드시 있어야 한다.

 

 

※ [Effective C++_item 4]: 객체 사용전, 반드시 그 객체를 초기화 하자!

객체가 이상한 값을 갖게 되는 상황을 방지하기 위해 모든 객체를 사용전, 반드시 항상 초기화 하자.

직접 초기화를 제외하면 C++초기화의 나머지는 생성자로 귀결된다. 

객체의 data member는 생성자의 본문이 실행되기 전초기화 되어야 한다. 따라서 member initializer list를 사용한다.

 

 

 

 

※ 소멸자: 생성자 앞에 ~를 붙인 형태로 직접 정의하지 않아도 default 소멸자가 자동 생성.

- 생성자에서 new로 할당한 메모리 공간을 소멸자에서 delete로 소멸시킨다.

 

 

 

 

※ *this 포인터 (객체 자기자신을 반환한다는 의미)

- this포인터는 객체의 주소를 저장한 포인터 변수이다.

- 매개변수와 멤버변수가 명확하게 구분될 수 있다.

 

※ [Effective C++_item 10]: 대입연산자는 *this의 참조자를 반환하도록 만들자!

※ const

const가 붙은 객체는 외부 변경을 불가능하게 해주며 다방면으로 사용된다.

클래스 내부: 정적멤버, 비정적 데이터 멤버 모두 상수로 선언 가능

클래스 외부: 전역, namespace 유효범위의 상수를 선언, static 객체 등에도 const를 붙일 수 있다.

포인터: * 뒤에 const를 붙여 상수포인터로 하거나 * 앞에 const 포인터가 가리키는 데이터를 상수로 할 수 있고 const char* const p 처럼 상수포인터, 상수데이터로도 만들 수 있다.

 

const의 가장 강력한 용도함수 선언을 할 때이다.

포인터, 반복자와 [포인터반복자참조자]가 가리키는 객체, 함수의 반환값 매개변수, 지역변수는 물론 멤버함수, 함수전체 등 여러곳에서 사용될 수 있다.

 

이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다는 의미로

const 함수 내에서는 const가 아닌 함수의 호출이 제한된다.

const 참조자를 이용한다면 const 함수만 호출이 가능하다.

class Numclass{
private:
    int num;
public:
    int GetNum() const { return num; }
};

class NumberClass{
private:
    int number;
public:
    void initNum(const Numclass& num){
        number = num.GetNum();
    }
};

 

const 반환값은 안전성과 효율성의 증가, 에러돌발상황감소 등 여러 상황을 효과적으로 막을 수 있다.

class Rational { . . . };
const Rational operator* (const Rational& lhs, const Rational& rhs);

만약 상수객체로 되어있지 않으면 아래와 같은 상황에서 Error가 발생한다.

Rational a, b, c;
(a * b) = c;  // a*b의 결과에 operator= 을 호출한 것이 되어버린다.

 

const 멤버 함수해당멤버함수가 상수객체에 대해 호출될 함수라는 뜻으로 2가지의 중요성을 갖는다.

1. 클래스로 만들어진 객체를 변경가능한 함수와 가능하지 않은 함수 구분을 시켜준다.

2. 객체전달을 상수 객체에 대한 참조자로 진행하여 상수객체를 사용하기 위해 상수멤버함수를 준비한다.

const char& operator[] (std::size_t pos) const { return text[pos]; }

void print (const TextBlock& ctb){    // 상수객체 ctb
    cout << ctb[0];  //TextBlock::operator[]의 상수
}

 

그렇다면 어떤 멤버함수가 const라는 것은 무슨 의미일까?

1.비트수준 상수성(물리적 상수성): 어떤 멤버함수가 정적멤버를 제외한 어떤 데이터도 건드리지 않음
2.논리적 상수성: 일부 비트정도는 바꿀수 있되 그것을 사용자가 모르게만 하면 상수멤버 자격이 있음

Q. 상수멤버 및 비상수멤버 함수가 기능적으로 똑같게 구현되었다면?

A. 코드중복을 피하는것이 좋은데, 이때 비상수 버전이 상수버전을 호출하도록 해야한다.

 

 

※ const 멤버 변수와  const static 멤버변수의 초기화 차이.

- const 멤버 변수: initializer를 이용해 멤버변수(객체)를 초기화 해야 한다. 이로 인해 선언과 동시에 초기화 되는 Binary code가 생성 가능하다.

class Country {
private:
    const int KoreaNum;
public:
    Country(const int KoreaNum) : KoreaNum(KoreaNum){ }
};

- const static 멤버변수: 선언과 동시에 초기화가 가능하다.

const static int KoreaNum = 82; //Country class의 public멤버
cout << "국번" << Country::Korea;

 

 

 

 

 

 

※ constexpr (C++11)

기존의 const 보다 더 상수성에 충실하며 일반화된 상수표현식 (Generalized constant expression)을 사용할 수 있게 해줌.

변수, 함수, 생성자 함수에 대하여 일반화된 상수표현식을 통해 컴파일 타임에 평가도리 수 있도록 처리해주는 키워드이다.

C++17부터는 람다함수에서도 constexpr키워드의 사용이 가능해졌다.

 

§ 변수에서의 사용

const와 constexpr의 차이점

[const]: const변수의 초기화를 런타임까지 지연시킬 수 있다.

[constexpr]: 반드시 컴파일 타임에 초기화 되어있어야 한다.

따라서 초기화가 안되어있거나 상수가 아닌 값으로 초기화 시도하면 컴파일이 되지 않는다.

constexpr float x = 42.f;    // OK
constexpr float y { 108.f }; // OK
constexpr int i;             // error C2737: 'i': 'constexpr' 개체를 초기화해야 합니다. (uninitialized 'const i' [-fpermissive])
int j = 0;                   // error: 'int j' is not const
constexpr int k = j + 1;     // error C2131 : 식이 상수로 계산되지 않았습니다.(the value of 'j' is not usable in a constant expression)

 

 

 

§ 함수에서의 사용

constexpr을 함수반환값에 사용시 다음의 제약이 있다.

1. 함수에 constexpr을 붙일 때는 inline을 암시한다.
2. 즉, 컴파일타임에 평가하기에 inline함수들과 같이 컴파일 된다.

만약 constexr의 함수인자들이 constexpr규칙에 어긋나면 컴파일타임에 실행되지 못하고 런타임에 실행된다.

constexpr int factorial(int n) {
    // 지역 변수 없음, 하나의 반환문
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

// C++11에서는 컴파일 에러 발생
// C++14부터 가능
constexpr int factorial(int n) {
    // 지역 변수
    int result = 0;

    // 여러 개의 반환문
    if (n <= 1)
        result = 1;
    else
        result = n * factorial(n - 1);

    return result;
}

 

 

 

 

 

 

 

 

 

※ C++의 입출력 연산자, [>>, <<]

C++의 입출력은 C언어처럼 printf();를 사용해도 무방하다. 다만 입출력에 대해 istream& operator >>(istream&, Coord&);와 ostream& operator<<(ostream&, const Coord&); 처럼 이미 C++의 #include<iostream>에 연산자 오버로딩이 되어 있기 때문에 std::cout << 출력하고 싶은 내용or변수 << std::endl;으로 printf(%d)와 같이 변수의 data성격에 따른 출력 타입을 고려해줄 필요가 없어졌다.

 

C와 C++의 달라진 점은 여러가지가 있는데 bool이라는 자료형 추가, 함수 및 연산자 오버로딩, 클래스, inline, Call-by-Reference 등이 있다.

 

※ C가 아닌 C++ 사용시의 장점

- classes

- inheritance (derived classes)

- strong typing

- inlining

- default arguments

 

※ C++은 언어들의 연합체로 바라봐야 한다.

C++은 여러 개의 하위 언어를 제공하며 C++의 어느 부분을 사용하느냐에 따라 programming이 달라진다.

1. C언어

2. 객체지향 개념의 C++ : class, encapsulation, inheritance, polymorphism, virtual function(Dynamic binding)

3. template C++ : 주류 C++과는 다르며 Effective C++의 item 48에서 다루고 있다.

4. STL : container, iterator, algorithm, 함수객체 등 template library를 중심으로 다룬다.

 

 

※ l-value와 r-value

l-value: 특정 메모리 위치를 가리키며 변수로 존재한다. (즉, 주소값을 취할 수 있는 연산자)

r-value: 일시적이고 주로 literal 상수이다. (주소값을 취할 수 없는 연산자)

int setValue(){ return 6; }

setValue() = 4;  // Err

setValue함수는 rvalue인 숫자 6을 반환한다.

따라서 대입연산자의 왼쪽 피연산자로 사용할 수 없기에 오류가 발생한다.

 

int global = 100;
int& setValue(){ return global; }

setValue() = 4;  // ok

여기서 setValue함수는 참조를 반환한다. 참조는 global 변수의 메모리 장소를 가리키기 때문에 lvalue로 할당할 수 있다.

 

lvalue에서 rvalue로 변환은 가능하다.

int x = 1; // l-value
int y = 2; // l-value
int z = x+y; // l-value -> r-vlaue

하지만 rvalue에서 lvalue로의 변환은 불가능하다.

 

 

 

 

 

※ 함수 오버로딩

이때, 함수 오버로딩은 C언어와 다르게 함수의 매개변수 선언형태가 다르면 동일한 이름의 함수정의 허용이 가능해 졌다.

예를 들어 매개변수가 다른것 뿐만 아니라 const선언만으로도 

int Func(int num) { return this->num; }
int Func(int num)const { return this->num; }

두 함수의 이름은 같지만 단지 const를 붙였을 뿐인데 오류가 발생하지 않는다.

 

 

 

※ 함수와 Default 값

int Func (int num = 7) {…}

int main(){
    Func()    // 7이 전달 될 것으로 간주
}

함수전달인자가 왼쪽부터 채워지기에 함수 작성시에는 오른쪽부터 Default값을 채워야 한다.

 

 

 

※ inline 함수: C의 #define 즉, 매크로 함수와 동일한 기능을 한다.

 [차이점]

  - #define: preprocessor로 compile전에 실행되며 값의 치환을 주목적으로 전처리기에 의해 실행

  - inline: 전처리가 아닌 compiler에 의한 처리과정으로 Effective C++에 의하면 주로 inline함수의 애용을 추천한다.

#define square(x) ((x)*(x))

inline int square(int x){
    return x*x;
}

위의 예시에서 두 내용은 서로 같은 의미를 내포하며 출력 시 5의 제곱은 sqaure(5)로 출력할 수 있다는 매력이 있다.

매크로 함수도 자료형 정의가 필요없다는 장점이 존재하지만 아래와 같은 치명적인 단점이 존재한다.

#define CALL_MAX(a,b) f((a) > (b) ? (a) : (b))
...
int x = 5, y = 0;

CALL_MAX(++x, y);     // a가 2번 증가
CALL_MAX(++x, y+10);  // a가 1번 증가

위의 경우, (++a>b) ? ++a : b 로 해석되기에 a가 2번 증가하게 되어 버리는 것이다.

따라서 다음과 같이 매크로의 효율은 그대로 유지하면서 타입안전성까지 취하는 inline함수에 대한 template을 사용한다.

template<typename T>
inline void callMax (const T& x, const T& y){
    f (x > y ? x : y);
}

따라서 Effective C++에서는 #define을 쓰려거든 const, enum, inline을 떠올리라는 내용을 item2에서 볼 수 있다.

 

※ [Effective C++_item 2]: #define을 쓰려면 const, enum, inline을 떠올리자 (전처리기보다 컴파일러를 가까이 하자.)

매크로는 일단 정의되면 끝날 때 까지 유효하며 컴파일러로 넘어가기 전, 전처리기가 밀어버린 후 상수로 바뀌어 버린다.

따라서 매크로는 클래스 상수 정의에도 쓸 수 없을 뿐만 아니라 캡슐화 혜택도 받지 못한다.

 

이에 대해 매크로 대신 상수를 쓰는 총 3가지의 해결책을 제시한다.

1. const 사용

#define ASPECT_RATIO 1.653 를 아래와 같이 상수로 사용하는 것이다.
const double AspectRatio = 1.653; 이는 위와 달리 사본이 사용때 마다 생기지 않고 딱 1개만 생긴다.

 

주의할 점1) 포인터와 포인터가 가리키는 대상까지 const로 선언 

const char* const name = "V2LLAIN";

주의할 점2) 클래스 멤버로 상수 정의 시, 상수의 사본개수를 1개 이하로 하고 싶을 때.

class Game {
private:
    static const int Num = 5;  //static 멤버로 만들어 상수로 선언한다.
};

 

2. enum hack을 이용한다. (const 보다는 #define에 가까운 방식, 쓸데없는 메모리 할당X)

class CostEstimate{
private:
    enum {  Num = 5  };
};

3. inline template (Effective C++_item 30)을 이용한다.

template<typename T>
inline void callMax (const T& a, const T& b) { f (a > b ? a : b); }

 

 

 

 

※ namespace:

범위지정연산자(::)를 이용해 namespace지정 시 사용하며 namespace는 변수명이 같아도 자신만의 namespace에 선언하면 충돌하지 않는다. 따라서 namespace를 중첩해서도 쓸 수 있다. (cf. ::를 변수앞에 붙이면 global variable을 의미.) 또한, std라는 namespace에 대해 using namespace std;라 선언해 std::cout을 cout이라 사용가능하다.

#include <iostream>
using namespace std;

namespace Parent{
    int num = 0;
    namespace Child{
        int num1 = 1;
        namespace GrandChild{
            int num2 = 2;
        }
    }
}

int main(){
    cout << Parent::num << endl;
    cout << Parent::Child::num1 << endl;
    
    namespace G = Parent::Child::GrandChild;
    cout << G::num2 << endl;
}

 

※ reference variables (참조변수)

참조형 변수는 Ampersand 기호, &를 사용하여 나타내는데 우리는 이런 기호를 C언어에서 사용해본 적이 있다.

바로 포인터와 관련된 내용을 배울 때 사용한 적이 있는데, 아래와 같다.

int num = 3;
int *ptr = &num;

cout << ptr;  // ptr변수로 num의 주소값인 0xc3dcbffb64 출력 
cout << *ptr; // ptr변수로 num의 값인 3 출력

이때 사용된 &는 num의 주소값을 받아온다는 의미로 사용된 &연산자이다.

 

그렇다면 참조변수의 기호 &는 무엇일까?

자신이 참조하는 변수를 대신할 수 있는 일종의 별칭으로 참조자 수에는 제한이 없다.

★배열"요소"도 변수로 간주되어 참조자 사용이 가능하다.

int arr[10];
int &ref1 = arr[1];

※배열 전체를 참조값으로 함수에 전달하기

void func (const int (&arr)[3]){
    for (int i = 0; i < 3; i++) {
        cout << arr[i];
    }
}

유의 사항: 괄호() 안에 &와 참조배열이름을 적어야 하며 [ ]안에는 반드시 배열 길이를 명시해야 한다.

 

※ 참조변수와 const

const변수를 참조하기위해서는 참조변수를 반드시 const로 선언해야 한다.

함수의 참조매개변수를 const로 선언하면 변수의 값을 변경할 수 없고 상수 또한 인자로 전달가능하다.

const int &ref = 30; //30이라는 const

 

※ Call - by - reference

참조변수로 같은 메모리 공간을 접근하면서 다른 변수의 이름으로 참조가능한 방식이다.

이로 인해 함수 밖의 메모리공간에서도 접근 및 변경이 가능하다.

변수를 매개변수로 하면  함수 호출 때 마다 메모리 할당이 발생한다.

참조변수를 매개변수로 하면  함수 호출을 해도 메모리 할당이 발생하지 않는다.

이때, 반환값은 참조형태여서 반드시 참조변수로 반환받아야 한다.

 

 

※ new와 delete

C언어의 malloc과 free를 대신해서 C++에서는 new와 delete를 사용한다.

객체 생성에서는 new를, 소멸에서는 delete를 사용하여 Heap segment에 할당, 반환을 하며 

new를 이용해 heap에 할당된 변수에 대해 참조자 선언이 가능하다.

 

이와 관련된 설명은 뒤에 객체 생성 및 소멸과 관련하여 설명하겠다.

 

 

※ Prologue _ 이름짓기(Effective C++)

포인터의 이름을 지을 때, T에 대한 포인터 (pointer to T)를 줄여서 pt라고 하는 경우가 많다.

참조자에서도 비슷하게 T에 대한 참조자 (referenece to T)를 줄여서 rf라 사용할 수 있다.

gamecharcter *pgc;
gamecharcter& rgc;

 

C언어와 Java의 중간형태를 띠고 있는 C++은 C언어의 강력한 기능중 하나인 memory 관리와 Java에서 처럼 Class를 쓸 수 있다는 점에서 매력점이 있다.

 

다만 다른 여러 언어들에 비해 괴랄하고 방대한 문법으로 인해 매우 배우기 난해하다고 생각한다. 

C++은 매니아 층이 있는 만큼 여러 발전형태가 이루어져 왔는데 C++은 물론 STL과 Modern C++ 등만 생각해도 배울 내용들이 C에 비해 너무나도 방대해졌다 생각한다.

따라서 독학 혹은 더 C++에 대해 배워야 할 경우 다음과 같은 책들을 추천한다.

 

초심자: 열혈C++ 프로그래밍 [http://www.yes24.com/Product/Goods/3816661]

             객체지향 프로그래밍을 위한 C++의 이해 [http://www.yes24.com/Product/Goods/69761804]

             시작하자! C++17 프로그래밍 [http://www.yes24.com/Product/Goods/90862480] <=개인적으로 괜찮다고 생각

 

초급: C++ Primer 4판 번역 [https://product.kyobobook.co.kr/detail/S000000676786],

             A Tour of C++ [https://www.stroustrup.com/Tour.html] _C++창시자가 쓴 책이니 꼭 읽어보도록 하자.

중급: Effective C++ ★★★★★ [http://www.yes24.com/Product/Goods/17525589] _C++을 배운다는 사람이 이 책을 모르면 간첩이라 할 만큼 매우매우 중요한 테크닉 서적, 반드시 꼭 읽어야 하는책 

             전문가를 위한 C++ [http://www.yes24.com/Product/Goods/77669043] _ C++ 빨간책

        TCPL [http://www.yes24.com/Product/goods/23441719] _ C++로 먹고 사려면 창시자의 책 한번은 읽어봐야한다 생각한다.(대략 1400쪽 가량이나 되는 책이니 입문서로 추천하기는 좀 그렇고 C++에 익숙해지면 읽어볼만한 책)

 

             

고급: Modern C++ Design, The C++ Programming Language, C++14 STL 철저입문 등은 C++을 더 깊게 공부하면 추천

         C++로 쉽게 풀어쓴 자료구조 [http://www.yes24.com/Product/Goods/30340314] 까지 공부한 후 알고리즘 공부 추천

 

물론 필자는 아직 Effective C++읽는 중이다... 적어도 책 내용이 이해가 될 때 까지 최대한 반복회독이 목적이다.

+ Recent posts