7일차 - Cpp

JeongChaeJin·2021년 4월 20일
0

한컴아카데미

목록 보기
3/6

new & delete

  • [] : 갯수를 의미
int * arr1 = new int[3];
  • () : 값을 의미
int * ptr1 = new int(3);
  • 포인터사용하지 않고, 힙에 할당된 변수에 접근 가능
int *ptr = new int;
int &ref = *ptr;
ref = 20;
cout << *ptr << endl; // 20

C++ 라이브러리

  • C 언어 라이브러리의 .h 확장자를 빼고 c 를 붙여라
    • cmath, cstdio, cstring ... 등
    • 이름공간 std에 선언됐다는 것 빼고는 별 차이가 없다.
    • 하지만, 오버로딩 되고 C++ 문법에 맞게 고쳐졌으므로 C++에서는 C++ 헤더를 권장

C++ Struct

  • 함수를 넣을 수 있다
#include <iostream>
using namespace std;

namespace CAR_CONST
{
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 300,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    };
}

using namespace CAR_CONST;

struct Car
{
    char gamerID[ID_LEN];
    int fuelGauge;
    int curSpeed;

    void ShowCarState()
    {
        cout << "ID : " << gamerID << endl;
        cout << "Fuel : " << fuelGauge << '%' << endl;
        cout << "Current Speed :  "<< curSpeed << "km/s" << endl;
    }

    void Accel()
    {
        if (fuelGauge <= 0) return;
        else fuelGauge -= FUEL_STEP;

        if(curSpeed+ACC_STEP >= MAX_SPD)
        {
            curSpeed=MAX_SPD;
            return;
        }

        curSpeed += ACC_STEP;
    }

    void Break()
    {
        if(curSpeed < BRK_STEP)
        {
            curSpeed = 0;
            return;
        }

        curSpeed -= BRK_STEP;
    }
};

int main(void)
{
    Car run99 = {"run99", 100, 0};
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();

    Car sped77 = {"sped77", 100 ,0};
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();

    run99.Accel();
    run99.Break();
    run99.ShowCarState();

    return 0;
}
  • Car 와 관련된 데이터를 처리하는 함수(기능) 을 struct에 넣을 수 있다.
  • enum을 다른 namespace에서 정의함으로써 가독성을 높일 수 있다.
    • 바로 구조체에 넣어도 되지만 부담될 경우 다른 namespace에서 정의함.
#include <iostream>
using namespace std;

namespace CAR_CONST
{
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 300,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    };
}

using namespace CAR_CONST;

struct Car
{
    char gamerID[ID_LEN];
    int fuelGauge;
    int curSpeed;

    void ShowCarState();
    void Accel();
    void Break();
};

void Car::ShowCarState()
{
    cout << "ID : " << gamerID << endl;
    cout << "Fuel : " << fuelGauge << '%' << endl;
    cout << "Current Speed :  "<< curSpeed << "km/s" << endl;
}

void Car::Accel()
{
    if (fuelGauge <= 0) return;
    else fuelGauge -= FUEL_STEP;

    if(curSpeed+ACC_STEP >= MAX_SPD)
    {
        curSpeed=MAX_SPD;
        return;
    }

    curSpeed += ACC_STEP;
}

void Car::Break()
{
    if(curSpeed < BRK_STEP)
    {
        curSpeed = 0;
        return;
    }

    curSpeed -= BRK_STEP;
}
    
int main(void)
{
    Car run99 = {"run99", 100, 0};
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();

    Car sped77 = {"sped77", 100 ,0};
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();

    run99.Accel();
    run99.Break();
    run99.ShowCarState();

    return 0;
}
  • 보통 프로그램 분석 시 함수의 내부 구현보다는 기능을 확인하므로 구조체에서는 함수의 선언만하고
  • 구현은 외부에서 접근지정연산자(::)를 통해 구현하는 것도 좋다.
  • 구조체 안에 함수가 정의되어있으면 인라인으로 처리하라는 의미가 내포되어 있다고 한다. 외부에 정의하면 이 의미가 사라지므로 inline을 붙여 구현해서 의미를 유지할 수도 있다.

Class & Object

  • C++ 에서 파일 분할

    • .h 파일 에 클래스의 선언을 담는다.
    #define __CAR__H__
    #ifdef __CAR__H__
    
    using namespace std;
    
    namespace CAR_CONST
    {
        enum
        {
            ID_LEN = 20,
            MAX_SPD = 300,
            FUEL_STEP = 2,
            ACC_STEP = 10,
            BRK_STEP = 10
        };
    }
    
    using namespace CAR_CONST;
    class Car
    {
        private:
            char gamerID[ID_LEN];
            int fuelGauge;
            int curSpeed;
        public:
            void InitMembers(char * ID, int fule);
            void ShowCarState();
            void Accel();
            void Break();
    };
    
    #endif
    • 클래스의 정의를 담는다 (멤버함수 구현) .cpp
    #include <iostream>
    #include <cstring>
    #include "Car.h"
    using namespace std;
    
    void Car::InitMembers(char * ID, int fuel)
    {
        strcpy(gamerID, ID);
        fuelGauge = fuel;
        curSpeed = 0;
    }
    
    void Car::ShowCarState()
    {
        cout << "ID : " << gamerID << endl;
        cout << "Fuel : " << fuelGauge << '%' << endl;
        cout << "Current Speed :  "<< curSpeed << "km/s" << endl;
    }
    
    void Car::Accel()
    {
        if (fuelGauge <= 0) return;
        else fuelGauge -= FUEL_STEP;
    
        if(curSpeed + ACC_STEP >= MAX_SPD)
        {
            curSpeed = MAX_SPD;
            return;
        }
    
        curSpeed += ACC_STEP;
    }
    
    void Car::Break()
    {
        if(curSpeed < BRK_STEP)
        {
            curSpeed = 0;
            return;
        }
    
        curSpeed -= BRK_STEP;
    }
  • 그리고 main에서 클래스 .h 헤더를 선언하고 프로그래밍한다.

  • 인라인 함수는 헤더파일과 함께 넣어야 하니 주의한다.

Class

  • Class 의 멤버 변수 선언문에서 초기화 까지 하는 것은 허용 되지 않는다.
private:
    const int a = 1000; // x
    const int a; // o
  • Class 의 두 가지 객체 생성 방법
  1. ClassName ObjName; // 일반적인 변수의 선언 방식
  2. ClassName * ptrObj = new ClassName // 동적 할당방식 (힙 할당)

정보 은닉 (Information Hiding)

  • 멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태(ex. 엑세스 함수)로 멤버 변수의 접근을 유도하는 것이 바로 '정보 은닉'
  • 좋은 클래스가 되기 위한 기본 조건 !

const 함수

  • 이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다.
    • const 선언 함수는 멤버변수의 값을 변경하는 코드가 삽입되면, 컴파일 에러가 발생한다.
  • 또한, const 함수는 const가 아닌 함수를 호출하지 못한다.
    • const로 선언되지 않은 함수는 멤버변수를 변경할 여지가있다. 이러한 여지도 주지 않으려는 것이다.
    • 안정성 지린다.

캡슐화 (Capsulation)

  • 캡슐화는 어려운 개념이다. 캡슐화의 범위를 정하기란 쉽지않다.
  • 캡슐화는 감싸는 개념이므로 정보 은닉이 기본 바탕이 된다.

생성자

#include <iostream>
using namespace std;

class SimpleClass
{
    private:
        int num1;
        int num2;
    public:
        SimpleClass()
        {
            num1 = 0;
            num2 = 0;
        }
        
        SimpleClass(int n)
        {
            num1 = n;
            num2 = 0;
        }

        SimpleClass(int n1, int n2)
        {
            num1 = n1;
            num2 = n2;
        }

        SimpleClass(int n1 = 0, int n2 = 0;)
        {
            num1 = n1;
            num2 = n2;
        }
        /*
        void ShowData() const
        {
            cout << num1 << ' ' << num2 << endl;
        }
        */
}

int main(void)
{
    SimpleClass sc1;
    sc1.ShowData();

    SimpleClass sc2(100);
    sc1.ShowData();

    SimpleClass sc3(100, 200);
    sc1.ShowData();
    
    return 0;
}
  • 생성자 함수의 이름은 클래스와 일치해야하며 반환형이 선언되어 있지 않다.
    • 실제로 반환하지도 않음.
  • 객체 생성시 자동으로 한 번 호출된다.
  • 오버로딩이 가능하며, 매개변수에 deafault 값을 지정할 수 있으므로 주석 처리한 생성자가 위의 3개를 다 커버할 수 있다.

멤버 이니셜라이저

#include <iostream>
using namespace std;

class SimpleClass
{
    private:
        int num1;
        int num2;
    public:
        SimpleClass() : num1(0), num2(0)
        {
            cout << num1 << ' ' << num2 << endl;
        }

        SimpleClass(int n) : num1(n), num2(0)
        {
            cout << num1 << ' ' << num2 << endl;
        }
        
        SimpleClass(int n1, int n2) : num1(n1), num2(n2)
        {
            cout << num1 << ' ' << num2 << endl;
        
        /*SimpleClass(int n1=0, int n2=0) : num1(n1), num2(n2)
        {
            cout << num1 << ' ' << num2 << endl;
        } 안됨 */
};


int main(void)
{
    SimpleClass sc1;
    SimpleClass sc2(100);
    SimpleClass sc3(100, 200);
    
    return 0;
}
  • 생성자 초기화에 멤버 이니셜라이저를 선호하는 편이다.
  • 초기화의 대상을 명확히 인식할 수 있다.
  • 성능에 약간의 이점이 있다.
    • 멤버 이니셜라이저는 num1(n1) -> int num1 = n1
      • 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다
        -> const 멤버 변수도 이니셜라이저를 이용하면 초기화 가능 !
      • "선언과 동시에 초기화" 된다는 점때문에 const, 참조자 변수도 초기화하는 데에 사용할 수 있다.

Default 생성자

  • 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야하는데, 생성자를 정의하지 않은 클래스는 C++ 컴파일러에 의해 디폴트 생성자라는 것이 자동으로 삽입된다.
    • 내부적으로 아무런 일도 하지 않는 생성자이다.
  • 생성자가 하나도 정의 되어 있지 않을 때에만 삽입된다.

소멸자

  • 보통 ~생성자함수이름()
SimpleClass * s2 = new SimpleClass(1, 3);
delete s2;
  • 포인터를 통한 동적 할당을 했을 때에는 delete를 해줘야 소멸자가 호출되었음.

this

  • 객체자신의 주소 값을 의미한다.
  • 활용에는 함수의 인자와 같은 지역변수와 같은 이름인 멤버 변수가 있다 ?
this -> name = 10;
  • 위 처럼 구분해서 할당할 수 있다. 해당 객체의 name 멤버변수를 의미한다.
ObjRef& name()
{
    return *this
}
  • 위는 참조 반환형 함수에서 사용할 시 참조의 정보. 즉, "참조 값"이 반환된다.

참조의 정보에 대한이해

  • 참조자에게는 참조의 정보가 전달된다.
  • ex) 변수 num을 참조할 수 있는 참조 값이 참조자 ref에 전달되어 ref가 변수 num을 참조할 수 있게 된다.
  • 전달되는 정보는 "참조 값" 이다.

복사 생성자

SoSimple slim2(slim1)
  • SoSimple형 객체를 생성해라
  • 객체이름은 slim2로 정한다
  • slim1을 인자로 받을 수 있는 생성자의 호출을 통해서 객체생성을 완료한다.
  • SoSimple slim2 = slim1 했을 때에도 복사 생성자가 호출되는 데, 이는 구현하지 않아도 디폴트 복사생성자로 인해 묵시적으로 변환 된다.

explicit

  • 위의 복사 생성자 묵시적 호출을 원하지 않을 때 사용한다.
  • 코드를 더 명확하게 해준다.
exlicit SoSimple(const SoSimple &copy) // & 반드시 있어야 복사생성자
		: num1(copy.num1), num2(copy.num2)
{
}
  • 위 처럼했을 시 = 를해도 묵시적 변환이 안일어나게 된다.
  • 그냥 코드를 명확하게 하기 위해 사용되는 키워드라고 보면된다.

복사생성자 호출 시점

  1. 기존 생성된 객체를 이용해 새로운 객체를 초기화하는경우
  2. Call-by-value 방식의 함수 호출 과정에서 객체를 인자로 전달하는 경우
  3. 객체를 반환하되, 참조형으로 반환하지 않는 경우
  • 멤버대 멤버로 복사

static 멤버변수 (클래스 변수)

  • 객체가 생성될 때마다 함께 생성되어 객체 별로 유지되는 함수가 절대 아니다 !
  • 메모리 공간에 딱 하나만 할당이 되어서 공유되는 변수이다.
#include <iostream>
using namespace std;

class SoComplex
{
    private:
        static int cmxObjCnt;
    public:
        SoComplex()
        {
            cmxObjCnt++;
            cout << cmxObjCnt << "번 째 SoComplex 객체" << endl;
        }

        SoComplex(SoComplex &copy)
        {
            cmxObjCnt++;
            cout << cmxObjCnt << "번 째 copy SoComplex 객체" << endl;
        }
};
int SoComplex::cmxObjCnt = 0;

int main(void)
{
    SoComplex cmx1;
    SoComplex cmx2=cmx1;
    SoComplex();

    return 0;
}
  • public 으로 선언되면 클래스의 이름 또는 객체의 이름을 통해서 어디서든 접근이 가능하다.
cout << SoSimple::simObjCnt << endl;

static 멤버함수

  • public으로 선언되면 클래스의 이름을 이용해서 호출이 가능하다.
  • 객체의 멤버로 존재하지 않는다.

const 객체의 특성

const SoSimple sim(2);
  • 이 객체를 대상으로는 const 멤버 함수만 호출이 가능하다.
    • 의미가 "이 객체의 데이터 변경을 허용하지 않겠다." 이기 때문이다.
    • 변경 시킬 수 있는 함수는 아예 호출하지 않는 것이다.
#include <iostream>
using namespace std;

class SoSimple
{
    private:
        int num;
    public:
        SoSimple(int n) : num(n) {}
        SoSimple& AddNum(int n)
        {
             num += n;
             return *this;
        }

        void ShowData() const
        {
            cout << "num : " << num << endl;
        }
};

int main(void)
{
    const SoSimple obj(7);
    // obj.AddNum(20);
    obj.ShowData();
    return 0;
}
  • obj.AddNum은 const 함수가 아니기 때문에 호출 시 컴파일 에러가 난다.
  • constant static 멤버는 이니셜라이저를 통해 초기화 해야한다.
const static int RUSSIA = 1807540;

mutable

  • const 함수 내에서 값의 변경을 예외적으로 허용한다.
  • 가급적 사용의 빈도수를 낮춰야 하는 키워드다.
    • const 함수는 인자의 변경이 없게 하기 위함인데, 이 의미를 없애기 때문이다.
#include <iostream>
using namespace std;

class SoSimple
{
    private:
        int num1;
        mutable int num2;
    
    public:
        SoSimple(int n1, int n2)
                : num1(n1), num2(n2)
        {}
        void ShowSimpleData() const
        {
            cout << num1 << ", " << num2 << endl;
        }

        void CopyToNum2() const
        {
            num2 = num1;
        }
};

int main(void)
{
    SoSimple sm(1, 2);
    sm.ShowSimpleData();
    sm.CopyToNum2();
    sm.ShowSimpleData();

    return 0;
}
profile
OnePunchLotto

0개의 댓글