벡터는 '동적 배열 클래스'입니다

 

1. 벡터는 배열(array)과 다르게 크기 수정이 언제는 가능하다

배열은 한번 크기를 설정한 이후 수정이 불가능하지만, 벡터는 언제는 크기 수정이 가능합니다.

이렇게 보면 크기수정이 매우 마법처럼 보이지만 사실은,

크기가 수정되면 새로운 배열을 만들어 기본 배열의 원소를 이동시키게 됩니다

사실은 하나의 배열을 늘렸다 줄였다 하는것이 아니라 !새로운!배열을 만들어 버립니다.

 

그렇기 때문에 벡터생성시 캐퍼시티를 미리 생성해주는것이 매우 중요합니다.

캐퍼시티를 미리 생성하지 않아도 벡터가 꽉차면 새로운 배열을 생성해서 늘려주긴 합니다만, 

상황에 따라서 퍼포먼스에 악영향을 미칠 수 있습니다.

여기서 캐퍼시티란, 벡터의 원소개수가 아니라 빈공간을 포함한 벡터의 크기입니다.

 

예를 들자면, 벡터라는 아파트에 캐퍼시티라는 빈 집을 미리 만들어놔야 원소라는 입주민을 언제든 받을 수 있습니다

만약 한층에 하나의 입주만 받는 20층짜리 아파트는 20가구의 입주민만 받을 수 있죠.

21번째 가구가 입주한다고 했을 때 이미 지어진 아파트에 1층을 올릴순 없습니다.

그래서 21층짜리 아파트를 새로 짓습니다.

그리고 이전 아파트에 살던 입주민들을 새로운 아파트로 재입주 시키게 됩니다.

그렇기 때문에 처음 아파트를 지을때 미리 캐퍼시티라는 빈 집을 입주민에 맞게 지어놔야 다시 아파트를 지을일이 없습니다.

 

꼭 추가될 원소개수를 고려해 캐퍼시티를 고려합시다.

 

2. 원소가 메모리상에 인접해 있어서 벡터의 어떤 원소든 쉽게 접근할 수 있지만,

항상 인접해야있어야하므로 벡터의 중간원소의 삽입,삭제가 일어나면 빈공간을 메꿔야하므로

빈번하게 일어나면 비효율적이다.

벡터는 모든 원소가 메모리상에 순서대로 붙어있습니다. 덕분에 원소의 순서가 보장되어 있지만, 중간에 원소가 삽입, 삭제가 발생하면 빈공간을 메꿔야하기 때문에 비효율적이고 이유는 해당 지점을 기준으로 모든 원소가 옆 메모리로 이동하게 됩니다.

 

중간삭제이슈:

위에 설명했듯이 20층짜리 벡터가 있고 입주민이 꽉차있는 상황에서 10층 입주민이 삭제라는 이사를 하게 되었습니다. 그럴때 10층을 빈 공간으로 두지 않고 11층 입주민은 10층으로, 12층입주민은 11층으로...20층입주민은 19층으로 이동하게 됩니다.

그저 원소 하나를 삭제했을 뿐인데 10개의 원소가 모두 이동하게 되었습니다.

환경에 따라서 가끔 한두번 그러는건 문제 없겠지만, 빈번하게 일어나면 퍼포먼스에 악영향을 끼치게 됩니다.

 

중간삽입이슈:

마찬가지로 20층짜리 벡터가 있고 입주민이 꽉차있는 상황에서 10층에 새롭게 입주하려는 입주민이 있습니다.

그럼 역시 기존10층입주민은 11층으로... 11층입주민은 12층으로... 20층입주민은 21층으로....

삭제와 마찬가지로 11개의 원소가 이동하게 됩니다. 게다가 21층을 필요로하니 아파트를 새로 짓기까지 해야합니다.

 

이렇게 모든 원소를 꼭 붙혀놓으려고 하는 이유는 언제는 쉽게 벡터의 원소에 접근하기 위해서 입니다.

만약 10번째 원소를 삭제했을 때 빈공간으로 놓으면 우리는 원소를 가져올 때마다 비어있는지 확인해야하고, 원소를 삽입할 때 마다 빈공간을 찾아야하고 굉장히 바빠질것입니다. 

 

순서를 보장받고싶진 않지만 벡터를 사용하고 싶을 때 사용할 수 있는 꼼수가 하나 있습니다.

순서를 보장받을 필요가 없다면 중간삽입을 할 필요가 없으므로 중간삭제에 대해서만 확인하면됩니다.

그 방법은 원소삭제후 빈 공간을 벡터의 마지막원소만 이동시켜 채워넣으면 됩니다.

 

마지막원소의 위치가 변경되었으니 순서는 바뀌었지만, 대신 중간삭제에 대해선 문제없이 할 수 있습니다.

해당 방법은 예제에 있습니다.


제가 구현한 벡터의 함수들입니다.

 

생성자()

생성자(std::initializer_list<Data>)

소멸자()

 

Reserve(): 캐퍼시티할당

Clear(): 배열의 모든원소 삭제(실제로 삭제되지 않고 배열사이즈를0으로 변경합니다. 캐퍼시티는 그대로입니다)

Num(): std::vector.size()와 동일한 함수로 배열의 원소개수를 알려줍니다

Max(): 배열의 캐퍼시티를 알려줍니다

 

Add(): 배열 마지막위치에 원소추가

Insert(): 배열의 원하는 위치에 원소추가

RemoveAt(): 배열의 원하는 위치의 원소를 삭제합니다(실제로 삭제되지 않고 배열사이즈를 1줄입니다)

RemoveAtSwap(): 배열의 원하는 위치의 원소를 삭제하고, 그 자리에 마지막원소를 가져와 채웁니다.(실제로 삭제되지 않고 배열사이즈를 1줄입니다). 해당방법을 사용하면 원소의 순서는 보장할 순 없지만, 삭제된 위치부터 마지막위치까지 원소를 +1만큼 이동시키지 않아도 됩니다.

PopBack(): 스택의 Pop()처럼 마지막 원소를 삭제하고 반환합니다.

 

그외에...

이터레이터

오퍼레이터[], =


기능을 추가로 넣는다면 Clear()는 원소사이즈를 0으로 변경하지만 새로운 함수를 만들어서 캐퍼시티까지 0으로 변경하는 방법도 있겠습니다.

Add, Num, Max등 기존 벡터함수이름와 다르게 언리얼엔진의 TArray를 참고해서 함수명을 작성했습니다.

제가 언리얼엔진을 써서 그런지 TArray의 함수명이 더 직관적이라 편하다고 생각됩니다

 

앞으로 시간될 때 공부도 할겸 Stack, LinkedList등등 기초 자료구조를 만들어서 포스팅할 생각입니다.

 


우선 클래스를 먼저 보여드리겠습니다

#pragma once

template <typename Data>
class Container_Master
{
protected:
	Data* my_base = nullptr;
	size_t my_capacity = 0;
	size_t my_size = 0;

	//캐퍼시티를 늘릴 때 지정된 값이 없다면 해당 값만큼 늘립니다
	size_t base_capacity = 32;
public:
	//컨테이너의 공간을 새로 생성합니다
	void Reserve(size_t capacity)
	{
		//기존 캐퍼시티가 더 크다면 재할당을 하지 않습니다
		if (my_capacity > capacity)
			return;

		Data* temp = new Data[capacity];
		my_capacity = capacity;

		//컨테이너에 원소가 있다면 새로 할당한 주소로 옮겨줍니다
		if (my_size >= 1)
		{
			for (size_t i = 0; i < my_size; i++)
			{
				temp[i] = my_base[i];
			}
			delete[] my_base;

		}

		my_base = temp;
	}

	//배열의 원소를 삭제하지않고
	//사이즈를 0으로 만듭니다
	//Clear()함수후에 원소룰 추가하면 기존에 있던 원소를 덮어씌웁니다
	void Clear()
	{
		my_size = 0;
	}

	//컨테이너의 원소개수입니다
	size_t Num()
	{
		return my_size;
	}

	//컨테이너의 캐퍼시티개수입니다
	size_t Max()
	{
		return my_capacity;
	}
	
    	//인덱스 유효성검사를 합니다
	//유효하지 않으면 throw
	bool CheckValidIndex(const size_t index)
	{
		if ((index >= 0) && (index < this->my_size))
		{
			return true;
		}
		else
		{
			throw printf("범위를 벗어난 인덱스를 사용하였습니다. 접근하려는 인덱스: %d, 배열크기: %d", index, this->Num());
		}
	}
};
#include "Container_Master.h"

template <typename Data>
class MyVector : public Container_Master<Data>
{

};

Container_Master를 부모를 두고 MyVector클래스가 있습니다.

벡터를 구현하다보니까 앞으로 스택을 구현할 때 중복될거같은 부분을 부모클래스로 올렸습니다.

아직 스택을 구현해본적이 없지만... 그럴거같아서요...

 

std::vector의 템플릿방식으로 클래스를 생성했습니다.

 

템플릿에 대한 자세한 내용은 여기서 확인해주세요

https://modoocode.com/219

 

https://modoocode.com/219

모두의 코드 씹어먹는 C++ - <9 - 1. 코드를 찍어내는 틀 - C++ 템플릿(template)> 작성일 : 2017-04-07 이 글은 77006 번 읽혔습니다. 에 대해서 배웁니다. 안녕하세요 여러분! 지난번 강좌 생각해보기는 잘

modoocode.com

 

벡터의 사이즈를 int, unsigned int를 사용하지 않고 size_t를 사용한 이유는 여기서 확인해주세요

https://forestbird0.tistory.com/37

 

[C++] size_t의 역할

size_t는 unsigned_int와 동일합니다 그렇다면 굳이 size_t가 존재하는 이유가 무엇일까요? size_t의 선언부를 보게되면 이렇게 되어있습니다 #ifdef _WIN64 typedef unsigned __int64 size_t; #else typedef unsigned int size_

forestbird0.tistory.com


https://github.com/ForestBird1/MyContainer

 

GitHub - ForestBird1/MyContainer

Contribute to ForestBird1/MyContainer development by creating an account on GitHub.

github.com

 


//새탭 생성
_driver.ExecuteScript("window.open();");

//*탭 포커싱
//마지막 탭(우측) 포커싱하기
_driver.SwitchTo().Window(_driver.WindowHandles.Last());

//첫번째 탭(좌측) 포커싱하기
_driver.SwitchTo().Window(_driver.WindowHandles.First());

//*특정 탭정보 저장, 특정 탭 포커싱

//현재 탭정보 저장
//string window = _driver.CurrentWindowHandle;

//모든 탭정보중 0번째 인덱스탭 저장
string window = _driver.WindowHandles.ToList()[0];
_driver.SwitchTo().Window(window);

//현재 탭 닫기
//탭을 닫은경우 포커싱을 잃어버리기 때문에 다른 탭으로 포커싱을 잡아줘야 합니다!!!
_driver.Close();

탭을 닫았으면 크롬드라이버는 포커싱중인 탭이 없기 때문에 문제가 생깁니다

꼭 다른 탭으로 포커싱을 잡아주세요!!

 

https://github.com/ForestBird1/TestSelenium.git

 

GitHub - ForestBird1/TestSelenium

Contribute to ForestBird1/TestSelenium development by creating an account on GitHub.

github.com

 


스크린샷

//*스크린샷 찍기
ITakesScreenshot _takesScreenshot = (ITakesScreenshot)_driver;
Screenshot screenshot = _takesScreenshot.GetScreenshot();
screenshot.SaveAsFile("screenshot.png", ScreenshotImageFormat.Png);

저장된 파일을 확인하면 웹 전체스크린샷이 찍혀있습니다.

그런데 만약 특정요소부분만 짤라서 사진을 찍고싶다면??

 


스크린샷 자르기

정확히 로고만 스크린샷을 찍고싶은데 어떻게 해야할까요?

어렵지 않습니다 로고이미지의 요소를 가져온뒤

가로, 세로, 위치 값을 가져와서 그만큼 전체스크린샷에서 잘라 저장하면 됩니다. 

//네이버 로고요소 가져오기
_web_elem = _driver.FindElement(By.XPath("//*[@id='header']/div[1]/div/div[1]/h1/a"));

//스크린샷을 비트맵으로 변경
Bitmap bitmap_screenshot = new Bitmap(new System.IO.MemoryStream(screenshot.AsByteArray));

//*전체스크린샷에서 로고크기에 맞춰자릅니다
Rectangle rect_crop_size = new Rectangle(_web_elem.Location.X, _web_elem.Location.Y, _web_elem.Size.Width, _web_elem.Size.Height);
bitmap_screenshot = bitmap_screenshot.Clone(rect_crop_size, bitmap_screenshot.PixelFormat);
bitmap_screenshot.Save(String.Format(Environment.CurrentDirectory + "//screenshot_2.png", ImageFormat.Png));

//*비트맵은 사용후 꼭 Dispose해야 메모리누수가 발생하지 않습니다
//*Dispose를 사용하지 않으려면 using문으로 사용해도 됩니다
bitmap_screenshot.Dispose();

차근차근 보시면 절대 어렵지 않습니다

 


화면에 보이지 않는 요소 스크린샷

로고는 대부분 사이트 상단에 있어서 웹 접속시 바로 보이기 때문에 스크린샷을 찍을 수 있습니다

그런데 꼭 스크롤을 해야할 정도로 밑에 있는 요소는 스샷을찍고 자르려고하면

System.OutOfMemoryException: '메모리가 부족합니다.'

라고 크래시가 납니다

위에서 우리가 비트맵에 스크린샷바이트값을 입력했고 그 범위를 넘긴것이라고 생각하면됩니다

우리가 직접 웹에서 스크롤을 해도 마찬가지로 크래시가 납니다

 

이유는 아무리 스크롤을해도 스크린샷의 사이즈인 가로,세로길이는 강제로 늘리지 않는 한 모니터사이즈만큼 고정되어 있습니다

그러나 요소의 위치는 스크린샷의 사이즈와 별개로 있기 때문에 단순히 요소의 위치만 구하고 스샷을 자르려고하면 문제가 생기기 마련입니다

전체스크린샷의 사이즈는 1000x1000이라면 스크롤하려는 요소의 위치는 1000,1300으로 스크린샷 사이즈,비트맵사이즈의 범위밖으로 넘어가게되기 때문에 문제가 생깁니다.

 

그래도 해결하는 방법인 다 존재하니 알아봅시다

우선 전체스샷을 찍을때 요소가 보여야겠죠? 이미 이런 기능은 존재합니다!!

개발자모드에서 바로 해당 요소로 스크롤이 됩니다 이걸 우리는 코드에서 작동시켜야하는데 방법은 말이죵

_web_elem = _driver.FindElement(By.XPath("//*[@id='footer']/div/div[3]/div[2]/div[1]/a/img"));
_driver.ExecuteScript("arguments[0].scrollIntoView(true);", _web_elem);

 

딱~ 해보시면 스크롤이 됩니다

이제 안보이던 요소를 스크린샷찍을 수 있게 되었습니다

그 다음 우리는 스크롤된 길이(y축)만큼 추가로 계산을 해야합니다

 

스크린샷은 왼쪽위를 (0,0)으로 기준점을 잡습니다

그런데 스크롤된 웹의 기준점은 (0, 스크롤길이) 입니다

이 격차만큼 우리는 계산을 해줘서 웹 기준점을 0, 0으로 맞춰줘야합니다

 

자 그럴려면 스크롤된 y축값을 가져옵시다

//웹 페이지의 Y기준점 찾기
var y_offset_object =_driver.ExecuteScript("return window.pageYOffset;");
int y_offset = (int)float.Parse(y_offset_object.ToString());

코드가 좀 이상하죠? int로 받고있는데 왜 굳이 float를 거치는지,,,

가끔 특정상황에서는 페이지Y오프셋이 소수점으로 떨어지는 경우가 있습니다.

그래서 바로 int.Parse를 하면 이상하게 작동하더라구요.

안전장치로 일단 float로 불러온뒤 int로 캐스팅후 가져왔습니다

물론 float로 가져와서 바로 사용해도 됩니다만 이따 뺄셈을 해야해서 정확한 계산을 하기 위해 int로 캐스팅해줬습니다

 

y축값을 가져왔으니 우리가 스샷을 찍으려는 요소의 위치를 y축값만큼 뺍니다

//*전체스크린샷에서 요소크기에 맞춰자릅니다
Rectangle rect_crop_size = new Rectangle(
    _web_elem.Location.X,
    _web_elem.Location.Y - y_offset, 
    _web_elem.Size.Width, 
    _web_elem.Size.Height);
bitmap_screenshot = bitmap_screenshot.Clone(rect_crop_size, bitmap_screenshot.PixelFormat);
bitmap_screenshot.Save(String.Format(Environment.CurrentDirectory + "//screenshot_scroll_crop.png", ImageFormat.Png));

y값만 계산했습니다

 

이 방법은 최대스크린샷사이즈보다 큰 요소는 찍기 어렵다는게 단점입니다...

 


분명 Capture node screenshot이라는 편한 기능이 있는데...

개발자모드에서 원하는 요소를 우클릭하면 Capture node screenshot이라는 기능이 있습니다

이걸사용하면 스크롤과 요소사이즈 상관없이 곧바로 요소를 스크린샷할 수 있습니다

하...근데 코드상에서 돌리는 방법은 아무리 삽질해도 안나오네요 아는사람있으면 댓글 부탁드립니다

 

 

https://github.com/ForestBird1/TestSelenium.git

 

GitHub - ForestBird1/TestSelenium

Contribute to ForestBird1/TestSelenium development by creating an account on GitHub.

github.com

 

+ Recent posts