VioletaBabel

2. 클래스 본문

BCA/10. 모던 C++ 입문
2. 클래스
Beabletoet 2018. 12. 28. 12:44

클래스는 데이터(멤버 변수), 함수(메서드, 멤버 함수), 타입 정의, 포함된 다른 클래스 등을 가질 수 있다.




1. friend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;
 
class one
{
public:
    one() { num = 100; }
    friend class two;
    void showNum() { cout << "one:" << num << endl; }
private:
    int num;
};
 
class two
{
public:
    two() { num = 0; }
    void getNum(one& o)
    {
        this->num += o.num;
        o.num = 0;
    }
    void showNum() { cout << "two:" << num << endl; }
private:
    int num;
};
 
int main()
{
    one o;
    two t;
    o.showNum(); // 100
    t.showNum(); // 0
    t.getNum(o);
    o.showNum(); // 0
    t.showNum(); // 100
}
cs


friend를 이용하면 private에도 접근 가능하게 한다.

여기서  8번 줄의 friend class two; 를 주석처리하면 20,21번 줄의 o.num 에서 접근할 수 없다고 에러가 난다.


friend는 가능한 선언하지 않는 것이 좋다.




2. static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
 
class one
{
public:
    void showNum() { cout << num << endl; }
    void setNum(int value) { num = value; }
    int getNum() { return num; }
    static int num;
private:
};
 
int one::num; // static은 별도로 선언
 
int main()
{
    one o1, o2;
    o1.setNum(100);
    o1.showNum(); // 100
    o2.setNum(o1.getNum() + 100);
    o1.showNum(); // 200
    o2.showNum(); // 200
}
cs


static은 공통으로 쓰는 것이라고 보면 된다.

싱글턴과도 일맥상통한다.




3. 생성자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
 
class one
{
public:
    int num1;
    int num2;
    one(int n1 = 0int n2 = 0
    { // 생성자를 이용한 초기화
        num1 = n1; num2 = n2;
    }
    void showNum() { cout << num1 << "/" << num2 << endl; }
};
 
int main()
{
    one o1;
    one o2(1);
    one o3(23);
    o1.showNum(); // 0/0
    o2.showNum(); // 1/0
    o3.showNum(); // 2/3
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
class one
{
public:
    one(int n1 = 0int n2 = 0) : num1(n1), num2(n2) {} // 생성자를 이용한 초기화 2
    void showNum() { cout << num1 << "/" << num2 << endl; }
private:
    int num1;
    int num2;
};
 
int main()
{
    one o1;
    one o2(1);
    one o3(23);
    o1.showNum(); // 0/0
    o2.showNum(); // 1/0
    o3.showNum(); // 2/3
 
    one o4(); //이건 함정. o4는 존재하지 않는다. 함수의 선언으로 해석하므로 ()에 주의.
}
cs

멤버 초기화 리스트(또는 초기화 리스트)라고 부르는 방식이다.

생성자 함수의 머리 부분 다음에 콜론을 붙이고, 초기화 순서와 정의 순서를 일치하여 넣어야 한다.

여기서 주의할 점은 21번째 줄. 그 다음에 o4.showNum();이라고 할라손 치면 o4는 존재하지 않는다며 에러가 터진다.



1) 디폴트 생성자

인수가 없는 생성자거나, 모든 인수가 기본값을 갖는 생성자.

가능하다면 디폴트 생성자를 정의하는 것이 좋다.

하지만, 일부 클래스의 경우(멤버 중 일부가 레퍼런스라거나)에는 디폴트 생성자를 정의하는 것이 어려우므로 주의.



2) 복사 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
using namespace std;
 
class one
{
public:
    one(int n1 = 0int n2 = 0) : num1(n1), num2(n2) {}
    one(const one& o) : num1(o.num1), num2(o.num2) {} // 복사 생성자
    void showNum() { cout << num1 << "/" << num2 << endl; }
    int num1;
    int num2;
};
 
int main()
{
    one o1(12)
        , o2(o1)// o2는 복사 생성자
        , o3{ o1 }; // o3는 복사 생성자에다 C++11의 축소 방지를 추가
    o1.showNum(); // 1/2
    o2.showNum(); // 1/2
    o3.showNum(); // 1/2
}
cs


복사 생성자를 사용할 때는 값에 의한 전달을 이용시 에러가 나며, 변경 가능한 레퍼런스로 사용하는 것을 비추천한다.

즉, const & 타입을 쓰는 것을 추천. 값으로 인수를 전달하려면 정의할 복사 생성자가 필요하므로 무한 루프에 빠진다(실제로는 막아놔서 에러만 뜬다).


디폴트 복사 생성자는 클래스에 포인터가 포함되는 경우 동작하지 않을 수 있다.

값이 아닌 주소를 복사해오므로 복사가 아닌 동시 참조가 되어버리는 탓.

만약 이런 문제를 막기 위해서면 클래스 안의 포인터를 unique_ptr로 하는 걸 추천한다.

메모리를 자동으로 해제할 뿐 아니라 복사 생성자가 자동으로 삭제되기 때문.


3) 변환과 명시적 생성자

C++에서는 암시적 생성자와 명시적 생성자를 구분한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
 
class complex
{
public:
    double r;
    double i;
    complex(double nr = 0double ni = 0.0) : r(nr), i(ni) {}
};
 
double real(complex c)
{
    return c.r;
}
 
double imag(complex c)
{
    return c.i;
}
 
double inline complex_abs(complex c)
{
    return sqrt(real(c)*real(c)*imag(c)*imag(c));
}
 
int main()
{
    cout << complex_abs(7.0<< endl;    // complex_abs함수에는 double을 인수로 받는 부분이 없는데도
                                        // double 타입을 허용하는 생성자가 있어서 암시적으로 생성해 처리
}
cs


이런 암시적 생성자는 explicit으로 막을 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
using namespace std;
 
class complex
{
public:
    double r;
    double i;
    explicit complex(double nr = 0double ni = 0.0) : r(nr), i(ni) {} // explicit
};
 
double real(complex c)
{
    return c.r;
}
 
double imag(complex c)
{
    return c.i;
}
 
double inline complex_abs(complex c)
{
    return sqrt(real(c)*real(c)*imag(c)*imag(c));
}
 
int main()
{
    cout << complex_abs(7.0<< endl;    // explicit이므로 에러!
}
cs


explicit은 생성자의 암시적 변환을 비활성화한다.



4) 생성자 위임 (C++11)

C++03에서는 여러 생성자 오버로드를 함으로써 서로 중복된 코드를 가지고 있는 경우가 빈번했다.

그래서 C++11부터는 생성자가 다른 생성자를 호출하는 위임 기능을 제공한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using namespace std;
 
class one
{
public:
    int a;
    int b;
    one(int ta, int tb) : a{ ta }, b{ tb }{}
    one(int ta) : one{ ta,0 } {}
    one() : one{ 0,0 } {}
};
 
int main()
{
}
cs


생성자를 위임하는 방식은 초기화가 더 복잡한 클래스일수록 더욱 유용하다.



5) 멤버의 기본값 (C++11)

그 외에도 멤버 변수의 기본값을 설정할 수 있는 기능이 나왔다.

이 기능을 이용해 생성자에서는 기본값과 다른 값이 들어가는 경우만 설정한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
using namespace std;
 
class one
{
public:
    one(int ta, int tb) : a{ ta }, b{ tb }{}
    one(int ta) : a{ ta } {}
    one() {}
private:
    int a = 0;
    int b = 0;
};
 
int main()
{
}
cs




4. 할당

(129p. 이 부분은 다시 볼 것. 뭐가 문제라는 건지 이해가 잘 안됨.)



5. 초기화 리스트 (C++11)

이건 멤버 초기화 리스트(위 3. 생성자에서 언급)와 별개임을 우선 명심하고 넘어가자.

클래스와 상관 없지만 후에 언급할 클래스의 유니폼 초기화 등에도 쓰일 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
#include<initializer_list>
using namespace std;
 
int main()
{
    initializer_list<int> a{ 100,200,300,400 };
    for (int i : a)
        cout << i << " "// 100 200 300 400
    cout << endl;
    a = { 10 };
    for (int i : a)
        cout << i << " "// 10
}
cs


다음과 같이 같은 타입의 값 목록으로 이루어진 것이 initializer list이다.


initializer_list를 인수로 둔 생성자를 이용해 다음과 같이 클래스에서 사용할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
#include<initializer_list>
using namespace std;
 
class one
{
public:
    one() :a{ 1 }, b{ 2 }{}
    one(initializer_list<int> values)
    {
        a = *(begin(values)); // 예전엔 values.begin()으로 사용
        b = *(begin(values) + 1);
    }
    void print() { cout << a << " " << b << endl; }
private:
    int a;
    int b;
};
int main()
{
    one o1;
    one o2 = { 2,3 }; // initializer_list를 인수로 가진 생성자를 거침
    o1.print(); // 1 2
    o2.print(); // 2 3
}
cs


배열, 벡터 등을 이용한다면 std::copy 함수를 이용해 값을 복사할 수 있다.

그리고 begin, end 함수의 경우 c++11부터 자유 함수가 되었으며, 이전엔 주석과 같이 멤버 함수로 사용하였다.



6. 유니폼 초기화 (C++11)


'BCA > 10. 모던 C++ 입문' 카테고리의 다른 글

1. C++ 기초  (0) 2018.12.27
Comments