게임을 설치할 때 경로를 바꿀 때가 있다. 내 게임의 이미지를 절대경로로 설정할 수는 없다.
D:\GM\Cpp
.
과 ..
로 표현하는 경로.
(.\
) : 현재 위치 경로..
(..\
): 현재 위치에서 상위 디렉터리로 이동D:\GM\Cpp..\
== D:\GM\
이런 파일경로를 include문에서도 사용한다.
#include "ConsoleScreen.h"
위 include문에는 현재 위치 경로가 생략된 것이다.
#include ".\ConsoleScreen.h"
include문에서 상대경로 가능.
<>
와 ""
의 차이<>
: 비주얼 스튜디오에 '포함 디렉토리'에 존재하는 파일을 사용하겠다.""
: 파일이 존재하는 폴더의 경로를 사용하겠다.포함 디렉터리는 헤더의 위치이다.
<>
로 include했을 때 비주얼 스튜디오는 포함 디렉터리 중에서 헤더 파일을 찾는다.
(비주얼 스튜디오에서는 특정 프로젝트마다 설정을 세팅할 수 있는데 다른 프로젝트에서는 적용되지 않는다.)
이상한 파일 경로가 있는데 이것은 비주얼 스튜디오에서 사용하는 매크로이다. 특정 문자나 기호를 어떤 경로로 생각하는 것이다. 하나의 매크로가 하나의 경로를 의미하지는 않는다. 여러 개 있을 수 있다.
;
로 경로와 경로를 구분한다. 실수로 지우면 안된다. 의미있는 경로를 추가했으면 꼭 붙여야 한다.
$(VC_IncludePath)의 값 :
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\include
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\atlmfc\include
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include
전처리기 > 컴파일러 > 어셈블러 > 링커
.obj
파일 만들기 (=> 실행 메모리 크기 결정됨.).exe
파일헤더 내에서 불필요한 헤더 include는 피해야 한다. 순환참조나 변수 재정의의 원인이 될 수 있다.
또한 헤더쪽에 구현이 있는 건 문제가 생길 여지가 있기 때문에 최대한 분할해야 한다. 헤더에서 클래스를 사용하면 헤더와 구현의 완전한 분리를 할 수 없다.
#include에서는 순환참조를 주의해야 한다.
A 클래스의 헤더에서 #include "B.h"
B 클래스의 헤더에서 #include "A.h"
→ A를 만들려면 B가 필요하고 B가 먼저 컴파일되면 A가 그걸 사용해야 한다. 그런데 B도 A가 필요하다.
컴파일러 :
A를 만들려면 B가 필요
B를 만들려면 A가 필요
A를 만들려면 B가 필요
B를 만들려면 A가 필요
...
많은 클래스끼리 순환참조가 생겨서 에러가 나면 찾기 어렵다.
근데 cpp파일에서 참조하는 건 됨!!
#pragma once
: cpp파일별로 적용된다. 헤더 중복을 파일 단위로 막아준다.(프로젝트 전체에 이 헤더가 한 번 있게 만드는 게 아님)
선언은 여러 번 가능하고 구현은 중복되면 안 된다. 따라서 헤더는 중복되어도 에러가 나지는 않는다.
#ifndef HEADERNAME_H / #define HEADERNAME_H
: #pragma once
와 동일한 역할을 한다. HEADERNAME_H가 define되지 않았다면 define하는 것.
#ifndef HEADERNAME_H
#define HEADERNAME_H
// 헤더 파일 내용
#endif
헤더에 전역 변수가 있을 때 헤더가 중복되면 같은 변수가 재정의 되었다는 에러가 난다. 어제 게임 코드에서는 전역변수가 const로 선언되었기 때문에 에러가 안 난 것이다.
어제 코드에서 Math.h(int2 클래스 있는 곳)와 같은 근본적인 헤더는 여기저기에서 사용하기 때문에 컴파일 과정에 헤더가 중복될 수 있다. 따라서 헤더에 전역변수가 있었다면 같은 이름의 변수가 여러 개 있다고 인식되는 것이다.
(cpp파일을 include하는 것은 금기..)
헤더에 전역 변수 쓰는 방법.
extern
: "이런 변수가 있어, 절대 초기화하면 안돼", 다른 파일에서 extern 변수 사용 가능.
// in Global.h
extern int Value; // int Value가 있을 거야.
헤더에서 extern 키워드로 선언한 전역변수는 다른 파일에서 그 전역변수를 정의해서 쓸 수 있다.
(헤더에 extern 키워드로 전역 변수를 선언해두면 나중에 cpp파일에서 정의해서 사용할 수 있다.)
// in Main.cpp
int Value = 10;
근데 꼭 헤더와 cpp파일 사이에 가능한 게 아니고 .cpp 사이에서도 가능한듯.
int TestVar = 10;
class Test {
};
#include <iostream>
extern int TestVar;
int main() {
printf("%d", TestVar);
return 0;
}
클래스의 헤더에서 다른 클래스의 헤더를 include해야 하는 경우는 2가지가 있다.
1. (내일 배울)상속.
2. 값형으로 쓸 때.
#include "Monster.h"
class Player{
Monster NewMonter; // Monster의 크기를 알아야 Player의 크기도 정의가 되기 때문.
};
Monster의 크기를 알아야하고 크기를 알려면 멤버변수들을 알아야 한다.
참조형(포인터나 레퍼런스)으로 사용하면 헤더를 include하지 않아도 쓸 수 있다.
모든 포인터와 레퍼런스는 8바이트이고, "이런 클래스가 있을 것이다" 라는 의미만 있기 때문에 선언 가능하다.
class 클래스이름*
으로 전방 선언이 가능하다.
class 클래스이름;
으로 전방 선언하고 선언은 참조형으로 해도 된다.
//#include "Monster.h"
class Player{
class Monster* NewMonter; // 이러이러한 클래스가 있을거다 라는 뜻, 포인터형이니까 크기는 8바이트 고정
};
Player, Monster는 모두 Att, Hp가 필요하다. 불합리적.
문법의 발전은 프로그래밍 코드의 재활용성을 증가시키기 위해서 발전해왔다.
상속은 코드 재활용성을 위해 필요하다.
" xxx분야의 클래스들이 공통적으로 가져야할 기능을 만든다. "
그 기능을 물려받기를 원하는 클래스이름 : 접근제한자 부모클래스이름
으로 사용 가능하다.
상속은 무조건 헤더를 추가해야 한다.
이유? 자식 클래스의 크기를 알아야 함 -> 부모를 상속받았으니 부모 크기를 알아야 함.
#include <iostream>
// 싸우는 애들이 공통적으로 가져야할 기능을 가진다.
class FightUnit {
public:
int Att = 10;
int Hp = 100;
void Damage(int _Att) {
Hp -= _Att;
}
};
class Player : public FightUnit {
};
class Monster : public FightUnit {
};
int main() {
Player NewPlayer = Player();
NewPlayer.Att = 20;
NewPlayer.Damage(20);
Monster NewMonster = Monster();
NewMonster.Att = 10;
NewMonster.Damage(20);
return 0;
}
(근디 선생님은 상속에서 접근 제한자에 public말고는 쓰실 일이 없었다고 함.)
상속 접근 제한자와 기존 접근 제한자를 비교해서 더 좁은 접근 제한자로 만든다.
부모에서 public나 protected이었던걸 자식에 protected나 private(상속 접근제한자)로 가져오는 것이 가능하다는 듯?
외부 | 자식 | 멤버 |
---|---|---|
O | O | O |
외부 | 자식 | 멤버 |
---|---|---|
X | O | O |
외부 | 자식 | 멤버 |
---|---|---|
X | X | O |
그냥 상속 접근 제한자로 바뀐다고 생각하면 된다. 한 번 더 상속을 하면 의미가 생길 수 있지만 보통 public 상속만 사용한다.
함수
GetPos(), GetRenderChar(), 변수
Pos, RenderChar, 헤더
"Math.h"#pragma once
#include "Math.h"
// 자식들이 공통적으로 가져야 할 기능을 만들어야 한다.
class ConsoleObject
{
public:
int2 GetPos();
void SetPos(const int2& _Pos);
char GetRenderChar();
protected:
int2 Pos = { 0, 0 };
char RenderChar = '@';
};
#include "ConsoleObject.h"
int2 ConsoleObject::GetPos()
{
return Pos;
}
char ConsoleObject::GetRenderChar()
{
return RenderChar;
}
void ConsoleObject::SetPos(const int2& _Pos) {
Pos = _Pos;
}
#include <iostream>
#include "ConsoleScreen.h"
#include "Galaga.h"
#include "Player.h"
#include "Bullet.h"
int main()
{
ConsoleScreen NewScreen = ConsoleScreen('*');
Galaga NewGalaga = Galaga();
Bullet NewBullet = Bullet({ 0,0 }, '^');
Player NewPlayer = Player({ ScreenXHalf, ScreenYHalf }, '@');
bool& Ref = NewBullet.GetIsFireRef();
NewPlayer.SetBulletFire(&Ref);
while (true)
{
NewScreen.ClearScreen();
NewGalaga.GalagaWallDraw(NewScreen);
int2 Index = NewPlayer.GetPos();
char Ch = NewPlayer.GetRenderChar();
NewScreen.SetPixel(Index, Ch); //플레이어 위치에 플레이어 char 찍기
if (true == NewBullet.GetIsFireRef())
{
NewBullet.SetPos({ NewPlayer.GetPos().X, NewPlayer.GetPos().Y - 1 });
NewScreen.SetPixel(NewBullet.GetPos(), NewBullet.GetRenderChar()); //총알 위치에 총알 char 찍기
}
NewScreen.PrintScreen();
NewPlayer.Update();
}
}
@block blast How do circular references between classes in C++ impact compilation, and what strategies can be used to resolve them, such as using forward declarations?