func sum(nums ...int) int {
sum := 0
fmt.Printf("nums 타입 : %T\n", nums)
for _, v := range nums {
sum += v
}
return sum
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5))
fmt.Println(sum(10, 20))
fmt.Println(sum())
}
이런식으로 ...
을 활용한다면 슬라이스 타입으로 인자를 받을수 있습니다.
만약 단순히 []int
형식으로 받게 된다면
func sum(nums []int) int {
sum := 0
fmt.Printf("nums 타입 : %T\n", nums)
for _, v := range nums {
sum += v
}
return sum
}
func main() {
fmt.Println(sum([]int{1, 2, 3, 4, 5}))
}
이런식으로 초기화한 값을 넣어주어야 합니다.
하지만 들어오는 타입이 여러개일떄도 있습니다.
sum(1,2,3,"Strig", 3.4)
등등이러한 경우에는 interface타입을 통해서 인자를 받을수 있습니다.
func sum(nums ...interface{})
하지만 여러개의 인자를 받게 되면 int타입이 아닌 경우도 생깁니다.
이런 경우에는 타입을 확인하면서 더해주어야 합니다.
func sum(nums ...interface{}) int {
sum := 0
fmt.Printf("nums 타입 : %T\n", nums)
for _, v := range nums {
fmt.Println(reflect.TypeOf(v))
if reflect.TypeOf(v) == reflect.TypeOf(sum) {
sum += v.(int)
}
}
return sum
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5))
fmt.Println(sum(10, 20, "string"))
fmt.Println(sum())
}
reflect.TypeOf
를 통해서 타입 확인이 가능합니다.defer 지연 실행
함수를 실행전에 실행을 보장하는 역할을 합니다.
함수내에서 작성이 가능하며 적용이 되는 부분은 함수 지연이 됩니다.
func add(a,b int) int{
defer 1번 코드
2번 코드
return a+b
}
이런 함수가 있다면
함수가 실행이 되면 2번이 먼저 실행이 됩니다.
그후 함수에 있는 코드가 다 실행이 되고 이제 return을 해야할때
Return이 되기전에 defer가 실행이 됩니다.
함수실행
-> 2번 코드 작동
-> 1번 코드 작동
-> return 작동
package main
import (
"fmt"
"os" // 파일을 사용해야 하기 떄문에
)
func main() {
f, err := os.Create("test.txt") //os에 파일이 있다면 호출
if err != nil {
fmt.Println(err)
return
}
defer fmt.Println("defer호출전 호출되는것")
defer f.Close()
defer fmt.Println("defer호출후 호출되는것")
fmt.Println("defer을 적용하지 않는 문구 입니다.")
fmt.Fprintln(f, "hello world")
- 이 부분은 파일에 적는 부분 입니다.
}
파일을 만드는 코드입니다.
파일을 만들떄에는 os와 소통을 하게 되고 이후 os의 자원을 반납해야 합니다.
os에 파일 핸들 요청
-> os에서 파일 핸들 제공
-> 작업을 완료하고 파일 핸들 반환
메모리를 가져오는 방식과 같기 떄문에 메모리를 가져오게 되고 이후 다시 반환해주는 작업을 의미합니다.
함수 내부에 함수 사용
func add(a, b int) int {
return a + b
}
func mul(a, b int) int {
return a * b
}
func getoperator(op string) func(int, int) int {
if op == "+" {
return add
} else if op == "*" {
return mul
} else {
return nil
}
}
func main() {
var operator func(int, int) int
operator = getoperator("+")
var result = operator(3, 4)
fmt.Println(result)
}
add, mul이라는 함수를 포함하고 있는 getOperator
함수를 이런식으로 활용가능합니다.
operator이라는 변수는 함수 타입이며 getoperator
함수로 초기화를 시켜줍니다.
이후 반환되는 값을 가지고 다른 변수에 할당함으로써 함수 활용이 가능합니다.
하지만 이런식으로 함수를 직접 적어서 타입을 선언하는 것은 매우 지저분하기 떄문에 보통은 별칭타입을 추가하여 작성합니다.
type Open func(int, int) int
이 부분은 주로 Youtube에 기록을 하였습니다.
링크드 리스트
다음 값을 가르키고 있는 자료구조 입니다.
간단하게 "container/list"
를 통해서 패키지를 활용 가능하고
앞쪽과 뒤쪽의 값을 기억하고 있는 링키트 리스트 같은 경우에는 더블 링크트 리스트 라고 합니다.
package main
import (
"container/list"
"fmt"
)
func main() {
v := list.New()
e4 := v.PushBack(4)
e1 := v.PushFront(1)
v.InsertBefore(3, e4)
v.InsertBefore(2, e1)
for e := v.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value, "")
}
fmt.Println()
for e := v.Back(); e != nil; e = e.Prev() {
fmt.Print(e.Value, "")
}
}
시간 복잡도에 대해서는 다루지 않았습니다.
어느 자료구조가 빠른지 적은지는 상황에 따라 달라지기 떄문입니다.
일반적으로 리스트의 경우에는 삽입 삭제에는 배열보다 우수하지만 이런 차이는 데이터가 많을떄에 유리하고
그냥 단순히 배열의 인덱스에 접근을 하는 행위는 리스트보다 배열이 더 우수합니다.
즉 데이터가 많고, 데이터의 삽입, 삭제가 많을떄에는 리스트를 고려해야 하지만
이러한 상황은 정말 수만개의 데이터가 있을때에 더 좋은 효율성을 발휘하는것이기 떄문에
일반적인 경우에는 배열을 사용하는것으로 알고 있습니다.
큐
들어간 순서대로 나오는 구조 입니다.
package main
import (
"container/list"
"fmt"
)
type Queue struct {
v *list.List
}
func (q *Queue) Push(val interface{}) {
q.v.PushBack(val)
}
func (q *Queue) Pop() interface{} {
front := q.v.Front()
if front != nil {
return q.v.Remove(front)
// remove 를 하면 Return값이 값을 뺸 값을 반환합니다.
} else {
return nil
}
}
func NewQueue() *Queue {
return &Queue{list.New()}
}
func main() {
queue := NewQueue()
for i := 1; i < 5; i++ {
queue.Push(i)
}
v := queue.Pop()
for v != nil {
fmt.Printf(" -> %v \n", v)
v = queue.Pop()
}
}
넣을떄에는 맨 뒤로 넣게 되고 뺼때에는 맨 앞에서부터 뺴는 역할을 하게 됩니다.
스택
반대로 맨 처음에 넣은 구조가 반대로 나오는 구조 입니다.
package main
import (
"container/list"
"fmt"
)
type Stack struct {
v *list.List
}
func (q *Stack) Push(val interface{}) {
q.v.PushBack(val)
}
func (q *Stack) Pop() interface{} {
back := q.v.Back()
if back != nil {
return q.v.Remove(back)
// remove 를 하면 Return값이 값을 뺸 값을 반환합니다.
} else {
return nil
}
}
func NewStack() *Stack {
return &Stack{list.New()}
}
func main() {
stack := NewStack()
books := [5]string{"a", "b", "c", "d", "e"}
for i := 0; i < 5; i++ {
stack.Push(books[i])
}
v := stack.Pop()
for v != nil {
fmt.Printf(" -> %v ", v)
v = stack.Pop()
}
}
ring
뱀이 뱀의 꼬리를 무는 구조로
맨 끝에 있는 값이 첫번쨰 값을 가르키는 구조 입니다.
package main
import (
"container/ring"
"fmt"
)
func main() {
r := ring.New(5)
n := r.Len()
for i := 0; i < n; i++ {
r.Value = 'A' + i
r = r.Next()
}
for j := 0; j < n; j++ {
fmt.Printf("%c ", r.Value)
r = r.Next()
}
fmt.Println()
for j := 0; j < n; j++ {
fmt.Printf("%c ", r.Value)
r = r.Prev()
}
}
맵
키와 값 형태로 데이터를 저장하는 자료구조
golang에서는 내장타입으로 활용 가능합니다.
map[키타입]값타입
map[string]int
m := make(map[string]string)
m["test"] = "테스트 입니다."
m["test2"] = "테스트2 입니다."
fmt.Printf("1은 %s입니다.", m["test"])
map을 사용할떄에는 항상 make
를 통해서 초기화를 해주어야 합니다.
맵을 순회할때에도 range를 사용가능합니다.
for k,v := range test{
fmt.Println(k,v)
}
하지만 이떄 나오는 키, 데이터는 순서보장이 안됩니다.
test := make(map[int]string)
test[1] = "string"
test[5] = "a"
// 후에 range를 활용하여 순회하면
// key값이 순서대로 1, 5가 나오지 않을수도 있습니다.
hashMap
을 사용하기 떄문입니다.삭제할떄에는 delete
를 활용합니다.
delete(맵 변수, 키 값)
또한 값이 있는지를 확인할떄에는 v,ok := m[3]
과 같이 확인 가능합니다.
m := make(map[string]string)
m["test"] = "테스트 입니다."
m["test2"] = "테스트2 입니다."
delete(m, "test")
v,ok:= m["test"]
fmt.Printf("1은 %s입니다.", m["test"])