본문 바로가기

Code/go

[golang] 구름으로 배우는 고언어 기초

한 눈에 끝내는 고랭기초

구름Edu에서 고랭배우고 있는데, 제가 자꾸 놓쳤던 부분을 메모해보려 합니다.

변수와 상수

이건 선언과 동시에 할당입니다. "선언"이 있다는 걸 까먹지 말아야겠습니다.

 

name := "velbie"

콘솔 출력과 입력

보통 Println() 을 사용하고, 좀더 자세히 찍을때 서식출력이 가능한 Printf() 를 사용했습니다.

입력은 주로 Scanf를 사용했습니다.

 

fmt.Println()
fmt.Print()
fmt.Printf()

var age, birth int
fmt.Scan(%age, &birth)

var height int
fmt.Scanf("%d\n", &height)

fmt.Scanfln()

%v 서식문자

이 문자를 봤을때 갸우뚱했습니다.

실수 출력할때 사용해야 겠습니다.

찾아보니깐 위에 코드처럼 여러 형식을 출력하기도 하고, 아래처럼 구조체 인스턴스를 출력할때도 사용됩니다.

구조체의 필드명까지 출력하려면 + 기호를 붙이고, 메타정보까지 출력하려면 #을 붙이면 됩니다.

 

fmt.Printf("모든 형식으로 출력: %v, %v\n", 54.234, "Hello")

type User struct { 
	name string 
    age int 
} 

func main() {
	u := User{"Gopher", 10} 
    fmt.Printf("%v", u) // {Gopher 10} 
}

fmt.Printf("%+v", u) // {name:Gopher, age:10} 
fmt.Printf("%#v", u) // main.user{name:"Gopher", age:10}

루프

일반 for 문, for range 문이 있습니다. for range는 map은 슬라이스와 map 모두 사용가능합니다.

 

for _, v := range studentSlice {
	fmt.Println(v.name, v.sex)

	for k, v := range v.subject {
		fmt.Println(k, v)
	}
	fmt.Println("----------")
}

배열과 슬라이스와 맵

슬라이스 선언법은 일반 선언법, make로 선언하는 법

맵 선언법도 일반 선언법과, make로 선언하는 법, 저는 아래와 같이 슬라이스는 make로, 맵은 그냥 선언합니다.

배열 append 하는게 없애고 새로 생성하는거여서 약간 특이합니다.

 

subject map[string]int = map[string]int{}
numbers []int = make([]int, 0, 0)
number = append(number, 1)

함수에 슬라이스 넘기고 받기

슬라이스를 num... 으로 펴서 넘기고 ...num 으로 모아서 받으면 됩니다.

 

func bubbleSort(nums ...int) []int{
	
	for j:=0; j<len(nums)-1; j++ {
		for i:=0; i<len(nums)-1; i++ {
			if nums[i] > nums[i+1] {
				var temp int
				temp = nums[i+1]
				nums[i+1] = nums[i]
				nums[i] = temp
			} 
	  }
	}
	return nums
}

nums = bubbleSort(nums...)

 

함수반환형과 괄호

함수반환형이 하나면 소괄호 없어도 되고 여러개면 있어야합니다.  그리고 예외로 Named return parameter 는 값이 하나라도 괄호를 사용해야합니다.

 

func inputFruit() (list []string) {
    return
}

함수넘기기와 일급함수

일급하수라는 의미는 함수를 기본타입을 사용하듯이 사용할수 있다는 것 입니다. String타입의 리터럴을 매개변수로 넘기고 반환할 수 있는 것 처럼, "함수타입의 리터럴을 매개변수로 넘기고 반환할 수 있습니다." -> 이게 말이 안되는데 일급함수에서는 말이 됩니다. 일급함수는 이급함수가 아닌 다른 타입과 동등한 일급이기 때문입니다.

이로써 클로져라는 개념이 가능해집니다. 

 

익명함수: 가볍게 찾지 않고 필요한 곳에 바로 실행할수 있는 함수가 필요합니다.  어디에 어떻게 사용되나면 이벤트처리, 콜백함수!, 클로져 'defer', 'Go루틴'등 , 변수에 초기화 등 여러곳에 사용됩니다.

변수에 초기화되는것은 일급함수니깐 가능한것 입니다. 이로써 또 다른 기능들이 사용가능해집니다. 

 

함수넘길때 너무 길어지니깐 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)
}

 

클로져

클로져를 통해 상태라는 개념이 생겨납니다.

클로져는 뭔가 복잡한 분기문같은거 를 추상화해서 함수로 구분을 해서 코드가 직관적으로 변하는것 같습니다. 

4개의 동전에 따라 계산하는 함수를 각각 만들면 낭비일 것입니다.
클로저를 이용해 함수를 한 개만 만들어보겠습니다.

 

package main

import "fmt"

/*타입문 작성은 선택입니다*/
type f func(int) int

func calCoin(cnt int) f { 
	sum := 0
	return func(amount int) int{ //클로저
		sum += cnt * amount
		return sum
	}
}

func main() {
	var coin10, coin50, coin100, coin500 int
	fmt.Scan(&coin10, &coin50, &coin100, &coin500)
	
	if coin10 < 0 || coin50 < 0  || coin100 < 0 || coin500 < 0  {
		fmt.Println("잘못된입력입니다.")
		return
	}
	add10 := calCoin(coin10)
	add50 := calCoin(coin50)
	add100 := calCoin(coin100)
	add500 := calCoin(coin500)
	
	totalmoney := add10(10) + add50(50) + add100(100) + 	add500(500)
	
	fmt.Println(totalmoney)
}

구조체

포인터 구조체(new) 말고 당분간 그냥 구조체 객체를 생성해서 사용해야 겠습니다.  생성자 함수도 있지만 지금은 잠깐 넘어가겠습니다. 구조체 안에 ,(콤마) 가 없습니다!

구조체는 사용자 임의로 하나 이상의 변수를 묶어 새로운 자료형을 정의한 것

 

package main

import "fmt"

type person struct {
	name    string
	age     int
	contact string
}

func addAgeRef(a *person) { //Pass by reference
	a.age += 4 //자동 역참조 * 생략
}

func addAgeVal(a person) { //Pass by value
	a.age += 4
}

func main() {
	var p1 = new(person) //포인터 구조체 객체 생성
	var p2 = person{}    // 빈 구조체 객체 생성

	fmt.Println(p1, p2)
	
	p1.age = 25
	p2.age = 25

	addAgeRef(p1) //&을 쓰지 않아도 됨
	addAgeVal(p2)

	fmt.Println(p1.age)
	fmt.Println(p2.age)

	addAgeRef(&p2) //&을 붙이면 pass by reference 가능
	fmt.Println(p2.age)
}

구조체 메서드

student := sStudent{} 이걸로 생성  충격의 포인터 리시버와 밸류 리시버, 여기서도 당분간 벨류 리시버만 사용하겠습니다.

 

package main

import "fmt"

type triangle struct {
	width, height float32
}

func (s triangle) triAreaVal() float32 { //Value Receiver
	s.width += 10
	return s.width * s.height / 2
}

func (s *triangle) triAreaRef() float32 { //Pointer Reciver
	s.width += 10
	return s.width * s.height / 2
}

func main() {
	tri1 := new(triangle)
	tri1.width = 12.5
	tri1.height = 5.2

	triarea_val := tri1.triAreaVal()
	fmt.Printf("삼각형 너비:%.2f, 높이:%.2f 일때, 넓이:%.2f \n", tri1.width, tri1.height, triarea_val)

	triarea_ref := tri1.triAreaRef()
	fmt.Printf("삼각형 너비:%.2f, 높이:%.2f 일때, 넓이:%.2f ", tri1.width, tri1.height, triarea_ref)
}

인터페이스

추상화(class)가 있으면 직관적일수 없는것 같습니다 . 그래서 고는 class는 없애고 인터페이스로 직관성을 강조한것 같습니다. 

매개변수로 인터페이스를 사용한다는 것은 구조체에 관계없이 인터페이스에 포함된 메소드를 사용하겠다는 뜻입니다.

 

printMeasure(r1, c1, r2, c2)

func printMeasure(m ...geometry) { //인터페이스를 가변 인자로 하는 함수
	for _, val := range m { //가변 인자 함수의 값은 슬라이스형
		fmt.Println(val.area()) //인터페이스의 메소드 호출
	}
}

 

아래 코드처럼 사용해도 역시 정상 작동합니다. 신기한점은 선언할때는 메서드와 인터페이스간의 연관성이 없지만. 사용할때는 인터페이스로 받아서 인터페이스에 선언된 껍데기 함수로 메서드를 호출하는게 신기했습니다.

 

package main

import (
	"fmt"
)

type geometry interface { //인터페이스 선언 Reat와 Circle 메도스의 area를 모두 포함
	area() float64
}

type Rect struct {
	width, height float64
}

func (r Rect) area() float64 {
	return r.width * r.height
}

func main() {
	r1 := Rect{10, 20}
	printMeasure(r1)
}

func printMeasure(r Rect) { //"인터페이스를 가변 인자로" 하는 함수
	var m geometry = r
	fmt.Println(m.area())
	// for _, val := range m { //가변 인자 함수의 값은 슬라이스형
	// 	fmt.Println(val.area()) //인터페이스의 메소드 호출
	// }
}