클린코딩
제가 평소에 코드를 짜면서 유의하면 좋을 것 같은 코딩 유의사항을 정리해보았습니다.1. 긴 매개변수 목록 (Long Parameter List)문제점: 너무 많은 매개변수가 있을 경우 함수의 가독성이 떨어지고, 관리가 어려워진다.나쁜 예시:void InitWeapon(FString Name, float Damage, float FireRate, int32 AmmoCount, float ReloadTime, USkeletalMesh* Mesh, USoundBase* Sound){ // 와, 많다 ...}좋은 예시: 구조체를 사용해 매개변수를 그룹화한다.struct FWeaponData{ FString Name; float Damage; float FireRate; int32 Am..
2025.03.27
C++
브루트포스 기법
1. 부르트포스브루트포스(Brute Force)란?가능한 모든 경우의 수를 전부 시도해보는 "무식한" 문제 해결 방법.최적화된 알고리즘을 떠올리기 어려울 때나 검증 과정에서 자주 활용됩니다.브루트포스의 시간 복잡도부분집합 탐색: 2^n순열 탐색: n!중첩 반복문을 통한 조합 탐색n이 작다면 충분히 시도할 만하지만, 지수적 증가로 인해 크기에 주의 필요.예: n=10  → 부분집합: 2^10=1024, 순열: 10! =3,628,800 2. 비트마스킹비트마스킹(Bitmasking)이란?이진수(0과 1)로 이루어진 비트를 활용하여 데이터를 표현하고 조작하는 기법보통 정수형 변수의 각 비트를 ON(1) 또는 OFF(0)로 설정하여 특정 상태를 관리할 때 사용.💡 왜 사용할까?배열을 사용하지 않고도 빠르게 여..
2025.03.11
C++
STL 기본 구조
STL이란?STL(Standard Template Library)은 C++의 내장 템플릿 라이브러리로, 컨테이너, 반복자(Iterator), 알고리즘 세 가지 요소로 구성됨.컨테이너(Container): 데이터를 저장·관리하는 자료구조반복자(Iterator): 컨테이너 내 데이터를 순회하는 포인터 역할알고리즘(Algorithm): 정렬, 탐색, 삽입, 삭제 등 다양한 기능 제공STL을 잘 활용하면 코딩 테스트에서 시간을 크게 절약할 수 있음. 직접 자료구조를 구현하는 것보다 훨씬 효율적.STL의 3가지 요소이 세 가지 요소는 서로 연동되어 강력한 기능을 제공함.컨테이너 → 데이터를 담는 창고반복자 → 창고 정리하는 도우미알고리즘 → 문제 해결을 돕는 용병1. 컨테이너 (Container)데이터를 저장하는..
2025.02.18
C++

클린코딩

언리얼 거토
|2025. 3. 27. 21:43

제가 평소에 코드를 짜면서 유의하면 좋을 것 같은 코딩 유의사항을 정리해보았습니다.

1. 긴 매개변수 목록 (Long Parameter List)

문제점: 너무 많은 매개변수가 있을 경우 함수의 가독성이 떨어지고, 관리가 어려워진다.

나쁜 예시:

void InitWeapon(FString Name, float Damage, float FireRate, int32 AmmoCount, float ReloadTime, USkeletalMesh* Mesh, USoundBase* Sound)
{
    // 와, 많다 ...
}

좋은 예시: 구조체를 사용해 매개변수를 그룹화한다.

struct FWeaponData
{
    FString Name;
    float Damage;
    float FireRate;
    int32 AmmoCount;
};

struct FWeaponAssets
{
    USkeletalMesh* Mesh;
    USoundBase* Sound;
};

void InitWeapon(const FWeaponData& InData, const FWeaponAssets& InAssets)
{
    // 훨씬 깔끔!
}

2. 전역 데이터 (Global Data)

문제점: 전역 데이터를 사용하면 예상치 못한 값 변경이 발생할 수 있으며, 디버깅이 어려워진다.

나쁜 예시:

UGameManager* GGameManager; // 전역!

void IncreaseScore()
{
    GGameManager->Score += 10;
}

좋은 예시: Subsystem을 활용해 데이터의 접근을 제한한다.

UCLASS()
class UScoreSystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

private:
    int32 Score;

public:
    void AddScore(int32 Amount)
    {
        Score += Amount;
    }

    int32 GetScore() const { return Score; }
};

3. 샷건 수술 (Shotgun Surgery)

문제점: 관련된 기능이 여러 클래스에 분산되어 있어 수정 시 여러 곳을 변경해야 한다.

나쁜 예시:

class APlayerCharacter : public ACharacter
{
public:
    void TakeDamage(float Amount);
};

class AWeapon : public AActor
{
public:
    float CalculateDamage();
};

class AMyGameMode : public AGameModeBase
{
public:
    void UpdateDamageLeaderboard();
};

좋은 예시: 데미지 로직을 하나의 시스템으로 모은다.

class UDamageSystem : public UObject
{
public:
    float CalculateDamage(AWeapon* Weapon, ACharacter* Target);
    void ApplyDamage(AWeapon* Weapon, ACharacter* Target);
    void UpdateDamageLeaderboard(ACharacter* Damager, ACharacter* Target, float Amount);
};

4. 기능 편애 (Feature Envy)

문제점: 특정 클래스가 다른 클래스의 데이터를 과도하게 사용하면 유지보수가 어려워진다.

나쁜 예시:

class UDamageCalculator : public UObject
{
public:
    float CalculateDamageReduction(AMyCharacter* Character, float Damage);
};

좋은 예시: 필요한 연산을 해당 클래스 내부에서 처리하도록 한다.

class AMyCharacter : public ACharacter
{
public:
    float CalculateDamageReduction(float Damage) const;
};

5. 데이터 뭉치 (Data Clumps)

문제점: 비슷한 데이터가 여러 곳에서 반복되면 유지보수가 어려워진다.

나쁜 예시:

void FireWeapon(float Damage, float Range, float Accuracy);
void ShowWeaponStats(float Damage, float Range, float Accuracy);
void UpgradeWeapon(float& Damage, float& Range, float& Accuracy);

좋은 예시: 공통 데이터를 구조체로 묶는다.

USTRUCT(BlueprintType)
struct FWeaponStats
{
    GENERATED_BODY()

    float Damage;
    float Range;
    float Accuracy;
};

6. 기본형 집착 (Primitive Obsession)

문제점: 단순한 데이터 타입을 과도하게 사용하면 데이터 무결성이 깨질 위험이 있다.

나쁜 예시:

float Health;
float MaxHealth;

좋은 예시: 전용 클래스를 만들어 로직을 포함시킨다.

class FHealth
{
public:
    void ApplyDamage(float Amount);
private:
    float Current;
    float Max;
};

7. 게으른 요소 (Lazy Element)

문제점: 불필요한 중간 함수는 코드 복잡성을 증가시킨다.

나쁜 예시:

void FireProjectile(AProjectile* Projectile, FVector Direction, float Speed);

좋은 예시: 직접 핵심 로직을 실행한다.

class AProjectile : public AActor
{
public:
    void Launch(const FVector& Dir, float Speed);
};

8. 추측성 일반화 (Speculative Generality)

문제점: 필요하지 않은 확장성을 고려하면 코드가 불필요하게 복잡해진다.

나쁜 예시:

class AWeaponBase : public AActor
{
    virtual void SpecialAttack() = 0;
};

좋은 예시: 필요한 기능만 구현하고, 필요 시 확장한다.

class AWeapon : public AActor
{
public:
    void Attack();
};

9. 메시지 체인 (Message Chains)

문제점: 객체 참조가 깊어질수록 유지보수가 어렵다.

나쁜 예시:

Player->GetInventory()->GetEquippedWeapon()->GetSound();

좋은 예시: 중간 객체가 직접 필요한 데이터를 제공하도록 한다.

class APlayer
{
public:
    USoundBase* GetEquippedWeaponSound();
};

이와 같은 클린 코딩 원칙을 적용하면 유지보수성과 가독성이 높은 코드를 작성할 수 있다!

'C++' 카테고리의 다른 글

브루트포스 기법  (0) 2025.03.11
STL 기본 구조  (0) 2025.02.18

브루트포스 기법

언리얼 거토
|2025. 3. 11. 15:41

1. 부르트포스

브루트포스(Brute Force)란?

  • 가능한 모든 경우의 수를 전부 시도해보는 "무식한" 문제 해결 방법.
  • 최적화된 알고리즘을 떠올리기 어려울 때나 검증 과정에서 자주 활용됩니다.

브루트포스의 시간 복잡도

  • 부분집합 탐색: 2^n
  • 순열 탐색: n!
  • 중첩 반복문을 통한 조합 탐색

n이 작다면 충분히 시도할 만하지만, 지수적 증가로 인해 크기에 주의 필요.
예: n=10  → 부분집합: 2^10=1024, 순열: 10! =3,628,800

 

2. 비트마스킹

비트마스킹(Bitmasking)이란?

  • 이진수(0과 1)로 이루어진 비트를 활용하여 데이터를 표현하고 조작하는 기법
  • 보통 정수형 변수의 각 비트를 ON(1) 또는 OFF(0)로 설정하여 특정 상태를 관리할 때 사용.

💡 왜 사용할까?

  • 배열을 사용하지 않고도 빠르게 여러 상태를 저장할 수 있음
  • 비트 연산을 활용하면 탐색, 계산 속도가 빠름
  • 메모리를 절약할 수 있음

비트마스킹의 핵심 개념

  1. 각 비트는 특정 의미를 가질 수 있음
    예를 들어, 101이라는 이진수를 보자.
    • 첫 번째(맨 오른쪽) 비트: 1 → 켜짐 (ON)
    • 두 번째 비트: 0 → 꺼짐 (OFF)
    • 세 번째 비트: 1 → 켜짐 (ON)
  2. 비트 연산을 이용한 조작 방법
    연산 설명 예시
    AND & 특정 비트가 켜져 있는지 확인 101 & 001 = 001 (세 번째 비트가 켜져 있음)
    OR | 특정 비트를 켜기 100 | 010 = 110
    XOR ^ 특정 비트 토글(0 → 1, 1 → 0) 110 ^ 010 = 100
    SHIFT <<, >> 비트 위치 이동 001 << 2 = 100

 

3. 브루트포스 구현 방법

3-1. 중첩 반복문

  • 직관적이지만 범위가 크면 비효율적
  • 예제: 1~3까지의 숫자 3개를 중복 선택하여 나열
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        for (int k = 1; k <= 3; k++) {
            cout << i << " " << j << " " << k << "\n";
        }
    }
}

 

3-2. 비트마스킹을 활용한 부분집합 탐색

  • 집합 {1,2,3}의 경우 총 2^3=개의 부분집합 존재
  • 이진수 표현을 활용하여 부분집합을 탐색 가능
0 -> 000 -> 공집합
1 -> 001 -> {3}
2 -> 010 -> {2}
3 -> 011 -> {2,3}
4 -> 100 -> {1}
5 -> 101 -> {1,3}
6 -> 110 -> {1,2}
7 -> 111 -> {1,2,3}

 

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> arr = {1, 2, 3};
    int n = arr.size();

 	// 0부터 2^n - 1까지 부분집합의 모든 경우 탐색
    for (int i = 0; i < (1 << n); i++) {
        cout << "{ ";
        for (int j = 0; j < n; j++) {
            if (i & (1 << j)) { 
            // i번째 원소가 포함되었는지 확인
            // bit는 정수지만, 2진수로 보면 비트의 집합
            // (1 << j)의 역할: 특정 비트만 체크
            // i & (1 << j) != 0 이면 True 이기 때문에 해당 자리 원소 출력
                cout << arr[j] << " "; 
            }
        }
        cout << "}\n";
    }
}

 

정수 n(1 ≤ n ≤ 100)이 주어질 때, 다음 식을 만족하는 (a, b, c)의 개수를 구하는 문제를 불필요한 중복 제거를 하여 풀어보세요. (제출 + 발표)

a + b^2 + c^3 = n
#include <iostream>
using namespace std;

int main() {
    int n = 10000;  // 입력값
    int count = 0;

    for (int b = 1; b * b <= n; b++) {  // b^2가 n을 초과하지 않는 범위까지만 탐색
        for (int c = 1; b * b + c * c * c <= n; c++) {  // b^2 + c^3이 n을 초과하지 않는 범위까지만 탐색
            int a = n - (b * b + c * c * c);
            if (a >= 1 && a <= 100) {  // a가 자연수(1~100) 범위 내에 있어야 유효한 해
                count++;
            }
        }
    }

    cout << count;
}

'C++' 카테고리의 다른 글

클린코딩  (0) 2025.03.27
STL 기본 구조  (0) 2025.02.18

STL 기본 구조

언리얼 거토
|2025. 2. 18. 22:12

STL이란?

STL(Standard Template Library)은 C++의 내장 템플릿 라이브러리로, 컨테이너, 반복자(Iterator), 알고리즘 세 가지 요소로 구성됨.

  • 컨테이너(Container): 데이터를 저장·관리하는 자료구조
  • 반복자(Iterator): 컨테이너 내 데이터를 순회하는 포인터 역할
  • 알고리즘(Algorithm): 정렬, 탐색, 삽입, 삭제 등 다양한 기능 제공

STL을 잘 활용하면 코딩 테스트에서 시간을 크게 절약할 수 있음. 직접 자료구조를 구현하는 것보다 훨씬 효율적.


STL의 3가지 요소

이 세 가지 요소는 서로 연동되어 강력한 기능을 제공함.

  • 컨테이너 → 데이터를 담는 창고
  • 반복자 → 창고 정리하는 도우미
  • 알고리즘 → 문제 해결을 돕는 용병

1. 컨테이너 (Container)

데이터를 저장하는 구조로, vector, list, deque 등이 있음.

vector (동적 배열)

  • 연속된 메모리 블록을 사용해 랜덤 접근이 빠름
  • push_back() / pop_back() 으로 끝부분 추가·삭제 효율적
  • 중간 삽입·삭제 시 데이터 이동이 발생해 비효율적

📌 주요 함수

vector<int> v;
v.push_back(10); // 맨 뒤 추가
v.pop_back();    // 맨 뒤 삭제
v.insert(v.begin() + 1, 20); // 중간 삽입
v.erase(v.begin() + 2); // 중간 삭제
 

list (이중 연결 리스트)

  • 중간 삽입·삭제 빠름 (포인터만 변경하면 됨)
  • 메모리가 연속적이지 않아 랜덤 접근 느림
  • 순차적 추가·삭제에 적합

📌 주요 함수

list<int> lst;
lst.push_back(30);
lst.push_front(20);
lst.insert(next(lst.begin()), 25); // 중간 삽입
lst.erase(lst.begin()); // 첫 번째 원소 삭제

deque (양방향 동적 배열)

  • 양쪽 끝 삽입·삭제 빠름
  • 랜덤 접근 가능
  • 중간 삽입·삭제는 비효율적

📌 주요 함수

deque<int> dq;
dq.push_back(10);
dq.push_front(20);
dq.pop_back();
dq.pop_front();

 

Map (Key - Value)

  • (key, value) 쌍을 저장하는 자료구조
  • 키를 통해 값을 빠르게 탐색 가능
  • 레드-블랙 트리 기반검색, 삽입, 삭제 O(log N)
  • 키는 유일하며 자동 정렬됨

📌 주요 함수

map<int, string> mp;
mp.insert({1, "Apple"}); //키-값 쌍 삽입
mp[2] = "Banana";  // operator[key]키로 값에 접근 또는 삽입
if (mp.find(1) != mp.end()) // 키로 요소 검색 (iterator 반환)
mp.erase(2); // 키로 요소 제거
int size = mp.size(); // 요소 개수 반환

 

Set

  • 유일한 키만 저장 (중복 불가)
  • 레드-블랙 트리 기반O(log N)
  • 자동 정렬됨

📌 주요 함수

set<int> s;
s.insert(10);
s.insert(5);
s.insert(20);
if (s.find(10) != s.end()) 
    cout << "10 exists in set" << endl;
s.erase(5);
cout << "Set size: " << s.size() << endl;

    // lower_bound(value), upper_bound(value)
    cout << "Lower bound of 10: " << *s.lower_bound(10) << endl;
    cout << "Upper bound of 10: " << *s.upper_bound(10) << endl;
}

2. 반복자 (Iterator)

컨테이너 원소를 순회하는 포인터 역할.

  • begin(), end() → 순차 탐색
  • rbegin(), rend() → 역순 탐색
  • ++it, --it → 이동
  • *it → 값 참조

📌 반복자 사용 예시

vector<int> v = {3, 1, 4, 1, 5, 9};
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    cout << *it << " ";
}

3. 알고리즘 (Algorithm)

STL이 제공하는 다양한 기능 활용 가능.

 

📌 정렬 예시

vector<int> v = {3, 1, 4, 1, 5, 9};
sort(v.begin(), v.end()); // 정렬

 

 

📌 이진 탐색 예시

binary_search(v.begin(), v.end(), 4); // 4가 존재하면 true

어떤 컨테이너를 써야 할까?

중간 삽입/삭제 많음 → list
랜덤 접근 필요 → vector
양쪽 끝 추가/삭제 많음 → deque

 

'C++' 카테고리의 다른 글

클린코딩  (0) 2025.03.27
브루트포스 기법  (0) 2025.03.11