조건문과 반복문1
if문
if(조건식)
{
조건식이 참일 경우 실행할 코드
}
if(조건식)
// 조건식이 참일경우 실행할 코드가 한줄일때 { } 생략 가능
else if는 if문의 조건식이 거짓일때, 새로운 조건식을 사용하여 실행 여부를 결정하는 조건문
else if 는 else 안에 if가 있는거라고 그냥 생각하면 논리구조상 이해하기 편하다.
중첩조건문
하나의 조건문안에 또다른 조건문이 포함된 형태이다.
switch문
변수나 식의 결과에 따라 다른 코드 블록을 실행하는 제어문.
책의 목차라고 생각하면 편하다.
switch( 변수나 식)
{
case 값 1: 값1이 나온경우 실행되는 코드.
break; -> 가장 근접한 스코프를 하나 벗어난다.
case 값 2: 값2이 나온경우 실행되는 코드.
break;
...
default: 모든 case문에 해당하지않을때 실행되는 코드. // 생략이 가능하다.
break;
--------
디버그 보는방법
해당 라인에 f9를 누르면 빨간점이찍히며 시작할 정류장을 만들고
디버그로 재생을 시작한다 (f5)
입력값을 받아야한다면 콘솔창에 입력값을 주면 지정한 라인까지 이동한다.
스크립트아래 로컬, 호출스택이 켜져있는지 체크해주면 좋다.
안보인다면 디버그탭 -> 창 -> 조사식, 자동, 지역, 호출 스택등을 눌러서 켜주면 된다.
f10은 다음 정거장, 즉 다음 라인으로 움직이며 값을 확인한다.
-------
3항 연산자 -> if else문과 같은 역할을한다.
(조건식) ? 참일경우값 : 거짓일 경우 값;
예:
int currentExp = 1200;
int requiredExp = 2000;
3항 연산자
string result = (currentExp >= requiredExp)? "레벨업 가능" : "레벨업 불가능" ;
Console.writeLine(result); // 레벨업 불가능
if else 문으로 나타내면
if( currentExp >= requiredExp)
{
Console.writeLine("레벨업 가능");
}
else
{
Console.WriteLine("레벨업 불가능");
}
-------
swith문 응용
int playerScore = 100;
string playerRank = "";
switch(playerScore/10)
{
case 10:
case 9:
playerRank = "A";
break;
case 8:
playerRank = "B";
break;
case 7:
playerRank = "C";
break;
case 6:
playerRank = "D";
break;
default:
playerRank = "F";
break;
}
Console.WriteLine($"Player Rank: {playerRank}");
해당 playerscore은 100점으로 할당됐는데
90~99점까지는 앞에자리가 9다 , 10으로 나누게되면 몫들은 정수형이므로 소숫점을 버리게된다.
그래서 90~99점까지는 10으로 나누면 9가 된다.
80~89점까지도 마찬가지의 메커니즘이 적용된다.
이처럼 가볍게해서 case문을 만들수있다.
case 10은 아무것도 안써져있을대는 default로 반환했었다.
그렇기에 case10을 써줬다. 다만, 별도의 랭크를 부여할것이아니라면 위의 예시와 같이
실행코드를 적지않으면 생략이라고 암묵적으로 여겨지며, 그아래 case의 실행코드들을 똑같이 적용받는다.
따라서 case10은 A이며
위와 같은 상황에서 출력은 A를 출력한다. case 중첩이 가능한것이다.
----------
알파벳 판별프로그램
char input = Console.ReadLine()[0]; 뒤에 [0] 을 붙여서 입력한 문자열 가장 앞에 문자를 가져오라고 인덱싱 가능하다.
입력한 문자열이 몇개던간에 맨앞에 한글자만 가져온다. 문자로 받기때문에 한칸씩 0부터 올라가면된다.
예를들어 [1] 이라하고 입력값을 1a 로 타이핑하면 마찬가지로 알파벳이라고 출력한다.
문자열은 사전식으로 검사도 가능하지만.
문자열도 컴퓨터는 정수형을 사용함. 문자표를 가지고있고 문자와 숫자를 대칭해서 사용하고있다.
따라서 관계연산자들이 잘 동작함
if ((input >= 'a' && input <= 'z') || (input >= 'A' && input <= 'Z'))
{
Console.WriteLine("입력한 문자는 알파벳입니다.");
}
else
{
Console.WriteLine("입력한 문자는 알파벳이 아닙니다.");
}
입력한 문자열이 몇개던간에 맨앞에 한글자만 가져온다. 문자로 받기때문에 한칸씩 0부터 올라가면된다.
예를들어 [1] 이라하고 입력값을 1a 로 타이핑하면 마찬가지로 알파벳이라고 출력한다.
뿐만아니라 앞서말했듯, 문자형은 내부적으로 정수(unicode 코드 포인트)로 표현되는데,
이 코드값들이 알파벳인 경우 연속적인 범위를 이룬다.
소문자 'a'의 코드값은 97, 'z'는 122인데, 이 사이에는 알파벳 소문자들이 연달아 배치되어있고
그 사이에 다른형태의 문자나 숫자가 껴있지는 않다.
숫자의 경우엔 '0'(48)~'9'(57) 사이에 위치한다. 즉 범위가 겹치지않기때문에 위와 같이 조건문을 작성하면
이 범위 안에 해당하면 알파벳임을 보장할 수 있다.
--------------
반복문
for문
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
스코프 안 실행코드 실행하고 증감을 실행하는데, 조건이 맞지않다면 실행코드를 실행하지않으므로
0부터 9까지 출력된다.
for (int i = 1; i < 21; i+=2)
{
Console.WriteLine(i);
}
이런식으로하면 1 3 5 7 ... 19 까지 홀수를 출력한다
int i를 바깥에서도 써야한다면 바깥에서 선언해줘야한다. 안그러면 for문장이 끝나면 안쓰이는 지역변수다.
int i = 0;
for (i = 1; i < 21; i+=2)
{
Console.WriteLine(i);
}
while 문
조건식이 참인 동안 코드블록 반복실행함.
while( 조건식)
{
}
for문으로 구현하는걸 마찬가지로 구현가능하다, 어느걸 더 편하게 쓸수있냐에 따라 맞게 쓰면된다.
반복조건을 체크하기엔 for문이 더 좋지만
조건식에 따라 반복되는 실행코드에 더 집중하고싶으면 while문을 사용하면된다.
int i = 0;
while (i < 10)
{
Console.WriteLine(i);
i++;
}
이것도 상단의 for문 기본식과 같은 값 0부터 9까지 출력한다.
----------
최소한 한번은 코드블록을 실행하고 조건을 검사하기위해선
do- while문이 존재한다.
int sum = 0;
int num;
do
{
Console.WriteLine("숫자를 입력하세요 (0입력시 종료)");
num = int.Parse(Console.ReadLine());
sum += num;
} while (num != 0);
Console.WriteLine($"입력한 숫자의 합은 {sum}입니다.");
이렇게되면 do에 있는 실행코드블럭을 실행하고 while문 안의 조건을 체크해서 0이 아니라면 계속해서
반복한다. 반복해서 더해주는것이다. num 값이 입력문에 의해 할당받으면서 while문의 조건을 체크해서
0을 입력하는 순간 do 내용은 반복을 끝내고 빠져나온다. 그리고
Console.WriteLine($"입력한 숫자의 합은 {sum}입니다."); 을 실행하여 sum의 합을 알려준다.
----------
foreach는 배열이나 컬렉션에 대한 반복문을 작성한다.
string[] inventory = { "apple", "banana", "orange", "grape", "kiwi" };
foreach ( string item in inventory) // 조건문 소괄호 안에 string은 컬렉션 inventory의 자료형이다.
{
Console.WriteLine(item);
}
-----
중첩반복문
for( int i= 0; i < 5; i++)
{
for( int j = 0; j< 3; j++)
{
Console.WriteLine("i = {0}, j = {1}", i, j); // 소괄호안은 $" i = {i}, j = {j}" 이런식으로도 작성가능
}
}
이러면 출력값은
i = 0, j = 0
i = 0, j = 1
i = 0, j = 2
i = 1, j = 0
i = 1, j = 1
i = 1, j = 2
i = 2, j = 0
i = 2, j = 1
i = 2, j = 2
i = 3, j = 0
i = 3, j = 1
i = 3, j = 2
i = 4, j = 0
i = 4, j = 1
i = 4, j = 2
---------
중첩반복문을이용한 구구단
for (int i = 1; i <= 9; i++)
{
for (int j = 2; j <= 9; j++)
{
Console.Write($"{j}x{i}={j*i}\t");
}
Console.WriteLine();
}
출력시에
2x1=2 3x1=3 4x1=4 5x1=5 6x1=6 7x1=7 8x1=8 9x1=9
2x2=4 3x2=6 4x2=8 5x2=10 6x2=12 7x2=14 8x2=16 9x2=18
2x3=6 3x3=9 4x3=12 5x3=15 6x3=18 7x3=21 8x3=24 9x3=27
2x4=8 3x4=12 4x4=16 5x4=20 6x4=24 7x4=28 8x4=32 9x4=36
2x5=10 3x5=15 4x5=20 5x5=25 6x5=30 7x5=35 8x5=40 9x5=45
2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 7x6=42 8x6=48 9x6=54
2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 8x7=56 9x7=63
2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 9x8=72
2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
행과 열을 바꿔보고싶다면
for (int i = 2; i <= 9; i++)
{
for (int j = 1; j <= 9; j++)
{
Console.Write($"{i}x{j}={j*i}\t");
}
Console.WriteLine();
}
로해서 바꿔주면
2x1=2 2x2=4 2x3=6 2x4=8 2x5=10 2x6=12 2x7=14 2x8=16 2x9=18
3x1=3 3x2=6 3x3=9 3x4=12 3x5=15 3x6=18 3x7=21 3x8=24 3x9=27
4x1=4 4x2=8 4x3=12 4x4=16 4x5=20 4x6=24 4x7=28 4x8=32 4x9=36
5x1=5 5x2=10 5x3=15 5x4=20 5x5=25 5x6=30 5x7=35 5x8=40 5x9=45
6x1=6 6x2=12 6x3=18 6x4=24 6x5=30 6x6=36 6x7=42 6x8=48 6x9=54
7x1=7 7x2=14 7x3=21 7x4=28 7x5=35 7x6=42 7x7=49 7x8=56 7x9=63
8x1=8 8x2=16 8x3=24 8x4=32 8x5=40 8x6=48 8x7=56 8x8=64 8x9=72
9x1=9 9x2=18 9x3=27 9x4=36 9x5=45 9x6=54 9x7=63 9x8=72 9x9=81
이 된다. 안쪽의 for문이 먼저 도니까 그걸 기준으로 연산되도록 변수들의 위치나 순서를 바꿔주면 된다.
----------
break; 감싸고있는 하나의 반복문 구문만 벗어난다.
continue; 이번회차만 무시하고 증감으로 떠난다.
예시를보자, 해당 코드는
1
2
4
5
7
을 출력하고 끝나는 코드다
for (int i = 1; i <= 10; i++)
{
if (i % 3 == 0)
{
continue; // 3의 배수인 경우 다음 숫자로 넘어감
}
Console.WriteLine(i);
if (i == 7)
{
break; // 7이 출력된 이후에는 반복문을 빠져나감
}
}
continue는 이번회차를 무시하고 증감한다. 즉 continue 아래의 코드들은 읽지않고
다시 i++ 로 간 후에 증감한 i를 기준으로 for문의 조건 ( i<=10)을 체크하고
충족한다면 안쪽 코드를 읽는다.. 3의배수인 3을 거르고
1
2
4
5
까지 출력이 반복되다가
6일때 3의배수가 되므로 한번더 continue로 증감으로 넘어가
i가 7일때 조건을 체크한다 i<=10은 충족했으니
7을 출력후
if문의 조건을 체크했는데 i==7이 성립되었다.
이때 break한다고 하니, for문을 중지시키고 밖으로 나오며 실행이 종료된다..
------------
응용이다..
int sum = 0;
while (true) // 항상 참일테니까 이것은 무한루프에 빠지겠다. 즉 break문이 나올것이라는 뜻. for( ; ; ) 해주면 마찬가지로 무한루프다.
{
Console.Write("숫자를 입력하세요: ");
int input = int.Parse(Console.ReadLine());
if (input == 0)
{
Console.WriteLine("프로그램을 종료합니다.");
break;
}
if (input < 0)
{
Console.WriteLine("음수는 무시합니다.");
continue;
}
sum += input;
Console.WriteLine("현재까지의 합: " + sum);
}
Console.WriteLine("합계: " + sum);
해당 while문은 0을 입력하기전까지 음수를 제외하고 계속해서 더하는 반복문이며
0을 입력하면 break가 되며 결과값 합계를 출력하고 실행을 종료한다.
-----------
배열과 while문, if, else if 문을 응용한 가위바위보게임을 보자
string[ ] choices = { "가위", "바위", "보" };
string userChoice = " ";
string computerChoice = choices[new Random().Next(0, 3)];
while (userChoice != computerChoice)
{
Console.Write("가위, 바위, 보 중 하나를 선택하세요: ");
userChoice = Console.ReadLine();
if (userChoice == computerChoice)
{
Console.WriteLine($"컴퓨터의 선택: {computerChoice}");
Console.WriteLine("비겼습니다!");
}
else if ((userChoice == "가위" && computerChoice == "보") ||
(userChoice == "바위" && computerChoice == "가위") ||
(userChoice == "보" && computerChoice == "바위"))
{
Console.WriteLine($"컴퓨터의 선택: {computerChoice}");
Console.WriteLine("당신이 이겼습니다!");
}
else
{
Console.WriteLine($"컴퓨터의 선택: {computerChoice}");
Console.WriteLine("당신이 졌습니다!");
}
}
해당 내용의 흐름은 전부 이해가 됐으나 문법적인 부분이 조금 생소해서 찾아봤다.
string computerChoice = choices[new Random().Next(0, 3)];
바로 이부분이다.
choice[] 안에 인덱싱을 랜덤하게 만들어주는 상황인데
임시 Random 객체를 생성하고 그 객체에서 바로 Next(0,3)을 호출하여 인덱스값을 0,1,2중 랜덤하게 얻은뒤
choices 배열에서 해당 인덱스의 문자열 값을 선택하는 구조이다.
만약 객체가 재사용될 필요가 있다면 미리 Random 객체를 변수에 할당 후 사용하는것이 더 좋다.
Random rnd = new Random();
string computerChoice = choices[rnd.Next(0,3)];
이런식으로 말이다.
좀더 나아가서
Unity에는 비슷한 문법이 있어 비교해보자.
Random.Range() 와 Random.Next()는 쓰임이 비슷하다. 모두 소괄호 안의 두 숫자를 ( x, y) 식으로 넣어서
x이상 y미만의 난수를 선택하는 메서드이다.
조금 다르다면,
Random.Range()의 경우 UnityEngine 네임스페이스의 정적 메서드로
물리계산, 게임오브젝트 행동에 따라 난수를 제공할때 많이 사용된다.
별도의 인스턴스 생성 없이 바로 호출할 수 있어 간편하게 사용가능하다.
Random.Next()의 경우 .Net Framework의 System.Random 클래스에 속한 인스턴스 메서드다.
일반 c# 애플리케이션에서 난수 생성할때 사용된다.
난수 생성을 위해선 클래스를 인스턴스화해야 하며, 동일한 인스턴스를 계속 사용하거나 시드값을 명시적으로 관리가능하다.
반환값 및 범위 처리를 봐도 조금 다른데,
우선 정수형 난수에 있어서는 그 범위가 동일하다. 앞서말했듯
min은 포함되고 max는 제외된다.
x 이상 y미만의 수 중에서 하나를 반환하는것이다.
실수형 난수로 가면
Random.Range의 경우 최소값은 포함, 최대값은 포함될 수도 있다.
Random.Next의 경우엔 달리 이렇게 쓰이는데
Random.NextDouble() 메서드를 사용해야하며, 0.0이상 1.0미만의 double 값을 반환한다.
필요에따라 범위를 조정하는 추가연산이필요하다.
정리하자면,
Random.Next()는 인스턴스 생성이 필요해서
string computerChoice = choices[new Random().Next(0, 3)]; 와 같이 썼다면
using UnityEngine; 일경우에(unity 엔진에서 쓸경우에)
정적메서드인 Random.Range()의 경우엔 전역에서 쓸수있으며 인스턴스 생성이 필요없다.
따라서 아래와 같이 문법으로 쓸수있다.
string computerChoice = choices[Random.Range(0, 3)];
---------------
숫자맞추기 응용
int targetNumber = new Random().Next(1, 101);
int guess = 0;
int count = 0;
Console.WriteLine("1부터 100사이의 숫자를 맞춰보세요!");
while (guess != targetNumber)
{
guess = int.Parse(Console.ReadLine());
count++;
if (guess < targetNumber)
{
Console.WriteLine("더 큰 숫자를 입력하세요.");
}
else if (guess > targetNumber)
{
Console.WriteLine("더 작은 숫자를 입력하세요.");
}
else
{
Console.WriteLine($"정답입니다! {count}번 만에 맞췄습니다.");
}
}
-------
배열과 컬렉션
메모리에 공간을 할당을 받고나서는 늘리거나 줄일수없다.
int[ ] itemPrices = { 100, 200 , 300, 400, 500}; // 생성과 동시에 초기화도 진행함
int totalPrice = 0;
for( int i = 0; i<itemPrices.Length; i++) // Legnth는 이어지는 컬렉션의 길이를 뜻한다.
{
totalPrice += itemPrices[i];
}
Console.WriteLine("총 아이템 가격: " + totalPrice + "gold");
-----------
배열 코드스니펫
int[] scores = new int[5]; // 5명의 학생 성적을 저장할 배열
// 성적 입력 받기
for (int i = 0; i < scores.Length; i++)
{
Console.Write("학생 " + (i + 1) + "의 성적을 입력하세요: ");
scores[i] = int.Parse(Console.ReadLine());
}
// 성적 총합 계산
int sum = 0;
for (int i = 0; i < scores.Length; i++)
{
sum += scores[i];
}
// 성적 평균 출력
double average = (double)sum / scores.Length; // sum과 scores.Length 모두 정수이기때문에 average도 정수로 떨어져버린다. 형변환해주자.
Console.WriteLine("성적 평균은 " + average + "입니다.");
-----------
배열 코드 스니팻 숫자 맞추기
Random random = new Random(); // 랜덤 객체 생성
int[] numbers = new int[3]; // 3개의 숫자를 저장할 배열
// 3개의 랜덤 숫자 생성하여 배열에 저장
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = random.Next(1, 10);
}
int attempt = 0; // 시도 횟수 초기화
while (true)
{
Console.Write("3개의 숫자를 입력하세요 (1l~9): ");
int[] guesses = new int[3]; // 사용자가 입력한 숫자를 저장할 배열
for ( int i =0; i < guesses.Length; i++)
{
guesses[i] = int.Parse(Console.ReadLine()); // 사용자 입력을 정수로 변환하여 배열에 저장
}
int correct = 0;
for ( int i =0; i < numbers.Length; i++)
{
for (int j= 0; j < guesses.Length; j++)
{
if(numbers[i] == guesses[j])
{
correct++; // 맞춘 숫자 카운트
break; // 중복 체크 방지
}
}
}
attempt++;
Console.WriteLine($"{attempt}번째 시도: {correct}개의 숫자를 맞췄습니다.");
if(correct ==3)
{
Console.WriteLine("모든 숫자를 맞췄습니다.");
break;
}
먼저 생성된 배열 numbers[] 안에 난수로 3개의 값이 들어갔다.
그리고 guesses[]는 사용자의 입력값을 3번 받아서 값을 3개 넣어줬다.
이중 반복문안에 if를 통해 체크하는데 여기서 알아야할점은
고정되서 while문 바깥에서 할당된 numbers[0], numbers[1], numbers[2] 값과
while문안에서 매번 조건 체크후 반복해서 사용자 입력에 따라 재 할당되는
guesses[0], guesses[1], guesses[2]를 비교해야하는것이다.
Length를 이용해서 2차원 배열로
numbers guesses
0 0
1 1
2 2
를 크로스 체크해야하는 것이다.
그래서 이중반복문안에 넣어서 체크하며, if문을 이용해 특정 배열 두개가 값이 같아질때
break를 통해 인접 for문을 빠져나오게 해서 중복체크를 방지한다.
뭔말인가하면, guesses에 입력값을 2 3 3 이런식으로 3을 중복해서 써넣었을때,
numbers안에 3이 해당값으로 들어가있다면 break; 가없다면 전부 순회하기때문에 correct가 한번더 올라갈 수있다.
------
다차원배열
여러개의 배열을 하나로 묶어놓은 배열
행과 열로 이루어진 표 형태와 같은 구조
int[,] array = new int[2, 3]; // 2행 3열의 int형 2차원 배열 생성
//다차원 배열 초기화
array[0, 0] = 1;
array[0, 1] = 2;
array[0, 2] = 3;
array[1, 0] = 4;
array[1, 1] = 5;
array[1, 2] = 6;
// 선언과 함께 초기화
int[,] array2 = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } }; // 3개짜리 2개
int[ , ]array3 = new int[3, 4] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; // 4개짜리 3개
// 3차원 배열의 선언과 초기화
int[,,] array3D = new int[2, 3, 4] // 4개짜리가 3개인데, 2층이다.
{
{ { 1, 2, 3, 4, },{5,6,7,8 },{9,10,11,12 }},//3행4열짜리
{ { 13,14,15,16 },{17,18,19,20 },{21,22,23,24 }}//3행4열짜리
};
---------
게임맵 구현
int[,] map = new int[5, 5]
{
{ 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 0, 1, 0, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1 }
};
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if (map[i, j] == 1)
{
Console.Write("■ ");
}
else
{
Console.Write("□ ");
}
}
Console.WriteLine();
}
하게되면
■■■■■
■□□□■
■□■□■
■□□□■
■■■■■
가 출력된다.
-----------
컬렉션
System.Collections.Generic 네임스페이스가 필요
참고로,
C#에서는 클래스나 구조체 바깥에 실행문을 작성할 수 없으며,
모든 실행문은 반드시 메서드나 생성자 등의 실행 범위 안에 있어야 한다.
foreach문 같은 실행문. Add 같은 실행문들은 메서드안에 넣어놓거나 생성자 안에 넣어놓아야 제대로 동작한다.
List : 가변적인 크기를 갖는 배열, 실제로배열은 아님
List를 생성할때는 List에 담을 자료형을 지정
List<int> numbers = new List<int>();
numbers.Add(1); // 리스트에 데이터 추가
numbers.Add(2);
numbers.Add(3);
numbers.Remove(2); //리스트에서 데이터 삭제
foreach(int number in numbers)
{
Console.WriteLine(number); // 리스트의 데이터 출력
}
// remove 한 2 빼고 1, 3 출력
// generic에는 Length가 없음, 실제로 메모리상 이어지지않기때문. 대신 count를 쓴다
그러니까
List<int> numbers = new List<int>();
numbers.Add(1); // 리스트에 데이터 추가
numbers.Add(2);
numbers.Add(3);
numbers.Remove(2); //리스트에서 데이터 삭제
for(int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
이런식으로도 표현할 수 있다.
Dictonary : 키와 값으로 구성된 데이터를 저장
딕셔너리는 중복된 키를 가질 수 없고, 키와 값의 쌍을 이루어 데이터를 저장
Dictionary<string, int> scores = new Dictionary<string, int>(); // 키와 값이 쌍을 이루므로 객체 생성시 < > 안에 이런식으로 ,쉼표로 키와 값의 자료형을 넣어준다.
scores.Add("John", 85);
scores.Add("Jane", 92);
scores.Add("Bob", 78);
scores.Remove("Bob");
foreach(KeyValuePair<string, int>pair in scores) // 키와 값이 페어로 되어있으니 이런 문법을 쓴다.
{
Console.WriteLine($"{pair.Key} scored {pair.Value}"); // 표현할때도 .(닷)으로 접근해서 표현한다. .Key .Value
}
----------------
컬렉션중에 몇개가 더있다.
Stack: 후입선출 구조를 가진 자료 구조
바구니를 생각하면, 맨마지막에 들어온 놈이 처음에 나가야한다.
Stack<int> stack1 = new Stack<int>();
stack1.Push(1);
stack1.Push(2);
stack1.Push(3);
int value = stack1.Pop(); // 가장 위의 요소를 덜어내며 반환한다.
Console.WriteLine(value); // 3
쓰임:
1. Undo 기능: 플레이어의 동작기록을 저장해두고, 마지막 동작부터 되돌리는 기능.
2. 상태관리: 게임내 상태 변경( 팝업창, 메뉴 등) 의 이전 상태 복귀에 사용
3. 재귀적 알고리즘: 재귀 호출을 명시적으로 관리할때 사용.
Queue: 선입선출 구조를 가진 자료 구조
파이프의 형태 줄서기, 순서대로 줄서고, 순서대로 입장하는 느낌
Queue<int> queue1 = new Queue<int>();
queue1.Enqueue(1);
queue1.Enqueue(2);
queue1.Enqueue(3);
int value = queue1.Dequeue(); // 가장 먼저 추가된 요소
Console.WriteLine(value); // 1
쓰임:
1. 이벤트 처리: 게임이벤트(사용자 입력, ai 작업, 네트워크 이벤트 등)을 순서대로 처리
2. 대기열 관리: NPC의 행동 스케쥴링이나 작업순서를 결정할때 유용
3. 게임 루프 내 작업 분배: 실행할 작업들을 큐에 넣고 순서대로 실행할 경우.
HashSet: 중복되지않는 요소들이 이루어진 List와 흡사한 집합
HashSet<int> set1 = new HashSet<int>();
set1.Add(1);
set1.Add(2);
set1.Add(3);
foreach (int element in set1)
{
Console.WriteLine(element);
}
List가 아무리 가변적이어도 장단점이 명확하므로 필요에의해서만 사용하는게 낫다.
List는 메모리 사용량을 증가시킨다. 동적으로 크기를 조정하기때문
List는 배열보다 데이터 접근시간도 증가시킨다.
List를 사용하면 배열보다 코드복잡도가 증가한다.
따라서 데이터 구조를 선택할때 데이터의 크기와 사용목적을 고려하여 적절히 선택하자.
쓰임:
수집품관리: 플레이어가 수집한 고유 아이템의 목록관리
충돌검사: 게임 내에서 특정 요소가 이미 처리되었는지 (방문한 위치, 활성화된 오브젝트등) 빠르게확인
상태 추적: 특정 상태나 조건이 한번만 적용되도록 보장할때..
----------------
메서드와 구조체
메서드의 역할과 중요성
:
코드 재사용성
모듈화
가독성과 유지보수성
코드의 중복제거
코드의 추상화
구조와 문법
[접근제한자] [ 리턴타입] [메서드 이름] ([매개변수])
{
//실행코드
}
접근제한자로 메서드에 접근할 수 있는 범위를 정한다.
private - 클래스 내부에서만 쓸수있음
public - 클래스 외부 어디서든 접근가능
protected - 나랑 상속, 연결고리가 있는 애들만 쓸수있음
return값이 없을경우 void를 사용한다.
// 예시 1: 반환 값이 없는 메서드
public void SayHello()
{
Console.WriteLine("안녕하세요!");
}
// 예시 2: 매개변수가 있는 메서드
public void GreetPerson(string name)
{
Console.WriteLine("안녕하세요, " + name + "님!");
}
// 예시 3: 반환 값이 있는 메서드
public int AddNumbers(int a, int b)
{
int sum = a + b;
return sum;
}
메서드 호출방법
[메서드 이름]([전달할 매개변수]);
AddNumbers(10, 20);
매개변수는 메서드에 전달되는 입력값, 메서드 내에서 이 값을 활용하여 원하는 작업을 수행한다.
반환값을 사용해보자면,
수행한 결과를 호출자에게 반환하는 값.
int AddNumbers(int a, int b)
{
int sum = a + b;
return sum;
}
// 메서드 호출 및 반환값 사용
int result = AddNumbers(10, 20);
Console.WriteLine("Sum: " + result);
반환값이 없다면, 즉 호출자에게 반환하지않음.
void PrintMessage(string message)
{
Console.WriteLine("Message: " + message);
}
// 메서드 호출
PrintMessage("Hello, World!");
메서를 호출하면 출력은 한다만, 값이 재할당되지는 않는셈이다.
---------
메서드 호출 응용
static void PrintLine()
{
for(int i = 0; i < 10; i++)
{
Console.Write("=");
}
Console.WriteLine();
}
static void PrintLine2(int count)
{
for (int i =0; i< count; i++)
{
Console.Write("=");
}
Console.WriteLine();
}
static int Add(int a, int b)
{
return a + b;
}
static void Main(string[] args)
{
PrintLine();
PrintLine2(20);
int result = Add(10, 20);
Console.WriteLine(result);
}
디버그 모드로 f9로 체크한 뒤 f11을 누르면 함수들도 파고들어가며 매개변수같은 값들도 전부 체크한다.
이걸로 체크해보면 좋다. f10은 함수를 뛰어넘어 체크한다
------------
메서드의 오버로딩 : 동일한 이름의 메서드를 다양한 매개변수 목록으로 다중 정의한다.
매개변수의 개수, 타입, 순서가 다른 여러 메서드를 동일한 이름으로 정의할 수 있다.
반환값이 달라도 동일하게 취급하고, 매개변수만으로 구분한다.
void PrintMessage(string message)
{
Console.WriteLine("Message: " + message);
}
void PrintMessage(int number)
{
Console.WriteLine("Number: " + number);
}
// 메서드 호출
PrintMessage("Hello, World!"); // 문자열 매개변수를 가진 메서드 호출
PrintMessage(10); // 정수 매개변수를 가진 메서드 호출
여기서 반환 자료형 부분 void , 하나는 int여도 동일하게 취급할 수 있음. 무슨말인가하면
예를들어 동일한 매개변수라면 앞에 long, int 반환값이 다르다고 해도 동일하니 에러가 뜬다는뜻
static long AddNumbers(int a, int b) // long 자료형이 더 큰 데이터로 괜찮, float을 반환형으로 써도 마찬가지
{
return a + b;
}
static int AddNumbers(int a, int b) // 에러가 뜸 그 이유는 소괄호의 매개변수가 위에 메서드와 같기때문임.
{
return a + b;
}
따라서 이런식으로 표현가능
static int AddNumbers(int a, int b)
{
return a + b;
}
static float AddNumbers(float a, float b)
{
return a + b;
}
static int AddNumbers(int a, int b, int c)
{
return a + b + c;
}
// 메서드 호출
static void Main(string[] args)
{
int sum1 = AddNumbers(10, 20); // 두 개의 정수 매개변수를 가진 메서드 호출
int sum2 = AddNumbers(10, 20, 30); // 세 개의 정수 매개변수를 가진 메서드 호출
float sum3 = AddNumbers(10.5f, 20.5f); // 두 개의 실수 매개변수를 가진 메서드 호출
}
Console.WriteLine(); 만해도 커서를 올려보면 오버로딩을 받고있다
--------------
재귀호출: 메서드 자기가 자기를 호출하는것, 무한루프에 빠질수도있음
재귀호출을 함으로서 더 쉽게 해결할 수 있는 부분도있다.
재귀호출은 호출스택에 호출된 메서드의 정보를 순차적으로 쌓고, 메서드가 반환되면서 스택에서
순차적으로 제거되는 방식으로동작한다.
조금더 쉽게 풀어쓰자면, 재귀함수는 자신과 같은 함수가 본인(함수)안에 들어있는상태이며,
해당 라인(자신의 함수를 넣은 부분)이 실행되는순간 바깥의 본인(함수)는 실행을 멈추고
라인에 쓰인 함수를 먼저 실행후, 반환까지한다. 그다음에 다시 바깥의 본인(함수)의 그다음 라인을 읽거나
읽을게없다면 반환한다. 이 구조가 스택구조다.
기저조건에 도달하면 더이상의 재귀호출이 이루어지지않고, 이때부터 순서대로 다시 스택구조로서 올라가면된다.
internal class Program
{
static void CountDown(int n)
{
if (n <= 0) // 기저 조건
{
Console.WriteLine("Done");
}
else
{
Console.WriteLine(n);
CountDown(n - 1); // 자기 자신을 호출
Console.WriteLine("이코드는 실행될까요?");
}
}
// 메서드 호출
static void Main(string[] args)
{
CountDown(5);
}
값은
5
4
3
2
1
Done
이코드는 실행될까요?
이코드는 실행될까요?
이코드는 실행될까요?
이코드는 실행될까요?
이코드는 실행될까요?
이라고 출력된다.
5를 넣었을때 else를 충족하여
5를 출력하고
CountDown(4)를 호출하여
4를 출력하고
....
쭉 가서
CountDown(1) 를 호출하여
1을 출력하고
CountDown(0)을 호출하니
if문의 조건에 걸려서
Done이 출력된다. 그다음에 실행할게없이 반환하며 해당 CountDown(0)은 종료되고
중단되었던
CountDown(1)의 CountDown(0)을 호출했던 라인에서 빠져나와 다음라인
Console.WriteLine("이코드는 실행될까요?")를 실행하여
이코드는 실행될까요? 를 출력하는 것이다.
그다음은 CountDown(1)은 종료되고 빠져나와서
CountDown(2)에 대해서 다음라인을 마찬가지로 읽으면
이코도는 실행될까요? 를 출력한다.
...
그런식으로 쭊 CountDown(5) 까지
이코드는 실행될까요? 를 출력하면 프로그램이 종료된다.
재귀함수에서 반환이란, 메서드가 스코프를 닫고 종료되는 순간으로, 함수가 호출된 직전의 위치로 제어권을
돌려주는 것을 말한다.
조금더 응용하자면 위에를 직접 재귀라하며, 간접 재귀도 존재한다.
상호 재귀라고도하며 함수 A가 함수 B를 호출하고 함수 B가 다시 함수 A를 호출하는 경우를 말한다.
static void FunctionA(int n)
{
if(n <= 0)
{
Console.WriteLine("A 종료");
}
else
{
Console.WriteLine("A: " + n);
FunctionB(n - 1);
}
}
static void FunctionB(int n)
{
if(n <= 0)
{
Console.WriteLine("B 종료");
}
else
{
Console.WriteLine("B: " + n);
FunctionA(n - 1);
}
}
static void Main(string[] args)
{
FunctionA(5);
}
주로 어떨때 사용될까?
1. 트리나 그래프 자료구조 탐색, 트리구조는 각 노드가 자식노드를 가지고,
전체 구조가 하위 트리로 나뉘기때문에 재귀적 탐색이 적합하다.
예를들어, 파일 시스템 탐색, 디렉터리 탐색 등이 있다.
2. 수학적 정의가 재귀적일 수 있다.
팩토리얼, 피보나치 수열 등이다.
3.문제를 분할 정복 방식으로 해결할때
분할정복 알고리즘: 복잡한 문제를 여러 작은 문제로 나눈뒤, 각각을 해결하고 합치는 방식이다.
4. 간혹 반복문보다 간단하게 표현가능하다.
어떤 데이터를 반복적으로 처리해야하거나, 복잡한 조건에 따라 분기하는 경우 재귀함수를 사용
하면 코드의 가독성을 높인다.
재귀호출은 호출스택에 저장하므로 너무 깊은 재귀호출은 스택오버플로우를 일으킨다.
단, 함수에 자신이 아닌 다른 함수가 들어있다고 재귀함수라 부르지않는다.
함수 A 안에서 함수 B를 호출한다고 해서 이를 바로 재귀라고 하지는 않는다. 함수 B가 독립적으로 실행되고,
A로부터 완전히 분리된 작업을 수행한다면, 그건 그저 일반적인 함수 호출이다.
이번에는 반환값이 있는 경우의 재귀함수의 예제다
static int Factorial(int n)
{
// 기저 조건: n이 0이면 팩토리얼의 값은 1입니다.
if(n == 0)
{
return 1;
}
else
{
// 재귀 호출: n * (n-1)의 팩토리얼을 구함
return n * Factorial(n - 1);
}
}
static void Main(string[] args)
{
int result = Factorial(5);
Console.WriteLine(result); // 5! = 120 출력
}
Factorial(5)는 5 * Factorial(4)를 반환하기 위해 Factorial(4)를 호출합니다.
Factorial(4)는 4 * Factorial(3)를 반환하기 위해 Factorial(3)를 호출합니다.
이런 식으로 계속되어, Factorial(0)에서 1을 반환하게 됩니다.
반환값의 전달 과정 (스택 반환)
Factorial(0)에서 1을 반환하면, 그 값을 호출한 Factorial(1)로 전달합니다.
Factorial(1)는 1 * 1 = 1을 반환합니다.
이후 Factorial(2)는 2 * 1 = 2를 반환하고,
Factorial(3)는 3 * 2 = 6,
Factorial(4)는 4 * 6 = 24,
마지막으로 Factorial(5)는 5 * 24 = 120을 반환합니다.
최종적으로 Main에서 이 값을 받아 출력하게 됩니다.
기저 조건은 재귀호출이 무한 반복되지않도록 함수의 종료지점을 제공하는 역할이다.
반환값은 함수가 종료되면서 뱉어내는데, 가장 깊은 호출부터 차례대로 값이 전달되면서 최종결과가 계산된다.
------------
구조체
여러개의 데이터를 묶어서 하나의 사용자 정의형식으로 만들기위한 방법
값형식임, 어딘가에 대입하거나 할당할때 순전히 복사가 일어남.
struct 키워드를 사용함
멤버는 (필드)변수와 메서드로 구성된다.
struct Person
{
public string Name;
public int Age;
public void PrintInfo()
{
Console.WriteLine($"Name: {Name}, Age: {Age}");
}
}
구조체 사용은 변수를 선언하여 사용가능하다.
멤버에는 접근할때 . 연산자 사용함.
Person person1;
person1.Name = "John";
person1.Age = 25;
person1.PrintInfo();
-------------
-----------
라이브강의
변수와 자료형
//변수와 자료형
//Rpg 게임을 만든다고 가정
//캐릭터
//아이템
//적
//몬스터
//상점
//레벨
//필요한 클래스부터 만든다. 라고 생각하면된다.
class Player
{
//변수
//1. 멤버변수 -> 메모리의 힙 공간에 저장됌
//클래스 내부에서 선언되는 변수
// 가바지컬렉션이 메모리에서 자동관리한다.
//c++에서는 자동으로 관리안해줘서 개발자들이 직접 관리해야함.
//멤버 변수끼리는 형이 달라도 이름이 같으면 안됌.
private int mp = 200;
private float levelexp = 100;
public int skillMp = 255;
//2. 지역변수 -> 메모리의 스택공간에 저장됌
private void Test()
{// <- Test() 함수 시작부분
int testhp = 10; // 함수 내부에서 선언되는 변수
//지역변수의 특징
//함수 내에서만 작동되고 밖에서는 못쓴다.
//함수 내부에서만 쓰이므로 지역변수는 접근제한지정자가 없다.
//그냥 private이라 생각하고 사용하면된다.
//지역변수는 임시데이터라고 말할수있다.
// 함수내에서 사용되고 함수가 종료되면 자동으로 소멸된다.
int mp = 100; //멤버변수와 이름이 같아도된다, 이때 멤버변수가 아닌 지역변수로 여겨진다.
Console.WriteLine(mp); // 멤버와, 지역변수가 같을때, 100이된다. 우선권은 지역변수로 가게된다.
//동일한 함수내에 지역변수의 경우에도 이름이 같으면 안된다.
}// <- Test() 함수 끝부분
private void Test2()
{
//testhp = 20; //오류! 같은 변순데 Test()에서 선언되어서 여기선 못씀
mp = 30; // 멤버변수이므로 사용가능
}
//3. 정적변수 -> static ->프로그램시작 , 프로그램이 존재하는동안 존재
public static int staticInt = 10;
// 정적변수는 메모리의 데이터영역에 저장됌
// 멤버, 지역, 정적변수의 메모리 영역 저장공간이 다르기때문에 소멸되는 순간도 다르다.
//레벨
//이름
//능력치
//직업정보
//공격력
//경험치
/*플레이어를 만든다 가정했을때
* 이것들을 변수로 사용한다고 이해한다
*/
//변수가 선언되는 구조가 있다.
// 접근제한 지정자 / 자료형 / 식별자 = "그 자료형에 맞게"
// 접근제한 지정자 : public, private, protected(상수관련), internal
public int level = 1; // "1" 이라고 하면 오류가뜸, 자료형(int)에 맞게 할당해야함
private string playerName = "플레이어";
private int exp = 100;
private float power = 10.5f;
public void Test3(int num)
{
num = 30;
}
}
class Monster
{// <- 몬스터 클래스 시작부분
private byte hp = 100; // 256이라 치면 범위를 벗어나서 빨갛게 뜸
private float exp = 100f;
// 자료형
// 정수형 자료형
// 크기 범위
// byte 1바이트 0~255
// sbyte 1바이트 -128~127
// short 2바이트 -32,768~32,767
// int 4바이트 -2,147,483,648~2,147,483,647
// 1바이트와 4바이트의 연산차이는...
// 바이트가 클수록 메모리(RAM)에 가져다 쓰는 양이 많음
// 16GB => 16,384MB => 16,384,000KB => 16,384,000,000Byte
// 즉 1byte, 4byte ...너무 신경을 안써도된다. 크기가 매우 크기때문이다.
// 실수형 자료형
// float 4바이트 -3.402823E+38 ~ 3.402823E+38 (약 7자리)
// double 8바이트 -1.79769313486232E+308 ~ 1.79769313486232E+308 (약 15자리)
//논리형 자료형
// bool 1바이트 true / false
//문자형 자료형
//string 문자수 * 2바이트
//char 2바이트 문자1개
}// <- 몬스터 클래스 끝부분
class Shop
{
}
class Item
{
}
internal class Program
{
static void Main(string[] args)
{
Player newPlayer = new Player(); // 객체 생성
// 실행시 Player 클래스 내의 변수들의 데이터가 메모리에 저장된다.
newPlayer.level = 20; // public이므로 사용가능
int a = 20;
newPlayer.Test3(a);
Console.WriteLine(a); // 20이 출력된다.
//Test3()에서 num은 지역변수이므로 Test3()에서만 사용가능하다.
//139번줄에서 Test3()의 num은 소멸되고난뒤이므로 그냥 20이 출력된다.
//함수식 내에 return;이라는 값 반환을 주면 반환이 되므로 30이 출력이 가능하다.
}
-----------
기본적으로 값형식이 메서드에 인자로 전달될때는 값복사가 일어나므로 메서드 내에서 매개변수의 값을
아무리 변경해도 호출한 쪽의 변수에는 영향을 주지않는다. 따라서 Test3() 메서드의 매개변수 num
을 30으로 바꾸어도 실제 인자로 전달된 변수a의 값은 20이 할당되어있기때문에 변수a는 여전히 20을 유지한다.
만약, 마지막에서 Test3() 메서드 내의 매개변수 int num의 실행코드 의 값을 반환해서
출력하고싶다면 두가지 방법을 써볼 수 있다.
1. ref 키워드를 사용한다.
ref 키워드는 메서드에 전달되는 인자가 참조로 전달되서 메서드 내에서의 변경이 호출한 쪽에도 반영된다.
class Player
{
public void Test3(ref int num)
{
num = 30;
}
}
internal class Program
{
static void Main(string[] args)
{
Player newPlayer = new Player();
int a = 20;
newPlayer.Test3(ref a); // ref 키워드를 사용하여 a를 전달
Console.WriteLine(a); // 출력 : 30
}
}
이 될수있다.
2. 메서드에서 값을 반환하는 방법
메서드에서 변경된 값을 반환하고 호출측에서 그 값을 변수에 할당하는 것이다.
class Player
{
public int Test3(int num)
{
num = 30;
return num;
}
}
internal class Program
{
static void Main(string[] args)
{
Player newPlayer = new Player(); // 객체 생성
int a = 20;
a = newPlayer.Test3(a); // 반환값을 a에 대입
Console.WriteLine(a); // 출력: 30
}
}
여기서 주의할 점은 반환값을 a에 대입을 다시 해줘야한다는 점이다.
반환만 시킨다면 a에 재할당 되지않으므로 여전히 a의 값은 20인 상태이다.
즉, a = newPlayer.Test3(a); 라는 코드가 아니라
기존처럼
newPlayer.Test3(a); 였다면 반환된 값 30이 a에 할당해주지않고 버려지기때문이다.
--------------------------------------------------
행렬

다차원행렬 3차원행렬 [2, 3, 4] // 3행 4열짜리 행렬이 2층 , 뒤에 하나 더깔린 개념

'Unity 개발 공부' 카테고리의 다른 글
| [내배캠] 본캠 9일차. 메서드 연산자 복습 , 그리고 TextRpg 진행과정 (5) | 2025.04.17 |
|---|---|
| [내배캠] 본캠 8일차. 인터페이스, 열거형, 예외처리, 참조형, 값형, 델리게이트, 람다, LINQ (0) | 2025.04.16 |
| [내배캠] 본캠 6일차. 기초문법 변수,자료형 그리고 기초연산문제 복습 (0) | 2025.04.14 |
| [내배캠] 본캠 5일차. 기능구현 전체 정리. 셔플, 코루틴 등, (0) | 2025.04.11 |
| [내배캠] 본캠 4일차. 팀 카드 뒤집기 게임 Git 병합 충돌 #4 (0) | 2025.04.10 |