- 대문자로 시작하는 함수 (export/import)
어디선가 export될 함수(혹은 import되어서 사용된 함수)이다. 함수가 소문자로 시작한다면 그 파일 내에서만 사용될 것이라는 의미이다.
package main
import (
"fmt" // formating
)
func main() {
fmt.Println("hello world") // 외부 export되어서 import된 함수는 첫문자가 대문자
}
- 변수(var)와 상수(const), short declaration operator, iota
사실 일반 문법에서 보자면 Go의 방식이 맞다.
contant, variable.
:=(Short Assignment Statement) 는 함수 내부에서만 사용 가능하며 타입 추론이 일어난다.
JS : 상수(const) 변수(let) var는 사용 금지
Go : 상수(const) 변수(var) let 없음.
package main
import "fmt"
func main() {
// 상수는 const
const name string = "Darren"
const isHandsome bool = true
// 변수는 var
var friend string = "Martin"
friend = "Mckee"
fmt.Println(friend)
// := 는 타입 추론. '변수'에 할당
// := 는 함수(func) 내에서만 작동합니다.
coworker := "Billy"
fmt.Println(coworker)
}
상수는 아래와 같이 ()로 묶어 독특한 방법으로 선언할 수 있습니다.
- 할당하지 않으면 전 값을 가짐
- 상수에선 메모리 주소값에 접근할 수 없음
- iota를 적으면 index를 가짐
- 상수는 리터럴과 같은 취급이며, 컴파일될 때 리터럴로 변환되기 때문에 CPU 자원을 사용하지 않고, 계산된 값 자체로 변환됨. => 메모리 주소값에 접근할 수 없는 이유임.
package main
import "fmt"
const (
c1 = 10
c2 // 똑같은 10
c3 = iota // index
c4 // index
c5 // index
c6 = "what"
c7 // 똑같은 what
)
func main() {
fmt.Println(c1, c2, c3, c4, c5, c6, c7)
}
상수 선언시 타입을 명시할 수도 있고 하지 않을 수도 있습니다.
const (
a = 42
b int = 42
)
func main() {
fmt.Println(a)
fmt.Println(b)
}
iota를 다음과 같이 응용할 수 있습니다.
const (
_ = iota
kb = 1 << (iota * 10)
mb = 1 << (iota * 10)
gb = 1 << (iota * 10)
)
func main() {
fmt.Printf("%d \t %b\n", kb, kb) // 1024 10000000000
fmt.Printf("%d \t %b\n", mb, mb) // 1048576 100000000000000000000
fmt.Printf("%d \t %b\n", gb, gb) // 1073741824 1000000000000000000000000000000
}
타입없는 상수
이 녀석은 변수에 복사될 때 타입이 정해지기 때문에 여러 타입에 사용하고자 한다면 타입없는 상수가 편리합니다.
package main
const PI = 3.14 // typeless constant
const FloatPI float64 = 3.14
func main() {
var a int = PI * 100; // 가능
var b int = FloatPI * 100 // Error. type err
}
- Go의 타입
타입은 다음과 같다.
int/float에 bit가 세세하게 나뉘어져 있는 것이 인상깊다.
처음부터 int 32, 64 를 구별해가면서 할 필요 없다. 우선 ease하게 구성한 후 트래픽이 높아진 후 비트 한 쪽이라도 아껴야할 때 사용하면 된다.
- Built-in string type: string.
- Built-in boolean type: bool.
- Built-in numeric types:
- int8, uint8 (byte), int16, uint16, int32 (rune), uint32, int64, uint64, int, uint, uintptr.
- float32, float64.
- complex64, complex128.
go lang spec을 참고해보자. 2^8 이므로 256까지 가능하고,
unsigned가 아니면 반으로 갈라 127까지 가능하다는 어림셈법을 할 줄 알아야 한다.
uint8 the set of all unsigned 8-bit integers (0 to 255)
uint16 the set of all unsigned 16-bit integers (0 to 65535)
uint32 the set of all unsigned 32-bit integers (0 to 4294967295)
uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615)
int8 the set of all signed 8-bit integers (-128 to 127)
int16 the set of all signed 16-bit integers (-32768 to 32767)
int32 the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
Java로 이해하자면
int8 8비트(1byte). java로 치면 byte
int16. 16비트. java로 치면 short
int32 32비트. java로 치면 int
int64. 64비트. java로 치면 long
그렇다면 int는? 여러분이 Go를 실행하는 환경 따라 다릅니다.
int와 uint는 최소 4바이트 크기의 데이터 타입입니다.
이는 32비트 시스템에서는 4바이트(32비트) , 64비트 시스템에서는 8바이트(64비트)입니다
앞에 u가 붙으면 unsigned다.
*int 와 같이 앞에 *을 붙이면 포인터 변수를 선언할 수 있습니다. 이후에 살펴봅시다.
float32. 부동소수점 7자리 정밀성 보장. java의 float
float64. 부동소수점 15자리 정밀성 보장. java의 double
func main() {
var num int = 3
var num2 float32 = 3.14
var num3 float64 = 3.14
fmt.Println(unsafe.Sizeof(num)) // 64비트 시스템 8비트
fmt.Println(unsafe.Sizeof(num2)) // 4비트
fmt.Println(unsafe.Sizeof(num3)) // 8비트
}
complex는 복소수이다.
rune은 char이다. '' 홑 따옴표로 감싸줘야 한다.
반면 string은 "" 겹 따옴표로 감싸줘야 한다.
string은 `` (백틱)으로도 할당할 수 있습니다. 이 경우 이스케이프 문자까지 순수한 문자로 칩니다.
func main() {
var char rune = 'A'
var str string = "hello"
fmt.Println(char) // 유니코드 65 반환
fmt.Println(str)
}
각 타입에 지정된 byte에 주의하여 오버/ 언더플로우가 발생하지 않도록 주의합시다.
자료형 | 선언 | 크기(byte) |
실수 | float32 | 4 |
float64 | 8 | |
복소수 | complex64 | 8 |
complex128 | 16 |
자료형 | 선언 | 크기(byte) |
문자열 | string | 16 |
자료형 | 선언 | 크기(byte) |
정수(0, 양수) | byte | 1 |
정수 | rune | 4 |
go101.org/article/type-system-overview.html
rune이 필요한 이유 (한국어를 쓰려면 utf-8이 필요하니까)
string은 일종의 배열처럼 여겨진다. byte 슬라이스로 컨버젼하면 byte는 uint8(8비트) 이기 때문에 아스키 코드로 인코딩된다. 당연히 한글, 한자 등을 사용하고 있는에 byte로 컨버전하면 잘못된 결과물을 반환한다.
이럴 때는 rune(32bit = 4byte)을 사용하여 유니코드 포인트를 반환받으면 정상적인 결과물을 받을 수 있다.
func main() {
s := "안녕" // 한글이므로 ascii로 표현 불가
bs := []byte(s) // 8bit인 byte로 컨버젼
fmt.Println(bs) // ascii 번호 반환하나 잘못됨
rs := []rune(s) // 반면 run으로 컨버전
fmt.Println(rs) // [50504 45397] 과 같은 정상적인 유니코드 포인트를 반환함
}
만약 영어만 사용해서 ascii를 사용하는 byte도 문제가 없고, 이를 유니코드 포인트로 변환하고 싶다면 %#U를 통해 변환할 수 있다.
func main() {
s := "String"
fmt.Printf("%T\n", s) // string
bs := []byte(s) // byte는 uint8의 alias
fmt.Println(bs) // byte slice로 conversion [83 116 114 105 110 103] ascii 인코딩
fmt.Printf("%T\n", bs)
for i := 0; i < len(s); i++ {
fmt.Printf("%#U", s[i]) // Unicode code point로 변화 U+0053 U+0074 ...
}
}
- n 진법
8진수는 숫자 앞에 0을 붙이고, 16진수는 숫자 앞에 0x 또는 0X를 붙입니다.
package main
import "fmt"
func main() {
oct := 0723
fmt.Println(oct)
hex := 0xf
fmt.Println(hex)
}
func main() {
s := "H"
bs := []byte(s)
fmt.Println(bs[0])
fmt.Printf("%T\n", bs[0])
fmt.Printf("%b\n", bs[0]) // 1001000 binary
fmt.Printf("%x\n", bs[0]) // 48 16진법
fmt.Printf("%#x\n", bs[0]) // 0x48 16진법. x의 대,소문자 여부 차이는 기본
fmt.Printf("%#X\n", bs[0]) // 0X48 16진법
}
- 비트 연산자
& AND 연산, | OR 연산, ^ XOR 연산, &^ 비트 클리어 연산
<<, >>와 같은 시프트 연산자
package main
import "fmt"
func main() {
var x1 int8 = 34
var x2 int8 = 3
fmt.Printf("%b \n", x1 & x2) // AND 연산
fmt.Printf("%b \n", x1 | x2) // OR 연산
fmt.Printf("%b \n", ^x1) // 비트 반전
fmt.Printf("%b \n", x1 &^ x2) // 비트 클리어 연산
fmt.Printf("%b \n", x1 << 1) // 왼쪽 시프트
fmt.Printf("%b \n", x1 >> 1) // 오른쪽 시프트
}
func main() {
var a uint8 = 3
fmt.Printf("%d\n", a) // 3
fmt.Printf("%b\n", a) // 0000 0011
b := a << 1
fmt.Printf("%d\n", b) // 6
fmt.Printf("%b\n", b) // 0000 0110
c := a >> 1
fmt.Printf("%d\n", c) // 1
fmt.Printf("%b\n", c) // 0000 0001 (오른쪽으로 밀리면서 맨 우측에 있던 숫자는 사라짐)
}
비트 쉬프팅을 어디에 쓸까 했는데, 2진수의 용량 계산에 유용하다는 걸 알게 되었다.
func main() {
kb := 1024
mb := kb * 1024
gb := mb * 1024
fmt.Printf("%d \t %b\n", kb, kb) // 1024 10000000000
fmt.Printf("%d \t %b\n", mb, mb) // 1048576 100000000000000000000
fmt.Printf("%d \t %b\n", gb, gb) // 1073741824 1000000000000000000000000000000
}
커스텀 타입 underlying type
아래처럼 hotdog라는 타입을 만들어서 사용할 수 있습니다. 타입을 찍어보면 main.hotdog가 나오구요.
이 녀석은 Enum처럼 사용하면 유용합니다!
package main
import "fmt"
// hotdog라는 타입
type hotdog int
var b hotdog
func main() {
var a int
b = 3
// a = b error! main.hotdog를 int에 할당할 수 없음
a = int(b) // convert
fmt.Println(a)
fmt.Println(b)
fmt.Printf("%T", b) // main.hotdog
}
- 형변환 (conversion임. casting이 아님. go spec을 찾아보면 casting이란 말이 전혀 없음)
Conversion changes the type of an expression to the type specified by the conversion.
int + float => float 과 같은 묵시적 형변환은 생략하고
강제 형변환은 [type](var) 꼴로 가능합니다. java랑 똑같다고 생각하면 됩니다.
func main() {
var num int = 10
fmt.Println(unsafe.Sizeof(num)) // 8 byte (x64 시스템)
var change float32 = float32(num)
fmt.Println(change)
fmt.Println(unsafe.Sizeof(change)) // 4 byte
}
package main
import "fmt"
func main() {
var num1, num2, num3 int
fmt.Scanln(&num1, &num2, &num3)
tnum1 := float32(num1)
tnum2 := uint(num2)
tnum3 := int64(num3)
fmt.Printf("%T, %f\n", tnum1, tnum1)
fmt.Printf("%T, %d\n", tnum2, tnum2)
fmt.Printf("%T, %d\n", tnum3, tnum3)
}
- 함수 (func) 작성
JAVA랑 비슷함. Go도 마찬가지로 그와 같은 방식으로 타입을 선언해줘야한다. C의 문법과 상당히 닮아있다.
package main
import "fmt"
func multiply(a int, b int) int {
return a * b
}
func main() {
fmt.Println(multiply(2, 4))
}
- fmt 패키지
stdlib에서 가장 처음 보는 패키지입니다. fmt 패키지의 콘솔 출력 함수는 Println, Print, Printf 외에도
파일 출력을 위한 Fprintln, Fprint, Fprintf와
string형으로 반환되는 Sprintln, Sprint, Sprintf가 있습니다.
일반적으로 다른 패키지에서도 F함수들은 파일 출력을 위해 사용되고 S함수들은 반환 값을 string 형으로 출력하는 함수입니다.
선언 | 출력 형태 |
Println | 개행 자동 추가 |
개행 자동 추가하지 않음 | |
Printf | 포멧 지정자를 이용하여 개발자가 원하는 형태로 출력 |
선언 | 입력 형태 |
Scanln | 공백으로 구분하여 입력 |
Scan | 공백으로 구분하여 입력. 단, 입력을 마쳤을 때 개행(enter)해야 함 |
Scanf | 포멧 지정자를 이용하여 개발자가 원하는 형태로 입력 (근데 상식적으로, 포맷 대로 입력을 하기가 매우 번거롭기 때문에 좀처럼 사용하지 않음) |
기억하기 어렵다면 다음 처럼 기억하면 됩니다.
f가 붙음 => formmating 가능
ln가 붙음 => 공백으로 구분됨
예를 들어 Sprintf의 경우 출력물을 string 형태로 반환하되 formmating이 가능한 것입니다.
s := fmt.Sprintf("%d, %s, %t", x, y, z) // string 반환
fmt.Printf(s)
func main() {
var a = 234.1234;
var c = 13.14;
fmt.Printf("%08.2f \n", a) // 41234.12 즉, 8자리인데 소수점 2자리. 빈 공간은 0을 채움
fmt.Printf("%8.2f \n", a) // 41234.12 즉, 8자리인데 소수점 2자리. 빈 공간은 그냥 둠
fmt.Printf("%08.5g \n", a) // 04.1e+04 즉, 8자리인데, 출력되는 숫자는 5개
fmt.Printf("%f \n", c) // 아무 것도 안 되어 있으면 소수점 이하 6자리까지 출력
}
독특한 건 Go에선 int형 원소를 가진 배열들을 반복문 필요 없이 곧바로 아래처럼 출력할 수 있다는 겁니다.
func main() {
arr := [2]int{1, 2}
fmt.Printf("%d", arr)
}
서식 문자는 아래와 같은 것들이 있습니다
필요할 때나 찾아보면 됩니다. %v가 깡패다...
서식문자 | 출력 형태 |
%t | bool |
%b | 2진수 정수 |
%c | 문자 |
%d | 10진수 정수 |
%o | 8진수 정수 |
%x | 16진수 정수, 소문자 |
%X | 16진수 정수, 대문자 |
%f | 10진수 방식의 고정 소수점 실수 |
%F | 10진수 방식의 고정 소수점 실수 |
%e | 지수 표현 실수, e |
%E | 지수 표현 실수, E |
%g | 간단한 10진수 실수 |
%G | 간단한 10진수 실수 |
%s | 문자열 |
%p | 포인터 |
%U | 유니코드 |
%T | 타입 |
%v | 모든 형식 |
%#v | #을 이용해구분할 수 있는 형식 표현 |
package main
import "fmt"
func main() {
var a, b int;
n, err := fmt.Scan(&a, &b) // 변수의 메모리 주소를 파라미터로 받아야 함.
if err != nil {
fmt.Println(n, err)
} else {
fmt.Println(n, a, b)
}
}
package main
import "fmt"
func main() {
var num1, num2, num3 int
fmt.Scanln(&num1, &num2, &num3) // 공백으로 구분
result := num1 * num2 + num3
fmt.Printf("%d x %d + %d = %d", num1, num2, num3, result)
}
func main() {
var num1, num2, num3 int
fmt.Println("전화번호 입력")
fmt.Scanf("%d-%d-%d", &num1, &num2, &num3)
fmt.Println(num1, num2, num3)
}
패딩을 언어 자체에서 지원하기도 합니다.
package main
import "fmt"
func main() {
var a = 123;
fmt.Printf("%5d \n", a) // 우측 정렬
fmt.Printf("%05d \n", a) // 우측 정렬 + 빈 공간 0으로 pad
fmt.Printf("%-5d \n", a) // 좌측 정렬
}
Scan 다중 사용시 주의할 점!
키보드 입력을 예로 들어봅시다. 키보드 keyup 당 한씩 넘어오는 문자들의 stream은 기본적으로 선입선출입니다.
Hello 치면 H가 먼저 나와야 하는게 당연하겠죠?
그런데 스트림을 읽어내는 과정 중 에러가 발생하면, 처리되지 못하고 남아 있는 스트림을 제거할 필요가 있습니다.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
stdin := bufio.NewReader(os.Stdin) // Reader
var a, b int;
n, err := fmt.Scanln(&a, &b)
if err != nil {
fmt.Println(err)
str, err := stdin.ReadString('\n') // Scanln으로 받았으니 \n가 나올 때까지 읽어서 버퍼는 지우기
fmt.Println(str, err)
} else {
fmt.Printf("%d개의 변환 완료", n)
fmt.Print(a, b)
}
n, err = fmt.Scanln(&a, &b)
if err != nil {
fmt.Println(err)
stdin.ReadString('\n')
} else {
fmt.Println(n, a, b)
}
}
- runtime
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.GOOS) // window
fmt.Println(runtime.GOARCH) // amd64
}
'Programming Language > 🐿️ Go (Golang)' 카테고리의 다른 글
Go의 기초 문법 (3) : 포인터와 역참조 (0) | 2021.05.14 |
---|---|
Go의 기초 문법 (2) : 반복문 for, range, 조건문 if, switch (0) | 2021.05.14 |
Go 설치 (0) | 2021.05.14 |
Go 패키지 & Go 모듈 (0) | 2021.05.13 |
문자, 문자열 특집 : rune 타입과 string 관련 trick (0) | 2021.05.11 |