공통점

  • 원소의 중복 허용 x
    • 원소의 중복을 원한다면 map라이브러리내 multimap을 활용
  • key : value 구

map

  • 내부 원소가 저장되는 자료구조가 '균형 이진 검색 트리(Balanced binary search tree)'로 구현됨
    • 내부 원소가 저장될 때 알아서 정렬되어 저장 (default는 오름차순)
      • key값이 정렬되므로 key의 순서가 중요한 경우 적합
  • 삽입, 삭제, 검색의 시간 복잡도 O(log n) 보장
  • 트리 구조를 유지하기 위해 노드포인터 등 추가적인 메모리 사용
    • 해시테이블을 유지해야하는 unordered_map에 비해 비교적 메모리 사용이 효율적
  • 정렬 기준 커스터마이징 가능
    • map<{key 타입}, {value 타입}, greater<{key 타입}>> : 내림차순
#include <map>
#include <iostream>
#include <string>
using namespace std;

map<int, string> m;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		string str = "원소";
		str += char(i + '0');
		m[i] = str;
	}

	// 검색
	map<int, string>::iterator itr1 = m.find(4);
	if (itr1 != m.end()) {
		// 찾음
		cout << itr1->first << "\n"; // 4
		cout << itr1->second << "\n"; // 원소4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
	m[-1] = "원소-1";
	for (map<int, string>::iterator itr = m.begin(); itr != m.end(); ++itr) {
		cout << itr->second << " "; // -1 0 1 2 3 4 5 6 7 8 9
	}

	// 삭제
	m.erase(5);

	map<int,string>::iterator itr2 = m.find(5);
	if (itr2 != m.end()) {
		// 찾음
		cout << itr2->second << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

unordered_map

  • 내부 원소가 저장되는 자료구조가 '해시 테이블(Hash table)'로 구현됨
    • 내부 원소가 저장될 때 순서가 보장되지 않음
  • 삽입, 삭제, 검색의 평균 시간 복잡도는 O(1)이지만,
    최악의 경우 해시 충돌 시 O(n)
  • 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수에 메모리를 추가 사용
    • 기본적으로 더 많은 메모리를 사용할 가능성이 높다.
  • 사용자 정의 해시 함수와 동일성조건(equal function)을 지정 가능
    • #include <iostream>
      #include <unordered_map>
      
      // 사용자 정의 타입
      struct Point {
          int x, y;
      
          // 동일성 비교를 위한 == 연산자 정의
          bool operator==(const Point& other) const {
              return x == other.x && y == other.y;
          }
      };
      
      // 사용자 정의 해시 함수
      struct PointHash {
          size_t operator()(const Point& point) const {
              return std::hash<int>()(point.x) ^ std::hash<int>()(point.y << 1); // XOR와 시프트 연산
          }
      };
      
      // 사용자 정의 동일성 비교 함수
      struct PointEqual {
          bool operator()(const Point& a, const Point& b) const {
              return a.x == b.x && a.y == b.y;
          }
      };
      
      int main() {
          // 사용자 정의 타입과 해시/비교 함수 지정
          std::unordered_map<Point, std::string, PointHash, PointEqual> pointsMap;
      
          // 데이터 삽입
          pointsMap[{1, 2}] = "Point A";
          pointsMap[{3, 4}] = "Point B";
      
          // 데이터 출력
          for (const auto& pair : pointsMap) {
              std::cout << "(" << pair.first.x << ", " << pair.first.y << "): " << pair.second << '\n';
          }
      
          return 0;
      }
#include <unordered_map>
#include <iostream>
#include <string>
using namespace std;

unordered_map<int, string> um;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		string str = "원소";
		str += char(i + '0');
		um[i] = str;
	}

	// 검색
	unordered_map<int, string>::iterator itr1 = um.find(4);
	if (itr1 != um.end()) {
		// 찾음
		cout << itr1->first << "\n"; // 4
		cout << itr1->second << "\n"; // 원소4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
	um[-1] = "원소-1";
	for (unordered_map<int, string>::iterator itr = um.begin(); itr != um.end(); ++itr) {
		cout << itr->second << " "; // 0 1 2 3 4 5 6 7 8 9 -1
	}

	// 삭제
	um.erase(5);

	unordered_map<int,string>::iterator itr2 = um.find(5);
	if (itr2 != um.end()) {
		// 찾음
		cout << itr2->second << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

  map unordered_map
자료구조 균형 이진 검색 트리 해시 테이블
삽입, 검색, 삭제의 시간 복잡도 O(log n) O(1) (최악 O(n))
메모리 사용 트리 구조를 유지하기 위해 노드포인터 등 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수

 

반응형

공통점

  • 원소의 중복 허용 x

set

  • 내부 원소가 저장되는 자료구조가 '균형 이진 검색 트리(Balanced binary search tree)'로 구현됨
    • 내부 원소가 저장될 때 알아서 정렬되어 저장 (default는 오름차순)
  • 삽입, 삭제, 검색의 시간 복잡도 O(log n) 보장
  • 트리 구조를 유지하기 위해 노드포인터 등 추가적인 메모리 사용
  • 정렬 기준 커스터마이징 가능
    • set<int, greater<int>> : 내림차순
#include <set>
#include <iostream>
using namespace std;

set<int> s;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		s.insert(i);
	}

	// 검색
	set<int>::iterator itr1 = s.find(4);
	if (itr1 != s.end()) {
		// 찾음
		cout << *itr1 << "\n"; // 4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
    	s.insert(-1);
	for (set<int>::iterator itr = s.begin(); itr != s.end(); ++itr) {
		cout << *itr << " "; // -1 0 1 2 3 4 5 6 7 8 9
	}

	// 삭제
	s.erase(5);

	set<int>::iterator itr2 = s.find(5);
	if (itr2 != s.end()) {
		// 찾음
		cout << *itr2 << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

unordered_set

  • 내부 원소가 저장되는 자료구조가 '해시 테이블(Hash table)'로 구현됨
    • 내부 원소가 저장될 때 순서가 보장되지 않음
  • 삽입, 삭제, 검색의 평균 시간 복잡도는 O(1)이지만,
    최악의 경우 해시 충돌 시 O(n)
  • 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수에 메모리를 추가 사용
    • 기본적으로 더 많은 메모리를 사용할 가능성이 높다.
  • 사용자 정의 해시 함수와 동일성조건(equal function)을 지정 가능
    • #include <iostream>
      #include <unordered_set>
      #include <string>
      
      // 사용자 정의 해시 함수
      struct CustomHash {
          std::size_t operator()(const std::string& key) const {
              std::size_t hash = 0;
              for (char ch : key) {
                  hash += ch; // 간단한 예: 문자열의 각 문자 값을 더함
              }
              return hash;
          }
      };
      
      // 사용자 정의 동등 비교 함수
      struct CustomEqual {
          bool operator()(const std::string& a, const std::string& b) const {
              return a == b; // 단순히 문자열이 같은지 확인
          }
      };
      
      int main() {
          std::unordered_set<std::string, CustomHash, CustomEqual> customSet;
          customSet.insert("hello");
          customSet.insert("world");
      
          for (const auto& item : customSet) {
              std::cout << item << std::endl;
          }
      
          return 0;
      }
#include <unordered_set>
#include <iostream>
using namespace std;

unordered_set<int> us;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		us.insert(i);
	}

	// 검색
	unordered_set<int>::iterator itr1 = us.find(4);
	if (itr1 != us.end()) {
		// 찾음
		cout << *itr1 << "\n"; // 4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
	us.insert(-1);
	for (unordered_set<int>::iterator itr = us.begin(); itr != us.end(); ++itr) {
		cout << *itr << " "; // 0 1 2 3 4 5 6 7 8 9 -1
	}

	// 삭제
	us.erase(5);

	unordered_set<int>::iterator itr2 = us.find(5);
	if (itr2 != us.end()) {
		// 찾음
		cout << *itr2 << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

  set unordered_set
자료구조 균형 이진 검색 트리 해시 테이블
삽입, 검색, 삭제의 시간 복잡도 O(log n) O(1) (최악 O(n))
메모리 사용 트리 구조를 유지하기 위해 노드포인터 등 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수

 

반응형

Resource Acquisition Is Initialization

: C++의 중요한 프로그래밍 원칙으로,
리소스의 할당과 해제를 객체의 생성자와 소멸자를 통해 관리하는 방식
명시적인 리소스 해제 코드없이 자동 소멸되어 관리 됨.

  • 리소스의 할당 -> 객체의 생성자
  • 리소스의 해제 -> 객체의 소멸자

RAII 주요 개념

리소스(예: 메모리, 파일 핸들, 소켓 등)
  1. 리소스 획득은 객체의 생성에 묶인다.
    • 리소스를 객체의 생성자에서 획득
    • 리소스를 사용할 수 있는 상태를 객체의 생명주기에 따라 결정
  2. 리소스 해제는 객체의 소멸에 묶인다.
    • 객체의 소멸자가 호출될 때, 생성자에서 할당된 리소스를 자동으로 해제
    • 명시적으로 리소스를 해제할 필요가 줄어든다.

장점

  • 예외 안전성
    : 예외가 발생하면 소멸자가 호출되어 리소스가 확실히 해제
  • 메모리 누수 방지
    : 소멸자에서 리소스를 해제하므로 메모리 누수를 방지
  • 코드 간결화
    : 리소스 관리를 명시적으로 하지 않아도 됨
  • 가독성 향상
    : 리소스 관리 코드가 간결하고 명확해짐

활용 사례

1. 스마트 포인터

스마트 포인터(std::unique_ptr, std::shared_ptr)는 RAII의 대표적인 구현입니다.
#include <memory>
#include <iostream>

void example() {
    std::unique_ptr<int> ptr(new int(42)); // 리소스 할당
    std::cout << *ptr << std::endl;        // 리소스 사용
} // ptr이 스코프를 벗어나면서 자동으로 delete 호출

2. 파일 핸들 관리

#include <fstream>
#include <iostream>

void example() {
    std::ifstream file("example.txt"); // 파일 열기 (리소스 할당)
    if (!file.is_open()) {
        std::cerr << "파일을 열 수 없습니다.\n";
        return;
    }
    // 파일 작업
} // file이 스코프를 벗어나면서 자동으로 close 호출

3. 뮤텍스 잠금

C++ 표준 라이브러리의 "std::lock_guard"는 RAII로 뮤텍스 잠금을 관리합니다.
#include <mutex>
#include <iostream>

std::mutex mtx;

void example() {
    std::lock_guard<std::mutex> lock(mtx); // 뮤텍스 잠금
    std::cout << "Critical section\n";     // 보호된 코드
} // lock이 스코프를 벗어나면서 자동으로 unlock 호출

 

출처)

 

개체 수명 및 리소스 관리(RAII)

리소스 누수 방지를 위해 최신 C++에서 RAII 원칙을 따릅니다.

learn.microsoft.com

 

반응형
정적(Static)은 '고정된'이라는 의미

정적 멤버

  • 클래스에 고정된 멤버
  • 객체를 생성하지 않고 사용할 수 있는 필드메서드
    • 필드 : 정적 필드
    • 메서드 : 정적 메서드
  • 객체가 없어도 클래스 별 메모리에 할당 됨.

정적 멤버 선언

  • 정적 필드와 정적 메서드를 선언하려면, 필드와 메서드 선언 시 Static키워드를 붙이면 된다.
    • public class 클래스 {
      	// 정적 필드
          static 타입 필드 [= 초기값];
          
          // 정적 메서드
          static 리턴타입 메서드 ( 매개변수 선언, ... ) {
          	...
          }
      }
  • 클래스 로더가 클래스(바이트코드)를 로딩해서 메서드 메모리 영역에 적재할 때, 클래스 별로 관리 된다.
    • 정적 멤버는 클래스에 고정된 멤버이기 때문.
    • 인스턴스 멤버는 객체가 생성될때마다 새로운 메모리공간(주로 Heap)에 객체별로 관리된다.
      • 각 인스턴스는 고유한 값을 가지게 된다.
    • 어떤 객체라도 "공통된" 또는 "변하지 않는" 또는 "객체마다 가지고 있을 필요가 없는" 공용 데이터는 정적 필드로 선언하는 것이 좋다.
      • ex) Circle 클래스에서 원의 넓이와 둘레를 구하는 메서드를 제공 중이다.
        • 이 때, 반지름(radius)필드 값은 원의 객체마다 다르지만 파이(π)는 모든 원 객체가 동일하다.
          • public class Circle {
            	int radius;
            	static double pi = 3.14159;
            }
        • 이 때, 원의 넓이와 둘레를 구하는 방법(메서드)는 모든 원 객체가 동일하다.
          • public class Circle {
                int radius;
                static double pi = 3.14159;
            
                static double getArea(int radius) {
                    return pi * radius * radius;
                }
            
                static double getRound(int radius) {
                    return 2 * pi * radius;
                }
            }

정적 멤버 사용

클래스.필드;
클래스.메서드( 파라미터 값, ...);

주의 사항

객체가 없어도 사용가능하기 때문에, 정적 메서드 내부에 인스턴스 필드나 인스턴스 메서드를 사용할 수 없다.

 

반응형

'Programming Language > JAVA' 카테고리의 다른 글

[JAVA] for문 vs .stream() 비교  (4) 2024.11.13
흑백배달기사 프로젝트 진행 중 PR을 통해 팀원과 코드리뷰를 남기는 상황에서 궁금증이 생겼습니다.
for문과 .stream()을 혼용해서 사용하던 중 "어떤 상황"에 "어떤 코드"를 작성하는게 적절한지 알아보는 계기가 되었습니다.


Stream API

Stream API의 도입으로 반복문의 사용 방식에 큰 변화가 생겼다. Stream은 컬렉션 데이터를 필터링, 매핑, 집계 등의 작업으로 가공하는 선언형 스타일을 지원한다. "함수형 프로그래밍" 개념을 바탕으로 간결하고 가독성 높은 코드 작성이 가능해졌다.


for문

  • 직접적인 반복 제어
    • continue, break와 같은 반복문 제어를 자유롭게 사용할 수 있다.
      특정 조건에 따른 반복을 더 쉽게 제어하는 상황에서 용이
  • 단순한 반복 작업
    • 데이터가 적거나 연산이 단순할 경우 성능상 유리
    • 오버헤드로 인한 손해가 병렬처리로 얻는 이점보다 크기에 데이터가 적을 때 적합
  • 예외처리
    • try-catch 블록을 반복문 내 간편히 적용할 수 있어
      예외 상황에 유연한 대처가 필요할 때 적합

.stream()

  • 병렬처리
    • .parallelStream()을 활용해 멀티코어 CPU에서 병렬처리를 쉽게 구현할 수 있어
      대용량 데이터에 대한 처리 성능을 높일 수 있기다.
  • 코드 간결성
    • filter(), map(), collect() 등의 메서드 체이닝하여 코드가 더 직관적이고 가독성이 높아진다.
  • 함수형 프로그래밍 스타일
    • 데이터 가공과 처리 방식을 더 함수형 스타일로 구성할 수 있어, 복잡한 작업을 단순화할 수 있다.

 

정리

  • 데이터가 크고 복잡한 변화이 필요 => steam()
    • 코드의 간결함과 성능을 얻기 위해
  • 직접적인 흐름제어가 필요한 경우 => for문

 

반응형

'Programming Language > JAVA' 카테고리의 다른 글

[JAVA] 정적 멤버와 Static  (0) 2024.11.22

[들어가기 전]

#define R_MAX 20 + 1

~~~

int wall[R_MAX * 2][R_MAX * 2]

위 코드에서 R_MAX * 2는 어떤 정수일까요?

1. 42

2. 22

 

정답을 아시는 분은 뒤로 가셔도 좋습니다! ㅎㅎ

 

정답은 2번 입니다!

왜 이런 결과가 나왔는지는 아래쪽에 있습니다.

 

개인적으로 Problem Solving을 하면서

상수를 저장할 때, 어떤 상수인지 목적을 나타내는 용도나

반복문을 사용하면서 for(int i=s; i<e; ++i)를 매번 작성하기 번거로워 FOR(i,s,e)의 형태로 변환해

사용하곤 했습니다.

개념을 모르고 사용하다보니 Problem Solving중 디버깅을 하면서

이상한 삽질을 해 원인이 무엇인지 알아보게 되어

이 글을 작성하게 되었습니다 😂😂

 

[ #define은 무엇인가? ]

MS의 공식문서에 따르면 #define은 지시문이라 표현하고 있고,

'''토큰 문자열과 식별자 또는 매개 변수가 있는 식별자의 연결인 매크로를 만듭니다. 매크로가 정의된 후 컴파일러는 소스 파일에서 발생하는 각 식별자를 토큰 문자열로 대체할 수 있습니다.'''

라고 합니다.

출처) https://learn.microsoft.com/ko-kr/cpp/preprocessor/hash-define-directive-c-cpp?view=msvc-170

 

#define 지시문(C/C++)

자세한 정보: #define 지시문(C/C++)

learn.microsoft.com

 

하지만 공식문서로 곧바로 이해하기 어려워

타 블로그의 글들을 참고해

이해해보고자했습니다.

 

한 문장으로 표현해보면

매크로를 정의할 때 앞에 써주는 키워드 입니다.

 

# : 전처리 지시자

define : 정의

#define : 컴파일 이전에 정의

 

[사용 방법]

#define "매크로 상수명" "매크로 확장 문자열"
#define "매크로함수명(전달할 인자, ...) "매크로 확장 문자열"

#define DUMMY 213456789
#define FOR(i,s,e) for(int i=s; i<e; ++i)

위와 같이 사용할 수 있습니다.

참고) https://blankspace-dev.tistory.com/353

 

[주의 사항]

이 글을 쓰게 된 원인이 바로 아래와 같은 주의 사항 때문이었습니다.

바로

연산자 우선순위를 명확히 표시해야한다.

입니다.

 

실사례였던 예시로 보여드리겠습니다.

#define R_MAX 20 + 1

~~~

int wall[R_MAX * 2][R_MAX * 2]

저의 목적은 wall배열의 크기를 42 * 42크기로 초기화하고자 하였습니다.

그러나 이 경우 아쉽게도 배열의 크기는 22 * 22입니다.

 

그 이유는 #define은 단순 매크로이지 변수가 아니기 때문입니다.

따라서,

R_MAX * 2 = (20 + 1) * 2 = 21 * 2

42가 아닌,

R_MAX * 2 = 20 + 1 * 2

이므로

22의 값이 나오게 됩니다.

 

그렇기에 42로 나오게 하기 위해선

#define R_MAX (20 + 1)

의 형태로 작성해주어야 합니다.

 

여러분은 저와 같은 삽질 하지 않기를....😂😂😂

(코드내 다른 곳에서 이틀 동안 고민했다는 건 비밀입니다. ㅎㅎ)

 

추가로,

1. 컴파일 후 기호 테이블에 들어가지 않기 때문에 디버깅의 어려움

2. 타입 안전성이 떨어짐

등의 이유가 있다고 합니다.

참고) https://d-yong.tistory.com/13

 

덧붙여 상수의 경우에는 const를 사용하는편이 더 좋다고 합니다.

이 이유도 추후에 찾아봐야겠습니다.

반응형

최근 알고리즘 학습을 하다가 궁금한 점이 생겼다.

(자료형)* (변수명) vs (자료형) *(변수명)

와 같은 *의 위치에 따라 차이가 있는 것일까?

 

정답은 없다.

단지, 개발자의 코딩 스타일의 차이일뿐이라고 한다.

 

두 방법 모두

포인터 변수는 메모리 주소를 저장하고

이 주소에 있는 데이터에 접근을 할 수 있다.

 

컴파일러는 두 가지 모두를 정확하게 이해하고 처리한다.

따라서, 어떤 표기법을 사용하더라도 포인터 변수의 기능은 동일하게 동작한다.

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

틀린 부분이 있다면 조언 주시면 감사하겠습니다.

반응형

+ Recent posts