객체와 멤버변수
class Account
{
public:
bool Withdraw(double amount) // 출금
{
if (balance - amount < 0)
{
return false;
}
balance -= amount;
}
bool Deposit(double amount) // 예금
{
balance += amount;
}
private:
std::string name;
double balance = 0;
};
int main()
{
Account kim_account;
kim_account.Deposit(100);
Account lee_account;
lee_account.Deposit(200);
}
멤버 변수는 하나의 객체에 종속된 것이 아니다. 각 개체마다 독립된 멤버 변수를 사용한다.
위 코드에서 kim_account, lee_account는 서로 다른 객체이기 때문에, 서로 독립된 balance를 갖는다.
따라서 각각의 balance 값은 100, 200이 될 것이다.
멤버 변수를 클래스 밖에서 정의하기
class Account
{
public:
bool Deposit(double amount);
private:
std::string name;
double balance = 0;
};
bool Account::Deposit(double amount)
{
balance += amount;
}
앞전 코드에서는 Deposit 함수에 대한 정의를 Class 내에서 해주었다.
하지만, class 내에서 선언만 해주고, 그 함수의 동작에 대한 정의는 class 밖에 나중에 해줘도 된다.
디렉토리 구성
일반적으로 클래스를 포함한 C++ 프로젝트를 구성할 때, 위와 같은 디렉토리 구조를 따를 수 있다.
예를 들면 Account 클래스에 대한 정의는 헤더 파일 폴더의 Account.h에 해두고, 해당 클래스에 존재하는 멤버 함수들에 대한 정의는 소스 파일 폴더의 Account.cpp에 정의한다. 마지막으로 사용자는 해당 클래스는 main.cpp에서 사용하는 것이다.
# Account.h
#pragma once
#include <string>
class Account
{
public:
bool Deposit(double amount);
private:
std::string name;
double balance = 0;
};
# Account.cpp
#include "Account.h"
bool Account::Deposit(double amount)
{
balance += amount;
}
# main.cpp
#include "Account.h"
#include <iostream>
#include <string>
int main()
{
Account kim_account;
kim_account.Deposit(100);
Account lee_account;
lee_account.Deposit(200);
}
이때 Account.cpp와 main.cpp에서 Account라는 class를 사용하기 위해 #include "Account.h"를 통해 헤더를 불러온 것에 주의한다.
C++에서 구조체와 클래스
struct Person
{
std::string name;
};
int main(){
Person p;
p.name = "Kim";
}
- C++은 구조체와 클래스 모두 사용 가능하다.
- 구조체와 클래스는 기본 접근 권한 차이 외에는 차이가 존재하지 않는다.
- Class : private이 기본값
- Struct : public이 기본값
따라서 위 코드에서 struct Person이라는 구조체 내에 public, private을 명시하지 않았지만, std::string name;은 구조체 내에 존재하므로 자동으로 public이 되어 main 함수에서 실행이 가능하다.
하지만, 위 코드에서 struct → class Person으로 바꿔준다면? 👉 std::string name;은 private이 되어 main 함수에서 name을 호출하는 것이 불가능하다.
생성자
- 클래스와 동일한 이름을 갖는 멤버 변수! (특수한 기능을 갖는다)
- 객체가 생성될 때 자동으로 호출됨
- 반환형이 존재하지 않음
- 초기화 목적으로 사용
소멸자
- 클래스와 동일한 이름 앞에 "~"이 붙는 멤버 함수
- 객체가 소멸할 때 자동으로 호출됨
- 반환형 및 파라미터가 존재하지 않음
- 메모리 및 리소스 해제 목적으로 유용하게 사용
- 오버로딩 불가능. 소멸자는 단 하나만 정의할 수 있다.
생성자와 소멸자의 사용 예시
#include <iostream>
#include <string>
class Account
{
public:
Account()
{
std::cout << "계좌 생성됨" << std::endl;
}
~Account()
{
std::cout << "소멸자 호출됨" << std::endl;
}
private:
double balance = 0;
std::string name;
};
int main()
{
Account kim_account; # 생성자 호출 구간
}# 소멸자 호출 구간
Class 이름이 Account이므로, Account()는 생성자, ~Account()는 소멸자임을 알 수 있다.
main 함수에서 Account kim_account;로 객체를 하나 생성할 때, 자동으로 Account() 생성자가 호출된다. 따라서, 이 순간에 "계좌 생성됨" 이라는 문구가 출력된다.
또한, class로 만들어낸 객체는 자신이 생성된 지역을 벗어나면 자동으로 소멸되는데, 여기서는 main 함수를 벗어날 때 자동으로 소멸자가 호출되며 객체가 소멸한다. 이 코드에서는 설정한대로 "소멸자 호출됨" 이라는 문구가 출력된다.
잠깐! 생성자와 소멸자를 직접 정의해주지 않아도 이제까지 객체가 잘만 생성/소멸 되었던 것 같은데?
👉 맞다. 바로 기본 생성자 덕임.
기본 생성자
- 클래스의 생성자를 직접 구현하지 않으면 컴파일러가 기본적으로 알아서 만들어 준다. (코드상에는 나타나지 않음)
- 따라서 생성자가 없는 상태에서도 객체를 만드는 것이 가능
- 하지만 인자가 없는 클래스 생성자도 구현하여 값을 초기화 해주는 것이 좋다.
위 코드를 실행해보면 kim_account라는 객체는 멤버 변수로 쓰레기 값을 갖는다.
쓰레기 값은 방지하는 것이 안전하기 때문에, 다음과 같이 생성자를 정의하여 값을 초기화해주는 것이 좋다.
class Account
{
public:
Account()
{
balance = 0.0;
name = "None";
}
private:
double balance = 0;
std::string name;
};
int main()
{
Account kim_account;
}
생성자 여러 개 만들기
class Account
{
public:
Account()
{
balance = 0.0;
name = "None";
}
Account(double val, std::string str)
{
balance = val;
name = str;
}
private:
double balance = 0;
std::string name;
};
int main()
{
Account kim_account;
Account lee_account { 100.0, "Lee" };
}
위 코드처럼 c++클래스에서는 생성자는 여러 개 만들 수 있는데, 이를 생성자 오버로딩이라고 한다.
다만, 각 생성자들은 서로 다른 매개변수를 입력받아야 한다. (서로 같은 타입의 매개변수를 동일한 개수만큼 받는 생성자는 2개 이상 만들 수 없으며, 만약 만들더라도 컴파일 에러가 발생한다.)
Account kim_account 👉 입력 인자가 없으므로, 자동으로 Account() 생성자가 호출Account lee_account { 100.0, "Lee" } 👉 입력 인자가 double, str 형태로 하나씩 존재하므로 자동으로 Accout(double val ~~)이 호출
즉, 하나의 클래스에서 여러 개의 생성자를 가질 수 있으며, 어떤 입력 인자를 입력하여 객체를 호출하냐에 따라 컴파일러가 자동으로 알맞은 생성자와 매치시켜 실행한다.
이 경우에서 Account() 생성자를 삭제한다면?
class Account
{
public:
Account(double val, std::string str)
{
balance = val;
name = str;
}
private:
double balance = 0;
std::string name;
};
int main()
{
Account kim_account; # 기본 생성자가 생성되지 않음!!
Account lee_account { 100.0, "Lee" };
}
앞서 기본 생성자를 언급하면서, Account()라는 기본 생성자는 따로 정의하지 않아도 컴파일러가 자동으로 생성 해준다고 했었다. 하지만! 기본 생성자가 생성되는 특별한 조건 하에서 가능했던 것이다.
사용자가 생성자를 하나 이상 설정해준 경우에는 기본 생성자가 동작하지 않는다. 위 코드처럼 이미 선언된 생성자가 존재하면, 컴파일러는 Account kim_account; 라는 코드를 보고 이를 어느 생성자에 매치할 지 알 수 없게 되기 때문이다.
객체의 동적 할당
class Account
{
public:
Account() {};
Account(double val, std::string str)
{
balance = val;
name = str;
}
private:
double balance = 0;
std::string name;
};
int main()
{
Account kim_account;
Account lee_account { 100.0, "Lee" };
Account* park_account_ptr = new Account{ 200.0, "Park" };
}
이번에는 객체를 동적으로 생성해보았다. park_account_ptr은 스택영역에 정의되며, new Account의 주소를 담고있다.
또한, 실제 객체는 힙 영역에 정의된다.
'C++' 카테고리의 다른 글
[C++] 객체지향 프로그래밍 - 클래스와 객체 (1) | 2024.10.20 |
---|---|
[C++] 참조자 Reference (0) | 2024.10.20 |
[C++] 포인터를 함수에 input, return (1) | 2024.10.13 |