이유 : 각 객체의 헤더파일에서 계속 헤더 파일을 참조하게 되면 각 객체를 호출 할때마다 호출할 헤더코드가 가지고 있는 또 다른 헤더파일에 관련된 코드를 호출하게 되기때문에 컴파일 시간이 길어진다.
그래서 이러한 문제점을 해결하기 위해
Forward Declaration- 전방선언을 사용한다.
전방선언은
전방 선언(Forward Declaration)은 컴파일러에게 특정 클래스, 구조체, 함수 등의 존재를 미리 알리는 선언으로 이를 통해 해당 객체가 나중에 정의될 것임을 알려준다. 단순히 컴파일러에게 "이 이름이 존재한다"는 것을 알려주는 역할
헤더파일에서 전방선언을 한 뒤
정확하게 사용하는 것은 cpp파일에서 헤더파일을 가져온 뒤 사용하게 된다.
정리하자면
문제점
컴파일 시간 증가: 헤더 파일을 여러 번 포함하면 컴파일러가 포함된 모든 헤더 파일을 다시 처리해야 합니다. 이는 특히 대규모 프로젝트에서 컴파일 시간을 크게 증가시킬 수 있습니다.
순환 종속성: 클래스들이 서로를 참조할 때, 헤더 파일에서 직접 #include를 사용하면 순환 종속성 문제가 발생할 수 있습니다. 이는 컴파일 오류를 초래할 수 있습니다.
의존성 증가: 불필요한 헤더 파일을 포함하면 파일 간의 의존성이 증가하여 코드 수정 시 많은 파일을 다시 컴파일해야 할 수 있습니다. 이는 코드 유지 보수를 어렵게 만듭니다.
코드 복잡성: 헤더 파일에 너무 많은 #include가 있으면 코드가 복잡해지고, 어떤 헤더 파일이 실제로 필요한지 파악하기 어려워집니다.
장점
컴파일 시간 단축: 헤더 파일을 덜 포함함으로써 컴파일러가 처리해야 할 파일의 양을 줄여 컴파일 시간을 단축할 수 있다
순환 종속성 해결: 클래스들이 서로를 참조하는 경우, 전방 선언을 통해 순환 종속성 문제를 해결할 수 있다
의존성 관리: 파일 간의 의존성을 줄여, 코드 수정 시 재컴파일되는 범위를 최소화할 수 있다
코드 명확성: 헤더 파일을 깔끔하게 유지하고, 필요한 정의만 소스 파일에서 포함하여 코드의 명확성을 높일 수 있다.
반드시 포함해야 할 경우:
부모 클래스에서 상속받을 때
부모 클래스의 멤버 변수와 함수를 사용할 수 있도록 하기 위해 헤더 파일을 포함한다.
타입의 크기가 필요할 때
클래스의 새로운 인스턴스를 생성해야 하는 경우, 해당 타입의 크기를 알아야 하므로 헤더 파일을 포함한다.
멤버 변수/함수에 접근할 때
클래스의 멤버 변수나 함수를 사용할 때는 해당 클래스의 정의를 알아야 하므로 헤더 파일을 포함한다.
이러한 경우를 제외하고는, 전방 선언을 사용하여 헤더 파일의 불필요한 포함을 피할 수 있다. 이를 통해 컴파일 시간을 단축하고, 코드의 의존성을 줄일 수 있다.
이제 코드를 수정해보자 밑의 코드를 헤더파일에서 지워주자
#include "Components/CapsuleComponent.h"
Bird.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "Bird.generated.h"
class UCapsuleComponent; //전방선언
UCLASS()
class SLASH_API ABird : public APawn
{
GENERATED_BODY()
public:
ABird();
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(VisibleAnywhere)
UCapsuleComponent* Capsule;
};
이제 본격적으로 Pawn클래스에 매쉬를 붙여보자
매쉬에는 static과 skeletal이 있는데 static은 말 그대로 정적인 애니메이션이 불가능한 매쉬이고 skeletal은 뼈대로 구성된 매쉬로 애니메이션이 가능하다.
먼저 헤더파일에 변수를 추가해주자 이때 변수는 전방선언으로 선언해주자
class USkeletalMeshComponent;
private:
UPROPERTY(VisibleAnywhere)
UCapsuleComponent* Capsule;
UPROPERTY(VisibleAnywhere)
USkeletalMeshComponent* BirdMesh;
석유덩어리를 찾는데 BFS를 사용하는 것은 알았는데 이걸 어떻게 활용할지가 생각이 정말 안났다.
일단 전체를 순회하면서 덩어리를 찾는데 BFS를 사용 - 이 과정에서 덩어리로 인식할 수 있게 아이디를 부여
열을 기준으로 행을 순회하면서 아이디에 해당하는 그룹의 사이즈가 아직 시추되지 않았다면 시추하고 사이즈를 저장한다.
마지막으로 이 중에서 가장 큰 값을 결과값으로 둔다.
#include <vector>
#include <queue>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int DIRS[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
int n, m;
vector<vector<int>> land;
vector<vector<bool>> visited;
vector<vector<int>> component; //같은 그룹묶어주기
unordered_map<int, int> component_size; //그룹의 사이즈
//같은 그룹찾기 -> BFS로
int bfs(int x, int y, int comp_id) { //x,y,그룹아이디
queue<pair<int, int>> q;
q.push({ x, y });
visited[x][y] = true;
component[x][y] = comp_id;
int size = 0;
while (!q.empty()) {
auto [cx, cy] = q.front(); q.pop();
size++;
for (int d = 0; d < 4; d++) {
int nx = cx + DIRS[d][0];
int ny = cy + DIRS[d][1];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny] && land[nx][ny] == 1) {
q.push({ nx, ny });
visited[nx][ny] = true;
component[nx][ny] = comp_id;
}
}
}
return size;
}
int solution(vector<vector<int>> land_input) {
land = land_input;
n = land.size();
m = land[0].size();
visited.assign(n, vector<bool>(m, false));
component.assign(n, vector<int>(m, -1));
component_size.clear();
int comp_id = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 석유가 있고 방문하지 않은 위치에서 BFS 실행
if (land[i][j] == 1 && !visited[i][j]) {
int size = bfs(i, j, comp_id);
component_size[comp_id] = size;
comp_id++;
}
}
}
vector<int> oil_per_column(m, 0);
for (int j = 0; j < m; j++) {
unordered_map<int, bool> touched; // 현재 열에서 이미 포함된 덩어리를 추적하는 맵
for (int i = 0; i < n; i++) {
// 석유가 있고 현재 열에서 아직 포함되지 않은 덩어리라면
if (land[i][j] == 1 && !touched[component[i][j]]) {
oil_per_column[j] += component_size[component[i][j]]; // 덩어리 크기를 현재 열의 석유량에 추가
touched[component[i][j]] = true; // 컴포넌트 아이디에 해다아하는 값이 없을때 -> 현재 덩어리를 포함했다고 표시
}
}
}
return *max_element(oil_per_column.begin(), oil_per_column.end());
}
이제 액터에 스태틱매쉬를 붙여 게임화면에 보일 수 있도록하자. 블루프린트에서 스태틱매쉬를 추가한 뒤 Sphere을 추가해주자
이 것을 코드로 구현해 보겠다.
일단 코드 상에서 인스턴스를 건드리려면 아직 액터들이 모두 생성되지않은 생성자 단계가 아니라 BeginPlay이후에 이루어져야한다.
또한 이 컴포넌트의 하위 객체를 이용할 때는 템플릿 함수를 사용하여 지정해주어야한다.
이 함수는 포인터를 반환해준다.
그럼 이제 매쉬를 저장해줄 포인터 변수를 선언해주자
UStaticMeshComponent* ItemMesh;
그리고 생성자에서 매쉬를 붙여주자
AItem::AItem()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
ItemMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ItemMeshComponent"));
RootComponent = ItemMesh;
}
제일 먼집부터 순회하면서 배달 및 수거를 진행하고 이를 통해 해당 집에 대한 왕복횟수를 계산하면서 최종적인 이동 거리를 계산해주면 된다.
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
//결국은 그리디
long long solution(int cap, int n, vector<int> deliveries, vector<int> pickups) {
long long answer = 0;
int delivery_load = 0; // 현재 트럭에 실린 배달 상자 수
int pickup_load = 0; // 현재 트럭에 실린 수거 상자 수
// 가장 먼 집부터 순회
for (int i = n - 1; i >= 0; --i) {
// 해당 집에 배달하거나 수거할 상자가 있는 경우에만 작업
if (deliveries[i] > 0 || pickups[i] > 0) {
int rounds = 0; // 해당 집에 대한 왕복 횟수
// 배달 또는 수거할 상자가 트럭 용량보다 많은 경우 추가로 왕복해야 함
while (delivery_load < deliveries[i] || pickup_load < pickups[i]) {
rounds++;
delivery_load += cap; // 트럭에 최대 용량만큼 배달 상자를 추가로 실음
pickup_load += cap; // 트럭에 최대 용량만큼 수거 상자를 추가로 실음
}
// 해당 집에서 배달할 상자를 트럭에서 내림
delivery_load -= deliveries[i];
// 해당 집에서 수거할 빈 상자를 트럭에 실음
pickup_load -= pickups[i];
// 왕복 거리를 답에 추가 (집까지의 거리 * 2 * 왕복 횟수)
answer += (i + 1) * 2 * rounds;
}
}
return answer;
}
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#define DRAW_SPHERE(Location) if (GetWorld()) DrawDebugSphere(GetWorld(),Location,25.f,12,FColor::Red,true);