짧은 설명
- 클래스의 구성요소 : 멤버 변수, 멤버 함수
- 비교 연산자 : 같은 메모리를 ‘복사’ 해서 하나하나 비교한다.
- 각 객체의 ID가 필요한 이유
- 프로그램 종료 후 재시작할때 마지막 좌표를 유지해야하는것과 유지안해도 되는것을 구별하기 위해 사용할 수 있다.
- 앞으로 파일을 분할시켜서 예제를 풀어야한다.
- main
- 클래스가 구현된 헤더파일
- cpp 파일
객체와 클래스
객체의 생성 및 소멸 시기
class MyClass {
public:
MyClass() { // 생성자
cout << "생성자가 호출되었음!!" << endl; //1
}
~MyClass() { // 소멸자
cout << "소멸자가 호출되었음!!" << endl; //2
}
};
void testLocalObj() {
cout << "=== testLocalObj 함수 시작 ===" << endl; //3
MyClass localObj; // 지역 객체 생성
cout << "=== testLocalObj 함수 끝 ===" << endl; //4
} // 여기서 localObj 소멸
int main() {
cout << "=== main 함수 시작 ===" << endl; //5
testLocalObj();
cout << "=== main 함수 끝 ===" << endl; //6
}
- 5 - 3 - 1 - 4 - 2 - 6 순서로 호출된다.
- 생성자 : 어떤 클래스의 객체든 간에 메모리에 올라가는 순간 초기화가 진행되는 함수다.
- 생성자 규칙
- 함수와 유사하지만 반환형을 적지 않는다.
- 함수의 이름을 클래스와 동일하게 선언한다.
- 생성자는 ‘오버로딩’이 가능하다.
- 기본 생성자가 없다면 ‘컴파일 에러’가 일어난다.
- 소멸자 : 클래스 객체가 소멸할때 호출되는 함수다.
- 함수든 프로그램이든 끝나는곳에 객체가 있으면 소멸한다.
- 소멸자 규칙
- ~을 쓰고 클래스 이름과 동일하게 작성한다.
- 소멸자는 ‘오버로딩’이 없다.
- 생성자든 호출자든 private로 선언하면 객체를 생성할때는 문법적으로 틀린것이 없지만 객체 생성 이후에 생성자에 들어가지 못하게 되기에 ‘컴파일 에러’가 일어난다.
- 생성자와 소멸자를 작성하지 않았더라도 프로그램이 실행될 때 자동을 생성해서 동작한다.
- 함수와 생성자 차이점
- 함수 : 사용자가 호출하고 싶을때 호출된다.
- 생성자 : 객체가 선언될 때 1번만 호출된다.
생성자 오버로딩
class Character {
private:
int hp;
int power;
string name;
public:
// 기본 생성자
Character() {
hp = 100;
power = 10;
name = "무명";
}
// 매개변수가 있는 생성자
Character(string n, int h, int p) {
name = n;
hp = h;
power = p;
}
void showInfo() {
cout << "이름: " << name << endl;ㅂ
cout << "HP: " << hp << endl;
cout << "공격력: " << power << endl;
}
};
int test(Character i)
{
return 0;
}
int main() {
Character hero1; // 기본 생성자 호출
Character hero2("전사", 150, 20); // 매개변수 생성자 호출
hero1.showInfo();
cout << "----------" << endl;
hero2.showInfo();
}
- 기본생성자 호출 : 일반적으로 객체를 선언하면 호출된다.
- 매개변수 생성자 호출 : 객체를 선언할때 함수에 매개변수를 넣는것처럼 호출한다.
- 이름없이 클래스 이름과 생성자만 호출할 수 있다.
- 임시객체 라고 부른다.
- 리터럴 상수처럼 사용할 수 있다.
- Character char1 = Character(”이름”, 1, 2); 형태다.
생성자 - 멤버 초기화 리스트 (멤버 이니셜라이저)
class Game {
private:
const int maxLevel; // 상수 멤버
int& currentLevel; // 참조 멤버
int score;
public:
// 멤버 초기화 리스트 사용
Game(int max, int& level)
: maxLevel(max), currentLevel(level), score(0) {
// 생성자 본문
}
void showInfo() {
cout << "최대 레벨: " << maxLevel << endl;
cout << "현재 레벨: " << currentLevel << endl;
cout << "점수: " << score << endl;
}
};
int main() {
int level = 1;
Game myGame(99, level);
myGame.showInfo();
}
- 상수 멤버와 참조 멤버는 반드시 초기화 리스트에서 초기화
- 변수를 만들때 초기화와는 다르다.
- 변수 : 변수 선언 후 초기화 진행 (2단계)
- 초기화 리스트 : 생성 되자마자 초기화 진행 (1단계)
- 매개변수에서 초기화를 하면 기본 생성자와 동일한 효과가 있지만 멤버 이니셜라이저를 사용하는것이 더 좋다.
- 클래스명(매개변수) : 멤버변수(매개변수) {} 형태로 사용한다.
- 성능상 이점 (직접 초기화)
- 참조 멤버는 멤버 이니셜라이저로만 초기화할 수 있다.
정적 멤버 (static)
- 멤버는 static과 instance로 나뉘어진다.
- 기본 ; instance
- 정적 : static
- 클래스마다 단 하나 존재하는 멤버들이다.
- 인스턴스의 유무와 상관 없이 프로그램 시작할때 만들어져서 프로그램 끝날때 소멸한다.
- 인스턴스 없이 접근할 수 있다.
- 한정자 라고 부른다.
정적 멤버 변수 및 메서드
class Player {
private:
static int totalCount; // 전체 플레이어 수
string name;
public:
Player(string n) : name(n) {
totalCount++;
}
~Player() {
totalCount--;
}
static int getTotalCount() {
return totalCount;
}
};
// static 멤버 변수 초기화 (클래스 외부에서)
int Player::totalCount = 0;
int main() {
cout << "현재 플레이어 수: " << Player::getTotalCount() << endl;
Player* p1 = new Player("전사");
Player* p2 = new Player("마법사");
cout << "현재 플레이어 수: " << Player::getTotalCount() << endl;
delete p1;
cout << "현재 플레이어 수: " << Player::getTotalCount() << endl;
delete p2;
cout << "현재 플레이어 수: " << Player::getTotalCount() << endl;
}
- 특징
- 모든 객체가 공유하는 변수
- 객체 생성 전에도 존재
- 클래스명::변수명 으로 접근 가능
- 클래스명을 네임스페이스를 사용하는 문법처럼 사용한다.
- 인스턴스 객체를 통해서도 접근할 수 있다.
- static 함수 내부에는 인스턴스 멤버는 들어갈 수 없다.
- this 포인터도 static이 클래스 단위로 있기 때문에 접근할 수 없다.
- 전역 영역에서 ‘초기화’ 해야한다.
- static 사용처
- 인스턴스마다 있지않고 하나로 사용해도 괜찮은 값
정적 추가 설명
Vector2::Add(vec1, vec2);
- 이걸 보면 static 함수를 선언해야겠다고 떠올려야한다.
- 이유 : 클래스 자체 호출로 객체 선언 없이 함수를 호출했다.
- 왜냐하면? static으로 선언하면 함수 자체를 호출할 수 있으니까
new 키워드
- 동적할당 키워드다.
- 사용자가 원할떄 객체를 생성하는 키워드다.
- new 데이터형(생성자에 매개변수가 필요하면 넣는다.);
delete 키워드
상수형 메서드 const
- const 앞 : 상수형을 반환한다.
- const 뒤 : const 함수라는 것이다.
- 일반 상수보다 제약이 1개 더 있다.
class Item {
private:
string name;
int price;
public:
Item(string n, int p) : name(n), price(p) {}
// const 메서드 - 멤버 변수를 수정하지 않음
int getPrice() const {
// price = 100; // 에러! const 메서드에서는 수정 불가
return price;
}
string getName() const {
return name;
}
// 일반 메서드
void setPrice(int p) {
price = p;
}
};
int main() {
const Item sword("용의 검", 5000);
// const 객체는 const 메서드만 호출 가능
cout << sword.getName() << ": " << sword.getPrice() << "원" << endl;
// sword.setPrice(3000); // 에러! const 객체는 일반 메서드 호출 불가
}
- const 함수 내부에서 멤버 변수를 수정할 수 없다.
- const 함수를 호출하면 객체 데이터가 변경되지 않는다는 보장이 가능하다.
- 기본 함수는 호출하면 객체의 데이터가 변경될 수 있다.
- 읽기 전용 get 함수는 전부 붙여서 쓰는게 좋다.
- 같은 이름의 함수를 써도 컴파일 에러가 안난다.
- 일반객체도 const 함수를 호출할 수 있지만, 일반 함수도 같이 있을경우 일반 객체는 일반 함수만 호출된다.
상수형 오버로딩 사용 조건
- 매개변수의 갯수 및 데이터형
- const가 붙었는가 안붙었는가
메서드 선언과 정의 분리
// Character.h 파일 내용
class Character {
private:
int hp;
int mp;
public:
Character();
void attack();
void useSkill();
int getHp() const;
};
// Character.cpp 파일 내용
#include <iostream>
using namespace std;
Character::Character() {
hp = 100;
mp = 50;
}
void Character::attack() {
cout << "공격!" << endl;
}
void Character::useSkill() {
if (mp >= 10) {
cout << "스킬 사용!" << endl;
mp -= 10;
}
}
int Character::getHp() const {
return hp;
}
- 컴파일 시간 단축
- 구현부 숨기기 가능
- 코드 관리 용이
- 파일이 분할하더라도 해당 클래스, 해당 선언이 있는 파일 내부에 선언되어있다면 접근이 가능하다.
연산자 오버로딩
- 기본 기능으로 만들어진 연산자를 매개변수를 바꿔서 구현할 수있다.
- 필요한 연산자 만 재정의해서 쓰면 된다.
class Vector2D {
private:
float x, y;
public:
Vector2D(float x = 0, float y = 0) : x(x), y(y) {}
// + 연산자 오버로딩
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
void print() const {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Vector2D pos1(10, 20);
Vector2D pos2(30, 40);
Vector2D pos3 = pos1 + pos2; // operator+ 호출
cout << "pos1: "; pos1.print();
cout << "pos2: "; pos2.print();
cout << "pos3: "; pos3.print();
}
해석
Vector2 operator+(const Vector2& vec1);
vec1.operator+(vec2); == vec1 + vec2
- 연산자 : operator기호 → 표준 함수 중의 산술 연산자 함수를 의미한다.
- 좌항을 오퍼레이터를 호출하는 인스턴스, 매개변수를 우항을 의미한다.
friend Vector2 operator*(float lhs, const Vector2& rhs);
void Print();
};
Vector2 operator*(float lhs, const Vector2& rhs);
- 오버로딩이 좌우 매개변수로 필요하다면 전역함수로 구현해야한다.
- 전역함수로 구현해야하는 상황이 아니면 반드시 멤버 함수로 선언해야한다.
- 전역함수로 클래스 private 변수에 접근하는 방법
- get 함수를 써서 넣는다.
- friend 키워드 사용
freind 키워드
- 클래스의 멤버가 아닌 함수 또는 별도의 클래스에 있는 모든 멤버에게 멤버 수준의 액세스 권한을 부여하는 키워드다.
- private 접근 지정자를 해칠 수 있어서 사용을 자제해야한다.
- 전역 함수 또는 클래스 이름 자체가 올 수 있다.
- 해당 클래스의 private 또는 protected에 접근이 가능하게 된다.
구조체 및 객체 매개변수 작성법
Vector2 Vector2::Add(const Vector2& vec1, const Vector2& vec2)
- &를 붙이면 값 복사 과정이 스킵되어 더 빨라진다.
- const 를 붙이면 값 변경이 안된다.
- 결론 : 연산이 더 빨라지고 값 변경이 일어나지않아 안전하다.
- 매개변수로 구조체나 클래스 객체를 넣는 매개 변수는 이렇게 작성해야한다.
- float같은 단일 변수를 갖고있는건 그냥 써도 된다.