[Flutter] Dart 기초1

ivor·2022년 10월 17일
0

App

목록 보기
1/3

Flutter를 이용해 앱을 개발해보기로 했다. 때문에 우선 Dart에 대해 배워야 할 것 같아서 강의를 기반으로 Dart에 대해 공부하기로 했다.

앞으로 이어질 글들은 '[코드팩토리] [입문] Dart 언어 4시간만에 완전정복(from inflearn)'를 보고 공부한 내용들의 개인적 기록이 될 것이다.

기록하며 복습하고 나중에도 참고하려고 한다.

(혹시 틀린 점이 있거나 설명이 부족한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.)
(코드 작성은 DartPad를 사용했습니다. 간단하게 Run도 가능하고 에러가 나는 부분들을 모두 표시해줘서 편했습니다.)


변수 선언

int num = 10;
String sports = 'football';
double num2 = 5.3;
bool isTrue = true;

위처럼 변수를 선언할 때 맞는 변수의 type을 함께 선언한다.
int num = '10';처럼 변수 num의 type을 int로 선언해놓고 '10'이라는 문자열을 할당하면 오류가 발생한다.

비슷한 맥락에서 처음에 int형으로 선언한 변수에 대해 String형 값을 할당하려 하면 오류가 발생한다.

int num = 10;
num = '12'; // error!
int num = 10;
num = 12; // okay!

정리하자면 변수 선언 시에 type을 정해야 하고, 그렇게 정한 type을 재할당(수정) 시에도 고려해야 한다는 것이다.

그렇다면 처음에 특정 type을 정하고 싶지 않으면 어떻게 할까?

var

var를 활용하면 된다. var 사용 시 할당값을 바탕으로 type을 자동 추론한다. 이렇게 추론된 type은 runtimeType을 통해 확인할 수 있다.

var num = 10;
print(num.runtimeType);

다만 이렇게 선언된 type은 추후 수정할 수 없다. (다른 type의 값을 재할당할 수 없다는 의미이다. 같은 type의 값으론 수정할 수 있다.)

dynamic

dynamic도 활용할 수 있다. 마찬가지로 선언 시 할당값을 바탕으로 type을 자동 추론한다.

dynamic num = 10;
print(num.runtimeType);

var vs. dynamic

var와 다른 점은 초기 선언된 type과 다른 type으로 재할당이 가능하다는 점이다.

dynamic num = 10;
num = '12'; // okay!
var num = 10;
num = '12'; // error!

그런데 여기서 특이사항이 하나 있었다. 다음의 방식으로 var를 사용하면 다른 type의 값으로 재할당이 가능했다.

var num;
num = 10;
num = '12'; // okay!

print()를 이용한 출력

대부분의 언어와 마찬가지로 print()를 이용해 출력을 할 수 있었다.
보통은 print('abc');와 같은 형태로 문자열을 출력하는데 만약 문자열 안에 변수값을 넣어서 출력하고 싶다면 해당 변수를 ${}로 감싸면 된다.

int num = 11;
print('축구 경기에서는 팀당 ${num}명의 선수가 선발 출전할 수 있다.');

nullable vs. non-nullable

앞서 변수 선언 시 type을 함께 입력해야 한다고 했다. 그런데 어떤 변수에 정수가 할당될 수도, null이 할당될 수도 있다면 어떻게 해야 할까?
이때는 nullable 변수로 선언하면 된다.

int num = 10;
num = null; // error!
int? num = 10;
num = null; // okay!

반대로 변수에 !를 붙여 non-nullable임을 알릴 수 있다고 한다.

void main() {
  int? num = 10;
  print(num);
  num = null;
  print(num);
  num = 10;
  print(num);
  num = null;
  print(num!);
}

위의 코드를 실행하면 다음과 같은 출력을 확인할 수 있다.

final vs. const

어떠한 변수를 수정 불가능한 변수로 만들고 싶을 수도 있다. 그럴 때 finalconst를 사용할 수 있다.

final int num = 10;
num = 12; // error!

const int num2 = 10;
num2 = 12; // error!
// final, const는 뒤의 type을 생략해도 된다.
final num = 10;
const num2 = 10;

그럼 둘의 차이점은 뭘까?
const는 코드가 실행되기 전에, 즉 build될 때에 해당 변수에 할당되는 값이 정해져 있어야 한다.

다음과 같은 예시를 살펴보자.

final now = DateTime.now(); // okay!
const now2 = DateTime.now(); // error!

DateTime.now()해당 코드가 실행되는 순간의 시각이다. 때문에 final 코드는 올바르게 실행되지만 const로 시작하는 코드는 에러를 발생시킨다.
빌드될 때엔 '코드가 실행될 순간의 시각'을 결정지을 수 없기 때문이다.

참고링크: '컴파일, 링크, 빌드, 실행'

Operators

다양한 operator들이 있다.
사칙연산이나 i++와 같은 표현을 사용할 수 있다.
특이하거나 몰랐던 점들에 대해서만 기술하자면 다음과 같다.

  • ??=
    num ??= 3;
    변수 num이 null type이라면 3을 할당
    (구체적으로 들어가면 num = num ?? 3과 같은데 ??는 좌항이 null이 아니면 좌항값을, null이면 우항값을 반환한다.)
  • is
    • num is int;
      num이 int type이라면 true, 아니면 false 반환
    • num is! int;
      num이 int type이 아니면 true, 맞으면 false.

List, Map, Set

List

예시는 다음과 같다.

List<int> listExample = [1,2,3];

List<> 안에 실제 리스트의 원소 type을 적어준다.
다음의 매서드 등을 활용할 수 있다.

  • listExample.add()
  • listExample.remove()
  • listExample.indexOf()

listExample.length로 원소의 개수(길이)를 반환받을 수 있다.

Map

파이썬의 dictionary를 생각하면 될 것 같다. Key: Value 쌍을 가진다.
선언 예시는 다음과 같다.

// #1 
Map<String, int> ageOfStudent = {
	'James': 19,
    'Sarah': 18,
    'Tony' : 17
};

// #2
Map<String, int> ageOfStudent = {};
ageOfStudent['James']  = 19;
ageOfStudent['Sarah'] = 18;
ageOfStudent['Tony'] = 17;

// #3
Map<String, int> ageOfStudent = {};
ageOfStudent.addAll({
	'James': 19,
    'Sarah': 18,
    'Tony': 17
});

삭제는 Key와 remove()를 이용한다.

ageOfStudent.remove('Tony');

Set

Set은 중복을 제거하여 원소들을 저장한다.

Set<int> numbers = {1,2,3,3} // {1,2,3}으로 저장됨.

numbers.add(3);
numbers.remove(1);
numbers.contains(2);

조건문

if, else if, else

int num = 11;

if(num % 3 == 0){
	print('num % 3 == 0');
} else if(num % 3 == 1){
	print('num % 3 == 1');
} else {
	print('num % 3 == 2');
}

switch

int num = 11;

switch(num % 3){
	case 0:
    	print('num % 3 == 0');
        break;
	case 1:
    	print('num % 3 == 1');
        break;
	default:
    	print('num % 3 == 2');
        break;
}

반복문

for

// #1
for (int i=0; i<10; i++){ // ';'로 구분된 각 항은 각각 초기값, 반복문 실행조건, 각 반복이 종료된 후 수행할 작업을 의미한다.
	print(i);
} 

// #2
for (int i=0; i<10; i+=1){
	print(i);
}

while

// while
int i = 0;

while(i<10){ // while()안 조건에 대해 true면 {}안의 작업 수행
	print(i);
}

//do-while
int i = 0;

do{ // 일단 최소 한번 수행 후 while 조건 확인 및 반복.
	print(i);
}while(i<10);

function

함수를 선언할 때 대체로 반환형, 함수 이름, 파라미터, 함수 내용을 표기한다.

int funcExample(int x, String y, double z){
	print(x);
    print(y);
    print(z);
    
    return x;
}

positional parameters

위의 예시 속 파라미터를 말한다. 함수 호출 시 입력되는 값들이 순서대로 x, y, z에 할당된다.

named parameters

반면 순서와 상관없이 이름을 지정해서 할당해줄 수 있다.
다음 예시를 보자.

int funcExample2({int x, int y, int z}){
	print(x);
    print(y);
    print(z);
    
    return x + y + z;
}

void main(){
	print(funcExample2(y:2, x:3, z:1));
}

함수 선언 시 파라미터를 {}로 감싼다. named parameters로 취급하겠다는 의미이다.

함수를 호출할 땐 각 parameter명과 할당할 값을 :를 이용해 명시한다.
출력 화면은 다음과 같을 것이다.

함수 내의 print() 순서대로 x, y, z값이 차례로 출력되었고 함수 자체의 반환값인 x + y + z = 6이 마지막으로 출력되었다.

optional parameters

위의 예시들은 함수 호출 시 반드시 할당받아야 하는 파라미터들로 구성되었다. 하지만 때로는 어떤 파라미터는 선택적으로 받아올 때도 있다.

pos. params.

먼저 positional parameters의 경우엔 opt. params.를 []를 이용해 받는다. 다음 예시를 보자.

int funcExample3(int x, [int y, int z]){
	return x + y + z;
}

이렇게 선언할 경우 x는 반드시 받아와야 하는 파라미터, y와 z는 선택적으로 받아와도 되는 파라미터가 된다. 즉 funcExample3(3)처럼 함수를 호출할 수 있다는 뜻이다.

다만 중요한 점이 하나 있다.

  • y, z 위치에 값을 할당하지 않으면 y, z 각각은 null이 할당되는데 그럴 경우 int y, int z라는 부분에서 에러가 발생하게 된다. y, z는 int형으로 선언되었는데 null이 들어왔기 때문이다.

    • 여러가지 해결책이 있겠지만 앞서 배운 것들을 떠올려 int? y, int? z처럼 선언하여 nullable하게 바꿔주는 방안을 사용할 수 있을 것이다.
  • 하지만 또다시 오류가 발생하게 된다. x + y + z을 반환해야 하는데 정수와 null 사이에서 '+' 연산을 수행할 수 없기 때문이다.

    • 마찬가지로 여러 해결책이 존재할 것이다. 조건문을 사용하여 특정 값이 null일 경우 해당 값을 제외한 총합을 반환하는 방법이 있을 것이다.
    • 혹은 [int y=1, int z=2]와 같이 기본값을 활용하는 방법도 있을 것이다.

named params.

named parameter의 경우엔 반대로 필수적으로 입력받아야 하는 파라미터 앞에 required를 붙인다.

int funcExample4({required int x, int y, int z}){
	return x;
}

즉 여기서는 x는 반드시 입력받아야 하는 파라미터이고 y,z는 그렇지 않은 파라미터가 된다.

다양한 함수 선언 형태

// #1
funcExample5(String name, int backNumber, [String position='goalkeeper', int? age]){
}

// #2
funcExample6(String name, {required int backNumber, String position='goalkeeper', int? age}){
}

// #3
funcExample7(name, {required backNumber, position, age}){
}

type을 지정하지 않으면?

funcExample8(x){
	print(x.runtimeType(x));
}

void main(){
	funcExample8('1');
	funcExample8(1);
}

이처럼 parameter의 type을 지정하지 않으면 입력되는 값에 따라 자동으로 추론되는 것을 확인했다.
(함수 반환형도 마찬가지다.)

함수의 반환값?

앞서 봤듯이 return 뒤의 값을 함수의 반환값이라고 한다.
보통 함수를 선언할 때 가장 먼저 반환값의 type이 명시되고, 함수를 호출할 때 함수의 반환값이 나오게 된다.

String funcExample9(){
	return '9';
}

void main(){
	print(funcExample9());
}

이 코드의 경우 '9'를 출력하게 된다.

그리고 여기서 알 수 있듯 void는 아무것도 반환하지 않는 함수를 뜻한다.

void와 'null 반환'은 다르다.

funcReturnNull(){
}

void main(){
	print(funcReturnNull());
}

void funcVoid(){

void main(){
	print(funcVoid());
}

arrow function

funcExample10(int x, {required int y, required int z}){
	return x + y + z;
}

위 함수는 아래와 같이 선언할 수 있다.

funcExample10(int x, {required int y, required int z}) => x + y + z;

=>를 사용해서 return값을 명시해주는 것인데 이 때문에 'arrow'라는 이름이 붙은 것 같다.

typedef

일종의 함수 규격을 정의한다고 이해했다. 예시를 보는게 더 알아보기 편할 것 같다.

typedef Operation = int Function(int x, int y, int z);

int add(int x, int y, int z){
	return x + y + z;
}

void main(){
	Operation opertaion = add;
    print(operation(1,2,3));
}

Operation은 반환형이 int이고 필수로 입력받아야 하는 정수 위치 인자 x, y, z를 갖는 함수의 규격이라고 생각했다.

add함수는 같은 규격으로 정의되었기에 main 함수 내에서 변수 operation에 할당될 수 있다. 즉 operation(1,2,3)은 곧 add(1,2,3)과 그 동작이 같다고 볼 수 있다.

추가하자면 위 방식보다는 다음의 예시처럼 많이 쓰인다고 한다.

typedef Operation = int Function(int x, int y, int z);

int calculate(int x, int y, int z, Operation operation) => operation(x, y, z);

int add(int x, int y, int z) => x + y + z;

int subtract(int x, int y, int z) => x - y - z;

int multiple(int x, int y, int z) => x * y * z;

void main(){
	print(calculate(1,2,3,add));
    print(calculate(1,2,3,subtract));
    print(calculate(1,2,3,multiple));
}

파이썬이 가장 접근성이 높다고 하는 이유를 알게 되었다.😭

그래도 새로운 언어를 배우는 것 자체가 꽤 재밌었고 (비록 지금은 코끼리 더듬는 기분이지만) 언어가 가진 철학이나 추구하는 방향이 조금씩 보이는 것 같아 흥미로웠다. (type 명시, 언뜻 보면 불필요해 보이는 typedef의 사용 등)

profile
BEST? BETTER!

0개의 댓글