[golang] 함수

함수

  • 함수의 기본적인 특징
  1. 함수 선언할 때 쓰는 키워드는 'func'이다.
  2. '반환형'이 괄호()뒤에 명시된다. 물론 '매개변수형'도 '매개변수이름'뒤에 명시된다.
  3. 함수는 패키지 안에서 정의되고 호출되는 함수가 꼭 호출되는 함수 앞에 있을 필요는 없다.
  4. '반환값'이 여러 개일 수 있다. 이럴 때는 '반환형'을 괄호로 묶어 개수만큼 입력해야한다. (반환형1,반환형2)형식, 두 형이 같더라도 두 번써야한다.
  5. 블록시작 브레이스({)가 함수 선언과 동시에 첫 줄에 있어야 한다.
  • 함수를 호출하는 방법은 함수이름(전달인자이름)이다.

  • 함수는 기본적으로 매개변수와 리턴값의 유 무에 따라서 4개의 형태로 나눌 수 있다.

  1. 매개변수가 있고, 반환 값도 있는 형태
  2. 매개변수가 있고, 반환 값이 없는 형태
  3. 매개변수가 없고, 반환 값이 있는 형태
  4. 매개변수가 없고, 반환 값이 없는 형태
package main

import "fmt"

func guide(){
	//매개변수가 없고 , 반환값도 없음
	fmt.Print("두 정수를 입력해 주세요.\n")
}

func input()(int,int){
	//매개변수가 없고, 반환값이 2개
	var a,b int
	fmt.Scanln(&a,&b)
	return a,b 
}
func multi(a,b int) int {
	// 매개변수가 존재하고, 반환값도 존재 
	return a * b
}

func printResult(num int){
	//매개변수가 존재하고, 반환값은 없음. 
	fmt.Printf("결과값은 %d입니다\n",num)
}



func main(){
	guide() 
	num1, num2 :=input() //반환값 2개가 num1,num2로 들어감
	result := multi(num1,num2)
	printResult(result)
}

- 전역변수와 지역변수

  • 지연변수와 전역변수는 위치에 따라 그 유형이 결정된다. 지역변수는 중괄호{}안에서 선언된 변수를 말한다. 이렇게 선언된 지역변수는 선언된 지역내에서만 유효한다. 반대로 전역변수는 중괄호 밖에서 선언된 변수를 말한다.이 두 변수의 차이는 1. 메모리에 존재하는 시간 2. 변수에 접근할 수 있는 범위이다. 지역변수는 해당 지역에서 선언되는 순간 메모리가 생성되고, 지역을 벗어나면 자동으로 소멸된다. 하지만 전역변수는 코드가 시작되서 선언되는 순간 메모리가 생성되고 코드전체가 끝날때까지 메모리를 차지한다. 이것이 메모리에 존재하는 시간이다.
package main

import "fmt"

func exampleFunc1(){
	var a int = 10
	
//main이 종료된 상황이 아니기때문에 여전히 main의 a는 존재, 지역변수는 실행되고 있는 지역에서만 유효하므로 다른 지역 변수의 변수명과는 상관없음. 따라수 변수명이 같ㅇ면 가린다는 느낌으로 생각하기 
	
	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 // 지역변수 선언 28로 초기화 
	
	exampleFunc1()
	exampleFunc2()
	
	fmt.Println("main의 a는", a)
	
}
package main

import "fmt"

var a int = 1 // 전역변수 선언 (main보다 먼저 memory 할당됨->언제어디서든지쓸수있음.)

func localVar()int{
	var a int = 10 //지역변수 (이름이 같지만 전역변수를 가림)
	
	a +=3
	return a
}

func glovalVar()int{
	a += 10 // 전역변수 사용
	
	return a
}
func main(){
	fmt.Println("지역변수 a의 연산", localVar())
	fmt.Println("전역변수 a의 연산", glovalVar())
}

- 매개변수

  • go언어에서 함수는 func 함수이름(매배변수이름 매개변수형)반환형이 기본형식이다. 여기서 중요한 부분은 '함수이름','매개변수','반환형'이다. 왜냐허면 각 부분의 사용법에 따라 함수의 기능과 역할이 바뀌기 때문이다.

pass by value

이 방식은 인자의 값을 복사해서 전달하는 방식이다. 따라서 복사한 값을 함수안에서 어떠한 연산을 하더라도 원래 값은 변하지 않는다. 함수를 호출할 때는 함수이름(변수이름)만 입력하면 된다.

package main

import "fmt"

func printSqure(a int){
	a *= a
	
	fmt.Println(a) // 16 
}

func main(){
	a := 4 // 지역변수
	
	printSqure(a) 
	
	fmt.Println(a) //4
    // 복사한 값을 함수안에서 어떠한 연산을 해도 원래 값은 변하지 않는다.
}

pass by refernece

go 언어에서는 c언어의 핵심 개념인 '포인터'라는 개념을 지원한다.

&: 주소, * 직접참조

  • c언어에서는 배열 이름 자체가 배열의 첫번째 인덱스 요소의 주솟값이지만 go언어는 그런것이 없고, 주솟값은 어떤 변수 앞에 &을 붙이는 것만 기억하면 된다.
  • go언어에서는 직접 참조를 원하면 포인터 변수 앞에 *를 붙이면 된다.
  • 함수를 호출할떄는 주솟값 전달을 위해 "힘수이름(&변수이름)"을 입력하고 함수에서 매개변수이름을 입력할때는 값을 직접 참조하기위해서 *를 매개변수형 앞에 붙인다. 그리고 함수 안의 매개변수앞에 모두 * 붙여야한다.
package main

import "fmt"


func printSqure(a *int){
	
	*a *= *a
	
	fmt.Println(*a) //16
	
}

func main(){
	a := 4 // 지역변수 선언
	
	printSqure(&a) // 참조를 위해서 a의 주솟값을 매개변수로 전달
	
	fmt.Println(a) //16
    //  // 이렇게 a의 값을 printSqure 함수안에서 참조함으로써 다른함수에서 연산을 했음에도 원래 값이 변함

} 

가변인자함수

'가변 인자 함수'는 전달하는 매개변수의 개수를 고정한 함수가 아니라 함수를 호출할때마다 매개변수의 개수를 다르게 전달할 수 있도록 만든 함수이다. 보통 함수를 만들때 매개변수의 개수를 정해놓는다. 하지만 함수를 사용하다보면 매개변수의 개수가 변할때가 존재한다. 이럴때 go언어의 가변 인자 함수로 동일한 현의 매개변수를 n개 전달할 수 있다. 이떄 조건은

  • n개의 동일한 형의 매개변수를 전달한다.
  • 전달된 변수둘은 슬라이스 형태이다.
  • 함수의 선언은 "func 함수이름(매개변수이름... 매개변수형) 반환형" 형식이다. 즉 매개변수 앞에 ...을 꼭 붙여야한다.
  • 매개변수로 슬라이스를 전달할수 있는데 (다른 컬렉션은 불가) 슬라이스를 전달할 때는 슬라이스 이름 뒤에 ...를 붙여서 "함수이름(슬라이스이름...)"형식으로 함수를 호출해야한다.
package main

import "fmt"

func addOne(num ...int) int{
	var result int
	fmt.Println(num) // [1,2]

	for i:= 0; i < len(num); i++{
		result += num[i] // 매개변수가 슬라이스롤 들어오기 때문에 
	}
	
	
	return result
}


func main(){
	
	num1,num2 := 1,2
	nums := []int{10,20,30,40} 
	
	fmt.Println(addOne(nums...)) // 슬라이스 넘기기
	fmt.Println(addOne(num1,num2))
	
	
}

- 반환값(리턴값)

반환값

함수를 선언할 때는 매개변수를 굳이 사용하지 않아도 된다. 그리고 '가변 인자 함수'를 사용하면 고정된 개수의 매개변수를 전달하지 않아도 된다. 이처럼 go언어는 다른 언어와 다른 반환값의 특징이 있다. 바로 복수개의 반환값을 반환할수 있다는 것이다.

  • 반환값의 개수만큼 반환형을 명시해야한다. 2개 이상의 반환형을 입력할 때는 괄호()안에 명시한다.
  • 동일한 반환형이라도 모두 명시해야한다. ex) (int,int,int)
package main 

import "fmt"

func add(num...int)(int,int){
	var result int
	var count int
	
	for i:=0; i<len(num); i++{
		result += num[i]
		count++
	}
	
	return result, count
}

func main(){
	nums := []int{10,20,30,40,50}
	
	a,b := add(nums...) //슬라이스를 전달할때는 꼭 뒤에 ... 붙여야 함. 
	
	fmt.Println(a,b)
	
}

Named Return Parameter

이 이름을 직역하면 '이름이 붙여진 반환 인자'이다. 즉 이름이 붙여진 반환값이다. 함수에서 여러 개의 값을 반환할때 괄호 안에 반환형을 모두 명시해야 한다고 했다. 하지만 반환값이 많고 반환형이 다양하다면 가독성이 안 좋을 수 있다. 따라서 이때 Named return paramater를 사용한다. 이는 반환형과 반환 값의 이름을 같이 명시하는 것을 말한다. 코드 안에서 return 뒤에 명시하던 리턴 값들을 반환형 앞에 명시하는 것이다.

  • (반환값이름1 반환형1, 반환값이름2 반환형2 .... )형식으로 입력한다.
  • "반환값이름 반환형"자체가 변수 선언이다. 따라서 함수 안에서 따로 선언할 필요가 없다. 만약 선언하면 에러가 발생한다.
  • return을 생략하면 안 된다. 반환 값이 있을 때는 반드시 return을 명시해야한다.
  • 반환값이 하나라도 반환값 이름을 명시했다면 괄호 안에 써야한다.
package main

import "fmt"


func dessertList(fruit...string)(count int, list []string){
	//슬라이스를 받아오려고 
	//여기서 이미 count랑 list 선언됨
	
	fmt.Println(fruit) // 받아온 과일 슬라이스
	
	for i:=0; i<len(fruit); i++{
		list = append(list,fruit[i]) // 굳이,,,,?
		count ++ 
	}
		
	return

	
}
								 

func inputFruit()(list []string){
	//named return parameter : 반환형과 반환값 이름을 동시에 명시함. 
	// 즉 return list가 아님
	// 또한 list []string 이 자체가 변수선언
	
	for{
		var fruit string
		fmt.Print("과일을 입력하세요: ")
		fmt.Scanln(&fruit)
		
		if fruit != "!"{
			list = append(list,fruit)	
		}else{
			fmt.Print("입력을 종료합니다 \n")
			break
		}
	}
	
	return
}

func main(){
	
	fmt.Println("디저트를 먹을 과일을 입력하고 출력합니다. \n1을 입력하면 입력을 멈춥니다. ")
	list,count := dessertList(inputFruit()...) // ...는 슬라이슬 전달하기위해
	// inputFruit함수 자체를 dessertList 함수의 전달인자로 사용함.
	
	fmt.Println(list,count)
}

- 익명 함수

익명함수는 이름이 없는 함수를 말한다. 함수의 이름을 마우렇게나 막 붙이는 경우는 없기 때문에 함수의 이름은 상징적이고 매우 중요하ㅐ다. 예를들어 숫자를 더하는 기능의 함수는 "add"라고 붙일 수 있다. 그렇다면 익명함수는 뭘까?
코드를 작성할때 아무런 규칙 없이 마구잡이로 작성하는 것보다 코드의 기능별로 '함수화'하는것이 중요하다고 배웠다. 그런데 함수의 단점은 바로 프로그램의 속도 저하이다. 함수의 선언 자체가 프로그래밍 전역으로 초기화되면서 메모리를 잡아먹고, 기능을 수행할 때마다 함수를 찾아서 호출해야하기 때문이다.
그래서 이러한 단점을 보완하기 위해 나온 함수가 익명 함수이다.

package main

import "fmt"

func main() {
	
	//함수1
	func() {
		fmt.Println("hello")
	}()
	
	
	//함수2
	func(a int, b int) {
		result := a + b
		fmt.Println(result)
	}(1, 3)

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

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

위의 예제를 보면

  1. 함수의 이름은 없고 그 외의 형태는 동일하다.
    2.함수의 블록 마지막 {} 뒤에 괄호()를 사용해 함수를 바로 호출한다. 이 괄호안에는 매개변수를 넣을 수있다.

이렇게 익명 함수의 가장 큰 특징은 그 자리에서 만들고 그 자리에서 바로 실행하는 것이다. main함수 밖에 선언하고, 필요할때 불러오는 선언함수와는 다르게 가벼운 느낌이다. 선언함수는 반환값을 변수에 초기화함으로써 변수에 바로 할당이 가능하다. 익명 함수도 똑같은 기능을 하는데 이때 차이점은 변수에 초기화된 익명 함수는 변수 이름을 함수의 이름처럼 사용할 수 있다는 것이다.

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,15}
	
	//익명함수
	addAnnoymous := func(nums...int) (result int){
		for i := 0; i<len(nums); i++{
			result += nums[i] // 더하기 
		}
		
	return
	}
	
	fmt.Println(addAnnoymous(nums...)) //64
	fmt.Println(addDeclared(nums...)) //64 
}

위의 선언 함수와 익명함수는 똑같은 값을 출력하는 것을 확인할수 있다. 그러나 두 함수의 차이점은 내부적으로 읽는 순서가 다르다. 선언 함수는 프로그램이 시작됨가 동시에 모두 읽는다. 하지만 익명 함수는 그 자리에서 실행되기 때문에 해당 함수가 실행되는 곳에서 읽는다. 즉! 선언 함수보다 익명함수가 나중에 읽힌다.

package main

import "fmt"

func add(){
	fmt.Println("선언 함수를 호출했습니다. ")
}

func main(){
	
	add := func(){
		fmt.Println("익명 함수를 호출했습니다.")
	}
	
	add() // "익명 함수를 호출했습니다" 출력 -> 선언 함수가 익명 함수가 나중에 읽히기 때문
}

일급함수(fist-class function)

위의 일급함수라는 의미는 함수를 기본 타입과 동일하게 사용할 수 있어 함수 자체를 다른 함수의 매개변수로 전달하거나 다른 함수의 반환 값으로 사용될수 있다는 것이다. 따라서 함수는 다른 타입들과 비교했을 때 높은 수준의 용법이 아니라 같은 객채로서 사용될 수 있다.

ㅇㅇㅇㅇㅇ

좋은 웹페이지 즐겨찾기