대표 사진!

익명 함수

 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변수[키]" 호출합니다.

+ Recent posts