2. Android studio를 시작 후, AVD Manager 아이콘을 클릭 후 Create Virtual Device를 클릭 한다.
혹시라도 AVD manager가 보이지 않는다면 메뉴 상단에 Tools -> AVD Manager 를 클릭 한다.
3. Create Virtual Device를 클릭 하게 되면 Select Hardware가 나오는데, 여기서 난 Pixel3 XL을 선택했다.
Pixel3 XL 선택
4. System image 섹션에서 version R을 선택 후 라이센스 동의, 그리고 Next 버턴을 클릭 하게 되면 안드로이드 버전 R이 다운로드 된다.
Android R 다운로드
5. 설치가 끝나게 되면 Verify Configuration 페이지가 나오게 된다. 그래픽에서 Hardware - GLES 2.0을 선택 후 Finish
Hardware - GLES 2.0 선택
6. Finish button을 누르게 되면 아래와 같이 Device list에 Pixel3 XL API 30이 나타난다. Pixel3 XL은 우리가 선택한 디바이스 이며, API30은 우리가 사용할 API 버전이다. 설치한 에뮬레이터가 잘 작동하는지 테스트 하기 위해 플레이 버튼을 누르게 되면 에뮬레이터가 실행 되어진다. 에뮬레이터가 정상적으로 실행 되었다면 셋팅 완료. 에뮬레이터 로딩 되는데 시간이 조금 걸릴 수 있다..
Flutter는 구글에서 개발한 UI 툴킷으로 모바일, 웹, 그리고 데스크탑 앱 개발에 사용 될 수 있다. 공부를 하기전 리서치 해본 결과 내가 생각하기에 가장 큰 장점은 Dart라는 하나의 프로그래밍 언어로 안드로이드와 iOS앱을 동시에 개발 할 수 있다는 점이다. 이 부분은 추후 자세히 더 공부를 해봐야 겠다.
첫번째로 Flutter SDK 설치 하기, Flutter Software Development Kit
C: 드라이브 Development 폴더를 만든 후 다운 받은 SDK를 설치 하였다. 압축을 풀게 되면 아래와 같은 구조가 되었다.
Flutter SDK
Windows console에서 플러터 커맨드를 실행 하기 위해 Flutter path를 Environement Variable Path에 등록 시켰다.
Flutter가 정상적으로 작동을 하는지 테스트를 해보자. 테스트를 위해서 Windows command prompt를 실행 후 flutter doctor를 실행 하자. 실행시 Welcome to Flutter! 라고 하면서 console에 플러터 관련된 무엇인가 출력이 된다면, flutter가 정상적으로 path에 등록이 된 것이다.
프로그램을 작성하는데 있어서 메모리를 이해 하는 것은 매우 중요하다. 만약 우리가 게임 어플리케이션을 작성하고 실행을 하게 되면 이 앱은 컴퓨터 메모리의 로딩이 된다.
우리가 작성한 모든 코드들은 메모리의 로딩이 되며, 컴퓨터의 CPU가 메모리상에 로딩된 커맨드들을 액세스 하고 실행을 하게 된다. 우리가 작성한 프로그램을 하드 디스크에 저장하고 실행을 하게 되면,
첫번째로 프로그램이 메모리로 로딩이 되고, CPU가 메모리상에 로딩된 커맨드들을 액세스를 하며 실행을 하는 구조이다.
이러한 메모리를 관리 하고 액세스를 할 수 있게 해주는 녀석이 바론 포인터이다. 포인터는 쉽게 integer 이다. 포인터는 컴퓨터 메모리의 주소를 저장하고 있는 integer 이다. 데이터 타입 (Data type) 과 상관없이 포인터는 메모리의 주소값을 가지고 있는 integer 이다. 데이터 타입은 프로그램을 작성할 때 엔지니어들에게 이 포인터는 몇 바이트인지만을 나타내 줄 뿐 포인터의 성질?을 바꿀 수 없다.. 포인터는 단지 메모리의 주소값을 가지고 있는 integer 이다.
심플한 포인터 하나를 정의 하였다. nullptr은 c++11 에서 새로 소개된 것으로 NULL 값을 가질 수 있는 곳에 사용 할 수 있다. NULL은 실질적으로 #define NULL 0 이다. 메모리 주소 0 는 유효하지 않은 값으로 프로그램을 실행 할 경우 에러가 난다. 아래 예제를 실행하게 되면 에러가 나지만 여전히 ptr은 포인터 이다.
#include <iostream>
int main()
{
int var = 8;
void* ptr = &var;
std::cout << ptr << std::endl;
std::cin.get();
}
위 예제를 실행 하게 되면 var 변수의 메모리 주소 값 0x0077FBEC 출력이 되는것을 알 수 있다. 보는것과 같이 16진수 형태의 숫자가 반환 되었으며 이것은 integer 이다. 여기서 & 앰퍼센드는 변수의 주소값을 반환할때 사용되는 키워드 이다. 0x0077FBEC var 변수의 메모리의 주소 값이며, 이 메모리 주소 안에 var 변수의 값이 저장되어져 있다.
Visual Studio 에서 Debug 모드로 코드를 실행 후 실질적으로 변수 var의 메모리 값을 확인 할 수 있다.
Breake point를 std::cout << ptr << std::endl; line 에 만든 후 디버깅 모드로 실행하자.
위 메뉴에서 Debug -> Windows -> Memory -> Memory 1을 실행
Visual Studio 메모리 뷰 활성화 시키기
var 변수 메모리 주소 확인
위에 보는것과 같이 var변수의 메모리 주소 값은 0x00cffe4c 이다. 메모리 창을 열고 메모리 주소 값을 입력을 하게 되면 실제 0x00cffe4c 메모리 주소를 확인 할 수 있다. 위에 보는 것과 같이 이 메모리 주소상에는 숫자 8이 저장되어져 있는 것을 확인 할 수 있다. ( int 타입은 4 bytes 이기 때문에 총 4개의 byte가 할당 되어진 것을 볼 수 있다. )
아래 예제는 double 타입의 포인터를 생성하고 var의 메모리 주소값을 저장 한다. 위에서 언급한것과 같이 포인터를 공부하는데 있어서 데이터 타입은 중요하지 않다. 데이터 타입은 몇바이트의 메모리 값을 할당 해야 하는지 CPU에게 알려줄 뿐이다.
#include <iostream>
int main()
{
int var = 8;
double* ptr = (double*)&var;
std::cout << ptr << std::endl;
std::cin.get();
}
위에 예제를 위와 같은 방법으로 실행을 하고 메모리 뷰에서 확인을 하면, 총 4 bytes의 메모리가 할당되어져 있고 value 8이 저장되어져 있는것을 확인 할 수 있다.
인천공항에서 홍콩으로, 다시 홍콩에서 멜번으로 총 15시간? 의 비행을 하여 멜번에 도착을 하였다. 정확하게 5월5일 어린이날에 멜번 땅을 처음으로 밟았다. 한국에서 나름 유명한 강남 어학원에서 3개월간 실력을 갈고 닦았지만, 해외에는 일본 외에는 가본적이 없었던지라.. 호주에서의 삶은 막막 하였다. But!! 다행히 나에겐 몇년전 먼저 호주에서 자리를 잡고 있었던 와이프가 있었기에, 지금까지 살아 남을 수 있었던것 같다.. ㅎㅎ
호주 대학교 삶과, 직장생활, 인터뷰, 호주에서 아들 셋을 키우는 삶 등등 호주에서의 삶에 대해 다시 기록으로 남기도록 하겠다.
2009년 5월5일 호주 정착
2009년 6월 ~ 2010년 어학원에서 공부 시작
2010년 Swinburne Uni에서 Computer Science 학위 시작
2010년 Swinburne Uni에서 Java 교수님 연구실에 연구 보조원으로 들어감
2011년 학교에서 지원해주는 industry based learning (IBL)을 통하여 취업 인터뷰 시작
여기서 capture clause와 parameters 는 생략 될 수 있습니다. 또한 리턴타입 (return type) 은 optional 이며 만약 생략될 경우 auto 타입이 사용됩니다. 마지막으로 람다펑션의 이름 또한 생략 될 수 있습니다. 종합해 보면 람다 펑션은 아래와 같이 구현 할 수 있습니다.
#include <iostream>
int main()
{
[]() {}; // 캡쳐, 파라미터와 리턴 타입이 생략 되었다.
return 0;
}
위 예제에서 람다 펑션을 find_if 함수 안에 정의 하였다. (람다펑션의 시작은 항상 capture clause 으로 시작 되기 때문에[] 보이면, 아!! 이것은 람다펑션이구나!! 생각을 해주면 된다.) 이러한 사용 방식을 함수 리터럴 (function literal) 이라고 한다. 위와 같이 함수 안에 람다를 정의를 하게 되면 코드의 가독성이 떨어질 수 있다. 이러한 문제를 해결 하기 위해 우리는 람다 변수를 정의 한 후 람다 펑션을 사용 할 수 있다.
예를 들어, std::all_of함수를 사용하여 배열의 모든 요소가 짝수인지 확인합니다. 아래 예제는 std::all_of 펑션 안에 람다를 정의 하였다.
// 코드의 가독성이 떨어지는 좋지 않은 example
return std::all_of(array.begin(), array.end(), [](int i){ return ((i % 2) == 0); });
위 코드는 아래와 같이 바꿀 수 있습니다.
// isEven 변수에 람다를 저장 한다.
auto isEven {
[](int i)
{
return ((i % 2) == 0);
}
}
return std::all_of(array.begin(), array.end(), isEven);
여기서 람다의 타입은 무엇일까? 람다의 타입은 컴파일시 컴파일러가 유니크한 타입을 만들어 준다.
람다의 타입은 컴파일 타임까지 알 수는 없지만, 람다를 저장하기 위한 방법은 아래와 같이 여러 가지가 있다.
Capture clause가 생략되었을 때 함수펑션 (function pointer)를 사용할 수 있다.
// A regular function pointer. Only works with an empty capture clause.
double (*addNumbers1)(double, double) {
[](double a, double b) {
return (a + b);
}
};
std::cout << addNumbers1(1, 2) << std::endl;
Capture clause가 사용될 경우 std::function을 이용하여 람다를 정의 할 수 있다.
// Using std::function.The lambda could have a non - empty capture clause(Next lesson).
int aa = 10;
std::function<int(double, double)> addNumber2;
addNumber2 = [aa](double a, double b) {
return (aa + a + b);
};
addNumber2(3, 4);
위 예제를 실행시 17이 출력 된다.
마지막으로 auto를 사용하여 람다를 정의 할 수 있다.
// Using auto. Stores the lambda with its real type.
auto addNumbers3{
[](double a, double b) {
return (a + b);
}
};
addNumbers3(5, 6);
람다의 실제 유형을 사용하는 유일한 방법은 auto keyword을 사용하는 것입니다. auto는 std::function에 비해 오버 헤드가 없다는 이점도 있습니다.
하지만 람다를 정의할때 항상 auto 을 사용 할 수 있는 것은 아니다. 예를 들어 람다를 매개변수로 함수에 전달하고 호출자가 전달하는 람다의 타입을 결정 하기 때문에 auto를 사용 할 수 없다. 이러한 경우엔 std::function을 사용하여 정의 할 수 있다.
#include <functional>
#include <iostream>
// We don't know what fn will be. std::function works with regular functions and lambdas.
void repeat(int repetitions, const std::function<void(int)>& fn)
{
for (int i = 0; i < repetitions; ++i)
{
fn(i);
}
}
int main()
{
repeat(3, [](int i) {
std::cout << i << '\n';
});
return 0;
}
첫번째로 데이터 타입이 나오며, 그 뒤로 함수 포인터의 이름, 그리고 포인터를 가르키는 * 이 함수 포인터 이름 앞에 붙습니다. 그리고 함수 포인터가 가르키는 함수의 parameter 타입을 정의 합니다.
예를들면
1. 함수의 리턴형은 int 여야 하고, int 값과 std:string 2개의 파라미터를 받는 함수여야 한다
2. 함수의 리턴형은 void, 파라미터는 받지 않는 함수여야 한다.
3. 함수의 리턴형은 bool 이며, std::string 을 파라미터로 받는 함수여야 한다.
함수 포인터에 가장 빠르게 익숙해 지는 방밥은 계속 사용하는 것입니다. 간단한 void 함수를 만든 후 void 함수를 함수 포인터를 할당 후, 함수 포인터를 사용하여 할당된 void 함수를 호출해 보도록 하겠습니다.
#include <iostream>
void firstFunction() {
std::cout << "first function without any paramter" << std::endl;
}
int secondFunction(int a, int b) {
return a + b;
}
bool thirdFunction(std::string name) {
return !name.empty();
}
int main() {
void (*firstPtr)() = firstFunction;
// 리턴타입이 void이며, parameter를 받지 않는 포인터 함수 선언; 선언과 동시에 firstFunction의 주소값을 포인터함수에 할당
firstPtr();
int(*secondPtr)(int, int) = secondFunction;
// 리턴타입이 int이며, 두개의 int 값을 파라미터로 받는 포인터 함수 선언; 선언과 동시에 secondFunction의 주소값을 포인터 함수에 할당
std::cout << secondPtr(10, 20) << std::endl;
bool (*thirdPtr)(std::string) = thirdFunction;
if (thirdFunction("name")) {
std::cout << "return value is true" << std::endl;
}
}
함수를 다른 함수의 파라미터로 전달. (Callback Function)Passing functions as arguments to other functions
함수 포인터는 다른 함수의 파라미터로 전달 될 수 있으며, 흔히 콜백펑션이라고 부른다. 여기서 중요한 포인트는 함수를 전달 할때 정의되어져 있는 함수포인터의 리턴타입, 파라미터의 값일 정확하게 일치 하여야 한다.
#include <utility> // for std::swap
#include <iostream>
// 세번째 인자는 포인터 함수로, bool 값을 리턴 하며, 두개의 int 값을 받는 comparisonFcn
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int))
{
for (int i = 0; i < (size - 1); ++i)
{
int bestIndex = i;
for (int currentIndex = i + 1; currentIndex < size; ++currentIndex)
{
// 세번째 인자로 받은 함수 펑션을 호출한다. comparisonFcn은 두개의 int 값을 비교하여 True or False를 리턴한다.
if (comparisonFcn(array[bestIndex], array[currentIndex]))
{
bestIndex = currentIndex;
}
}
std::swap(array[i], array[bestIndex]);
}
}
// 오름차순 정렬 함수. X 와 Y 의 값을 비교하여 X 가 Y 보다 클 경우 True를 작을 경우 False를 반환 한다.
bool ascending(int x, int y)
{
return x > y;
}
// 내림차순 정렬 함수. X 와 Y 의 값을 비교하여 X 가 Y 보다 작을 경우 True를 클 경우 False를 반환 한다.
bool descending(int x, int y)
{
return x < y; // swap if the second element is greater than the first
}
// 결과 출력 하는 함수
void printArray(int* array, int size)
{
for (int index{ 0 }; index < size; ++index)
{
std::cout << array[index] << ' ';
}
std::cout << '\n';
}
int main()
{
int array[9]{ 3, 7, 9, 5, 6, 1, 8, 2, 4 };
// descending 함수를 3번재 파라미터로 전달.
// selectionSort 함수는 파라미터로 int 타입의 Array, int 값과 , bool (*comparisonFcn)(int, int) 함수 포인터를 받는다.
selectionSort(array, 9, descending);
printArray(array, 9);
// ascending 함수를 3번재 파라미터로 전달.
// selectionSort 함수는 파라미터로 int 타입의 Array, int 값과 , bool (*comparisonFcn)(int, int) 함수 포인터를 받는다.
selectionSort(array, 9, ascending);
printArray(array, 9);
return 0;
}
예제에서 보는것과 같이 ascending 함수와 descending 함수를 selectionSort 펑션의 파라미터 값으로 전달을 하여 selectionSort 함수 안에서 ascending과 descending를 호출 하여 사용하는 것을 볼 수 있다.
그렇다면 왜? 이렇게 사용을 하는것일까? 함수 포인터를 사용하게 되면 우리는 다양한 함수를 selectionSort 함수 안에서 콜백펑션으로 사용을 할 수 있다. But 함수의 리턴 타입과 정의되어져 있는 파라미터 값이 일치 해야 한다.
위에서 언급한것과 같이 함수 포인터의 문법은 data-type (*fcnName)(parameters) 이다. 위에 예제에서 보는것과 같이 콜백펑션을 사용 하기 위에 bool (*comparisonFcn)(int, int) 를 파라미터로 정의 하였다. 만약 bool (*comparisonFcn)(int, int) 함수포인터를 전반적인 프로젝트 내에서 사용을 한다고 가정을 하면 코드의 가독성이 떨어진다.
이 문제를 해결 하기 위해서 우리는 type aliases를 사용 할 수 있다.
bool (*comparisonFcn)(int, int); 함수 포인터는 using comparisonFcn = bool(*)(int, int); 로 바꿀 수 있다.
comparisionFcn은 리턴 타입이 bool 이면서, 두개의 int 파라미터를 가지고 있는 함수를 포인팅 할 수 있다.
Type aliase를 사용하여 우리는 위 예제를 아래와 같이 바꿀 수 있다.
using comparisonFcn = bool(*)(int, int);
void selectionSort(int* array, int size, comparisonFcn compare)
// 이 부분도 comparisonFcn 이 아닌 compare로 바꿔 주어야 한다
// 세번째 인자로 받은 함수 펑션을 호출한다. comparisonFcn은 두개의 int 값을 비교하여 True or False를 리턴한다.
if (compare(array[bestIndex], array[currentIndex]))
{
bestIndex = currentIndex;
}
C++ 11부터 <functional> standard library 에 정의되어져 있는 std::function 을 사용하여 함수 포인터를 정의 할 수 있다.
#include <functional>
bool validate(int x, int y, std::function<bool(int, int)> fcn);
// std::function 펑션, 리턴 타입은 bool 이며, 두개의 int값을 파라미터로 받고 있다
위에 예문과 같이 < 리턴타입 ( 파라미터1, 파라미터2) > 정의되어져 있다. 만약 파라미터가 없고 bool 값만 리턴 한다면 std::function<bool () > 와같이 정의를 하면 된다.
std::function<void(const std::string&)> mErrorHandler;
// 리턴 값은 void 이며, const타입의 string 파라미터를 받는 함수 포인터 정의
Type aliase 와 std::function 을 함께 사용하기.
using comparisonFcn = std::function<bool(int, int)>;
void selectionSort(int* array, int size, comparisonFcn compare)
// 이 부분도 comparisonFcn 이 아닌 compare로 바꿔 주어야 한다
// 세번째 인자로 받은 함수 펑션을 호출한다. comparisonFcn은 두개의 int 값을 비교하여 True or False를 리턴한다.
if (compare(array[bestIndex], array[currentIndex]))
{
bestIndex = currentIndex;
}