익명 함수
c++의 람다와 std::function을 사용해서 이름 없는 함수를 처리하는 것처럼 go에서도 이름없는 함수를 사용하는 방법을
제공합니다.
func main() {
// NoNamedFun이라는 변수에 익명함수를 설정합니다.
NoNamedfun := func(nums ...int) (total int) {
for _, num := range nums {
total += num
}
return
}
// 익명함수를 호출합니다.
println(NoNamedfun(5, 10, 15))
// 익명함수를 파라미터로 전달가능
CallNonamedFun(NoNamedfun)
}
// 익명의 함수를 파라미터로 전달
func CallNonamedFun(call func(nums ...int) (total int)) {
println(call(1, 2, 3, 4))
}
예제를 보면 c++보다는 더 일관된 형태의 이름없는 함수를 선언 할 수 있습니다.
또한 익명의 함수를 파라미터로 전달 할 수 있습니다. c++에서는 std::function을 사용해야 되지만
go에서는 직관적인 호출이 가능합니다.
//func(nums ...int) (total int)을 nonamed로 변환
type nonamed func(nums ...int) (total int)
func CallNonamedFun(call nonamed) {
println(call(1, 2, 3, 4))
}
또한 c++의 using처럼 함수형 파라미터를 직관적으로 표현할 수 있습니다.( 사용법은 typedef 비슷하네요.)
클로져
package main
func adder(str string) func(int) int {
sum := 0
return func(x int) int {
sum += x
println(str, " address = ", &sum, " sum = ", sum)
return sum
}
}
func main() {
pos, neg := adder("pos"), adder("neg")
pos(1)
neg(-2)
pos(1)
neg(-2)
pos(1)
neg(-2)
}
익명 함수를 사용하면 외부의 변수를 캡쳐 할 수 있습니다. 캡쳐된 변수를 함수내에서 사용 할 수 있습니다.
캡쳐된 변수는 함수마다 개별적인 주소 값을 가지고 있습니다. 예제를 보면 pos와 neg 함수에서 캡쳐한
sum이라는 변수는 서로 다른 주소 값을 가지는 것을 확인 할 수 있습니다.
Array
func main() {
// int형의 3개짜리 배열을!선언
var intArray [3]int
// Index는 0에서 부터
intArray[0] = 0
intArray[1] = 1
intArray[2] = 2
for index, val := range intArray {
println(index, val)
}
}
go에서는 Array를 통해서 연속적인 메모리 공간에 데이터를 저장하는 방식을 제공합니다.
Array의 첫요소의 인덱스는 0으로 할당됩니다.(c++과 같음, Zero based array)
Array는 고정된 크기를 가지기 때문에 선언한 크기 이상의 데이터를 담을 수 없습니다.
// 초기값을 지정할 수 있습니다.
intArray := [3]int{0, 1, 2}
// ...를 사용해서 배열의 크기를 생략 가능
intArray2 := [...]int{0, 1, 2}
Slice
// Slice를 literal로 선언합니다.
intSliceLiterals := []int{9, 8, 7, 6, 5}
fmt.Println(intSliceLiterals)
intArray := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Slice를 배열에서 추출합니다.
intSlice := intArray[1:4]
fmt.Println(intSlice)
// Slice를 Make함수를 통해서 생성합니다.
// make(타입, 길이, 용량)을 입력합니다.
intSliceMake := make([]int, 8, 12)
fmt.Println(intSliceMake)
go의 Array는 고정된 사이즈를 가지기 때문에 사용하기가 까다롭습니다.
(Array는 재할당이 없으므로 고정된 크기는 Array를 사용하는 것이 효율적입니다.)
반면에 Slice는 배열 요소에 대해서 동적으로 유연한 접근하는게 가능하기 때문에 실제로 더 많이 사용되어진
컬렉션입니다. (c++의 std::vector와 비슷)
Slice를 생성하려면 리터럴 타입으로 직접 선언하거나 make함수를 사용합니다.
부분 슬라이스
intArray := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Slice를 배열에서 추출합니다.
// 1번인자 ~ 3번인자까지 복사, 마지막 인덱스는 포함되지 않습니다.
intSlice := intArray[1:4]
fmt.Println(intSlice)
// 출력 {1, 2, 3}
Slice는 다른 Array나 Slice로 부터 부분적으로 할당 받을 수 있습니다.
예제에서는 intSlice는 intArray로부터 [1:4]범위의 인자를 할당 받습니다.
여기서 주의할 점은 범위의 첫번째 값은 inclusive고 마지막 값은 exclusive입니다.
예제에서 intArray[1:4]가 뜻하는 것은 intArray의 1번 인자부터 3번인자까지 Slice로 생성합니다.
추가적으로 인자값을 생략할 수 있는데요 첫번째 인자를 생략하면 0으로 인식,
두번째 인자를 생략하면 마지막 인덱스가 자동으로 대입합니다.
인덱스 범위 | 설 명 |
[2:5] | 2번 인덱스부터 4번 인덱스까지 Slice 생성 |
[:8] | 0번 인덱스부터 7번 인덱스까지 Slice 생성 |
[3:] | 3번 인덱스부터 마지막 인덱스까지 Slice 생성 |
[:] | 전체를 Slice로 생성합니다. |
Slice 추가 및 병합
func main() {
slice := make([]int, 1, 1)
fmt.Println(slice)
// {1} 인자를 추가합니다.
slice = append(slice, 1)
fmt.Println(slice)
// {2,3,4,5} 인자를 추가합니다.
slice = append(slice, 2, 3, 4, 5)
fmt.Println(slice)
}
배열은 크기 이상으로 데이터를 추가할 수 없지만 슬라이스는 동적으로 데이터를 추가할 수 있습니다.
append라는 함수를 통해서 가능한데 예제 소스를 참고하시면 사용방법을 익힐 수 있습니다.
인자를 추가하게 되면 내부적으로 메모리 재할당이 발생합니다.
func main() {
// SliceA와 SliceB를 선언
sliceA := []int{1, 2, 3, 4, 5}
sliceB := []int{6, 7, 8, 9, 10}
// SliceA와 SliceB를 병합 => SliceTotal
sliceTotal := append(sliceA, sliceB...)
fmt.Println("sliceTotal = ", sliceTotal)
}
append에 인자로 Slice를 추가하게되면 Slice를 병합할 수 있습니다. 주의 할 점은 두번째 슬라이스뒤에 ... 을 붙여야
합니다.
Slice 복사
// slice copy 결과를 출력합니다.
func CopyPrint(target []int, source []int) {
fmt.Println("target cap = ", cap(target))
copy(target, source)
fmt.Println(target)
}
func main() {
source := []int{0, 1, 2}
// target이 source보다 큰 경우
CopyPrint([]int{3, 4, 5, 6, 7}, source)
// target이 source보다 작은 경우
CopyPrint([]int{3, 4}, source)
// target이 source보다 같은 경우
CopyPrint([]int{3, 4, 5}, source)
}
copy함수를 통해서 slice를 복사할 수 있습니다.
예제 결과를 보면 copy는 target 크기만큼만 복사되도록 안전하게 구현되어 있는 것을 확인 할 수 있습니다.
Slice 재 할당
// Slice의 이전 용량을 저장합니다.
var beforeCap int = 1
// Slice에 인자를 할당 할때 메모리 변경 확인
func SliceAllocateCheck(slice *[]int) {
// slice의 길이와 용량, 주소값 출력
fmt.Println("Slice Len = ", len(*slice), " cap = ", cap(*slice), " address = ", &(*slice)[0])
// slice 에 인자 추가
*slice = append(*slice, 1)
// 용량이 변경되었다면 주소를 확인해서 재할당 확인
if beforeCap != cap(*slice) {
fmt.Println("reallocate Slice[0] address = ", &(*slice)[0])
beforeCap = cap(*slice)
}
}
func main() {
intSliceMakeNone := make([]int, 1, 1)
beforeCap = 1
fmt.Println("Start Slice[0] address = ", &intSliceMakeNone[0])
for i := 0; i < 17; i++ {
SliceAllocateCheck(&intSliceMakeNone)
}
}
// 최종 수행결과
Start Slice[0] address = 0xc000104060
Slice Len = 1 cap = 1 address = 0xc000104060
allocate Slice[0] address = 0xc000104090
Slice Len = 2 cap = 2 address = 0xc000104090
allocate Slice[0] address = 0xc0001020a0
Slice Len = 3 cap = 4 address = 0xc0001020a0
Slice Len = 4 cap = 4 address = 0xc0001020a0
allocate Slice[0] address = 0xc000114040
Slice Len = 5 cap = 8 address = 0xc000114040
Slice Len = 6 cap = 8 address = 0xc000114040
Slice Len = 7 cap = 8 address = 0xc000114040
Slice Len = 8 cap = 8 address = 0xc000114040
allocate Slice[0] address = 0xc00010e080
Slice Len = 9 cap = 16 address = 0xc00010e080
Slice Len = 10 cap = 16 address = 0xc00010e080
Slice Len = 11 cap = 16 address = 0xc00010e080
Slice Len = 12 cap = 16 address = 0xc00010e080
Slice Len = 13 cap = 16 address = 0xc00010e080
Slice Len = 14 cap = 16 address = 0xc00010e080
Slice Len = 15 cap = 16 address = 0xc00010e080
Slice Len = 16 cap = 16 address = 0xc00010e080
allocate Slice[0] address = 0xc000116000
Slice Len = 17 cap = 32 address = 0xc000116000
수행 결과를 확인해보니 Slice는 재할당이 발생될 때마다 현재 용량(capacity) 의 2배로 할당하는 것을 확인 할 수
있습니다.
Map
map은 key와 value로 구성되어 있으며 내부적으로는 해시 테이블로 구현 되어 있습니다. (c++ unordered_map 비슷)
func main() {
// 선언만 된 map은 nil의 값을 가집니다.
var mapContainer map[int]string
if mapContainer == nil {
println("ZeroValue map is nil")
}
// map을 출력해보면 주소값이 출력된다. pointer라는 이야기!
println(mapContainer)
// nil map에는 데이터 추가 불가 Runtime Error 발생
mapContainer[0] = "111"
}
map의 선언은 "var 변수명 map[key타입]value타입" 으로 할 수 있습니다.
선언만 된 map을 출력해보면 Address 타입이 출력되는 것을 보아 pointer 타입이라는 것을 확인 할 수 있습니다.
그렇기 때문에 선언만 된 map은 nil의 값을 가지며 데이터도 추가 할 수 없습니다.
만약에 선언만 된 map에 데이터를 추가하려고 하면 runtime에러가 발생됩니다.
func main() {
// 선언만 된 map은 nil의 값을 가집니다.
var mapContainer map[int]string
// make 함수를 통한 초기화 작업
mapContainer = make(map[int]string)
// 리터럴을 통합 초기화 작업
mapContainerLiterals := map[int]string{
0: "A",
1: "B",
2: "C",
}
}
map을 사용하려면 초기화를 통해서 메모리를 생성해야 하는데 make 함수와 리터럴 타입을 통해서
초기화 할 수 있습니다. 초기화 된 map에 대해서는 데이터 추가 및 삭제가 가능합니다.
Map 데이터 추가 삭제
func main() {
// 선언만 된 map은 nil의 값을 가집니다.
var mapContainer map[int]string
println(mapContainer)
// make 함수를 통한 초기화 작업
mapContainer = make(map[int]string)
mapContainer[0] = "goo"
mapContainer[1] = "gle"
delete(mapContainer, 0)
delete(mapContainer, 3)
}
map 데이터를 추가를 하기 위해서는 "map변수[키] = 값" 형태로 호출하면 데이터가 추가됩니다.
데이터를 삭제하기 위해서는 "delete(map변수, 키)" 호출을 통해서 삭제 할 수 있습니다.
Map 데이터 체크
func main() {
DicEnKo := map[string]string{
"Apple": "사과",
"Banana": "바나나",
"Cat": "고양이",
"Dog": "개",
}
// map의 데이터를 읽으면 2개의 데이터를 리턴합니다.
// 리턴값 : 인자값, 존재여부(bool)
val, exists := DicEnKo["Apple"]
if exists {
println("find Apple => ", val)
} else {
println("not find Apple")
}
}
map에 데이터를 체크하기 위해서는 "인자값, 존재여부(bool) = map변수[키]" 호출합니다.
'golang' 카테고리의 다른 글
[golang] goroutines (0) | 2020.04.05 |
---|---|
[golang] 구조체, 메서드, 인터페이스 (0) | 2020.04.03 |
[golang] 변수, 상수, 키워드, 함수, 반복문 (0) | 2020.03.30 |
[golang] Package, Import (0) | 2020.03.29 |
[golang] Window golang 개발 환경 구축하기 (0) | 2020.03.29 |