728x90
클래스 선언 방법
class Book
{
private:
int current_page; // 멤버 변수
public:
void set_percent(); // 멤버 함수
int total_page;
. . .
};
Book my_book; // 클래스 Book 객체 my_book 선언
외부클래스 정의 방법
클래스 선언 밖에서 멤버함수를 정의할 때 주의사항
- 외부에서 멤버함수를 정의하려면 클래스 안에 정의할 멤버함수가 이미 있어야 한다. ( 오버라이딩? 같은 느낌 )
- 외부에서 정의하려면 함수의 몸체, 즉 { } 가 클래스 내에 있으면 안 된다.
사용법
class Member{
public:
Member();
void print();
};
Member::Member(){
printf("생성자 외부클래스로 정의~");
}
void Member::print(){
printf("멤버함수 외부클래스로 정의~");
}
this 포인터
- c++에서는 모든 멤버 함수가 자신만의 this포인터를 가지고 있다.
- this 포인터는 해당 멤버 함수를 호출한 객체를 가리킨다.
- this는 현재 객체의 주소를 저장한 포인터 변수이므로 반환할 때 참조 연산자(*)를 사용해 객체를 반환한다.
( *를 사용하지 않으면 this는 포인터이므로 주소값만 반환한다. ) - 정적 멤버함수는 this 포인터가 없다.
생성자
생성자는 3가지 타입이 있다. ➡️ 기본 생성자, 디폴트 생성자, 복사 생성자
1. 기본 생성자
#include<iostream>
using namespace std;
class Test
{
private:
int num1;
double num2;
public:
Test(int x, double y){
num1 = x;
num2 = y;
}
void printNum() const {
cout << num1 << " " << num2 << endl;
}
};
int main(){
Test test(10, 10.1); // 암시적 호출로 객체 생성
test.printNum();
Test test = Test(10, 10.1); // 명시적 호출로 객체 생성
test.printNum();
Test test; // 기본 생성자로 들어감
test.printNum();
Test(10, 10.1); // 임시객체 생성 ( 실행도중 잠깐만 사용되는 객체 ) ➡️ 다음줄로 넘어가면 사라진다.
return 0;
}
2. 디폴트 생성자
- 객체를 생성할 때 아무런 인자를 넘겨주지 않을 때 멤버 변수를 초기화하는 생성자
- 생성자를 만들지 않으면 컴파일러는 자동으로 텅 빈 생성자를 만든다.
예시1)
class Test{
private:
int num1;
double num2;
public:
Test(){}
void printNum() const{}
};
int main(){
Test test;
}
예시2)
class Test{
private:
int num1;
double num2;
public:
Test(int a=10, double b = 10.1){
cout << "디폴트 호출" << endl;
num1 = a;
num2 = b;
}
void printNum() const{}
};
int main(){
Test test; // a = 10. b = 10.1
Test test2(15,15.1) // a = 15, b = 15.1 ➡️ 여기도 디폴트 생성자로 들어가는데 a와 b값이 지정해준 값들로 들어감
}
3. 복사 생성자
c++은 아래와 같이 묵시적 변환을 해준다.
int num1 = 5; ➡️ int num1(5); 와 동일
int num2 = num1; ➡️ int num2(num1); 와 동일
객체 선언도 마찬가지로 묵시적 변환을 해준다.
A a;
A b = a; ➡️ A b(a); 와 동일하다. 즉, 복사이다!!
복사는 얕은 복사와 깊은 복사가 있다.
얕은 복사
- 객체를 복사할 때 기존 객체의 메모리 주소만 복사
- 디폴트 복사 생성자이다. 즉, 연산자 오버로딩으로 재정의하지 않아도 이미 존재하는 대입연산자이다.
- 대충 복사하는 느낌
얕은 복사의 문제점
#include<iostream>
#include<string>
using namespace std;
class A
{
pvivate:
char* name;
int age;
public:
A(const char* myname, int myage) : age(myage)
{
name = new char[strlen(myname) + 1];
strcpy(name, myname);
}
void ShowInfo() const {
cout << "이름 : " << name << endl;
cout << "나이 : " << age << endl;
}
~A(){
delete []name;
cout << "소멸자 호출" << endl;
}
};
int main(){
A a("이름", 20);
A b = a; // shallow copy로 주소값만 복사해온다. ( shallow copy가 디폴트 복사생성자임 )
a.ShowInfo();
b.ShowInfo(); // 객체 a의 name을 가리키는 주소값을 b도 할당받아 두 객체가 같은 문자열을 delete해서 에러 발생
return 0;
}
얕은 복사가 이루어지면서 a와 b가 둘 다 같은 주소를 delete해서 중복 소멸하는 문제가 발생한다.
그리고 b의 원래 주소는 다시는 접근할 방법이 없게 되면서 delete할 수가 없게 되어 메모리 누수가 발생한다.
깊은 복사
- 객체가 가진 멤버의 값과 형식 자체를 복사하여 "객체 자체"가 복사되는 것
- 복사 생성자를 직접 정의(연산자 오버로딩을 통해 재정의)함으로써 객체간의 같은 메모리 공간 참조를 막아준다.
#include<stdio.h>
class Sample
{
private:
int num1;
int num2;
public:
Sample(const Sample& Sp);
};
// 참조전달 방법으로 객체 자체를 가져와서 복사한다. ( 대입연산자를 직접 재정의한 것 )
Sample::Sample(const Sample& Sp){
printf("복사 생성자\n");
num1 = Sp.num1;
num2 = Sp.num2;
}
int main(){
Sample a;
Sample b(a);
// Sample b = a;와 같은 문장이다.
// c++에서 묵시적 변환한 것 ( Sample b = a;를 Sample b(a)로 알아서 변환해줌 )
// 묵시적 변환을 막아주고 싶으면 재정의한 부분 앞에 explicit을 써주면 된다.
// ➡️ 그러면 Sample b = a;는 에러가 난다.
// 묵시적 변환을 최소로 만들어주는게 좋은 코드이다.
// 묵시적 변환이 많이 발생할수록 결과를 예측하기 어렵다.
}
// 재정의한 복사(대입연산자)로 인해 깊은 복사가 이루어졌다.
'C,C++' 카테고리의 다른 글
C++ 상속 (0) | 2022.04.13 |
---|---|
C++ 접근 제어자 (0) | 2022.04.10 |
C++ enum ( 열거형 자료형 ) (0) | 2022.04.10 |
C++ 스마트 포인터 ( shared_ptr, make_shared ) (0) | 2022.04.10 |
C++ 범위 지정 연산자(::)와 namespace (0) | 2022.04.10 |