1. 스프라이트 셋에서 스프라이트 추출

스프라이트 셋을 불러온 뒤, 불러와진 텍스쳐파일에서 우클릭 -> 스프라이트 추출

 

추출 옵션은 적당히 선택하면 되나, 대체로 Grid를 사용하게 될것임..

정확한 너비를 모르겠다면, 셀 수 X,Y를 입력한 뒤 셀 너비 및 높이 옆의 돌아가기 버튼을 누르면 알아서 지정해준다.

https://youtu.be/W4oZq4tn57w?si=SxHKHf56uecrfZk1

 

어쩌다 찾은 자료.

블리자드 오버워치팀에서 17년도에 발표한 자료인데,

네트워크 동기화, 하이라이트, 킬캠 등의 자료를 담고 있다.

 

 

주말중에 여러번 돌려보면서 정리해볼 예정

최근에 다들 이름만 들어도 알 회사에 면접을 볼 일이 있었다.

그 전, 입사 테스트로 단방향 링크드 리스트 뒤집기라는 과제를 받았었는데, 손으로 짜려다가 머리가 고장나버리는 바람에 제대로 완성을 못하고, 머릿속으로만 복기를 했었다가 면접때 해당 문제 다시풀기 라는 초유의 사태를 만나 그대로 면접에서 대형 사고를 치고 말았다..

 

물론 떨어졌고.

 

너무 아쉽긴 하지만 실수해서 망한건 이미 망한거고...

이왕 이렇게 된거, 복기도 좀 하고 다시는 같은 실수를 안하도록 코드 정리를 해서 올려보고자 한다.

 


 

우선, 단방향 리스트는 대충 다음과 같은 느낌으로 구현된다.

 

struct List
{
	List* next;
	int data;

	List(int newData)
	{
		data = newData;
		next = nullptr;
	}
};

 

그리고 이걸 뒤집으려면, 단순히 생각하면 전체를 순회하면서 한바퀴 돌면 그만.

 

 

 

그래서 처음 생각했던 (그리고 면접때까지 결국 제대로 못 구현했던) 방식은 다음과 같다.

 

우선, 링크드리스트가 "1,2,3,4,5"가 순서대로 있다는 가정하에 설명을 이어가도록 하겠다.

List* ReverseList(List* list)
{
	List* Result;
	List* InputList = list;

	//끝부분 인식
	while (true)
	{
		if (InputList->next->next == nullptr)
		{
			Result = InputList->next;
			InputList->next = nullptr;
			Result->next = InputList;

			break;
		}
		InputList = InputList->next;
	}

	//순서 뒤집기
	List* ResultNow = Result->next;
	InputList = list;
	while (InputList->next != nullptr)
	{
		
		while (true)
		{		
			std::cout << InputList->data << std::endl;
			if (InputList->next->next == nullptr)
			{
				ResultNow->next = InputList;
				InputList->next = nullptr;
				ResultNow = ResultNow->next;

				InputList = list;
				break;
			}
			InputList = InputList->next;
		}
	}
	return Result;
}

 

굉장히... 길다.

 

로직을 간단히 설명하면, 먼저 링크드 리스트의 맨 마지막인 5를 찾고,

그 다음부터는 "뒤가 존재하지 않는 노드" 를 뒤집을 리스트의 끝에 집어 넣는 방식이다.

 

즉,

 

처음 While문 종료시

원본 리스트 :  1->2->3->4->5

뒤집은 리스트 : 5->4

뒤집힐 리스트 : 1->2->3->4

 

해당 상태가 되는데,

 

두번째 While문에서부터는 본인 노드의 다음 다음 노드가 비어있을때 본인을 집어넣는 방식으로 이루어진다.

 

그래서, 위와같이 4가 마지막인것이 2개가 존재하는것이 의도적인 작업인데, 이를 면접때 제대로 대답을 하지 못한게 좀 치명적인 실수였다고 생각이 든다...

 

여튼, 두번째 While문에서는 My->Next->Next == nullptr 일때, My를 집어넣는식으로 이루어진다.

 

물론, 당연히 결과는 잘 나온다.

 

하지만, 시간 복잡도 상으로는 O(n+n²) 라는... 좀 느린 수준의 코드고, 좀 어거지로 짠 느낌이 없잖아 있다.

 

 

우선, while 순회를 1개 블럭으로 줄여보자.

 

별 거 없다, 두개 합치면 그만이다.

List* ReverseList(List* list)
{
	List* Result = nullptr;
	List* ResultNow = nullptr;
	List* InputList = list;


	//순서 뒤집기
	InputList = list;
	while (InputList->next != nullptr)
	{		
		while (true)
		{		
			//std::cout << InputList->data << std::endl;
			if (InputList->next->next == nullptr)
			{
				if (Result == nullptr)
				{
					Result = InputList->next;
					InputList->next = nullptr;
					Result->next = InputList;
					ResultNow = Result->next;
				}
				else
				{
					ResultNow->next = InputList;
					InputList->next = nullptr;
					ResultNow = ResultNow->next;

				}
				InputList = list;
				break;
			}
			InputList = InputList->next;
		}
	}
	return Result;
}

어차피 if의 조건은 List->next->next == nullptr 로 동일하기 때문에 두개의 While을 하나로 합칠 수 있다.

 

하지만 이래도 O(n²) 의 시간복잡도가 나온다.

더 줄여보자.

 

: 해당 로직은 다음 블로그를 참고하였습니다 : 

https://seongonion.tistory.com/78

 

단방향 링크드 리스트 뒤집기 - 파이썬 (Python)

링크드 리스트 정리 https://seongonion.tistory.com/20?category=867075 링크드 리스트의 구현 및 연산 - 파이썬(Python) 링크드 리스트의 구현 (Node, __init__, __str__) 링크드 리스트를 구현하기 위해선, 각각의 데

seongonion.tistory.com

 

 

해당 블로그의 내용을 요약하면...

이전 위치, 현재 위치, 다음 위치를 기억하고 뒤집어가면서 진행하기 정도로 요약가능하다.

 

다음 위치를 진행 시키면서, 현재 위치의 다음 방향을 이전 위치로 기록하면서 진행하면 OK.

 

List* ReverseList(List* list)
{
	List* Result = nullptr;
	//순서 뒤집기
	List* prev = nullptr;
	List* temp = list->next;
	List* now = list;

	while (now != nullptr)
	{
		now->next = prev;
		if (temp == nullptr)
		{
			Result = now;
			break;
		}
		prev = now;
		now = temp;
		temp = temp->next;		
	}
	return Result;
}

 

이번에도 잘 나온다.

 

코드도 훨씬 깔끔하고 간결해져서, 가독성 측면에선 이쪽이 훨씬 나아보임.

 

 

 

 

..이라는 복기를 좀만 일찍했으면 좋았을걸~ 싶은 생각이 든다.

'프로그래밍 > C/C++' 카테고리의 다른 글

C++ 스마트 포인터  (0) 2024.09.04
std::sort()  (0) 2024.09.03
"개체에 일치를 방해하는 형식 한정자가 있음" 오류 해결법  (0) 2022.03.31
기록용 : 메모리 누수 찾기  (0) 2021.07.18
짧은 기록  (0) 2021.06.18

재료 :
냉동 칵테일 새우 26/30 300g / 너무 많이 넣었음. 반으로 줄이고, 잘라서 넣는게 나을듯

마늘 한줌 (대충 150g) - 반은 편썰고, 반은 반만 썰어서 넣음

양파 2개 - 하나는 원형, 하나는 깍뚝썰기 / 1개만 넣어도 충분함

양송이 5개 - 4등분

면-데세코 스파게티나 150g 정도

바질페스토 시판 크게 한숟가락 - 너무 많이넣음 찻숟가락 1개 정도면 충분할듯

페퍼론치노 약간 - 취향차이... 

 

 

 

새우는 꼬리 껍질 및 내장 다 빼서 레몬즙 담군 물에 넣고 대충 30분

 

 

올리브유 적당히 잠길만큼만 넣고 마늘 -> 새우 순으로 넣고 만들었는데..

 

 

 

비주얼은 나쁘지 않으나 내 기준에선 새우향이 너무 쌔서 다음에는 새우 자르고, 새우를 더 익혀서 만들거같음

면이 생각보다 빨리 익어서 감바스 알 하이요가 완성될때쯤부터 면 삶는게 맞을듯함

1. 꾸준히 개인 프로젝트 (언리얼 5 / 최신 따라가기) 진행하기

2. 운동 계속하기

3. DX 11 / 12 공부 계속 하기

4. 기회가 된다면 강연같은거 나가보기

https://github.com/ErikEJ/EFCorePowerTools

 

GitHub - ErikEJ/EFCorePowerTools: Entity Framework Core Power Tools - reverse engineering, migrations and model visualization in

Entity Framework Core Power Tools - reverse engineering, migrations and model visualization in Visual Studio & CLI - ErikEJ/EFCorePowerTools

github.com

 

EFCore라고... C# 관련 비주얼스튜디오 확장인데 (사실 닷넷 개발 안해서 잘 모름)

한국어 번역 관련 이슈있대서 번역 지원하고 왔어요

 

 

 

그냥 숟가락만 얹긴했지만 그래도 이름 한줄 얹었네요

 

 

좀 옛날에 작성한거라 정리가 덜 되어있습니다.. 시간날때마다 다듬을게요


1. 포인터 & 레퍼런스 

1) 포인터 : 특정 객체 / 변수 / 함수의 메모리 상 주소.

데이터를 복사하지 않고 메모리에 직접 접근하여 사용하기 위함.

 

2) 레퍼런스 : 특정 객체 / 변수 / 함수의 별명

특정 데이터의 원본이 필요할 때 사용.

 

3) 레퍼런스 접근 (&a)와 포인터 접근 (*a)를 쓰는 이유
(ex - void foo(* data) )

* 레퍼런스 : NULL 비허용, 대상 직접 할당, 참조 대상 변경 불가 (레퍼런스 변수가 참조중인 대상 변경불가)

* 포인터 접근 : nullptr 가능, 대상 주소 값 할당, 참조 대상 변경 가능 (포인터가 참조중인 대상 변경가능)

 

2. C++언어에서 추상 클래스와 인터페이스의 차이는?

: 추상 클래스란 하나 이상의 순수 가상 함수를 가지는 클래스.

순수 가상 함수란 반드시 구체적인 동작이 정의되어야 하는 함수로 "=0" 혹은 "PURE“ 을 선언과 함께 표기.

순수 가상 함수만으로 클래스가 이루어질 때, 이를 의미적으로는 인터페이스라고 함.

기본 C++에서는 "Interface"라는 명칭을 쓰지 않음. (언리얼 C++ 등 타 환경에서는 쓰는 경우가 있음)

 

3. 상속 관계 관련 1

1) 일반 상속으로는 기존 클래스의 필드와 함수를 재사용 할 수 있음.

2) Virtual 키워드가 붙은 케이스의 경우 함수를 재정의 가능. 이 경우 함수 호출 시 가상함수 테이블을 통해 호출된 함수가 부모의 것인지 자식의 것인지를 탐색하게 됨.

3) 상속은 A is a관계 (A는 B의 일종이다.)

4) 생성시 : 부모 클래스 생성자 -> 자식 클래스 생성자

5) 소멸시 : 자식 클래스 소멸자 -> 부모 클래스 소멸자.

* 이 때문에 소멸자에 virtual 키워드 빼먹으면 자식 클래스 소멸자만 호출하고 끝나서 메모리 누수 발생.

class AAAA
{
public :
AAAA() { cout << “+A” << endl;}
virtual ~AAAA() {cout << “-A” << endl;}
}

class BBBB : public AAAA
{
public :
BBBB() { cout << “+B” << endl;}
virtual ~BBBB() {cout << “-B” << endl;}
}

int main()
{
AAAA a = new A;
BBBB b = new B;

return();
}

---Answer
+A
+A
+B
-B
-A
-A

 

 

 

 

4. 상속 관련 2 : 가상함수 테이블

1) C++에서 가상함수 테이블에 대해 설명해보세요.

virtual 함수는 상속 시 Overriding 될 수 있는 함수.

Overriding시 부모 객체에서 함수 호출시 부모의 함수, 자식 객체에서 호출시 자식의 함수를 호출해야하는데,
이를 구별하기 위해 virtual 키워드가 붙은 함수들을 관리하려 만든 것이 가상함수 테이블.

 

2) 가상함수 테이블 포인터

상속관계가 구축되면 void * _vptr; 이라는 가상함수 테이블을 가리키는 포인터가 생김.

해당 포인터가 가리키는 값에 따라 호출되는 오브젝트가 부모인지, 자식인지를 찾고 결정함.

 

 

 

5. 상속 관련 3 : 오버라이딩과 "변수 잘림"

1) 오버라이딩 특수 케이스

class AA
{	public:
	virtual ~AA() = default;
		void fooA() { cout << "A's fooA" << endl; }
	virtual void fooB() { cout << "A's fooB" << endl; }
};
class BB : public AA
{	public:
	virtual ~BB() = default;
		void fooA() { cout << "B's fooA" << endl; }
	virtual void fooB() { cout << "B's fooB" << endl; }
};

int main() {
	AA* bar = new BB();
	bar->fooA();
	bar->fooB();
	return 0;
}

 

결과

코드 설명 :

virtual 키워드가 붙지 않은 함수는 가상함수테이블에 등록되지 않아 정의 타입과 관계없이 선언 타입 (부모로 선언되었다면 부모타입) 의 함수가 호출됨.

하지만 virtual 키워드가 붙은 함수는 정의 타입 의 함수가 호출됨.

, virtual 키워드가 붙지 않은 함수는 오버라이딩 해도 각 클래스에 종속적이며, virtual 키워드를 이용해 오버라이드 하면 실제 정의를 어떤 타입으로 했냐에 따라 다른 것이 호출됨.

 

6. 스마트포인터

스마트 포인터는 포인터처럼 동작하는 클래스, 자동으로 할당을 해제해주는 특성을 가짐.

auto_ptr은 복사 시 소유권 이전, shared_ptr은 포인터 자체적으로 레퍼런스 카운팅 실시.

따라서 auto_ptr은 사용도중 복사된 포인터가 삭제 시 원본 또한 할당 해제가 되는 문제가 발생할 수 있었기 때문에 C++11 표준에서는 다른 스마트 포인터들이 추가됨. C++17에서는 완전 삭제됨.

 

C++17 기준 유지되고 있는 스마트 포인터 리스트

1) unique_ptr : 소유권이 생긴 스마트 포인터. 일반적인 스마트 포인터.

2) shared_ptr : boost 라이브러리에 있던 그것. 레퍼런스 카운트를 하는 스마트 포인터.

3) weak_ptr : sheard_ptr의 순환 참조 제거를 위해 사용. 레퍼런스 카운트를 하지 않는 Shared_ptr

 

7. 기본 C++ 에서의 구조체 / 클래스 차이

기본 접근 한정자 차이 외의 나머지 동일

* struct 기본 접근 한정자 : public

* class 기본 접근 한정자 : private

 

* 타 언어 및 네이티브 C++이 아닌 타 C++에서는 다를 수 있습니다.

 

8. 메모리 초기화 함수 (malloc / calloc)

둘 다 메모리 할당 함수이나 그냥 할당만 하느냐 / 0으로 모든 비트를 미느냐 는 차이점이 있음.


malloc
는 그냥 할당만, calloc0으로 밀어버림.

 

팁 - calloc을 이용해서 0으로 초기화 한다 = 단순한 비트 클리어.

예를들어, float 배열을 calloc으로 할당한다고 해서 0.0이 되는 것이 아님.

또한, 메모리 할당 = 그 메모리에 무언가 쓴다는 목적인데, 정말 필요해서 0으로 밀어야하는것이 아니라면, 굳이 밀 필요는 없음.

 

 

9. C++ 환경에서의 32비트 / 64비트 차이

1) 포인터 크기 달라짐 (4바이트 / 8바이트)

2) 일부 자료형 크기 다름

(1) Windows 환경의 경우(LLP64) : long long 64비트, 나머지 동일

(2) 특정 환경 (ILP64) : int, long 64비트, 나머지 동일

(3) 리눅스 환경 (LP64) : long 64비트, 나머지 동일

3) 인라인 어셈블러 사용 가능 여부 (비주얼 스튜디오 기준) - 64비트에서는 인라인 어셈블러 사용불가.

따라서, 64비트에서 어셈블러 사용시 .asm 파일을 생성하여 따로 함수화 시켜서 사용함.

 

 

10. 포인터 관련

void foo (int* x, int *y)
{
    x = y;
    *x = 2;
}

int main()
{
    int a = 0;
    int b = 1;

    foo (&a,&b);

    cout << a << “:” << b << endl;
}

-- answer
a = 0
b = 2

 

설명 : foo 들어가면 int* x, int* y라는 지역 변수가 형성됨.

xy의 값을 집어넣음. (x = y )

x,y는 포인터이므로, x가 가리키는 값 = y가 가리키는 값이 됨.

yb를 가리키고 있고, x = y 이므로

*x = 2 => *y = 2 => b = 2 가 됨.

하지만 a는 값이 바뀐적이 없으니 그대로 0이 나옴.

 

 


C++ STL이나 기타는 다른 문서로 작성할게요 

개인작 프로젝트도 있고

Pixel Pipe 프로젝트도 있고...

 

이번 면접때 픽셀파이프 프로젝트 관련해서 질문 들어오길래 음 슬슬 할때가 됐구나 싶은 생각이 들었음.

 

근데 그거 하려면 DX11 / 12 다시 배워야하는데 어디서부터해야하지 

'신변잡기 > 일상' 카테고리의 다른 글

To do.  (0) 2024.07.22
하찮은걸 만들고 있습니다...  (0) 2024.02.11
오랫만에 개인작을 할까 합니다...  (0) 2024.02.08
PC 카톡은 왜 삭제기능이 없을까...  (0) 2022.11.20
근황  (0) 2022.04.25

+ Recent posts