원문 블로그 글 http://knowledgeisfree.tistory.com/111
요즘 바쁜 와중에도 짬 내서 구글의 Go 언어 공부하느라 더 정신은 없지만 재밌네요
하지만 아직까지도 저는 오토핫키를 가장 선호합니다, 처음 배운 언어고 가장 많이 사용하고 있어서 그런지...
인터프리터 언어의 일인자(?)인 파이썬보다도 더 자주 사용하는 거 같네요
클래스에 대한 글을 작성하려 했는데, 먼저 함수에 대해 충분히 알아야 할 것 같아서 이 글을 먼저 적게 되었습니다
함수를 사용하긴 하더라도, 제대로 활용하는 사람은 드문 것 같아서요
사실, 저도 잘 모릅니다;;
하지만, 더 배우고자 한다면 남을 가르치는 것이 도움이 된다고 했나요?
제가 함수에 대해 아는 것을 적어보려고 합니다
안내
- 저는 오토핫키를 포함한 다른 프로그래밍 언어를 모두 원어로 배워서, 한글 명칭을 잘 모릅니다... 대부분 영어로 적습니다
- 예제를 직접 실행해보세요, 이해에 많은 도움이 될 겁니다
일단 함수를 왜 사용하는지 알아야 겠죠, 함수를 사용하는 주된 이유는
- 코드의 유지 보수를 수월하게 하고
- 코드의 범용성을 높이고
- 생산성이 높아지기 떄문입니다
함수를 사용하는 이유가 goto, gosub 의 사용을 권장하지 않는 이유이기도 합니다
한두 개라면 몰라도 서브루틴에 대부분 goto를 사용한다면 코드가 굉장히 지저분(...) 하고
범용성이 제로에 가까워서, 새로 코드를 작성할 때 이전의 코드가 전혀 도움이 안 되죠
물론 대충 후다닥 만들때는 그냥 goto 로 만드는게 편하죠, 나중까지 생각한다면 함수로 만드는게 좋고요
Function (이하 함수)는 goto, gosub 과 같은 서브루틴(subroutine)의 기능에 추가로 파라미터(입력)을 받아 올 수 있습니다
그리고, 값 (value)을 리턴(Return) 할 수도 있죠.
아래 '더하기' 함수는 x, y 파라미터를 받아와 각각 더하고 합을 리턴합니다
더하기(x, y)
{
return x + y
}
'더하기'는 함수 이름이고, 괄호 안에 들어가는 x 와 y는 파라미터라고 부릅니다
이렇게 함수를 만드는 것을 Function Definition (함수 정의)라고 하는데요
'더하기' 함수를 사용하려면 반드시 x, y 파라미터에 해당하는 각각의 값을 보내줘야 하고
리턴되는 값을 변수에 저장하려면 ' := ' 오퍼레이터를 사용해야 합니다, 아래처럼요
합 := 더하기(10, 5) ; '합' 변수에는 '15'가 저장됩니다
값을 저장하지 않고, 아래처럼 그냥 함수만 사용할 수도 있습니다
더하기(10, 5)
그런데 '더하기' 함수에는 값을 리턴하는 외의 어떠한 명령도 없기 때문에 위 코드는 아무런 의미가 없죠
하지만 명령어(Command)를 사용할 때에는 유용합니다
변수로 저장해두고 계속 사용하지 않을 거라면, 아래처럼 사용하면 되죠
MsgBox % "3 더하기 2 는: " . 더하기(3, 2) ; ' 3 더하기 2는: 5 '
함수는 파라미터를 포함해 모든 표현을 *익스프레션으로 표현합니다
익스프레션: 변수, % 변수, :=, "문자"
따라서, 변수를 넘길 때는 따옴표("") 처리 없이, 문자를 넘길 때는 "문자 "로 표현해야 합니다
함수(변수, "문자 ")
함수명(첫 번째 파라미터, 두 번째 파라미터, 세 번째 파라미터, ...) 과 같은 형식으로 함수를 정의합니다
파라미터는 필수가 아니에요, 생략하고 함수명()로 정의할 수도 있습니다
ByRef 파라미터
함수가 받아온 파라미터는 기본적으로 로컬(Local) 변수로 사용됩니다, 하지만 *ByRef 파라미터로 받아온다면 그렇지 않죠
ByRef: By Reference의 약자, 참조한다는 뜻
무슨 말인지 모르시겠다고요?, 아래의 예를 읽다 보면 이해가 될 거예요 :)
Swap(ByRef x, ByRef y)
{
save := x
x := y
y := save
}
Swap 함수는 ByRef 파라미터의 사용으로 파라미터 x, y를 *Alias로 만듭니다
Alias: 이후에 작성할 멀티 스레드 글에서 자세히 알아보겠습니다, 지금은 그냥 이런 게 있구나 정도만 생각하세요
쉽게 말하면, ByRef를 사용함으로써
파라미터 x, y는 Swap 함수 내에서도, 코드 전체에 걸쳐서도 같은 데이터를 메모리에서 참조(Refer) 하게 되는 거죠
따라서 Swap 함수로 교환된 값을 함수 밖에서도 사용할 수 있습니다
만약에 ByRef를 사용하지 않는다면
함수 내에서는 x, y 값을 교환해서 저장하지만, 함수 밖에서는 아무런 변화가 없습니다
따라서, 코드 전체에서는 실제로 교환된 값을 사용할 수는 없습니다
말이 점점 어려워지네요, 역시 코드가 제일 쉽죠?
; ByRef 의 경우
a := 10
b := -10
MsgBox % "a : " a "`nb : " b ; a는 10, b는 -10
Swap(a, b) ;a 변수의 값은 b로, b변수의 값은 a에 저장됨
MsgBox % "a : " a "`nb : " b ; a는 -10, b는 10 로 값이 교환됨
Swap(ByRef x, ByRef y){
save := x
x := y
y := save
}
; ByRef 가 없다면
a := 10
b := -10
MsgBox % "a : " a "`nb : " b ; a는 10, b는 -10
Swap(a, b)
MsgBox % "a : " a "`nb : " b ; 여전히 a는 10, b는 -10
Swap(x, y){
save := x
x := y
y := save
}
읽기만으로 이해가 잘 되지 않는다면 코드를 실행해보고 결과를 확인해보세요
ByRef 의 장점은 여러 개의 값을 *리턴하는 효과를 가져온다는 거죠, 위 코드에서는 x, y 두 개의 값을 리턴했다고 보면 됩니다
반면에, 리턴(Return)으로는 한 개의 값만 리턴이 가능해요
리턴하는 효과?: 엄밀히 따지면, ByRef는 값을 리턴하는 게 아니라 값을 설정한다고 봐야 하기 때문
ByRef의 또 다른 장점으로는,
사이즈가 큰 문자열을 리턴할 때 return String 을 사용하는 것보다 속도가 빠르다는 점이 있습니다
웹페이지 소스나, json 파싱을 할 때 유용할 것 같지만,
속도 차이는 벤치마킹상이지, 사람이 체감하는 속도는 아니라서 막상 사용은 잘 안 합니다
옵션 파라미터
함수를 호출할 때마다 모든 파라미터에 값을 넘기려니 귀찮은 때가 많습니다
그럴 때, 파라미터에 기본값을 설정해두면 편하죠, 이러한 파라미터를 Optional Parameter (옵션 파라미터)라고 불러요
더하기(x, y, z := 0)
{
return x + y + z
}
더하기 함수의 3번째 파라미터 z는 기본값이 0으로 설정된 옵션 파라미터입니다
파라미터를 받아오지 않는 경우에는 0으로 설정되고, 받아온 경우에는 받은 값을 사용합니다
따라서, 더하기(5, 5) 의 경우에는 10, 더하기(5, 5, 5) 의 경우에는 15 가 리턴됩니다
다시 한번: 함수는 모든 표현을 익스프레션으로 한다고 했습니다, 그래서 z = 0 이 아닌, z := 0 을 사용하죠
그럼에도 불구하고 아직까지는 베이직 버전에서 이동한 개발자를 위해 ' = ' 도 허용됩니다
Variadic 파라미터
파라미터의 개수를 유동적으로 설정하고 싶은 경우가 많습니다, 특히 연산 작업을 할 경우에는 더욱 그렇죠
파라미터 이름 뒤에 * 처리를 하면 입력받은 파라미터의 개수만큼, 파라미터가 자동으로 설정됩니다
MsgBox % 더하기(10) ; 10
MsgBox % 더하기(10, 10) ; 20
MsgBox % 더하기(10, 10, 10) ; 30
더하기(params*) {
for i,p in params
Sum += p
return Sum
}
위에서 잠깐 설명했듯이 함수는 리턴 값을 호출자(Caller)에게 보내거나/저장할 수 있습니다
Test := returnTest() ; 변수 Test에 리턴값 123 저장
MsgBox, % Test
returnTest() {
return 123
}
return 은 한 개의 값만 리턴할 수 있는 반면에 ByRef를 사용하면 여러 개의 값을 리턴하는 효과가 있다고 했죠?
오브젝트나 배열을 생성해 리턴해도 동일한 효과를 얻을 수 있습니다
Test := returnArray()
MsgBox, % Test[1] "`, " Test[2] ; A , B
returnArray()
{
Test := ["A", "B"]
return Test
}
기본적으로 함수 내에서 사용되고, 생성하는 변수는 모두 로컬 변수입니다, 오직 함수 내에서만 사용되는 거죠
함수 밖 변수, 다른 함수의 변수와는 아무런 관련이 없어서, 이름이 동일해도 상관없습니다
로컬변수 := 1234
Add := 더하기(5, 5)
Sub := 빼기(10, 5)
MsgBox, % "함수 밖 로컬변수 - " 로컬변수 "`n더하기(5, 5) - " Add "`n빼기(10, 5) - " Sub
더하기(X, Y){
로컬변수 := X + Y
return 로컬변수
}
빼기(X, Y){ ; 파라미터가 X, Y 로 동일해도 상관없음
로컬변수 := X - Y ; 이름이 동일해도 상관없음
return 로컬변수
}
함수 밖에서도 변수를 사용하려면 아까 설명한 ByRef 을 이용할 수도 있고
함수 내에서 변수의 레벨을 global로 설정하면 됩니다
글로벌변수 := 111
MsgBox % 글로벌변수 ; 111
Func()
MsgBox % 글로벌변수 ; 222
Func(){
global 글로벌변수 ; 변수 '글로벌변수' 은 global 변수임
글로벌변수 := 222
}
여러 개의 글로벌 변수를 설정하려면 global, 변수 1, 변수 2, 변수 3, ...으로
모든 변수를 글로벌 변수로 설정하려면 global 을 적어주면 됩니다
Func(){
global 변수1, 변수2, 변수3, ... ; 변수1, 변수2, 변수3 을 글로벌변수로, 나머지는 로컬변수
...
}
Func(){
global ;모두 글로벌 변수
...
}
스태틱 변수는 기본적으로 로컬 변수지만, 추가로 함수 내에서 계속해서 값이 기억된다는 점이 특징입니다
무슨 말인지 코드로 확인해볼까요?
일단, 로컬 변수부터 확인해보죠
MsgBox % 누적(1) ; 1, 누적안됨
MsgBox % 누적(10) ; 10, 누적안됨
MsgBox % 누적(100) ; 100 , 누적안됨
누적(X){
aug += X
return aug
}
숫자가 누적이 되지 않습니다, 위 코드에서 누적 함수의 aug 변수를 스태틱 변수로 설정하면 어떻게 될까요?
MsgBox % 누적(1) ; 1
MsgBox % 누적(10) ; 11
MsgBox % 누적(100) ; 111
누적(X)
{
static aug ; 스태틱 설정
aug += X
return aug
}
숫자가 계속 누적돼서 나옵니다, 이처럼 스태틱 변수는 함수 내에서 값이 기억되어 사용되는 변수입니다
그래서 처음 1 이 기억되고, 거기다가 10을 더하고, 마지막으로 100을 더하는 거죠
여러 개의 스태틱 변수를 설정하려면 static, 변수 1, 변수 2, 변수 3, ...으로
모든 변수를 스태틱 변수 설정하려면 static 을 적어주면 됩니다
Func(){
static 변수1, 변수2, 변수3, ... ; 변수1, 변수2, 변수3 을 스태틱변수로, 나머지는 로컬변수
...
}
Func(){
static ;모두 스태틱 변수
...
}
글로벌 변수와 스태틱 변수를 구분해서 설정하려면 아래처럼 하시면 됩니다
global 글로벌 변수 1, 글로벌 변수 2, ...
static 스태틱 변수 1, 스태틱 변수 2, ..
함수 내에서도 함수를 호출할 수 있습니다, 예를 들어
ParentFunc()
ParentFunc(){
return ChildFunc()
}
ChildFunc(){
MsgBox, 안녕!
}
위 코드는 너무 간단해서 굳이 이렇게 쓸 필요는 없어 보입니다.
실제로 활용될 수 있는 아래 코드를 확인해볼까요?
Wait_img("img.bmp", true) ;이미지 찾을시 해당 이미지를 클릭하게한다
ClickFunc(1000, 500) ; 좌표 1000, 500 클릭
Wait_img(img, Click := false){
while !ErrorLevel = 0 ; 이미지를 찾을 때까지
ImageSearch, vX, vY, 0, 0, A_ScreenWidth, A_ScreenHeight, % A_ScriptDir . "\" img
return ((Click == true) ? ClickFunc(vX, vY) : 0)
}
ClickFunc(X, Y){
Click %X% %Y%
}
한 함수에 몰아넣으면 될 텐데 왜 이렇게 사용하냐고 물으신다면,
반복되는 서브루틴을 개별 함수로 나눠서, 각각의 함수의 범용성을 높이기 위해서죠. 코드도 단축되고요
((Click == true) ? ClickFunc(vX, vY) : 0) 가 뭐죠? c언어의 Ternary Operator 입니다, 오토핫키도 c로 작성된 언어라 모든 연산자와 표현식은 c 하고 똑같습니다
모르셨다면 if, else를 한 문장으로 축약하는 표현식이라고 알아두시면 됩니다
Click 파라미터가 true라면 이미지를 클릭하고, false 면 클릭하지 않습니다
프로그래밍에 대해 관심을 가졌다면, 언젠가 객체지향언어라는 것을 들어보았을 겁니다
C++, C# 그리고 자바 등 요즘 사용되는 언어는 대부분 객체지향언어인데요
객체지향프로그래밍의 이유는 점점 프로그램의 규모가 커지고, 개인보다는 집단이 개발하는 오픈소스 소프트웨어가 많아지기 때문이기도 합니다
객체로 나눠서 작성하면 유지 보수가 수월하고, 다른 개발자가 코드 개발에 참여하기도 쉽죠
그리고 객체지향언어에서 객체를 동적(Dynamic)으로 컨트롤하는 언어를 프로토타입 지향 언어라고 하는데
이와 같은 언어로는 대표적으로 자바스크립트가 있고, 오토핫키가 있습니다(!!!)
이 부분은 나중에 작성할 클래스 글에서 다루도록 하고, 일단은 함수 객체로 바로 넘어가죠 사실, 지식이 바닥남
함수 객체 (Func Object)는 함수 참조 Func Reference로 더 많이 불리는 거 같은데, 후자가 더 의미에 가까운 것 같습니다
말 그대로, 변수가 함수를 참조할 수 있게 하는 거죠. 해당 변수는 함수로의 포인터라고 이해해도 되고요
fn := Func("더하기") ; fn이 더하기 함수를 참조하도록 함
MsgBox % fn.Call(10, 10) ;함수 호출
더하기(X, Y)
{
return X + Y
}
정말 신기하지 않습니까!!!?
이를 사용하면 라벨을 기본적으로 사용하는 셋타이머가 함수를 실행하게 할 수도 있습니다
#Persistent
fn := Func("알림")
SetTimer, % fn, 5000 ; 5초마다 알림 함수 호출
알림()
{
MsgBox, 0, , 안녕, 3
}
사전에 파라미터를 설정해둘 수도 있습니다
#Persistent
fn := Func("알림").Bind("파라미터 바인드")
SetTimer, % fn, 5000 ; 5초마다 알림 함수 호출
알림(Txt)
{
MsgBox, 0, , % Txt, 3
}
이후에는 클래스와 멀티 스레드에 대해 알아보도록 하겠습니다, 언제 작성할지는 지금은 모르겠네요
잘 정리되어있네요 좋아요