동일한 자료형의 데이터들이 '연속'으로 저장되는 자료 구조
// 배열 선언
데이터_유형[] 배열_이름;
// 배열 초기화
배열_이름 = new 데이터_유형[크기];
// 배열을 한 줄로 선언 및 초기화
데이터_유형[] 배열_이름 = new 데이터_유형[크기];
// 배열 요소에 접근
배열_이름[인덱스] = 값;
값 = 배열_이름[인덱스];
기본적인 구조는 이렇다.
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; // 결과값을 실수로 출력하기 위해서 double로 변환한다
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개의 숫자를 입력하세요 (1~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;
}
}
배열과 반복문은 실과 바늘이라는 생각이 든다. 둘을 조합했을 때 아주 강력한 기능을 하며 다양하게 활용할 수가 있다.
둘 이상의 배열을 묶어놓은 배열이며, 행과 열로 이루어진 표와 같다고 이해하면 될 듯 하다. (실제론 아님 !)
// 2차원 배열의 선언과 초기화
int[,] array3 = new int[2, 3]; // 2행 3열의 int형 2차원 배열 선언
// 다차원 배열 초기화
array3[0, 0] = 1;
array3[0, 1] = 2;
array3[0, 2] = 3;
array3[1, 0] = 4;
array3[1, 1] = 5;
array3[1, 2] = 6;
// 선언과 함께 초기화
int[,] array2D = new int[3, 4] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
복잡한 데이터 구조를 효율적으로 관리하기 위해서 다차원 배열을 이용하며, 2차원 배열은 행과 열로 이루어진 데이터 구조를 다루기에 적합하다.
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) // i, j가 모두 1이라면~
{
Console.Write("■");
}
else
{
Console.Write("□");
}
}
Console.WriteLine();
}
출력값 !
■■■■■
■□□□■
■□■□■
■□□□■
■■■■■
배열과 비슷한 자료 구조이지만 크기가 가변적이다.
System.Collections.Generic 네임스페이스를 추가하여 사용한다.
가변적인 크기를 갖는 배열. Length 사용 xx
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);
}
키와 값으로 구성된 데이터를 저장한다.
중복된 키를 가질 수 없으며, 키와 값이 쌍을 이룬다.
using System.Collections.Generic;
Dictionary<string, int> scores = new Dictionary<string, int>(); // 빈 딕셔너리 생성
scores.Add("Alice", 100); // 딕셔너리에 데이터 추가
scores.Add("Bob", 80);
scores.Add("Charlie", 90);
scores.Remove("Bob"); // 딕셔너리에서 데이터 삭제
foreach(KeyValuePair<string, int> pair in scores) // 딕셔너리 데이터 출력
{
Console.WriteLine(pair.Key + ": " + pair.Value);
}
후입선출(LIFO) 형식의 자료 구조
Stack<int> stack1 = new Stack<int>(); // int형 Stack 선언
// Stack에 요소 추가
stack1.Push(1);
stack1.Push(2);
stack1.Push(3);
// Stack에서 요소 가져오기
int value = stack1.Pop(); // value = 3 (마지막에 추가된 요소)
선입선출(FIFO) 형식의 자료 구조
Queue<int> queue1 = new Queue<int>(); // int형 Queue 선언
// Queue에 요소 추가
queue1.Enqueue(1);
queue1.Enqueue(2);
queue1.Enqueue(3);
// Queue에서 요소 가져오기
int value = queue1.Dequeue(); // value = 1 (가장 먼저 추가된 요소)
중복되지 않은 요소들로 이루어진 집합
HashSet<int> set1 = new HashSet<int>(); // int형 HashSet 선언
// HashSet에 요소 추가
set1.Add(1);
set1.Add(2);
set1.Add(3);
// HashSet에서 요소 가져오기
foreach (int element in set1)
{
Console.WriteLine(element);
}
워낙 개념적인 부분들이라 계속해서 사용을 해봐야 이해도가 올라갈 것 같다.
리스트는 동적으로 크기를 조정할 수 있어 배열과는 다르게 유연한 데이터 구조를 구현할 수 있습니다. 하지만, 리스트를 무분별하게 사용하는 것은 좋지 않은 습관입니다.
메모리 사용량 증가
리스트는 동적으로 크기를 조정할 수 있어 배열보다 많은 메모리를 사용합니다. 따라서, 많은 데이터를 다루는 경우 리스트를 무분별하게 사용하면 메모리 사용량이 급격히 증가하여 성능 저하를 유발할 수 있습니다.
데이터 접근 시간 증가
리스트는 연결 리스트(linked list)로 구현되기 때문에, 인덱스를 이용한 데이터 접근이 배열보다 느립니다. 리스트에서 특정 인덱스의 데이터를 찾기 위해서는 연결된 노드를 모두 순회해야 하기 때문입니다. 이러한 이유로, 리스트를 무분별하게 사용하면 데이터 접근 시간이 증가하여 성능이 저하될 수 있습니다.
코드 복잡도 증가
리스트는 동적으로 크기를 조정할 수 있기 때문에, 데이터 추가, 삭제 등의 작업이 배열보다 간편합니다. 하지만, 이러한 유연성은 코드 복잡도를 증가시킬 수 있습니다. 리스트를 사용할 때는 데이터 추가, 삭제 등의 작업을 적절히 처리하는 코드를 작성해야 하므로, 코드의 가독성과 유지보수성이 저하될 수 있습니다.
따라서, 리스트를 무분별하게 사용하는 것은 좋지 않은 습관입니다. 데이터 구조를 선택할 때는, 데이터의 크기와 사용 목적을 고려하여 배열과 리스트 중 적절한 것을 선택해야 합니다.
메서드(Method)는 일련의 코드 블록으로, 특정한 작업을 수행하기 위해 사용되는 독립적인 기능 단위이다.
- 코드의 재사용성과 모듈화를 위해 사용되며, 필요할 때 호출하여 실행됩니다.
[접근 제한자] [리턴 타입] [메서드 이름]([매개변수])
{
// 메서드 실행 코드
}
리턴 타입이 없을 경우 void
사용
// 메서드의 호출
[메서드 이름]([전달할 매개변수]);
매개변수란 메서드에 전달되는 입력값이며, 필요한 경우 0개 이상을 정의할 수 있다.
메서드 호출 시 전달되는 값에 따라서 동적으로 결정이 되며, 해당 매개변수의 값을 전달해야 한다.
void PrintFullName(string firstName, string lastName) // 여러 매개변수 !
{
Console.WriteLine("Full Name: " + firstName + " " + lastName);
}
// 메서드 호출
PrintFullName("John", "Doe");
반환값은 메서드가 수행한 작업의 결과를 호출자에게 반환하는 값이다. 메서드의 리턴 타입에 지정되며, 해당 타입의 값을 반환해야 한다.
int AddNumbers(int a, int b)
{
int sum = a + b;
return sum;
}
// 메서드 호출 및 반환값 사용
int result = AddNumbers(10, 20);
Console.WriteLine("Sum: " + result);
메서드가 값을 반환하지 않을 때 void를 쓴다.
void PrintMessage(string message)
{
Console.WriteLine("Message: " + message);
}
// 메서드 호출
PrintMessage("Hello, World!");
동일한 이름의 메서드를 여러 매개변수 목록으로 다중 정의하는 개념이다. 메서드의 기능이나 작업은 동일하지만 입력값에 따라 다르게 동작해야 하는 경우에 사용한다.
void PrintMessage(string message)
{
Console.WriteLine("Message: " + message);
}
void PrintMessage(int number)
{
Console.WriteLine("Number: " + number);
}
// 메서드 호출
PrintMessage("Hello, World!"); // 문자열 매개변수를 가진 메서드 호출
PrintMessage(10); // 정수 매개변수를 가진 메서드 호출
오버로딩 기초 코드
int AddNumbers(int a, int b)
{
return a + b;
}
int AddNumbers(int a, int b, int c)
{
return a + b + c;
}
// 메서드 호출
int sum1 = AddNumbers(10, 20); // 두 개의 정수 매개변수를 가진 메서드 호출
int sum2 = AddNumbers(10, 20, 30); // 세 개의 정수 매개변수를 가진 메서드 호출
재귀 호출은 메서드가 자기 자신을 호출하는 것을 의미한다. 문제를 작은 부분으로 분할하여 해결하는 방법 중 하나로, 작은 부분의 해결 방법이 큰 문제의 해결 방법과 동일한 구조를 갖고 있는 경우에 적합하다. 호출 스택에 호출된 메서드의 정보를 순차적으로 쌓고, 메서드가 반환되면서 스택에서 순차적으로 제거되는 방식으로 동작한다.
void CountDown(int n)
{
if (n <= 0)
{
Console.WriteLine("Done");
}
else
{
Console.WriteLine(n);
CountDown(n - 1); // 자기 자신을 호출
}
}
// 메서드 호출
CountDown(5);
복잡한 문제를 단순하게 해결할 수 있는 장점이 있지만 종료 조건을 명확하게 하지 않으면 무한 루프에 빠질 가능성이 높아서 주의하며 사용해야 한다.
여러 개의 데이터를 묶어서 하나의 사용자 정의 형식으로 만들기 위한 방법이다. 값 형식(Value Type)으로 분류되며, 데이터를 저장하고 필요한 기능을 제공한다. 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();
소문으로 듣던 콘솔에 별 찍기이다.. for
반복문을 사용하여 순차적으로 만들어 보자.
우선 처음으로 생각한 건 한 번 반복문이 실행이 되고 break; 빠져나와서 다시 반복문을 실행해야 하는 건가? 싶었지만 잘 되지 않았다
* break;
** break;
*** break;
**** break;
***** break; 이런 방식?
그리고 출력을 Console.Write
, Console.WriteLine();
중 무엇을 사용해야 하는지 코드를 따로 빼어 실행시켜 보면서 개념을 익혔다.
Console.Write(1);
Console.WriteLine("줄바꿈");
Console.Write(12);
Console.WriteLine("줄바꿈");
Console.Write(123);
강의 자료들과 작성했던 예문들을 살펴보며 중첩 반복문을 이용해면 된다는 것을 깨달았다 !
for (int i = 1; i < 6; i++) // 별이 5층이니 다섯 번 반복
{
for (int j = 0; j < i; j++) // 반복 조건을 i의 값 보다 작을 때까지로 만들어서 조절
{
Console.Write("*");
}
Console.WriteLine(); // 아주 중요한 줄바꿈..!
}
// 출력값
*
**
***
****
*****
중첩 시킨 j 반복문이 1, 2, 3, 4, 5 순서로 실행되었다면, 삼각형을 뒤집어야 하니 5, 4, 3, 2, 1 순서로 실행시키면 될 것이다.
for (int i = 0; i < 5; i++)
{
for (int j = 5; j > i; j--) // j > i; j-- 로 감소시켜서 조정
{
Console.Write("*");
}
Console.WriteLine();
}
조건문 사용을 증가만 사용해왔어서 감소를 사용하려니 약간 헤메는 부분이 있었다. j가 i보다 작아지면 반복을 멈춘다는 개념을 이해하니 해결할 수 있었다.
// 출력값
*****
****
***
**
*
예제를 보자마자 도대체 이게 뭘까.. 싶은 마음이었지만 하나하나 뜯어보며 시작했다.
*앞에 4번의 스페이스가 들어가면 되는데 그걸 어떻게 하지...
Console.WriteLine(" *");
Console.WriteLine(" ***");
Console.WriteLine(" *****");
Console.WriteLine(" *******");
Console.WriteLine("*********");
우선 출력할 값을 미리 만들어 보고 고민을 시작했다. 중첩 반복문 사용은 확실한데 "*"앞에 " "을 어떻게 집어 넣을지 많은 시행착오가 있었다. \n \d \t 등을 사용해보면서 좌절..
하지만 결국 규칙을 스스로 만들어서 해결하였다.
for (int i = 1; i < 10; i += 2) // 1 3 5 7 9의 값이 필요하다
{
for (int j = 10; j > i; j -= 2) // 4 3 2 1 번 반복해야 한다.
{
Console.Write(" ");
}
for (int k = 0; k < i; k++) // 1 3 5 7 9 번 반복해야 한다.
{
Console.Write("*");
}
Console.WriteLine();
}
하위 반복문에 전달되어야 할 i값을 먼저 생각했고 마찬가지로 " "와 "*"이 몇 번 반복되어야 하는지 정해놓고 보니 규칙을 만들 수 있겠다고 판단이 됐다. continue를 사용하여 i의 짝수 값을 건너 뛰고 반복시켜야 하나 싶었지만 조건들의 수정으로도 충분히 작동하게 만들 수 있었다.
// 출력값
*
***
*****
*******
*********
각 문법의 개념적인 부분들은 아직 이해하기에는 어렵다고 느꼈다.. 역시 직접 사용해보며 동작하는 방식이 머리와 손에 충분히 익어야 이해할 수 있을 듯 싶다.
오늘의 과제 별 찍기는 스스로 생각하며 작성하는 코드의 참 맛을 알게 되었다. 실행 결과값을 생각하며 코드의 전체적인 흐름을 구상, 필요한 데이터들을 나열하며 규칙을 정의, 조건을 바꿔가며 완성시키기 이 일련의 과정들이 개인적으로 아주 재밌었고 뿌듯함이 이루 말할 수 없을 정도다 !