8월 16일 TIL
C#강의 2주차 내용 복습
배열
여러개의 변수가 같은 자료형일때 사용
배열의 1번째 인덱스 번호는 0부터 시작 → 배열의 첫번째 요소에 접근하려면
array1[0] = 1;
위와 같이 사용해야함
int[] itemPrices = { 100, 200, 300, 400, 500 };
int totalPrice = 0;
for (int i = 0; i < itemPrices.Length; i++)
{
totalPrice += itemPrices[i];
}
Console.WriteLine("총 아이템 가격: " + totalPrice + " gold");
배열의 요소에 접근할 때는 하나씩 접근하지 않고 반복문과 인덱스 번호를 이용하여 접근
다차원 배열
// 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 } };
// 3차원 배열의 선언과 초기화
int[,,] array3D = new int[2, 3, 4]
{
{ { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
{ { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } }
};
[x0, y0] 1 |
[x1, y0] 2 |
[x2, y0] 3 |
[x0, y1] 4 |
[x1, y2] 5 |
[x2, y1] 6 |
여러개의 배열을 붙인 구조
2차원 배열 : 축이 2개인 표와 같은 구조 - 컴퓨터 안에서는 일렬로 생성됨
3차원 배열 : 축이 하나 추가되어 3개인 구조
컬렉션
배열과 비슷하지만 크기 변경이 자유롭다
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);
}
위 코드에서는 2를 지워서 1과 3만 출력된다
for문이 아니라 foreach문을 사용하는 이유는 컴퓨터에서 일렬로 이어서 데이터를 저장하는 배열과 달리 Length를 사용할 수 없기 때문
반복문을 사용하려면 Count를 사용해야 한다
Dictionary
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);
}
키 | 밸류 |
A | 100 |
B | 80 |
C | 90 |
키와 밸류가 쌍을 이루는 데이터 구조
위 코드에서는 Bob을 호출하는 것으로 80이라는 밸류를 삭제
Stack - 후입선출
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 (마지막에 추가된 요소)
Queue - 선입선출
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
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);
}
List와 비슷하지만 차이점은 List는 중복 요소가 존재하는 반면 HashSet은 중복되지 않는 요소들로만 이루어져 있음
배열과 리스트
리스트의 유연성만 믿고 배열을 사용할 수 있는 모든 상황을 리스트로 대체하는 것은 좋지 않다
- 메모리 사용량 - 리스트는 동적으로 길이를 조정할 수 있지만 그만큼 배열보다 많은 메모리를 사용하게 되므로 리스트를 많이 사용하면 성능이 떨어질 수 있다
- 데이터 접근 시간 증가 - 배열과 달리 리스트는 컴퓨터 안에서 정렬되어 있지 않기 때문에 순서대로 접근이 가능한 배열과 다르게 리스트의 특정 요소를 찾으려면 컴퓨터가 일일히 뒤져봐야 하기 때문에 데이터 접근에 지연이 생긴다
- 코드 복잡도 증가 - 리스트는 데이터의 추가 및 삭제 등이 자유롭지만 그만큼 사용되는 코드의 줄 수가 늘어나므로 코드를 읽는것과 관리하는것이 어려워진다
메서드
C언어에서 함수라고 하는 것을 C#에서는 메서드라고 함
메서드를 사용하는 이유
- 같은 코드가 여러번 쓰일 때 메서드를 사용하면 더 간편하게 반복할 수 있음
- 코드의 가독성이 올라가고 오류가 났을 때 메서드 안에서 고치면 해결되는 등 유지보수가 쉬워짐
메서드 사용법
[접근 제한자] [리턴 타입] [메서드 이름]([매개변수])
{
// 메서드 실행 코드
}
접근제한자 : public, private 등 변수와 마찬가지로 접근할 수 있는 범위를 제한
리턴 타입 : int, float 등 메서드가 반환할 값의 타입을 지정 반환값이 없다면 void
메서드 이름 : 메서드를 호출할 때 사용될 이름 이미 C#내장 메서드인 Writeline 등이 여기에 해당한다
매개변수 : 메서드를 호출하면서 메서드에 전달할 값 0개 혹은 1개 이상으로 입력 가능
실행코드 : 메서드 이름과 매개변수를 입력하여 호출한 메서드가 실행할 코드의 내용
// 예시 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;
}
호출 방법
[메서드 이름]([전달할 매개변수]);
Console.WriteLine(SayHello());
여태까지 사용한 Console.WriteLine이 이미 함수 이름과 매개변수를 사용하여 호출한 메서드다
메서드 오버로딩
같은 이름의 메서드를 매개변수의 타입, 순서, 개수 등이 다르면 이름이 같더라도 다른 메서드로 취급
void PrintMessage(string message)
{
Console.WriteLine("Message: " + message);
}
void PrintMessage(int number)
{
Console.WriteLine("Number: " + number);
}
// 메서드 호출
PrintMessage("Hello, World!"); // 문자열 매개변수를 가진 메서드 호출
PrintMessage(10); // 정수 매개변수를 가진 메서드 호출
정리 하는 중 생긴 궁금증 : Console.WriteLine도 매개변수로 문자열과 숫자 다 사용 가능한데 그럼 Console.WriteLine도 메서드 오버로딩이 적용된 내장메서드인가?
메서드 오버로딩은 반환값의 변화는 체크하지 않는다
재귀호출
void CountDown(int n)
{
if (n <= 0)
{
Console.WriteLine("Done");
}
else
{
Console.WriteLine(n);
CountDown(n - 1); // 자기 자신을 호출
}
}
// 메서드 호출
CountDown(5);
메서드가 자기 자신을 호출하는 것
재귀호출을 사용하여 더 쉽거나 간결하게 풀 수 있는 문제들이 있지만 잘못하면 무한루프에 빠지기 쉽기 때문에 사용에는 주의가 필요
재귀호출은 스택 방식을 따라간다
호출된 메서드의 정보를 스택에 순서대로 쌓고 메서드가 반환되면 쌓아놓은 스택에서 순차적으로 제거
구조체
struct Person
{
public string Name;
public int Age;
public void PrintInfo()
{
Console.WriteLine($"Name: {Name}, Age: {Age}");
}
}
여러 변수들을 묶어서 하나로 만드는 사용자 정의 자료형
struct 키워드로 선언하고 구조체의 멤버는 변수와 메서드가 들어갈 수 있다
사용법
Person person1;
person1.Name = "John";
person1.Age = 25;
person1.PrintInfo();
변수 선언처럼 사용 가능하며 구조체 멤버에 접근할 때는 .연산자를 사용
객체 지향 프로그래밍의 특징
캡슐화
- 한 코드에서 여러가지 기능을 하는 것이 아니라 각각 필요한 기능들을 구현하고 그 기능들을 뭉쳐 새로운 구현을 할 수 있다
- 정보에 대한 은닉, 외부에 대한 접근 제한이 가능하고 코드의 안정성과 유지보수성 상승
상속
- 기존 클래스에서 새로운 클래스로 확장
- 공통된 부분을 상위클래스로 두고 하위클래스에서 각자의 기능을 구현
- 코드의 중복을 줄이고 구조화와 유지보수 용이하게 함
다형성
- 동일한 코드에서 다양한 처리들을 가능하게 하는 것 (EX - 메서드 오버로딩)
- 코드의 가독성과 재사용성 상승
추상화
- 복잡한 개념을 단순화하고 기능에 집중
- 세부 구현 내용은 미루고 핵심 개념에 집중
객체
- 데이터와 메서드를 가지고 동작하는 프로그램의 형태
- 모듈화, 재사용성 높음
클래스의 구성요소
- 필드 : 멤버 변수
- 메서드 : 멤버 함수
- 생성자 : 객체가 생성될 때 자동으로 호출되는 메서드
- 소멸자 : 메모리에서 해제되거나 소멸될 떄 자동으로 호출되는 메서드
클래스
- 객체를 생성하기 위한 설계도
- 속성과 동작들이 정의되어있음
- 클래스를 사용하여 인스턴스 생성
- 붕어빵 틀로 비유 가능 - 클래스 자체로는 할 수 있는게 별로 없고 클래스로 객체를 생성하여 객체를 사용해야함
객체
- 클래스의 인스턴스, 붕어빵
- 클래스로부터 생성되며 독립적인 상태임
클래스 선언과 인스턴스
클래스는 데이터와 메서드를 하나로 묶는 사용자 정의 타입. 구조체와 같이 사용자 정의 타입
class Person
{
public string Name;
public int Age;
public void PrintInfo()
{
Console.WriteLine("Name: " + Name);
Console.WriteLine("Age: " + Age);
}
}
Person p = new Person();
p.Name = "John";
p.Age = 30;
p.PrintInfo(); // 출력: Name: John, Age: 30
구조체와 클래스
- 클래스는 레퍼런스 타입 - Person p는 변수나 구조체처럼 실제로 공간이 생긴 것이 아님
new를 사용해 실제 공간을 만들고 그것을 p에 연결함 - new가 필요없이 바로 선언 가능한 구조체와 달리 new를 반드시 사용해야함
- 둘 다 사용자 정의 형식을 만들 때 샤용되지만 구조체는 값 형식이고 스택에 자동으로 할당되며 복사될 때 값이 복사된다
- 반면 클래스는 참조 형식이고 힙에 할당되며 이 작업은 컴퓨터가 자동으로 하는 것이 아닌 개발자가 한다
- 구조체는 상속이 불가능하지만 클래스는 단일 및 다중 상속이 가능하다
- 구조체는 작거나 단순한 데이터의 저장에 적합하고 클래스는 더 크고 복잡한 기능 구현에 적합하다
접근 제한자
class Person
{
public string Name; // 외부에서 자유롭게 접근 가능
private int Age; // 같은 클래스 내부에서만 접근 가능
protected string Address; // 같은 클래스 내부와 상속받은 클래스에서만 접근 가능
}
클래스, 필드, 메서드 등의 접근 범위를 제한하는 키워드
- public : 자유롭게 접근 가능
- private : 클래스안에서만 접근 가능
- protected : 클래스 내부와 상속받은 클래스에서 접근 가능
필드와 메서드
- 필드 : 클래스 내부에 선언된 변수. 데이터를 저장
- 메서드 : 클래스 내부에 선언된 함수. 클래스의 동작을 정의
필드
class Player
{
// 필드 선언
private string name;
private int level;
}
- 클래스, 구조체 내에 생성된 데이터를 저장하는 변수
- 객체의 특징이나 속성을 표현하기 위해 사용
- 보통 private로 외부의 접근을 제한
- 접근이 필요할 경우 접근 가능한 함수나 프로퍼티를 통해 간접적으로 접근
메서드
class Player
{
// 필드
private string name;
private int level;
// 메서드
public void Attack()
{
// 공격 동작 구현
}
}
- 클래스나 구조체의 기능을 정의
- 객체의 행동이나 동작을 구현
- 보통 public으로 외부에서 호출이 가능하도록 만듦
- 메서드를 호출하기 위해서는 new로 인스턴스를 생성 후 인스턴스를 통해 메서드를 호출
Player player = new Player(); // Player 클래스의 인스턴스 생성
player.Attack(); // Attack 메서드 호출
생성자와 소멸자
생성자
- 객체가 생성될 때 호출되는 메서드
- 객체를 초기화하고 초기값을 설정
- 클래스와 이름이 같고 반환값은 없음
- 객체를 생성할 때 new 키워드화 함께 호출
- 여러개 정의할 수 있고 매개변수의 개수나 타입에 따라 다른 생성자 호출 가능(생성자 오버로딩)
- 반환값, 매개변수가 없는 디폴트 생성자가 자동으로 생성되지만 사용자가 생성자를 직접 정의한다면 디폴트 생성자는 자동으로 생성되지 않음
class Person
{
private string name;
private int age;
// 매개변수가 없는 디폴트 생성자
public Person()
{
name = "Unknown";
age = 0;
}
// 매개변수를 받는 생성자
public Person(string newName, int newAge)
{
name = newName;
age = newAge;
}
public void PrintInfo()
{
Console.WriteLine($"Name: {name}, Age: {age}");
}
}
Person person1 = new Person(); // 디폴트 생성자 호출
Person person2 = new Person("John", 25); // 매개변수를 받는 생성자 호출
소멸자
- 객체가 소멸될 때 자동으로 호출되는 메서드
- 클래스와 이름이 같고 이름 앞에 ~을 붙여 구분
- 반환타입이 없고 매개변수를 가질 수 없음
- C#에선 가비지 컬렉터가 자동으로 메모리를 해제할 때 소멸자가 자동으로 호출
- 소멸자는 사용자가 호출하는 것이 아니기 때문에 오버로딩이 없음
class Person
{
private string name;
public Person(string newName)
{
name = newName;
Console.WriteLine("Person 객체 생성");
}
~Person()
{
Console.WriteLine("Person 객체 소멸");
}
}
프로퍼티
- private 등으로 은닉화 한 필드값을 외부에서 접근할 수 있게 만드는 중간 매개역
- 객체의 필드에 직접 접근이 아닌 간접 접근으로 접근을 허용하거나 거부하거나 유효한 데이터인지 등을 확인
프로퍼티 구문
[접근 제한자] [데이터 타입] 프로퍼티명
{
get
{
// 필드를 반환하거나 다른 로직 수행
}
set
{
// 필드에 값을 설정하거나 다른 로직 수행
}
}
- get과 set으로 구분
- 프로퍼티 이름은 대부분 사용하려는 변수를 대문자로 사용
- get은 값을 반환, set은 값을 대입
프로퍼티 사용 예시
class Person
{
private string name;
private int age;
public string Name
{
get { return name; }
set { name = value; }
}
public int Age
{
get { return age; }
set { age = value; }
}
}
Person person = new Person();
person.Name = "John"; // Name 프로퍼티에 값 설정
person.Age = 25; // Age 프로퍼티에 값 설정
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name과 Age 프로
접근 제한자 적용 및 유효성 검사
class Person
{
private string name;
private int age;
public string Name
{
get { return name; }
private set { name = value; } // set은 제한하고 get만 외부에서 사용
}
public int Age
{
get { return age; }
set
{
if (value >= 0)
age = value;
}
}
}
Person person = new Person();
person.Name = "John"; // 컴파일 오류: Name 프로퍼티의 set 접근자는 private입니다.
person.Age = -10; // 유효성 검사에 의해 나이 값이 설정되지 않습니다.
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name과 Age 프로퍼티에
접근하여 값을 출력합니다.
자동 프로퍼티
[접근 제한자] [데이터 타입] 프로퍼티명 { get; set; }
- 따로 구현하지 않아도 자동으로 필드의 역할을 진행
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Person person = new Person();
person.Name = "John"; // 값을 설정
person.Age = 25; // 값을 설정
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // 값을 읽어 출력
상속과 다형성
상속
- 기존 클래스를 확장 또는 재사용하여 자식 클래스를 생성하는 것
- 자식 클래스는 부모 클래스의 멤버를 상속받아 사용 가능
- 상속을 통해 부모클래스의 기능 확장 또는 수정하여 새 클래스 정의 가능
- 코드 재사용, 계층 구조로 코드 표현, 유지 보수성 증가
- 단일상속 : 하나의 자식 클래스가 하나의 부모 클래스만 상속받음
- 다중상속 : 하나의 자식 클래스가 여러개의 부모 클래스를 상속받음 C# 지원 안됨
- 인터페이스 상속: 클래스가 인터페이스를 상속받음 인터페이슨느 다중상속 지원
- 부모클래스 멤버에 접근: 부모 클래스에 정의된 멤버들을 자식 클래스에서 쉽게 재사용
- 메서드 재정의: 부모 클래스의 메서드를 그대로 사용할 필요 없이 재정의 하거나 확장하여 사용
- 상속의 깊이: 다수의 계층의 상속 구조 가능
상속의 깊이가 깊어지면 클래스 간 관계가 복잡해지므로 적절한 깊이로 맞출 필요가 있음\
접근 제한자와 상속
- 부모 클래스의 접근 제한자에 따라 자식 클래스가 접근할 수도 못할 수도 있음
// 부모 클래스
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public void Eat()
{
Console.WriteLine("Animal is eating.");
}
public void Sleep()
{
Console.WriteLine("Animal is sleeping.");
}
}
// 자식 클래스
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("Dog is bark.");
}
}
public class Cat : Animal
{
public void Sleep()
{
Console.WriteLine("Cat is sleeping.");
}
public void Meow()
{
Console.WriteLine("Cat is meow.");
}
}
// 사용 예시
Dog dog = new Dog();
dog.Name = "Bobby";
dog.Age = 3;
dog.Eat(); // Animal is eating.
dog.Sleep(); // Animal is sleeping.
dog.Bark(); // Dog is barking
Cat cat = new Cat();
cat.Name = "KKami";
cat.Age = 10;
cat.Eat();
cat.Sleep();
cat.Meow();
다형성
같은 타입이지만 다양한 동작을 수행할 수 있는 능력
가상 메서드
- 부모클래스에서 정의, 자식클래스에서 재정의
- virtual 키워드로 선언하며 필요할 때 자식클래스에서 재정의
- 자식클래스에서 부모클래스의 메서드 변경 또는 확장 가능
public class Unit
{
public virtual void Move()
{
Console.WriteLine("두발로 걷기");
}
public void Attack()
{
Console.WriteLine("Unit 공격");
}
}
public class Marine : Unit
{
}
public class Zergling : Unit
{
public override void Move()
{
Console.WriteLine("네발로 걷기");
}
}
// 사용 예시
// #1 참조형태와 실형태가 같을때
Marine marine = new Marine();
marine.Move();
marine.Attack();
Zergling zergling = new Zergling();
zergling.Move();
zergling.Attack();
// #2 참조형태와 실형태가 다를때
List<Unit> list = new List<Unit>();
list.Add(new Marine());
list.Add(new Zergling());
foreach (Unit unit in list)
{
unit.Move();
}
부모의 형태로 자식클래스 객체를 관리할 때 virtual로 자식 오브젝트에서 재정의되었는지 확인 후 재정의 되었다면 다시 자식 클래스에서 재정의된 메서드를 사용한다
추상 클래스와 메서드
- 직접 인스턴스를 생성할 수 없음
- 주로 상속 용도의 베이스로 사용
- abstract 키워드로 선언, 추상 메서드를 포함할 수 있음
- 추상 메서드는 구현되지 않은 메서드로 상속받은 자식 클래스에서 구현해야함
abstract class Shape
{
public abstract void Draw();
}
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square");
}
}
class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle");
}
}
List<Shape> list = new List<Shape>();
list.Add(new Circle());
list.Add(new Square());
list.Add(new Triangle());
foreach (Shape shape in list )
{
shape.Draw();
}
오버라이딩과 오버로딩
- 오버라이딩
- 부모 클래스에 이미 정의된 메서드를 자식 클래스에서 재정의
- 메서드 이름, 매개변수 및 반환타입이 똑같아야 함
- 이를 통해 자식클래스가 부모클래스의 메서드를 재정의하여 자신에게 맞는 동작으로 구현
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a shape.");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.Draw(); // Drawing a circle.
shape2.Draw(); // Drawing a rectangle.
- 오버로딩
- 이름이 같은 여러개의 메서드를 정의
- 함수 호출 시 골라서 불러올 수 있게 할 수 있음
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
Calculator calc = new Calculator();
int result1 = calc.Add(2, 3); // 5
int result2 = calc.Add(2, 3, 4); // 9
제너릭
제너릭 사용법
- 클래스나 메서드를 일반화시켜 다양한 자료형에 대응할 수 있도록 만드는 기능
- 코드를 하나만 짜놓고 다양한 자료형들을 사용할 수 있게 만듦
- 코드의 재사용성 향상
- C#에서는 <T> 키워드로 제너릭 선언
- 제너릭 클래스나 메서드에서 사용할 자료형은 사용 시점에 결정
- 사용할 때는 <T> 대신 구체적인 자료형 사용
// 제너릭 클래스 선언 예시
class Stack<T>
{
private T[] elements;
private int top;
public Stack()
{
elements = new T[100];
top = 0;
}
public void Push(T item)
{
elements[top++] = item;
}
public T Pop()
{
return elements[--top];
}
}
// 제너릭 클래스 사용 예시
Stack<int> intStack = new Stack<int>();
intStack.Push(1);
intStack.Push(2);
intStack.Push(3);
Console.WriteLine(intStack.Pop()); // 출력 결과: 3
- 제너릭을 2개 이상 사용할 떄 예시
class Pair<T1, T2>
{
public T1 First { get; set; }
public T2 Second { get; set; }
public Pair(T1 first, T2 second)
{
First = first;
Second = second;
}
public void Display()
{
Console.WriteLine($"First: {First}, Second: {Second}");
}
}
Pair<int, string> pair1 = new Pair<int, string>(1, "One");
pair1.Display();
Pair<double, bool> pair2 = new Pair<double, bool>(3.14, true);
pair2.Display();
출력 결과
First: 1, Second: One
First: 3.14, Second: True
out, ref 키워드
사용법
- 메서드에서 매개변수를 전달할 때 사용
- out: 메서드 반환 값을 매개변수로 전달
- ref: 매개변수를 수정하여 원래 값에 영향을 줄 때 사용
- 실제 변수에 영향을 주기 때문에 사용에 주의가 필요
// out 키워드 사용 예시
void Divide(int a, int b, out int quotient, out int remainder)
{
quotient = a / b;
remainder = a % b;
}
int quotient, remainder;
Divide(7, 3, out quotient, out remainder);
Console.WriteLine($"{quotient}, {remainder}"); // 출력 결과: 2, 1
// ref 키워드 사용 예시
void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}"); // 출력 결과: 2, 1
주의사항
- 값의 변경 가능성 : 메서드 내에서 해당 변수의 값을 직접 변경할 수 있으므로 주의가 필요
- 성능 이슈 : 값의 복사가 없기 때문에 성능상 빨라지지만 남용시 코드의 가독성이 떨어지고 유지보수가 어려워짐
- 변수 변경 여부 주의 : 매개변수가 메서드 내에서 무조건 변경되어야 하므로 값의 비교 등이 필요할 시 이전 값의 복사나 다른 변수에 저장하는 등의 조치가 필요