C++ Study 정리노트

금일 하루만에 C++ 책을 한권 모두 보았는데요.
이게 나이가 드니깐 하루만 지나도 바로 까먹어 버립니다.
추후 짧은 시간에 기억을 끄집어내기 위해 공부하면서 끄적인 노트를 올려놓습니다.

1.
다형성을 구현한 것이 가상함수이다.

2.
함수 prototype 에 기본값을 지정할 수 있다.

void func(int i=5, double d=1.23);

3.
c 와는 달리 아무데나 변수선언해서 사용 가능하다.

4.
지역변수와 전역변수 이름이 같을 때 전역변수에 접근하려면 영역 결정 연산자인 :: 를 붙인다.

::var = 123;

5.
c 와는 달리 const 변수를 배열의 크기로 지정 가능하다.
하지만, const 함수인자를 배열크기로 지정은 안된다.

const int size = 5;
char str[size];

6.
다음 두가지는 의미가 다르다.
(1) char *const ptr = mybuf;
(2) const char *ptr = mybuf;
(1) 은 ptr 이 다른 버퍼를 가리키도록 변경할 수 없다. 단지 *ptr = ‘a’ 와 같이 그 값만 변경 가능하다.
(2) 는 반대로 값의 변경은 안되고 다른 버퍼를 가리키도록 변경할 수 있다.

7.
inline 함수는 실제 호출될 때마다 함수코드가 삽입된다.
실행코드는 커지지만 매크로와 일반함수의 장점을 모두 갖는다.
매크로는 다음의 경우 결과가 전혀 의도치 않게 나올 수 있는 담점이 있다.

#define MAX(A,B)  ((A) > (B) ? (A) : (B))
i = MAX(x++,y++);
=> i = ((x++) > (y++) ? (x++) : (y++))

8.
포인터 외에 레퍼런스를 사용하여 특정변수를 다른이름으로 사용할 수 있다.
레퍼런스 변수는 선언과 동시에 초기화되어야하고 한번 초기화되면 다른 변수의 레퍼런스로 변경될 수 없다.
즉, 레퍼런스는 상수포인터(int *const ptr)랑 같지만 포인터처럼 자신과 자신이 가리키는 값이 명확히 구분되지 않는다.
자신이 아닌 가리키는 변수만을 조작 가능하다.

int  aa;
int &bb = aa;

aa 와 bb 는 &aa 와 &bb 를 찍어보면 완전히 같은 주소를 가리키는 동일체이다. 이름만 다를 뿐.

9.
레퍼런스를 함수 인자로 넘겨줄 때 컴파일러는 실제로 호출한 측 변수의 주소를 넘겨준다.

struct aa { int a; int b; }
struct aa myval;
func(&myval);

void func(struct aa &arg)
{
    arg.a = 10;  /* * 또는 -> 연산자를 사용할 필요없음 */
    arg.b = 20;
}

그러나, 혼동의 여지가 있기 때문에 되도록이면 호출측의 변수값을 변경하지 않을 때만 레퍼런스 매개변수를 사용한다.

10.
중복 함수 (function overloading)를 이용하여 같은 이름의 함수로 다른 인자들을 줄 수 있다.
컴파일러는 인자의 수와 데이터형을 비교하여 어떤 함수를 호출할지를 결정한다.

int get(int x);
float get(float x);
double get(double x);

11.
C++ 의 구조체도 클래스와 마찬가지로 함수를 멤버로 가질 수 있다.
클래스와 다른 점은 default 접근지정자가 클래스에서는 private 인 반면, 구조체는 public 이라는 것 뿐이다.
그러나, 혼동을 피하기 위해 구조체에서는 데이터 멤버만을 지정하고, 멤버함수를 지정해야할 때는 클래스를 사용하는 것이 일반적이다.

12.
주로 클래스 선언은 .h 파일에, 멤버함수 정의는 .cpp 파일에 작성한다.

13.
클래스 선언할 때 다음과 같이 .h 파일에 inline 예약어없이 선언해도 inline 함수로 인지한다.
.cpp 파일에 inline 을 붙여서 함수 정의한 것과 동일한 효과이다.

class aa
{
    int get() { return day; }
}

14.
다음과 같이 object 를 가리키는 포인터를 사용해서 object 멤버에 접근 가능하다.
하지만, ‘.’ 및 ‘->’ 연산자를 통해 오브젝트 외부에서 접근할 수 있는 클래스 멤버는 public 속성으로 지정된 것이어야 한다.

myClass aa;
myClass *ptr = &aa;
ptr->get();

다음과 같이 사용해도 접근 에러가 발생한다.

myClass aa;
aa.val = 10;  --> val 이 private 변수이면 object 내부의 멤버함수가 아닌 곳에서 접근한 것이므로 불가.

15.
접근 지정자 (access specifier)
(1) private : 오브젝트 외부에서 절대 접근할 수 없고, 멤버함수만이 접근할 수 있다. 파생 클래스에서도 접근할 수 없다. friend 클래스 함수에서는 접근할 수 있다.
(2) public : 어디에서든 접근할 수 있다.
(3) protected : 클래스 계층 외부에서는 접근할 수 없고 파생 클래스에서 접근 가능하다. (public 으로 파생된 경우만)

private, public, protected 지정자를 접근 유형 뿐 아니라 파생 유형에도 사용할 수 있다.
파생 클래스에서 기초클래스에 접근하는 것도 때로는 제한할 필요가 있을 수도 있기 때문이다.
** 그러나, C++ 에서 데이터를 감추는 기술은 허점이 있다. 포인터로 클래스 주소 처음을 가리키게 한 후 해당 포인터를 이용하여 값을 바꿀 수도 있기 때문이다.

16.
생성자
(1) 반환형을 가질 수 없다. void 도 하나의 형이므로 이것도 안된다.
(2) 인자를 다양하게 하여 overload 할 수 있다.
(3) public 속성으로 되어 있어도 외부에서 호출할 수 없다.
(4) 컴파일러가 제공하는 default 생성자는 아무일도 하지 않는다.

17.
소멸자
(1) 생성자의 (1)(3)(4)는 같다.
(2) 생성자와는 달리 인자를 다양하게 overload 할 수 없다. 인자가 없어야 한다.

18.
클래스 오브젝트도 생성과 소멸시점은 지역변수 전역변수와 같다.

19.
멤버변수를 초기화할 때 다음의 2 가지 방식이 있다.

myClass::init()
{
    aa = 10; bb = 20;
}
myClass::init() : aa(10), bb(20)
{
}

20.
Embedded 오브젝트 : 다른 클래스의 데이터 멤버로 사용되는 오브젝트

class myClass
{
    public:
        myClass();
    private:
        embedClass  emb; -> embedded 오브젝트
}

위와 같이 선언하면 myClass 오브젝트 생성 전에 embedClass 오브젝트 먼저 생성한다.
위의 19 번의 2 번째 방법의 초기화는 이렇게 embedded 오브젝트를 초기화 시 잘 이용된다.
그렇게 사용하지 않으면 embedClass 오브젝트의 변수 초기화를 별도로 해주어야 한다.

class myClass
{
    public:
        myClass() : emb(10,20)
        {
        };
    private:
        embedClass  emb;
}

21.
클래스 오브젝트가 여러개 생성되어도 별도의 메모리에 코드를 가지지는 않는다.
코드는 모든 오브젝트가 공유하되 각 데이터 멤버를 저장하는 영역만 별도로 갖는다.
그러면, 멤버함수 func()가 공통 코드 영역에 있다면, 해당 함수가 불리어졌을 때 func() 입장에서는 어떤 오브젝트가 불렀는지 어떻게 알 수 있을까 ?
이 정보가 바로 this 포인터이고 이 포인터를 알고 있기 때문에 func() 함수는 자신이 불리었을 때 어느 오브젝트의 데이터 멤버를 수정할 것인지를 알게 된다.

    classObj1     classObj2    classObj3
    val = 10      val = 20     val = 30
     \              |              /
      \             |             /
       \            |            /
    void myClass::func() {}    ==> code 영역 공유

** this 포인터는 멤버함수를 호출한 오브젝트를 가리키는 const 포인터이다. !!

22.
const 멤버 함수

int GetYear(void) const;

(1) 멤버변수 변경을 금지시킨다.
(2) const 가 붙지 않은 다른 멤버함수를 호출할 수 없다. (간접 변경 가능성 배제)

23.
const 오브젝트
오브젝트 생성 시 const 로 생성하면 초기화된 데이터 외의 다른 데이터를 저장할 수 없다.
const 가 붙지 않은 멤버함수도 호출할 수 없다.
따라서, const 오브젝트를 생성해야할 필요가 있는 클래스에는 멤버함수를 const 로 선언해야 한다.

const myClass classObj1(1,22,'aa');

24.
static 멤버 변수
static 으로 선언하지 않으면 오브젝트마다 변수 영역을 따로 가진다.
만약 은행 이자율과 같은 모든 오브젝트의 공통 값을 오브젝트마다 각각 가지고 있으면 낭비이므로, 일종의 클래스 범위의 전역변수와 같은 역할을 하기 위해 static 을 사용한다.
물론 클래스 외부의 함수에서는 접근할 수 없다.
static 멤버변수의 값을 변경시키는 방법은 두가지가 있다.
(1) 오브젝트에 ‘.’ 연산자를 이용하는 방법
즉, static 으로 선언되었어도 객체가 생성되면 해당 객체의 멤버함수를 통해 변경할 수 있다.

myClass obj1;
obj1.setval(10);

(2) 클래스에 ‘::’를 사용하는 방법
myClass::setval(10);
클래스에 속하는 모든 오브젝트에 대해 static 멤버변수가 변경된다는 것을 표현하기 위해 두 번째 방법을 많이 사용한다.

25.
static 멤버함수는 static 멤버변수에만 접근할 수 있다.
이는 메모리 구조를 생각해보면 쉽게 유추할 수 있다.
오브젝트 멤버함수는 this 를 통해 자신의 멤버변수에 접근이 가능하겠지만,
static 이 붙은 멤버함수는 일반 오브젝트 멤버변수에 어떻게 접근하겠는가 ? 또한 오브젝트가 없다면 ?
따라서, static 멤버함수는 오로지 같은 공통 영역에 항상 존재하는 static 멤버변수만 접근이 가능한다.
하지만, 반대로 static 멤버변수는 static 멤버함수만 접근가능한 것은 아니다. 오브젝트 멤버함수에서도 가능하다.

** 사실 static 멤버함수는 오브젝트가 없는 클래스의 static 멤버변수를 변경할 수 있게 하기 위한 것이다.
static 멤버함수는 static 멤버변수만 접근할 수 있다. static 이 아닌 다른 멤버함수도 호출할 수 없다.
오브젝트 멤버함수에서 사용하는 this 포인터도 사용할 수 없다.

26.
static 멤버변수의 초기화 방법

class myClass
{
    public:
        static int val;
}
int myClass::val = 10;

만약 아래와 같이 같이 static 변수가 global 로 선언되어 있지 않으면 해당 변수를 접근하는 static 함수를 사용하려고 하면 컴파일 시 link error 가 발생한다.

#include <stdio.h>
class c1
{
private:
    static int val;
public:
    static void setVal(int arg) { val = arg; }
};

//int c1::val = -1;
int main()
{
    c1::setVal(11);
    return 0;
}

27.
Friend class 의 경우 Friend 지정을 한 class 의 private 속성의 멤버변수값을 변경시킬 수 있다.
아래의 경우 friendClass 에서 myClass 쪽으로만 접근할 수 있다.
friend class 는 데이터 감추기 규칙을 깨는 것이므로 최소한으로 사용한다.

class myClass
{
    friend class friendClass;
    private:
        int val;
}

class friendClass
{
    public:
        void chgval(myClass mc) { mc.val = 10; }
}

28.
class 와 구조체의 차이점
(1) 구조체의 변수들은  public 이지만 클래스의 변수들은 기본적으로 private 이다.
즉, 클래스는 구조체와 달리 접근제어를 할 수 있다는 점이 다르다.
(2) 클래스는 생성자와 소멸자가 있다.
(3) 클래스는 오버로딩, 오버라이딩이 가능하다.

** 구조체도 상속을 할 수 있고 멤버함수도 가질 수 있다.
심지어 this 포인터도 사용할 수 있다. (대신 g++ 로 컴파일해야 한다.)

#include <stdio.h>
struct base
{
public:
     int val;
};
struct inherit : public base
{
public:
     int val1;

     int get() { return val1; }
     int get1() { return this->get(); }
};
int main()
{
     struct inherit obj;

     obj.val = 77;
     obj.val1 = 10;

     printf("%d : %d\n", obj.val, obj.get1());
}

29.
오브젝트를 다른 오브젝트에 대입할 때 컴파일러는 멤버 대 멤버 대입을 한다.

myClass a;
myClass b;
a = b;

이 때 일반적인 숫자형 멤버는 상관없지만, char[] 같은 경우는 값이 대입되는 것이 아니라 주소값이 복제된다.
이렇게 되면, 객체가 소멸될 때 buf = new char[10] 과 같이 생성된 buf 를 delete 로 해제할텐데 이중으로 해제되어 문제가 된다.
그리고, 원래 a.buf 는 해제되지 않고 낭비된다. 이럴 때 사용하는 것이 “대입 연산자 중복”이다.

30.
대입 연산자 중복은 29 에서 살펴본 문제를 해결하기 위해 대입 연산자를 overloading 하는 것이다.

class myClass
{
    void operator=(const myClass &arg);
private:
    int       length;
    char * buf;
}


void myClass::operator=(const myClass & arg)
{
    delete buf;
    length = arg.length;
    buf = new char[length + 1];
    ::strcpy(buf, s.buf);
}

myClass a(3, "abc");
myClass b;
b.operator=a;   => a.length 와 a.buf 를 b 로 복제

31.
복사생성자는 같은 클래스의 오브젝트를 레퍼런스 인자로 받아들이는 생성자이다.
이는 오브젝트 생성과 동시에 다른 오브젝트로 초기화를 하고 싶을 때 사용된다.

myClass(const myClass & arg);
myClass::myClass(const myClass & arg)
{
    // 자신을 인자로 받는 오브젝트로 초기화하는 코드
    // 이를 직접 구현하지 않으면 default 대입 연산자 형태로 대입되어 29 번의 문제가 그대로 발생한다.
}

32.
클래스 오브젝트 배열
(1) 클래스 오브젝트 배열 선언 시 배열의 각 요소에 대해 생성자가 호출된다.
(2) 각 배열 요소에 대해 다음과 같이 다른 인자들을 사용하는 생성자가 호출되도록 초기화할 수 있다.

myClass obj[4] = { myClass("string1"), myClass('*', 10), "string2", myClass() }

(3) 별도로 초기화하지 않는 배열 요소는 default 생성자로 초기화된다.
(4) new 를 이용하여 동적으로 배열 할당할 수 있다.

    myClass * array = new myClass[10];

(5) (4) 와 같이 할당 후 delete [] array 와 같이 해제하지 않고 [] 를 생략하면 array 포인터들과 첫번째 배열 요소만 해제된다.

33. 상속
기초(base)클래스와 파생(derived)클래스 : 계층적인 명확성, 코드 재사용성, 확장성의 장점

사원(Employee)  – 기초 클래스
/            \
정규직(Regular)    임시직(Temporary)
/
영업직(Sales) : 정규직은 영업직의 기초클래스
부서(Department) : 상속 계층은 아니지만 멤버로 Employee 의 List 를 가짐.

(1) 파생 클래스는 기초클래스의 모든 멤버들을 상속받는다.
다만, 생성자와 소멸자, Friend 만 제외하고.
파생클래스 = (기초클래스 멤버) + (기초클래스 멤버함수) + (파생클래스 멤버) + (파생클래스 멤버함수)
(2) 파생 클래스의 오브젝트가 생성될 때는 기초클래스의 오브젝트가 먼저 자동으로 생성된다. 소멸은 그 반대이다.
그러므로, 만약 생성자와 소멸자의 인자들이 다양할 때는 파생클래스 생성자를 호출할 때 그에 따른 기초클래스의 생성자의인자도 명확히 해주어야 한다.

#include <stdio.h>

class base
{
public:
     int val;
     base(int a) { val = a; }
     int get() { return val; }
};
class derived : public base
{
public:
     int val1;
     derived(int a) : base(a) { val1 = a; } ->inline이 아닌 형태로 작성해도 똑같이.
     int get1() { return val1; }
};
int main()
{
     derived obj(10);

     printf("%d : %d\n", obj.get(), obj.get1());
}

(3) 파생 클래스가 public 파생유형으로 기초클래스를 파생하면, 기초클래스의 private 멤버를 제외한 모든 멤버에 접근 가능하다. private 파생 유형으로 파생하면 자신(아버지)까지만 상속받고, 자신 이후로는 더 이상 기초클래스(할아버지)를 접근 못한다.
(4) default 파생유형은 private 이지만 너무 파생관계가 복잡해지기 때문에 public 을 사용하도록 한다.
(5) 파생클래스의 오브젝트는 맨 처음에 기초클래스 오브젝트를 포함한다. 따라서, 파생클래스를 가리키는 포인터는 기초클래스 오브젝트 포인터와 같은 주소를 가리킨다.
(6) 다중 상속 : 파생 클래스가 다수의 기초 클래스를 가지는 경우

class SalesManager : public Manager, public SalesMan

다중 상속은 멤버함수 호출 시 모호성이 존재하여 반드시 필요한 경우 외에는 사용하지 않는 것이 좋겠다.

34.
멤버함수 overriding
** 재정의(overriding)와 중복(overloading)
overloading 은 같은 클래스 또는 외부 영역에서 이름이 같고 매개변수가 다른 함수를 지정할 때 사용되고, overriding 은 파생되는 클래스에서 기초 클래스의 멤버함수의 기능을 재정의할 때 사용된다.

35.
클래스내에서 오브젝트 array 관리

class Department
{
    private:
        int              count;
        Employee * employees[10];
    public:
        void AddEmplyee( Employee & emp )
        {
            if( count < 10 )
            {
                empolyee[count++] = & emp;
            }
        }
};

36.
가상함수 (virtual function)
(1) 파생 클래스에서 재정의(overriding)될 것으로 기대되는 멤버함수이다.
기초클래스의 오브젝트 포인터를 통해 가상함수를 호출하면 역으로 파생클래스에서 재정의된 멤버함수가 호출된다.

#include <stdio.h>

class base
{
public:
     int val;

     base(int a) { val = a; }
     virtual int get() const = 0;
};

class derived : public base
{
public:
     derived(int a) : base(a) {}
     int get() const { return val; }
};

int main()
{
     derived obj(10);
     base    *b = (base *)&obj;

     printf("%d\n", b->get());
}

(2) 가상함수를 사용하는 모든 클래스는 모두 각각 하나씩의 가상함수 테이블을 가진다.
그리고, 각 클래스의 오브젝트는 해당 테이블을 가리키는 포인터를 가지게 될 것이다.
파생 클래스의 가상함수 테이블 = 기초 클래스의 가상함수
+ 기초 클래스의 가상함수를 overriding 한 가상 함수
+ 자신 만의 가상 함수

이러한 가상함수 테이블은 오브젝트마다 가지는 포인터와 가상함수를 한번 더 거쳐야하는 것 때문에 메모리 사용량 증가와 성능의 작은 하락을 가져올 수 있다.
(3) virtual int get() const = 0; 과 같이 = 0 을 사용하면 “순수 가상 함수”라고 하는데, 이는 파생 클래스에서 반드시 재정의되어야 한다. 이는 아무일도 하지 않고 파생클래스에 Interface 만 제공해주는 역할을 한다.
(4) 순수 가상함수가 선언되어 있는 클래스는 인스턴스(오브젝트)를 가질 수 없다. (컴파일 시 에러)

37.
연산자 중복 (operator overloading)
클래스 안에서 연산자의 의미를 변경시킴으로써 프로그램 코드의 가독성을 높일 뿐 아니라, 사용자 오브젝트를 마치 내장된 데이터형을 연산하듯이 사용할 수 있게 한다.
즉, 오브젝트로 아래와 같이 직관적인 연산을 수행할 수 있다.

myClass obj1, obj2, obj3;
obj3 = obj1 + obj2;
if( obj1 == obj2 )

(1) 연산자 중복을 구현할 때에는 operator 라는 예약어를 사용한다.
아래는 위의 2 가지 연산자를 overloading 해본 것이다.

class myClass
{
private:
    int       length;
    char * buffer;
public:
    myClass myClass::operator+(const myClass & s) const;
    int myClass::operator==(const myClass & s) const;
}


/* 두 object 의 + 연산 결과로 object 리턴 */
myClass myClass::operator+(const myClass & s) const
{
    char * buf = new char[length + s.length + 1];
    ::strcpy(buf, buffer);
    ::strcat(buf, s.buffer);
    myClass ret(buf);     / * return 할 결과 오브젝트에 합친 string 과 그 length 를 생성자에서 설정한다. */
    delete [] buf;
    return ret;
}

/* 두 object 가 같은지 여부 결과 리턴 */
int myClass::operator==(const myClass & s) const  
{
    return ::strcmp(buffer, s.buffer) == 0;
}

38.
템플릿 : 인자가 있는 데이터형이라고 부름. 데이터형인데 인자가 있다..???
(1) 함수 템플릿
min 값을 구하는데 인자는 T 형이다. T 형은 내장 데이터형 뿐 아니라 클래스가 올 수도 있다.

template <class T> T min( T x, T y)
{
    return ( x < y ) ? x : y;
}

위와 같이 사용하지 않으면 데이터 형에 따라 아래와 같이
다수의 함수를 모두 구현해야 한다.

int min( int x, int y ) { return ( x < y ) ? x : y; }
long min( long x, long y ) { return ( x < y ) ? x : y; }

함수 template이 처음 호출될 때만 컴파일러는 해당 데이터형을 지원하는 함수 버전을 생성한다.

(2) 클래스 템플릿
myClass 는 template 클래스이다.

#include <stdio.h>

template <class T>
class myClass
{
    public:
        myClass(T s) { val = s; val1 = s; }
        T get()     { return val; }
        void set( T a) { val = a; }
    private:
        T  val;
        T  val1;
};

int main()
{
     myClass <int> obj(10);
     obj.set(77);
     printf("%d\n", obj.get());
}

위의 T 데이터형은 myClass template 의 오브젝트가 생성될 때 <> 안에 지정된 데이터형으로 대체된다.

myClass <int> obj(10);

클래스 template 의 오브젝트가 생성되기 전에는 클래스 template 에 관한 어떤 코드도 생성되지 않는다.
멤버함수에 대한 코드도 명확하게 호출되지 전까지는 생성되지 않는다.

You may also like...