VioletaBabel
2. 클래스 본문
클래스는 데이터(멤버 변수), 함수(메서드, 멤버 함수), 타입 정의, 포함된 다른 클래스 등을 가질 수 있다.
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 = 0, int n2 = 0) { // 생성자를 이용한 초기화 num1 = n1; num2 = n2; } void showNum() { cout << num1 << "/" << num2 << endl; } }; int main() { one o1; one o2(1); one o3(2, 3); 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 = 0, int 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(2, 3); 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 = 0, int 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(1, 2) , 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 = 0, double 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 = 0, double 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 |
---|