Golang 함수정리

최승훈·2020년 7월 23일
1

Golang기초

목록 보기
1/7
post-thumbnail

Golang에서의 함수

1. 함수란

특정 기능을 위해 만든 여러 문장을 묶어서 실행하는 코드 블록 단위.
프로그램의 특정 기능들을 기능별로 묶어 구현해놓은 것.

2. 함수를 선언하는 방법

func 함수명 (매개변수들) (반환형들){
                        처리 구문
}

함수선언 예 1.

pakage main

import "fmt"

// introduce함수의 정의
func introduce() {
	fmt.Println("Hello My Name is Show")
}

func main() {
	// introduce함수의 호출
	introduce()
}

함수선언 예 2.

호출되는 함수가 꼭 호출하는 함수 앞에 있을 필요는 없음.

pakage main

import "fmt"

func main() {
	// introduce함수의 호출
	introduce()
}

// introduce함수의 정의
func introduce() {
	fmt.Println("Hello My Name is Show")
}

함수선언 예 3.

1.매개변수가 없고, 반환 값이 없는 형태

func PrintGuideMessage() {
	fmt.Println("포켓몬스터를 생성하는 프로그램입니다. \n 닉네임과 포켓몬 이름을 차례대로 띄어 써주세요.")
	fmt.Print("닉네임과 포켓몬 이름을 입력해주세요 :")
}

2.매개변수가 없고, 반환 값이 있는 형태

func userInput() (string, string) {
	var nickName, pokemonName string
	fmt.Scanln(&nickName, &pokemonName)
	return nickName, pokemonName
}

3.매개변수가 있고, 반환 값이 있는 형태

func makePlayerInfo(nickName, pokemonName string) PokemonPlay { 
	return PokemonPlay{
		PlayerName: nickName,
		PokemonName: pokemonName,
	}
}

4.매개변수가 있고, 반환 값이 없는 형태

func printPlayerInfo(play PokemonPlay) {
	fmt.Printf("입력하신 닉네임은 %s, 포켓몬 이름은  %s 입니다. \n", play.PlayerName, play.PokemonName)
}

전체 소스코드

package main

import "fmt"

type PokemonPlay struct {
	PlayerName string
	PokemonName string
}

func printGuideMessage() {
	fmt.Println("포켓몬스터를 생성하는 프로그램입니다. \n 닉네임과 포켓몬 이름을 차례대로 띄어 써주세요.")
	fmt.Print("닉네임과 포켓몬 이름을 입력해주세요 :")
}

func userInput() (string, string) {
	var nickName, pokemonName string
	fmt.Scanln(&nickName, &pokemonName)
	return nickName, pokemonName
}

func makePlayerInfo(nickName, pokemonName string) PokemonPlay {
	return PokemonPlay{
		PlayerName: nickName,
		PokemonName: pokemonName,
	}
}

func printPlayerInfo(play PokemonPlay) {
	fmt.Printf("입력하신 닉네임은 %s, 포켓몬 이름은  %s 입니다. \n", play.PlayerName, play.PokemonName)
}

func main() {
	printGuideMessage()
	n , p := userInput()
	r := makePlayerInfo(n,p)
	printPlayerInfo(r)
}

3.전역변수와 지역변수

지역변수

중괄호({})안에서 선언된 변수.
지역변수는 해당 중괄호 안에서 선언되는 순간 메모리가 생성.
해당 지역을 벗어나면 자동으로 소멸. (변수의 참조가없어짐 => 가비지컬렉션의 출동시간)

package main

import "fmt"

func testFunc1() {
	// testFunc1()의 지역변수 a 선언
	var a int = 10
	a++
	fmt.Println("testFunc1()의 지역변수 a는", a)
	// testFunc1()의 지역변수 a의 수명은 여기까지..
}

func testFunc2() {
	// testFunc2()의 지역변수 b,c 선언
	var b int = 20;
	var c int = 30;

	b++
	c++
	fmt.Println("b와 c는",b,c)
	// testFunc2()의 지역변수 b,c의 수명은 여기까지..
}

func main() {
	var a int =  28
	testFunc1()
	testFunc2()
	fmt.Println("main의 a는", a)
}

전역변수

특정지역(중괄호)밖에서 선언된 변수.
전역변수는 코드가 시작되어 선언되는순간 메모리가 생성.
코드 또는 어플리케이션이 끝나는 순간까지 메모리를 차지하고있음.

package main

import "fmt"

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

func localVarTest() int {
	// localVarTest()의 지역변수 a 선언
	var a int = 10
	a += 3
	return a
}

func globalVar() int {
	a += 3
	return a
}

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

지역변수와 전역변수의 차이

메모리에 올라가있는 시간.
변수에 접근할 수 있는 범위(스코프,포인터(함수포인터)).

4.매개변수

Pass By Value

인자의 값을 복사해서 전달하는 방식.
복사한 값을 인자로 받으므로 함수 안에서 어떠한 연산을 하더라도 원래값은 변하지 않음.
Why? 함수에 인자로 넘길 값을 복사해서 전달했기 때문.

package main

import "fmt"

func addFirstName(name string) {
	name = "choi" + name
	fmt.Println("Result :" , name)
}

func main() {
	name := "hana"
	addFirstName(name)
	fmt.Println(name)
}

Pass By reference

Java같은경우는 기본적으로 Pass By reference를 기초로 두고있음.

'&': 주소

'*': 직접참조

함수를 호출 할 때는 주솟값을 넘겨주기 위해 "함수이름(&변수이름)"의 형태로 호출
함수에서는 주소값(포인터)를 매개변수로 받기위해 "func 함수이름(*변수이름)"의 형태로 선언.

package main

import "fmt"

func addFirstName(name *string) {
	*name = "choi" + *name
	fmt.Println("Result :" , *name)
}

func main() {
	// 지역변수 선언
	name := "hana"
    // 참조를 위한 name의 주솟값을 매개변수로 전달
	addFirstName(&name)
	fmt.Println(name)
}

5.가변 인자 함수

전달하는 매개변수의 개수를 고정한 함수가 아닌, 함수를 호출할 때마다 매개변수의 개수를 다르게 전달할 수 있도록 만든 함수.
Java에서는 오버로딩을 통해 구현이 가능한 부분이지만, 엄연히 말하면 Java의 오버로딩과 가변인자함수의 개념은 다름.
Java의 오버로딩: 매개변수 형태와 개수에 따라 메소드를 여러개 만들어야함.
가변인자함수: 동일한 형의 매개변수 n개를 전달할 수 있음.

가변인자함수의 특징

  1. n개의 동일한 형의 매개변수를 전달.
  2. 전달된 변수들의 형태는 slice형
  3. "func FuncName(매개변수이름 ...매개변수형) 반환형"으로 선언 (매개변수형앞에 ...을 붙이면 됨)
  4. 매개변수로 슬라이스를 전달 할 수 있음. "함수이름(슬라이스이름...)"
package main

import "fmt"

// 가변인자로 string형태의 값 여러개를 받음
func addCaseOne(names ...string) string {
	var result string
	// for문을 이용한 num[i]의 순차적인 접근
	for i:=0; i < len(names); i++ {
		result = result + names[i]
		if i+1 != len(names) {
			result = result + ","
		}
	}
	return result
}

// 가변인자로 string형태의 값 여러개를 받음
func addCaseTwo(names ...string) string {
	var result string
	// for문을 이용한 num[i]의 순차적인 접근
	for i , v := range names {
		result = result + v
		if i+1 != len(names) {
			result = result + ","
		}
	}
	return result
}

func main() {
	s1, s2, s3, s4, s5 := "picachu", "raichu", "pairi", "kame", "hennaHana"
	ss := []string{"a1", "a2", "a3", "a4"}
	fmt.Println(addCaseOne(s1, s2))
	fmt.Println(addCaseOne(s1, s2, s4))
	fmt.Println(addCaseOne(ss...))
	fmt.Println(addCaseTwo(s3, s4, s5))
	fmt.Println(addCaseTwo(s1, s3, s4, s5))
	fmt.Println(addCaseTwo(ss...))
}

6.반환값(리턴값)

Golang에서는 복수개의 반환값을 반환할 수 있다.

복수개의 반환값을 가지는 함수

package main

import "fmt"

const (
	resultId = "example"
	resultPassword = "1234"
)

// 반환값의 개수만큼 반환형을 명시, 2개 이상의 반환형을 입력할 때는 괄호 () 안에 명시.
func checkLogin(id string, password string) (bool, string) {
	if id == resultId && password == resultPassword {
		return true, ""
	}
	return false, "아이디또는 패스워드가 일치하지 않습니다. id: " + id + " password: " + password
}


func main() {

	r, e := checkLogin("example", "1234")
	if !r {
		fmt.Println(e)
	}
	fmt.Println(fmt.Sprintf("Example Login Result : %v",r))

	if r, e = checkLogin("Show", "aaaa"); !r {
			fmt.Println(e)
	}
	fmt.Println(fmt.Sprintf("Show Login Result : %v",r))

	if r, e = checkLogin("Hana", "bbbb"); e != "" {
		fmt.Println(e)
	}
	fmt.Println(fmt.Sprintf("Hana Login Result : %v",r))

}

Named Return Parameter

이름이 붙혀진 반환 인자!
여러 개의 값을 반환할 때 괄호 안에 반환형과 반환 값의 이름을 같이 명시하는 형식.
코드 안에서 return뒤에 명시하던 리턴 값들을 반환형앞에 명시하는 것.

  1. (반환값이름1 반환형1, 반환값이름2 반환형2, 반환값이름3 반환형3, ...)의 형식.
  2. "반환값이름 반환형" 자체가 변수 선언 => 함수안에서 따로 변수를 선언할 필요가 없음.
  3. 'return'을 생략하면 큰일남.
  4. 반환값이 하나라도 반환값이름을 명시했다면 괄호안에 써야됨.

package main

import "fmt"

// 반환값에 이름을 붙여서 int형 count와 string슬라이스형 list를 반환
func getStudyMemberList(names ...string) (count int, list []string) {
	for _ , n := range names {
		// list , count 는 이미 Named Return Parameter가 되었으므로 변수가 초기화 되어있는 상태.
		list = append(list, n)
		count++
	}
	//생략하면 큰일 난다.
	return
}

func main() {
	studyMemberList := make([]string, 0)
	studyMemberList = append(studyMemberList, "aaa")
	studyMemberList = append(studyMemberList, "bbb")
	studyMemberList = append(studyMemberList, "ccc")

	count, names := getStudyMemberList(studyMemberList...)
	fmt.Println(fmt.Sprintf("Count : %d , NameList: %v", count, names))

}

7.익명함수

이름이 없는 함수.

왜 사용?

함수를 만드는것은 프로그램의 속도 저하를 가져옴.
함수를 선언하면 프로그램의 전역으로 초기화가 되면서 메모리를 잡아먹음.
함수를 호출 할 때마다 함수의 메모리주소를 검색해서 가져와서 써야함.
반면, 익명함수는 호출될때마다 메모리에 올라가서 처리가 끝나면 메모리에서 내려옴(지역변수와 같은 느낌적인 느낌?)
결론, 굳이 공통적으로 부를만한 기능(함수)가 아니면 익명함수를 적극이용.

예 1.

기본적인 임명함수의 사용법.

package main

import "fmt"

func main() {
	// 함수의 이름만 없고 그 외에 형태는 동일
	// 함수의 블록 마지막 브레이스(}) 뒤에 괄호(())를 사용해 함수를 바로 호출
	// 괄호 안에 매개변수를 넣을 수 있음.
	func(){
		fmt.Println("Hello EveryBody")
	}()
	
	func(name string, age int) {
		fmt.Println(fmt.Sprintf("Hello My Name is %s, %d years old" , name, age))
	}("show", 30)

        // 선언 함수는 반환 값을 변수에 초기화함으로써 변수에 바로 할당이 가능
	introducingMessage := func(name string, age int) string{
		return fmt.Sprintf("Hello My Name is %s, %d years old" , name, age)
	}("Hopangmen",100)
	fmt.Println(introducingMessage)

	name, age := "Golang" , 30
	introducingMessage2 := func(n string, age int) string {
		return fmt.Sprintf("Hello My Name is %s, %d years old" , name, age)
	}(name, age)
	fmt.Println(introducingMessage2)


}

익명함수는 그 자리에서 만들고 그 자리에서 바로 실행하는 것
함수의 '기능적인 요소'만 쏙 빼와서 어디서든 가볍게 활용하기 위해 사용

예 2.

변수에 함수를 초기화하여 변수를 함수처럼 활용하는 방법.

package main

import "fmt"

// 선언함수
func introMemDeclared(mems ...string) (result string) {
	for i , m := range  mems {
		if i+1 < len(mems) {
			result = result + m + ","
		} else {
			result = result + m
		}
	}
	return
}

func main() {
	memberList := make([]string, 0)
	memberList = append(memberList, "aaa")
	memberList = append(memberList, "bbb")
	memberList = append(memberList, "ccc")

	fmt.Println(introMemDeclared(memberList...))

	// 익명함수
	introAnonymous := func(mems ...string)(result string) {
		for i , m := range  mems {
			if i+1 < len(mems) {
				result = result + m + ","
			} else {
				result = result + m
			}
		}
		return
	}

	// 함수에 매개변수를 전달해 함수를 호출하는 것과 동일하게 익명 함수를 할당 받은 변수에 매개변수를 전달해서 사용
	fmt.Println(introAnonymous(memberList...))
}

예 3.

선언 함수와 익명 함수는 프로그램 내부적으로 읽는 순서가 다름.
선언 함수는 프로그램이 시작됨과 동시에 모두 읽음(메모리 로드).
익명 함수는 위 예시들처럼 그 자리에서 실행되기 때문에 해당 함수가 실행되는 곳에서 읽음(메모리 로드).
결론적으로 선언 함수보다 익명 함수가 나중에 읽힘.
같은 이름의 전역변수는 해당 흐름에 있는 지역변수에게 가려지는 것과 같은 개념.

package main

import "fmt"

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

func main() {
	
	// 지역 익명함수인 add가 실행됨.
	add := func() {
		fmt.Println("익명 함수를 호출했습니다.")
	}

	add()
}

일급함수(First-Class Function)

일급 함수라는 의미는 함수를 기본 타입과 동일하게 사용할 수 있어 함수 자체를 다른 함수의 매개변수로 전달하거나 다른 함수의 반환 값으로 사용될 수 있다는 것.

예 1.

함수를 매개변수형으로 사용할 때는 "매개변수함수이름 func(전달받는함수의매개변수형) 전달받는함수의반환형" 형태로 선언

package main

import "fmt"

// calc의 매개변수는
// 1. 함수 f => 매개변수로 int,int를 받고 int를 리턴하는 함수  
// 2. int a
// 3. int b
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)
}

예 2.

type문을 사용한 함수 원형 정의
함수형을 표현할 때는 전달받는 함수의 형태에 따라 형태가 달라질 뿐더러 길고 가독성이 떨어짐.

예를 들어, 전달 받는 함수가 매개변수가 5개고 반환형이 6개일 때는 그 함수를 매개변수로 사용할 때마다 그만큼을 명시해야 함.

이를 극복하기 위해 Go언어에서는 'type'문을 사용해 함수의 원형을 정의하고 사용자가 정의한 이름을 형으로써 사용.

package main

import "fmt"

//type문을 이용해 두 정수를 합하는 함수형을 calculatorNum으로 정의
type calculatorNum func(int, int) int 
//type문을 이용해 두 문자열을 복제하는 함수형을 calculatorStr로 정의
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문은 함수 원형 정의 뿐만이 아니라 구조체, 인터페이스 등을 정의하기 위해 사용.

출처: https://edu.goorm.io/lecture/2010/%EB%B0%94%EB%A1%9C-%EC%8B%A4%ED%96%89%ED%95%B4%EB%B3%B4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-go-lang

profile
안녕하세요

0개의 댓글