Go

Go 기본 문법 4

알맹리 2023. 7. 24. 19:51

1. 변수와 상수
2. 연산자
3. 자료형
4. 콘솔 출력과 입력 함수
5. 반복문
6. 조건문
7. 분기문

8. 제어문
9. 컬렉션
10. 함수

 

구름에듀의 '한 눈에 끝내는 고랭 기초' 강의를 참고했습니다.

 

9. 컬렉션

Go에서는 두 개 이상의 변수를 모아 놓은 것을 '컬렉션'이라고 합니다.

배열(Array)

Go에서 배열은 정적입니다. 고정된 배열 크기 안에 동일한 데이터를 연속적으로 저장합니다.

배열의 크기는 자료형을 구성하는 한 요소입니다.

var 배열이름 [배열크기]자료형

package main

import "fmt"

func main() {
	var arr1 [5]int   //길이가 5인 int형 배열 arr1을 선언
	fmt.Println(arr1) //숫자를 선언하지 않고 출력해보기

	arr1 = [5]int{1, 2, 3, 4, 5}        //배열 초기화
	fmt.Println(arr1, arr1[0], arr1[4]) //배열 전체와 인덱스에 저장된 값들 출력해보기

	arr2 := [4]int{4, 5, 6, 7} //:= 를 이용해 선언
	arr2[0] = 32               //인덱스를 이용해 값을 초기화
	fmt.Println(arr2)          //arr2 전체 출력해보기

	var arr3 = [...]int{9, 8, 7, 6} //[...]을 이용한 배열 크기 자동 설정
	fmt.Println(arr3, len(arr3))    //arr3 전체와  배열 크기 출력해보기
}

[...]을 이용해 배열 크기를 자동으로 설정할 수 있지만 만든 이후에 크기를 바꿀 수는 없습니다.

 

다차원 배열

package main

import "fmt"

func main() {
    var a = [3][3]int{
		{1, 2, 3},        
		{4, 5, 6},
		{7, 8, 9}, //3x3배열 초기화
    }
	
    fmt.Println(a[1][2]) //2행 3열의 값 출력
}

 

슬라이스(Slice)

슬라이스는 배열과 다르게 고정된 크기를 미리 지정하지 않고 이후에 필요에 따라 크기를 동적으로 변경할 수 있고, 부분 발췌가 가능합니다.

지금까지 다룬 자료형은 모두 자동으로 값을 할당하는데, 슬라이스는 선언할 때 크기를 미리 지정하지 않기 때문에 초기 값을 설정하지 않고 선언만 한다면 'Nil slice'가 됩니다. nil slice는 크기도, 용량도 없기 때문에 값을 지정할 수 없습니다.

 

슬라이스는 아무런 값을 초기화하지 않아도 배열의 위치를 가리키는 ptr과 배열의 길이 len, 전체 크기 cap 메모리를 가지고 있습니다.

슬라이스를 선언할 때는 선언과 동시에 값을 초기화할 때만 사용합니다. 

var a []int = []int{1, 2, 3, 4} -> 슬라이스를 선언함과 동시에 1, 2, 3, 4를 위한 메모리를 만든다는 의미

 

슬라이스는 참조타입이기 때문에 슬라이스를 복사하는 것은 같은 주소를 참조한다는 것입니다.

 

슬라이스의 길이와 용량을 지정하지 않고 선언만 해서 nil slice를 만들면 nil과 비교할 수 있고, true를 반환합니다.

package main

import "fmt"

func main() {
	var a []int        //슬라이스 변수 선언 아무것도 초기화 되지 않은 상태
	a = []int{1, 2, 3} //슬라이스에 리터럴값 지정

	a[1] = 10 //값이 할당되어 메모리가 생겼기 때문에 이렇게 접근 가능

	fmt.Println(a)

	var b []int //nil slice 선언

	if b == nil {
		fmt.Println("용량이", cap(b), "길이가", len(b), " Nil Slice입니다.")
	}
}

 

make() 함수를 이용한 슬라이스 선언

Go의 내장 함수인 make()를 사용하면 슬라이스를 생성함과 동시에 길이, 용량을 저장할 수 있습니다. 용량은 생략 가능합니다.

make(슬라이스 타입, 슬라이스 길이, 슬라이스의 용량) 

  • make 함수를 이용해 선언하는 슬라이스는 모든 요소가 0입니다.
  • make 함수를 이용해 슬라이스의 메모리를 할당하 후에 값을 다시 초기화하면 새로운 메모리를 할당하면서 그 전의 값은 사라집니다.
  • 길이; 초기화된 슬라이스 요소 개수, 후에 값을 추가하거나 삭제하면 그만큼 길이가 바뀝니다. len(컬렉션 이름)
  • 용량; cap(컬렉션 이름)
package main

import "fmt"

func main() {
	s := make([]int, 0, 3) // len=0, cap=3 인 슬라이스 선언

	for i := 1; i <= 10; i++ { // 1부터 차례대로 한 요소씩 추가
		s = append(s, i)

		fmt.Println(len(s), cap(s)) // 슬라이스 길이와 용량 확인
	}

	fmt.Println(s) // 최종 슬라이스 출력
}

 

슬라이스 추가, 병합, 복사

append() 함수를 이용해 슬라이스에 데이터를 추가할 수 있습니다.

슬라이스에 슬라이스를 추가해서 붙일 수 있습니다. 추가하는 슬라이스 뒤에 "..."을 입력해야 합니다.

package main
 
import "fmt"
 
func main() {
    sliceA := []int{1, 2, 3}
    sliceB := []int{4, 5, 6}
 
    sliceA = append(sliceA, sliceB...)
    //sliceA = append(sliceA, 4, 5, 6)
 
    fmt.Println(sliceA) // [1 2 3 4 5 6] 출력
}

 

copy() 함수를 이용해 한 슬라이스를 다른 슬라이스로 복사할 수 있습니다. copy(붙여넣을 슬라이스, 복사할 슬라이스)

package main

import "fmt"

func main() {
	sliceA := []int{0, 1, 2}
	sliceB := make([]int, len(sliceA), cap(sliceA)*2) //sliceA에 2배 용량인 슬라이스 선언

	copy(sliceB, sliceA)                              //A를 B에 붙여넣는다

	fmt.Println(sliceB)                               // [0 1 2 ] 출력
	println(len(sliceB), cap(sliceB))                 // 3, 6 출력
}

 

슬라이스의 일부분만 잘라서 복사할 수 있습니다. 붙여넣을 슬라이스 := 복사할 슬라이스[복사할 첫 인덱스:마지막인덱스+1]

package main

import "fmt"

func main() {
	c := make([]int, 0, 3) //용량이 3이고 길이가0인 정수형 슬라이스 선언
	c = append(c, 1, 2, 3, 4, 5, 6, 7)
	fmt.Println(len(c), cap(c))

	l := c[1:3] //인덱스 1요소부터 2요소까지 복사
	fmt.Println(l)

	l = c[2:] //인덱스 2요소부터 끝까지 복사
	fmt.Println(l)

	l[0] = 6

	fmt.Println(c) //슬라이스 l의 값을 바꿨는데 c의 값도 바뀜
	//값을 복사해온 것이 아니라 기존 슬라이스 주솟값을 참조
}

 

맵(Map)

맵은 key와 value 값을 매핑해 저장하는 해시테이블 입니다.

맵은 슬라이스와 같은 참조 타입 입니다.

map 선언 var 맵이름 map[key자료형]value자료형 -> 값을 초기화하지 않으면 Nil map이 됩니다.

package main

import "fmt"

func main() {
	var a map[int]string

	if a == nil {
		fmt.Println("nil map")
	}

	var m = map[string]string{ //key:value, 형식으로 초기화한다
		"apple":  "red",
		"grape":  "purple",
		"banana": "yellow",
	}

	fmt.Println(m, "\nm의 길이는", len(m))
}

 

map 변수의 추가, 갱신, 삭제

맵이름[key] = value 형식으로 값을 추가할 수 있습니다. 이미 있는 key값에 다시 다른 value 값을 저장한다면 최근 저장한 값으로 갱신됩니다.

delete(맵이름, key) 형식으로 key값에 해당되는 value 값이 같이 삭제됩니다.

package main

import "fmt"

func main() {
	//지역번호와 지역 저장
	var m = make(map[string]string)

	m["02"] = "서울특별시"
	m["031"] = "경기도"
	m["032"] = "충청남도"
	m["053"] = "대구광역시"

	fmt.Println(m)

	//동일한 key값으로 value값을 저장하면 갱신이 된다
	m["032"] = "인천"

	fmt.Println(m)

	//m에 있는 "031"key의 value와 함께 삭제
	delete(m, "031")

	fmt.Println(m)
}

 

map의 key 체크와 value 읽기

맵이름[key]; key에 저장되어 있는 value 값과 해당 키에 값이 존재하는지 안하는지 true/false 값도 반환합니다. 존재하지 않는 key값을 입력했다면 자료형에 따라 0이나 ""이 반환됩니다.

맵이름[key]는 value, true/false 순서로 반환하기 때문에 반환받고 싶은 값에 따라 변수를 설정해주면 됩니다.

  • val := 맵이름[key]
  • _, exist := 맵이름[key]
package main

import "fmt"

func main() {
	//지역번호와 지역 저장
	var m = make(map[string]string)

	m["02"] = "서울특별시"
	m["031"] = "경기도"
	m["032"] = "인천"
	m["053"] = "대구광역시"

	fmt.Println(m["032"])
	fmt.Println(m["042"], "빈 칸입니다.") //string형태로 존재하지 않는 key값은 ""가 출력된다

	val, exist := m["02"] //존재하는 key
	fmt.Println(val, exist)

	val, exist = m["042"] //존재하지 않는 key
	fmt.Println(val, exist)

	val = m["053"] //value 값만 반환
	fmt.Println(val)

	_, exist = m["053"] //true/false 값만 반환
	fmt.Println(exist)

	//맵도 똑같이 len() 함수를 사용할 수 있다 하지만 cap() 함수는 사용할 수 없다
	fmt.Println(len(m))
}

10. 함수

함수 선언 func 함수이름 (매개변수이름 배개변수형) 반환형

  • 함수 선언 키워드 func
  • 함수는 패키지 안에서 정의되고, 호출되는 함수가 꼭 호출하는 함수 앞에 있을 필요는 없습니다.
  • 반환값이 여러개라면 반환형을 괄호로 묶어 개수만큼 입력해야 합니다. 같은 형이라도 두 번 써야 합니다. (반환형1, 반환형2) 
  • 블록 시작 브레이스{가 함수 선언과 동시에 첫 줄에 있어야 합니다.
package main

import "fmt"

/*기능들만 모아놓은 함수들*/
func guide() { //매개변수 X 반환 값 X
	fmt.Println("두 정수를 입력받고 곱한 결과를 출력하는 프로그램입니다.\n두 정수를 차례로 띄어 써주세요.")
	fmt.Print("두 정수를 입력해주세요 :")
}

func input() (int, int) { //매개변수 X 반환 값 O(두 개)
	var a, b int
	fmt.Scanln(&a, &b)
	return a, b
}

func multi(a, b int) int { //매개변수 O, 반환 값 O
	return a * b
}

func printResult(num int) { //매개변수 O, 반환 값 X
	fmt.Printf("결과값은 %d입니다. 프로그램을 종료합니다.\n", num)
}

func main() { //간결해진 main문
	guide()
	num1, num2 := input()
	result := multi(num1, num2)
	printResult(result)
}

 

전역변수 지역변수

지역변수;

  • 중괄호 안에서 선언된 변수를 말하며, 지역변수는 선언된 지역 내에서만 유효합니다.
  • 지역변수는 해당 지역에 선언되는 순간 메모리가 생성되고 해당 지역을 벗어나면 자동으로 소멸됩니다.
package main

import "fmt"

func exampleFunc1() {
	var a int = 10 //지역변수 선언
	
	a++
	
	fmt.Println("exampleFunc1의 a는 ", a)
}

func exampleFunc2() {
	var b int = 20 //지역변수 선언
	var c int = 30 //지역변수 선언

	b++
	c++

	fmt.Println("b와 c는 ", b, c)
}

func main() {
	var a int = 28 //지역변수 선언

	exampleFunc1()
	exampleFunc2()

	fmt.Println("main의 a는", a)
}

 

전역변수;

  • 특정 지역(중괄호) 밖에서 선언된 변수를 말하며, 전역변수는 어느 곳에서든 유효합니다.
  • 전역변수는 코드가 시작되어 선언되는 순간 메모리가 생성되고 코드 전체가 끝날 때까지 메모리를 차지하고 있습니다.
package main

import "fmt"

var a int = 1 //전역변수 선언

func localVar() int { //지역변수로 연산
	var a int = 10 //지역변수 선언

	a += 3

	return a
}

func globalVar() int { //전역변수로 연산
	a += 3
		
	return a
}

func main() {
	fmt.Println("지역변수 a의 연산: ", localVar())
	fmt.Println("전역변수 a의 연산: ", globalVar())
}

 

매개변수

Pass by value;

인자의 값을 복사해서 전달하는 방식으로, 복사한 값을 함수 안에서 어떠한 연산을 하더라도 원래 값은 변하지 않습니다.

함수 호출 시; 함수이름(변수이름)

package main

import "fmt"

func printSqure(a int) {
	a *= a
		
	fmt.Println(a)
}
func main() {
	a := 4 //지역변수 선언
		
	printSqure(a)
		
	fmt.Println(a)
}

 

Pass by reference;

  • &: 주소, *: 직접참조
  • 함수 호출 시 주솟값 전달을 위해 함수이름(&변수이름)
  • 함수에서 매개변수 값을 직접 참조하기 위해서는 * 을 매개변수형 앞에 붙입니다.
package main

import "fmt"

func printSqure(a *int) {
	*a *= *a
	
	fmt.Println(*a)
}
func main() {
	a := 4         //지역변수 선언
	
	printSqure(&a) //참조를 위한 a의 주솟값을 매개변수로 전달
	
	fmt.Println(a)
}

가변 인자 함수;

전달하는 매개변수의 개수를 고정한 함수가 아니라 함수를 호출할 때마다 매개변수의 개수를 다르게 전달할 수 있도록 만든 함수입니다.

  • Go의 가변 인자 함수는 동일한 형의 매개변수를 n개 전달할 수 있습니다.
  • 전달된 변수들은 슬라이스 형태입니다.
  • 함수 선언 func 함수이름(매개변수이름 ...매개변수형) 반환형
  • 매개변수로 슬라이스를 전달할 수 있습니다. 함수이름(슬라이스이름...)
package main

import "fmt"

func addOne(num ...int) int {
	var result int

	for i := 0; i < len(num); i++ { //for문을 이용한 num[i] 순차 접근
		result += num[i]
	}
	
	return result
}

func addTwo(num ...int) int {
	var result int

	for _, val := range num { //for range문을 이용한 num의 value 순차 접근
		result += val
	}
	
	return result
}

func main() {
	num1, num2, num3, num4, num5 := 1, 2, 3, 4, 5
	nums := []int{10, 20, 30, 40}

	fmt.Println(addOne(num1, num2))
	fmt.Println(addOne(num1, num2, num4))
	fmt.Println(addOne(nums...))
	fmt.Println(addTwo(num3, num4, num5))
	fmt.Println(addTwo(num1, num3, num4, num5))
	fmt.Println(addTwo(nums...))
}

 

반환값

Go에서는 복수개의 반환값을 반환할 수 있습니다.

  • 반환값의 개수만큼 반환형을 명시해야 하고, 2개 이상인 경우 괄호 안에 반환형을 명시합니다.
  • 동일한 반환형이더라도 모두 명시해야 합니다.
package main

import "fmt"

func add(num ...int) (int, int) {
	var result int
	var count int
	
	for i := 0; i < len(num); i++ { //for문을 이용한 num[i] 순차 접근
		result += num[i]
		count++
	}
	
	return result, count
}

func main() {
	nums := []int{10, 20, 30, 40, 50}

	fmt.Println(add(nums...))
}

 

Named Return Parameter

반환형과 반환 값의 이름을 같이 명시하는 것을 말합니다.

  • (반환값이름1 반환형1, 반환값이름2 반환형2, 반환값이름3 반환형3, ...) 형식으로 입력합니다.
  • 반환값이름 반환형 자체가 변수 선언입니다. 따로 다시 선언하지 않아도 되고, 한다면 에러가 발생합니다.
  • return을 생략하면 안 됩니다.
  • 반환값이 1개더라도 반환값 이름을 명시했다면 괄호 안에 써야합니다.
package main

import "fmt"

func dessertList(fruit ...string) (count int, list []string) { //여기서 이미 선언된 것이다

	for i := 0; i < len(fruit); i++ {
		list = append(list, fruit[i])
		count++
	}

	return //생략하면 안 된다
}

func inputFruit() (list []string) { //Named return parameter는 값이 하나라도 괄호를 써야한다

	for {
		var fruit string
		fmt.Print("과일을 입력하세요:")
		fmt.Scanln(&fruit)

		if fruit != "1" {
			list = append(list, fruit)
		} else {
			fmt.Println("입력을 종료합니다.\n")
			break //반복문을 빠져나간다
		}
	}

	return
}

func main() {
	fmt.Println("디저트로 먹을 과일을 입력하고 출력합니다. \n1을 입력하면 입력을 멈춥니다.\n")
	count, list := dessertList(inputFruit()...) //함수를 변수처럼 사용할 수 있습니다
	fmt.Printf("%d개의 과일을 입력하셨고, 입력한 과일의 리스트는 %s입니다.\n", count, list)
}

필요에 따라 함수를 변수처럼 사용할 수 있습니다. 위 예시에서 inputFruit 함수를 dessertList 함수에 전달인자로 사용했습니다.

 

익명 함수

이름이 없는 함수입니다. 이름이 없다는 것 외에는 다른 함수와 동일합니다.

익명 함수는 만들고 바로 실행합니다. 

package main

import "fmt"

func main() {
	func() {
		fmt.Println("hello")
	}()

	func(a int, b int) {
		result := a + b
		fmt.Println(result)
	}(1, 3)

	result := func(a string, b string) string {
		return a + b
	}("hello", " world!")
	fmt.Println(result)

	i, j := 10.2, 20.4
	divide := func(a float64, b float64) float64 {
		return a / b
	}(i, j)
	fmt.Println(divide)
}

 

변수에 초기화한 익명 함수는 변수 이름을 함수의 이름처럼 사용할 수 있습니다.

package main

import "fmt"

func addDeclared(nums ...int) (result int) {
	for i := 0; i < len(nums); i++ {
		result += nums[i]
	}
	return
}

func main() {
	var nums = []int{10, 12, 13, 14, 16}

	addAnonymous := func(nums ...int) (result int) {
		for i := 0; i < len(nums); i++ {
			result += nums[i]
		}
		return
	}

	fmt.Println(addAnonymous(nums...))
	fmt.Println(addDeclared(nums...))
}

 

일급 함수(First-Class Function)

함수를 기본 타입과 동일하게 사용할 수 있어 함수 자체를 다른 함수의 매개변수로 전달하거나, 다른 함수의 반환 값으로 사용될 수 있습니다. 함수가 다른 타입보다 높은 수준의 용법이 아닌, 같은 객체로서 사용될 수 있습니다.

package main

import "fmt"

func calc(f func(int, int) int, a int, b int) int {
	result := f(a, b)
	return result
}

func main() {
	multi := func(i int, j int) int {
		return i * j
	}
	
	r1 := calc(multi, 10, 20)
	fmt.Println(r1)

	r2 := calc(func(x int, y int) int { return x + y }, 10, 20)
	fmt.Println(r2)
}

calc 함수는 함수 자체를 입력으로 받습니다.

calc 함수는 곱을 수행하는 익명함수(multi라는 변수에 선언된), 합을 수행하는 익명 함수를 전달 받아 2번 호출됩니다.

 

type문을 사용한 함수 원형 정의

type문을 사용해 함수의 원형을 정의하고, 사용자가 정의한 이름을 형으로 사용합니다.

package main

import "fmt"

//함수 원형 정의
type calculatorNum func(int, int) int 
type calculatorStr func(string, string) string

func calNum(f calculatorNum, a int, b int) int {
	result := f(a, b)
	return result
}

func calStr(f calculatorStr, a string, b string) string {
	sentence := f(a, b)
	return sentence
}

func main() {
	multi := func(i int, j int) int {
		return i * j
	}
	duple := func(i string, j string) string {
		return i + j + i + j
	}

	r1 := calNum(multi, 10, 20)
	fmt.Println(r1)

	r2 := calStr(duple, "Hello", " Golang ")
	fmt.Println(r2)
}

type문을 이용해 두 문자열을 복제하는 함수형을 calculatorStr로 정의하고, 두 정수를 합하는 함수형을 calculatorNum으로 정의했습니다. 따라서 두 함수를 전달받을 때 일일이 길게 선언하지 않고, 사용자가 정의한 형태만 명시할 수 있습니다.

'Go' 카테고리의 다른 글

Go 기본 문법 3  (0) 2023.07.24
Go 기본 문법 2  (0) 2023.07.23
Go 기본 문법  (0) 2023.07.23
Go가 무엇인고  (0) 2023.07.11