이 글은 골든래빗 《Tucker의 Go 언어 프로그래밍》의 03~8장 써머리입니다.
안녕하세요. 오늘은 간단한 Go의 역사와 코드가 실행되는 과정과 변수 및 연사자에 대해 리뷰할 예정입니다. 03~08 Chapter의 목차와 제가 리뷰할 때 pass 할 부분입니다. 참고로 책에는 연습문제들이 꽤 많은데 다지선답도 있어서 코딩 부분만 github 링크를 걸겠습니다. 내용은 더 보기 클릭
- 03 Hello Go World
- 3.1 Go 역사
- 3.2 Go 언어 특징 (pass)
- 3.3 코드가 실행되기까지
- 3.4 Hello Go World 코드 뜯어보기 (pass)
- 04 변수
- 4.1 변수란?
- 4.2 변수 선언
- 4.3 변수에 대해 더 알아보기
- 4.4 변수 선언의 다른 형태
- 4.5 타입 변환 (pass)
- 4.6 변수의 범위 (pass)
- 4.7 숫자 표현 (pass)
- 05 fmt 패키지를 이용한 텍스트 입출력
- 5.1 표준 입출력
- 5.2 표준 입력
- 5.3 키보드 입력과 Scan() 함수의 동작 원리
- 06 연산자
- 6.1 산술 연산자
- 6.2 비교 연산자
- 6.3 실수 오차 (pass)
- 6.4 논리 연산자
- 6.5 대입 연산자
- 6.6 연산자 우선순위 (pass)
- 07 함수
- 7.1 함수 정의
- 7.2 함수를 호출하면 생기는 일
- 7.3 함수는 왜 쓰나?
- 7.4 재귀 호출 (pass)
- 08 상수
- 8.1 상수 선언
- 8.2 상수는 언제 사용하나?
- 8.3 타입 없는 상수
- 8.4 상수와 리터럴
3.1 Go 역사
Go 언어는 2009년 구글에서 로버트 그리스머, 롭 파이크, 켄 톰슨 주축으로 개발된 오픈 소스 프로그래밍 언어를 발표하면서 세상에 등장했다. UNIX를 공부한 사람은 켄 톰슨이라는 이름을 들었던 기억이 있을 것이다 ㅋㅋ;;
2023년 2분기 때 github에서 pull requests 한 ranking 중 Go 언어가 3위를 장식하고 있다. 책에서는 2020년 3분기 기준으로 4위라 나와있는데 1위였던 Javascript가 5위로.... ㅋ 참고로 이 소스도 오픈 소스이기 때문에 github 주소를 남김
3.3 코드가 실행되기까지
본인이 생각했을 때 이 chapter에서 중요 한 내용은 module과 관련된 내용이지만 자세한 사항은 16장에 나온다 하니 일단 생략하고 리뷰해 보겠다. 책에서는 5단계?로 나눠 설명하고 있는데
- 폴더 생성
Go 언어에서 folder == package 다. 그 이유는 한 folder에 여러 개의 package들이 존재할 수 없으며, import 시 file 경로가 아닌 folder 경로 즉. 하나의 package를 사용하고 있는 모든 파일들을 import 한다. - *.go 파일 생성 및 작성 (go 확장자를 의미)
너무 당연하니 pass - Go 모듈 생성
Go 1.16 version부턴 Go module이 기본으로 적용됐다. 개인적으로 이 부분이 제일 귀찮기도 하고 제일 공부 안 해서 헷갈리는 부분이기도 한데 여기선 module init 하는 방법만 설정한다.
명령어는 위와 같다 moduleName 은 폴더 경로처럼 넣어 주면 되고 결과물로 go.mod라는 파일이 생성된다.go mod init moduleName
- 빌드
build라는 작업은 고급 언어(programming langange)를 기계어(cpu가 이해할 수 있는 0과 1로 이루어진 것)로 변환하는 작업이라 설명할 수 있다. 때문에 Golang은 GOOS와 GOARCH라는 환경 변수를 변경해서 자신에 맞는 운영체제와 cpu(architecture 아키텍처라고도 하는 듯)로 맞게 설정 후 자신의 코드를 build 할 수 있다. Golang이 지원하는 운영체제와 cpu를 go tool dist list 명령어를 사용해 확인할 수 있다. build의 결과물로 module init 할 때 사용한 moduleName과 같은 실행 파일이 생긴다. - 실행
moduleName과 같은 실행 파일을 실행하는 단계이다.
위에 방법들을 따라 해 보면
모듈을 초기화한 후 파일들 생성해 build 시 나오는 결과 파일의 이름은 ex이다.
참고로 main package가 없으면 build 자체가 안되니 꼭 필요하다.
4. 변수
책에서는 변수를 여러 chapter에 나눠 깊게 설명하지만 본인은 Golang에서 꼭 알아야 하는 부분과 가장 기본적인 내용만 리뷰하겠다. 책에 있는 내용을 그대로 스포 하는 게 맞지 않다 생각하기 때문에 주관적인 내용을 많이 넣을 것이다.
4.1 변수란?
본인은 어떤 프로그래밍 언어를 사용하든 변수는 어떤 것을 담을 수 있는 그릇의 이름표라 생각한다. 다만 여러 프로그래밍 언어마다 지원하는 방식이 다르기 때문에 강타입과 약타입으로 나눠지기도 하고 Golang은 강타입 언어라 변수에 어떤 것을 담냐에 따라 그릇 모양을 뚜렷하게 구분해야 한다. 프로그램적으로 표현하자면 어떤 것은 데이터, 그릇은 타입을 뜻하고 Golang에서 지원하는 타입은 정수, 실수, 문자열, 논리, 구조, 함수, 포인터, nil 등 여러 타입을 지원한다. 참고로 특이한 점으로 어떤 타입들은 별칭(alias)을 보유하고 있는데 byte는 uint8, rune은 int32에 대한 별칭이다. 엄청 특이한 부분도 있는데 int는 컴파일하는 OS가 처리할 수 있는 bit 단위에 따라 변화하는데 예를 들어 32bit 컴퓨터에서 int는 int32 크기를 갖지만 int32와 int를 연산 시 int32로 형변환 해야 한다는 점이다. 즉 int와 int32는 다르다.
4.2~4 변수 선언과 형태
변수를 선언을 이해하기 위해 선언, 초기화, 대입이라는 단어들을 이해해야 한다.
- 선언(declare)
컴파일러에게 데이터를 담는 그릇에 특정 이름표(변수명)를 붙고 사용하겠다 알려주는 것 - 초기화(initialization)
이름표(변수명)를 붙임과 동시에 데이터를 그릇에 넣는 것 - 대입(assignment)
이름표(변수명)로 그릇을 찾고 다른 데이터를 넣는 것
위와 같기 때문에 사용할 컴파일러가 컴파일할 때 변수명에 대한 몇 가지 규칙이 있는데 예약어(Golang의 명령어들 if, for, go, switch 등등...)들을 사용할 수 없고 변수명의 첫 단어에 숫자와 특수문자(언더바 제외)를 사용할 수 없다. 이 외 프로그래머들 사이에 지키면 좋은 권장 사항들을 code convention이라 하고 다양하게 있기 때문에 자신에게 맞는 방식을 찾아보면 좋을 듯하다.
Golang에서 변수를 선언하는 방법은 조금 많은 편인데 크게 명시적 방법과 묵시적 방법으로 구분할 수 있다.
참고로 Golang은 보통 세미콜론(;)을 붙이지 않도록 만들어진 언어이지만 콤마(,)는 엄청 잘 붙여야 한다.
- 명시적 방법
1-1. 변수 선언
var(variable)는 변수를 의미하고 a는 변수명 int는 a변수명이 저장할 수 있는 데이터 형식을 나타낸다. 이때 a변수명에는 int의 기본값인 0이 초기화된다.var a int
1-2. 변수 선언과 동시에 초기화
int 형식의 데이터를 저장할 수 있는 a라는 변수명을 갖은 변수를 선언과 동시에 10이라는 값으로 초기화한다.var a int = 10
1-3. 다중 변수 선언
1-1~2번을 기반으로 설정해 보면var ( a int -- 1번 b int = 10 -- 2번 c float64 = 0.12 -- 3번 ) a = 30 -- 4번
1. int형식의 데이터를 저장할 수 있는 a라는 변수를 선언 후 0이라는 값으로 초기화된다.
2. int 형식의 데이터를 저장할 수 있는 b라는 변수를 선언과 동시에 10이라는 값으로 초기화한다.
3. float64 형식의 데이터를 저장할 수 있는 c라는 변수를 선언과 동시에 0.12이라는 값으로 초기화한다.
4. a라는 변수에 30이라는 값을 대입한다. - 묵시적 방법
무시적 방법의 코드는 엄청 짧지만 코드에 대한 이록적인 부분이 꽤나 어려운 내용이다.
위와 같은 코드가 묵시적 방법인데 간단히 설명하면 a라는 변수명에 10이라는 값으로 초기화한다. 여기서 중요한 부분은 이때 a라는 변수명이 저장할 수 있는 데이터 형식은 무엇일까? 정답은 int이다. 이유를 설명하면 Golang은 타입추론이라는 개념이 존재하는데 타입추론을 간단하게 설명하면 값(value)에 의해서 변수가 저장할 수 있는 데이터 형식이 정해진다는 개념이다. 이때 주의해야 하는 부분은 정수의 기본 데이터 형식은 int, 실수는 float64다. 실무에서는 거의 묵시적 방법이 이점이 많아 주로 많이 사용된다.a := 10
5. fmt 패키지
솔직히 fmt는 error를 만들거나 format을 지정하여 출력 및 문자열로 만들기 위해 주로 사용되지 fmt을 사용하여 입력을 받거나 logging이 아닌 Print를 하는 경우는 드물다 생각한다.
참고로 본인이 다니는 회사에서 ubuntu 환경에 Golang 기반으로 만들어진 서버를 띄워 서비스 중이다. 가끔 테스트를 위해 fmt.Println를 사용하고 있긴 하지만 정작 fmt.Println으로 출력된 내용을 확인하기 위해서는 journalctl라는 쉘명령어로 확인할 수 있는데 아직까지도 이상한 로그가 어디서 출력되는지 못 찾고 있다.....
때문에 포맷문자만 리뷰하고 넘어갈 생각이다. (예제에서는 사용하기 때문에 찍먹 필수)
프로그래밍 언어에서는 여러 특수문자들을 사용하고 있는데 그중 % 기호는 나머지 연산자로도 사용하고 포맷문자임을 알리는 첫 문자로도 사용한다.
이렇듯 프로그래밍 언어에서는 특정 특수문자인 경우 명령어처럼 사용하는 문자들이 있는데 대표적으로 % 기호와 \(역슬래시) 기호가 있다. 예를 들면 \n은 개행하라는 명령어로 해석되는데 \(역슬래시)를 사용하면 다음에 오는 문자가 이스케이프 시퀀스(escape sequence)인 경우는 데이터가 아닌 명령어로 해석되고 단순히 특수문자가 오면 데이터로써 출력한다.
본론으로 돌아와 포맷문자에 대해 다시 이야기하겠다.
- %v
데이터 타입에 맞춰서 기본 형태로 출력합니다.
nil은 <nil>로 출력되고 문자 A('A')는 65로 표현되는데 이 이유는 rune은 int32의 별칭으로 사용되는 타입이기 때문이다. 즉 Golang에서 문자는 rune 타입인데 rune은 int32의 별칭임으로 'A'가 아닌 65가 출력된다는 뜻이다. 하지만 문자열 A("A")는 string 타입이기 때문에 A라 출력되는데 string인 기본적으로 rune의 나열이지만 Golang에서는 string을 불변(immutable)으로 관리하기 때문에 rune처럼 int32로 표현이 안 되는 것이다. - %T
데이터 타입을 출력합니다.
결과는 <nil> int32 string int로 나온다 당연하지만 rune은 int32다. - %t
bool 타입을 출력합니다.
결과는 쫌 특이한데 %!t(<nil>) %!t(int32=65) %!t(string=A) %!t(int=65) true false이다. bool 타입이 아니면 오류나 패닉이 아닌 포맷이 잘못 됐다고 출력이 된다. 본인은 nil과 false는 빼고 전부 true가 나올 것 같았는데 역시 최강타입을 지원하는 Golang인가... ?? - %d
10진수 정숫값으로 출력된다.
이 때도 정숫값이 아니면 %t 처럼 %!d(<nil>)로 나온다. - %b
binary의 약자를 사용하는 듯하고 2진수로 출력한다. - %c
정수 타입을 유니코드로 변경해 출력한다. - %f, %F
지수 형태가 아닌 실숫값 그대로 출력한다. - %s
문자열을 출력하는데 문자열이 아니면 %!s.... 이런 식으로 출력된다. - %p
이건 pointer를 출력하는 건데 메모리 주소를 출력한다 생각하면 된다. - 기타
%#v, %+v는 유용하긴 하나 struct를 리뷰할 때 같이 할 예정이라 뺏고 %q, %o, %O, %x, %X, %e, %E, %g, %G 이런 포맷 기호들은 사용해 본 적이 없어 pass~
6. 연산자
프로그래밍 언어를 소개하는 어떤 책이든 연산자를 소개하는 목차가 있는데 개인적으로 무언가를 만들기 위한 가장 기본인 내용이기 때문에 중요하지만 깊게 공부할 이유는 없다 생각한다. 그러므로 간단하게 설명해 보면 연산자는 크게 산술연산자와 논리연산자로 구분하는데 공통점은 연산자의 형태다. 단항연산자, 이항연산자, 삼항연산자 이런 식으로 n항연산자인 형태를 갖고 있는데 이걸 이해하기 위해서는 연산의 대상자인 피연산자(operand)를 이해해야 한다. 1 + 1이 있을 때 +는 이항연산자고 1은 피연산자를 의미한다. 단항연산자인 경우 부정(!) 연산자, 증감(++, --)가 있고 참고로 Golang에서는 삼항연산자를 지원하지 않는다. 이런 기본적인 개념만 이해했다면 +, -, *, /, %, &, |, ^, ^&, &&, ||, !, >>, <<, =, ==, *, ., [], () 등등 이러한 연산자들은 목적에 맞게만 사용하면 된다.
7. 함수
본인은 함수를 협업에서 제일 중요한 부분이라 생각하는데 함수를 선언할 때 어떤 이름을 사용하냐에 따라 굉장히 가독성이 좋아져 유지보수성도 같이 상승한다 생각한다. Golang은 조금 불편한..?? 점이 있는데 기본적으로 overloading이 불가능하다는 점이다. 즉 함수 시그니처가 함수명과 리시버로만 끝나는 것 같다. 반면으로 편한 점도 있는데 함수를 호출 시 여러 개의 반환값을 받을 수 있다. Golang은 함수에 접근제한자(access modifier)가 없어 파스칼케이스로 함수명을 정하면 public(다른 package에서도 접근 가능), 카멜케이스로 함수명을 정하면 private(같은 package에서만 접근 가능)처럼 동작한다. 여러 라이브러리들을 사용할 때 주로 struct를 많이 사용하는데 이 부분에도 적용할 수 있어 살짝 골떄리는 부분도 있지만 다음에 같이 리뷰하겠다. ㅎㅎ;;
본론으로 돌아오면 함수를 정의할 때 func이라는 예약어를 사용하며, 여러 방식으로 응용할 수 있는데 struct와 generic부분은 빼고 가장 기본인 여섯 가지 정도가 있다.
- input도 없고 output도 없는 함수
- input만 있는 함수
- input이 있는데 개수 제한 없이 받을 수 있는 함수(제약사항이 있음)
위 이미지는 파라메터를 개수 제한 없이 받을 수 있는 함수의 예시인데 타입은 같아야 하고 여러 타입의 파라메터를 받을 경우 파라메터의 마지막 위치에서 ... 기호를 사용해 똑같은 타입을 받을 수 있도록 코딩해야 한다.
s ...string의 타입은 []string 처럼 사용해야 하지만 함수 호출 시 parameter를 넣을 때 string으로만 넣는다.
결과를 출력해 보면
이런 식으로 출력되는 걸 확인할 수 있다. %#v 포맷은 Golang에서 변수를 표현하는 방식을 기반으로 값과 타입을 같이 출력해 준다. 참고로 fmt 패키지의 Println이나 Printf 함수들도 이런 방식으로 구현됐다. - output이 하나만 있는 함수
반환하는 타입을 파라메터를 명시하는 소괄호 옆에 명시한 후 함수의 body 영역에서 목적에 맞게 return 예약어를 사용해 반환한다. 사용 방식은 := 연산자를 사용하여 변수로 만들 수 도 있고 예시처럼 반환 값을 바로 사용할 수 있다.
- output이 여러 개 있는 함수
4번과 크게 다르지 않으나 반환하는 타입을 명시할 때 소괄호로 묶어야 한다.
4번일 때도 동일하지만 소괄호로 묶으면 변수를 선언하는 것 처럼 사용할 수도 있다.
주의할 점은 return 예약어를 비워두지 않고 값을 명시하면 그 값을 반환한다.
라이브러리들을 사용하다 보면 반환하는 값이 여러 개인데 사용하지 않는 값도 받아야 할 때가 있다. 그때는 언더바(_)로 무시할 수 있다.
Golang에서는 언더바(_)를 사용해 어떤 것을 무시하는 부분들이 꽤 많으니 기억해 두면 좋다. - input도 있고 output도 있는 함수
2~5번을 조합해서 사용하면 된다
이게 함수의 가장 기본적인 내용이지만 init이라는 함수명을 같은 함수를 정의하면 main함수 보다도 더 먼저 실행하는 함수도 있으니 참고로 알아두면 좋다.
8. 상수
상수는 설명할게 많이 없지만 개발할 때 요긴하게 사용되는 부분도 있다. 말 그대로 변하지 않는 값이고 몇 가지 규칙만 알고 있으면 끝이기 때문에 예제에서 자주 등장하니 그때마다 사용하는 방식을 이해하면 될 듯하다.
끄~읕!!
'Golang > Tucker' 카테고리의 다른 글
[묘공단] 4주차 (2) | 2023.10.22 |
---|---|
[묘공단] 3주차 (2) (1) | 2023.10.16 |
[묘공단] 3주차 (1) (2) | 2023.10.15 |
[묘공단] 2주차 (2) (1) | 2023.10.09 |
[묘공단] 1주차 (0) | 2023.09.24 |