지금까지 main 메서드 안에 한 줄씩 입력했지만 함수를 사용하면 여러 줄이 하나의 기능을 수행할 경우, 이를 묶어 반복적으로 활용하는 것이 가능해진다.
사용자에게 특정 숫자의 입력을 원하는 기능을 생각해보자
Console.Write("숫자를 입력해주세요 : ");
string input = Console.ReadLine();
int num;
int.TryParse(input, out num);
혹시 사용자에게 숫자를 입력 받고 싶을 때마다 해당 코드를 모두 작성해야할까?
이처럼 특정 기능을 수행하는 여러줄의 코드가 반복적으로 필요할 경우, 이를 함수(= 숫자 ! )로 표현하여 더 간편하게 작성하는 것이 가능하다. 이를 위해 우리가 생각해야하는 것은 다음과 같다.
static void PrintJoke()
{
Console.WriteLine("밤에 성시경이 두명 있으면?");
Console.Write("야간");
Console.Write("투");
Console.WriteLine("시경");
}
함수 실행 이후 반환값을 특별히 사용하지 않는 경우 void를 사용한다.
주의. 함수명은 파스칼 표기법을 따른다.
static void Main(string[] args)
{
PrintJoke();
}
밤에 성시경이 두명 있으면?
야간투시경
이처럼 함수를 지정한 경우 Main 메서드 내에서 다시 재활용하는 것이 가능하여, 반복되어 필요한 경우 함수로 입력하면 된다.
static void GradeMyjoke(string inputString)
{
Console.WriteLine("당신의 농담은 " + inputString + "입니다.");
}
위의 코드처럼 입력값에 따라 결과값이 달라지는 함수를 만들 수 있는데, 이 경우 입력값의 형태를 지정할 필요가 있다.
static void Main(string[] args)
{
GradeMyjoke(Console.ReadLine()); // input : 최악
}
당신의 농담은 최악입니다.
이처럼 사용자의 입력에 따라 출력값이 달라진다.
지금까지 void를 사용하여 반환값이 없을 경우의 함수를 사용했으나, 반환값이 존재하는 경우의 함수를 지정해보자.
static double GetPI()
{
double pi = 3.141592;
Console.WriteLine("파이값을 반환하였습니다.");
return pi;
}
위의 코드는 pi라는 값을 double 형태로 지정하고 해당 값을 return을 통해 함수 GetPI( )에 할당한 것이다. 이처럼 반환값이 존재하고 그 값을 사용할 경우 void가 아닌, 저장될 형태를 지정하여 함수를 설정한다.
static string MakeFishCake(string taste, int time)
{
if(taste == "")
{
Console.WriteLine("속 없는 붕어빵은 만들 수 없습니다.");
return "";
}
Console.WriteLine($"반죽물을 붓습니다.");
Console.WriteLine($"{taste} 앙금을 넣습니다.");
if (time > 10)
{
Console.WriteLine($"익을 때까지 {time}초 기다립니다.");
Console.WriteLine($"탄 붕어빵 완성!!");
return "탄 붕어빵";
}
else
{
Console.WriteLine($"익을 때까지 {time}초 기다립니다.");
Console.WriteLine($"{taste} 붕어빵 완성!!");
return $"{taste} 붕어빵";
}
}
return 은 값이 도출되었을 경우 해당 값을 반환시키고, 함수를 종료하겠다는 의미이다.
위의 함수에서 만약 사용자가 붕어빵의 속을 공백으로 냈다면, 함수값을 공백으로 반환하고 함수를 종료하는 것이다. 즉 아래의 긴 코드는 실행되지 않는다.
return은 값을 반환할 때 쓰이기도 하지만, 예외사항 또는 특수한 경우가 있는 경우 함수 맨 위에 작성하여 해당 상황에서는 계산할 필요 없이 함수를 조기 종료시키는 경우에 더욱 유용하게 사용이 가능하다.
static void Main(string[] args)
{
Console.Write("붕어빵 안에 넣을 속을 정해주세요 : ");
string selectedTaste = Console.ReadLine();
Console.Write("붕어빵을 굽기 위한 시간을 말씀해주세요 : ");
int selectedTime;
int.TryParse(Console.ReadLine(), out selectedTime);
MakeFishCake(selectedTaste, selectedTime);
}
위에서 정의한 MakeFishCake를 사용하여 입력값에 따라 함수값이 달라지도록 설정해보았다.
함수 내부 내용과 시간에 따라 설정한 함수에 의해 출력되는 내용이 달라지는 것을 확인할 수 있다.
static bool isAreaBigger(double height, double width, out double area)
{
area = height * width / 2;
bool isBigger = area > 100;
return isBigger;
}
위 코드에서 사용된 isAreaBigger 함수는 boolean 값을 반환받도록 되어있지만 추가로 area 값을 double 형태로 반환 받는 것도 가능하다. 이처럼 하나의 함수에서 동일한 매개변수로 연산된 다른 반환값을 추가로 얻고자 할 때 out을 사용한다.
static void Main(string[] args)
{
while (true)
{
switch ((Console.ReadKey(true).Key))
{
case ConsoleKey.W:
case ConsoleKey.UpArrow:
MoveForward();
break;
case ConsoleKey.A:
case ConsoleKey.LeftArrow:
MoveLeft();
break;
case ConsoleKey.S:
case ConsoleKey.DownArrow:
MoveBackward();
break;
case ConsoleKey.D:
case ConsoleKey.RightArrow:
MoveRight();
break;
}
}
}
지금까지는 입력 후 enter키를 눌러서 값을 컴퓨터에게 전달했다면, ReadKey( ) 는 입력과 함께 값을 바로 전달하는 함수 이다.
특히 ReadKey(True)의 경우 콘솔 창에 입력값을 표시하지 않고 입력값에 따라 바로 결과가 나온다는 점에서 굉장히 유용하게 쓰인다.
static void ToSwapNum(int swapNum1, int swapNum2)
{
int temp;
temp = swapNum1;
swapNum1 = swapNum2;
swapNum2 = temp;
Console.WriteLine($"스왑이 진행되었습니다. \na : {swapNum1}, b : {swapNum2} 입니다.");
}
위의 함수는 입력한 두 수의 값을 서로 바꿔주는 기능이다. 다만 이 경우 실제로 swapNum1과 swapNum2의 실제 값이 바뀐 것은 아니다. 함수 내에서 출력을 할 때 그렇게 보이도록 만든 것일 뿐 값은 여전히 그대로인 것이다.
참고. temp를 사용한 이유는 swapNum1의 값을 덮어씌울 때 기존의 값이 사라지기에 이를 temp에 넣어준 것이다.
static void Main(string[] args)
{
int a = 10;
int b = 20;
Console.WriteLine($"스왑하기 전. \na : {a}, b : {b} 입니다.");
ToSwapNum(a, b);
Console.WriteLine($"스왑 이후. \na : {a}, b : {b} 입니다.");
}
예를 들어 두 코드를 이어서 실행한다고 했을때의 결과를 보면 다음과 같다.
스왑하기 전.
a : 10, b : 20 입니다.
스왑이 진행되었습니다.
a : 20, b : 10 입니다.
스왑 이후
a : 10, b : 20 입니다.
결과를 보면 함수 자체에서 출력되는 2번째 결과는 스왑이 되어 값이 바뀌지만, 여전히 외부 값은 바뀌지 않은 채 3번째 결과처럼 그대로인 것을 볼 수 있다.
그럼 외부 값도 바꿔주려면 어떻게 해야되는데?
이때 사용하는 것이 바로 ref이다. 기존 함수가 입력 값을 복사해서 함수에서 사용했다면, Ref 함수의 경우 입력된 값을 실제로 참조해서 함수를 진행하기에 이에 따라 참조된 값 또한 변경되는 것이다.
static void ToSwapNumRef(ref int swapNum1, ref int swapNum2)
{
int temp;
temp = swapNum1;
swapNum1 = swapNum2;
swapNum2 = temp;
Console.WriteLine($"스왑이 진행되었습니다. \na : {swapNum1}, b : {swapNum2} 입니다.");
}
위와 같이 ref를 함수 뒤, 매개변수 앞에 붙여주면 ref 함수의 기능을 하도록 명령이 가능하다. 따라서 아래 코드도 살짝 바꿔서 실행하면 다음과 같다.
static void Main(string[] args)
{
int a = 10;
int b = 20;
Console.WriteLine($"스왑하기 전. \na : {a}, b : {b} 입니다.");
ToSwapNumRef(ref a, ref b);
Console.WriteLine($"스왑 이후. \na : {a}, b : {b} 입니다.");
}
스왑하기 전.
a : 10, b : 20 입니다.
스왑이 진행되었습니다.
a : 20, b : 10 입니다.
스왑 이후
a : 20, b : 10 입니다.
이렇게 스왑이 진행되어 외부값 또한 바뀐 것을 확인할 수 있다. 이때 ref 함수는 Call by reference 라고 하며, 일반 함수는 Call by value 라고 한다.
보통 1회용 데이터를 사용할 때 Call by value를 활용하기에, 게임 개발에서 활용하는 함수는 Call by reference인 경우가 더 많을 것 같다.
해당 구문에 있는 함수들을 의도에 맞도록 구현하시오
static void Main(string[] args)
{
int playerHealth;
while (true)
{
_repeatCount++;
playerHealth = InputPlayerHealth();
PrintRepeatCount();
if (IsZero(playerHealth))
{
Console.WriteLine("Game Over - 게임 종료");
break;
}
}
}
value값이 0 이하인 경우 true가 반환되도록 설계
static bool IsZero(int value)
{
bool isDied = (value <= 0);
return isDied;
}
return (value <= 0); // 둘 중에 무엇이 가독성이 더 좋은걸까...?
0이상 100이하의 숫자만을 입력받도록 설계
static int InputPlayerHealth()
{
double inputDouble;
Console.Write("플레이어의 체력을 입력해주세요 : ");
bool isInt = double.TryParse(Console.ReadLine(), out inputDouble);
while (!isInt)
{
_repeatCount++;
Console.Write("다시 입력해주세요 : ");
isInt = double.TryParse(Console.ReadLine(), out inputDouble);
}
while (inputDouble < 0 || inputDouble > 100)
{
_repeatCount++;
Console.Write("다시 입력해주세요 : ");
double.TryParse(Console.ReadLine(), out inputDouble);
}
return (int)inputDouble;
}
반복문이 실행될 때마다 카운트가 올라가고 출력이 되도록 설계
static void PrintRepeatCount()
{
Console.WriteLine($"반복문이 출력된 횟수는 {_repeatCount++}입니다.");
_repeatCount = 0;
}