XR 플밍 - 7. C# 프로그래밍 총정리 (4/11)

이형원·2025년 4월 11일
0

XR플밍

목록 보기
40/215

1. 자료형

자료의 형태를 지정하여 처리하는 방식을 명시하는 역할이다.

자료형 이름자료형 형태메모리 크기표현 범위비고
bool논리 자료형1bytetrue, false
int부호 있는 정수형4byte-2^31 ~ 2^31크기에 따라 / 부호에 따라 다른 변수 있음
float부동소수점 실수4byte3.4e - 38 ~ 3.4e + 38약 소수점 7자리
double부동소수점 실수8byte1.7e - 308 ~ 1.7e + 308약 소수점 15자리
char유니코드 문자형2byte'a', 'b', '한', ...따옴표 사용
string유니코드 문자열X(상이함)"abcde", "안녕하세요"쌍따옴표 사용

1.1 변수(variable)

데이터를 저장하기 위해 프로그램에 의해 이름을 할당받은 메모리 공간

  • 변수의 선언 및 초기화
  • 기본형
    (자료형) (변수명);
  • 선언과 동시에 초기화
    (자료형) (변수명) = (입력값);
  • 변수에 데이터 저장

대입 연산자 좌측에 변수를 배치

int iValue = 10; 	// 초기화
iValue = 20 		// 20이 저장됨
  • 카멜 표기법

변수명을 지정할 때 두 개 이상의 단어를 잇는 변수명일 시, 낙타 혹처럼 각 단어를 사이를 대문자로 표기하는 것

int attackDamage;
int charStrStatus;

1.2 상수(Constant)

프로그램이 실행되는 동안 변경할 수 없는 데이터로 프로그램 종료 시까지 변경

  • 상수의 선언 및 초기화
    상수는 선언과 동시에 초기화해야 한다.
const int Max = 200;

1.3 형변환(Casting)

데이터를 선언한 자료형에 맞는 형태로 변환하는 작업
형변환 과정에서 보관할 수 없는 데이터는 버려진다

  • 명시적 형변환

변환할 데이터의 앞에 반환할 자료형을 괄호 안에 넣어서 형변환 진행

int intValue = (int)1.2;
  • 암시적 형변환

변수에 데이터를 넣는 과정에서 자료형이 더 큰 범위를 표현하는 경우, 자동으로 형변환을 진행

float floatValue = 3;
double doubleValue = 1.2f;

doubleValue = floatValue;			// 오류 안 남
doubleValue = (double)floatValue;	// 오류 안 남 - 다만 자동으로 해줘도 이렇게 쓰는 걸 권장
  • 문자 형변환과 아스키코드 & 유니코드

아스키코드 : 이진법을 사용하는 컴퓨터에서 문자를 표현하기 위해 정해둔 문자와 숫자의 매칭표
유니코드 : 영어만 표현이 가능했던 아스키코드에서 전세계의 모든 문자를 다루도록 설계한 매칭표

Console.WriteLine($"유니코드 65는 {(char)65}로 표현됩니다.");

// 출력 : 유니코드 65는 A로 표현됩니다.
  • 문자열 형변환

문자열은 단순형변환이 불가하다.
따라서 Parse, TryParse를 이용하여 문자열에서 자료형을 변환할 수 있다.
각 자료형에서 ToString을 이용하여 자료형에서 문자열로 변환할 수도 있다.

int value = int.Parse("142")	// 142 숫자로 반환

bool fail = int.TryParse("abc", out value);	// 변환 실패, fail = false 반환
bool success = int.TryParse("123", out value);	// 변환 성공, success = true 반환, value에 123 반환 

2. 연산자(Operator)

프로그래밍 언어에서는 일반적인 수학 연산과 유사한 연산자들이 지원됨
C#는 여러 연산자를 제공하며 기본 연산을 수행할 수 있음

  • 산술 연산자

2.1 이진 연산자

iValue = 1 + 2;   + (더하기)
iValue = 3 - 1;   - (빼기)
iValue = 4 * 2;   * (곱하기)
fValue = 5f / 3f;   / (나누기) : 주의! 5 / 3 과 같이 int의 나눗셈은 소수점을 버림
iValue = 13 % 3;  % (나머지)

2.2 단항 연산자

iValue = +iValue;   + 단항연산자(양수) : 값을 반환
iValue = -iValue;   - 단항연산자(음수) : 값의 마이너스를 반환
++iValue;     ++ 전위증가연산자 : 값을 1 증가
iValue++;     ++ 후위증가연산자 : 값을 1 증가
--iValue;       -- 전위감소연산자 : 값을 1 감소
iValue--;       -- 후위감소연산자 : 값을 1 감소

2.3 전위연산자와 후위연산자

- 전위연산자 : 값을 반환하기 전에 연산

iValue = 0;
Console.WriteLine(iValue);   // output : 0
Console.WriteLine(++iValue); // output : 1
Console.WriteLine(iValue);   // output : 1

- 후위연산자 : 값을 반환한 후에 연산

iValue = 0;
Console.WriteLine(iValue);   // output : 0
Console.WriteLine(iValue++); // output : 0
Console.WriteLine(iValue);   // output : 1

2.4 대입 연산자

iValue = 10;    = : 오른쪽의 값을 왼쪽 변수에 대입

2.5 복합 대입 연산자

이진 연산자(op)의 경우
x op= y 는 x = x op y 와 동일
iValue += 5; // iValue = iValue + 5; 와 동일

2.6 비교 연산자

bValue = 3 > 1;     > : 왼쪽 피연산자가 더 클 경우 true
bValue = 3 < 1;     < : 왼쪽 피연산자가 더 작을 경우 true
bValue = 3 >= 1;   >= : 왼쪽 피연산자가 더 크거나 같은 경우 true
bValue = 3 <= 1;   <= : 왼쪽 피연산자가 더 작거나 같은 경우 true
bValue = 3 == 1;   == : 두 피연산자가 같은 경우 true
bValue = 3 != 1;    != : 두 피연산자가 다를 경우 true

2.7 논리 연산자

bValue = !false;      !(Not) : 피연산자의 논리 부정을 반환
bValue = true && false;   && (And) : 두 피연산자가 모두 true 일 경우 true
bValue = true || false;    ||(Or) : 두 피연산자가 모두 false 일 경우 false
bValue = true ^ false;   ^(Xor) : 두 피연산자가 다를 경우 true

2.8 연산자 우선순위

여러 연산자가 있는 식에서 우선 순위가 높은 연산자가 먼저 계산

1) 기본 연산     : a[i], x++, x--
2) 단항 연산     : +x, -x, !x, ~x, ++x, --x, (Type)x
3) 곱하기 연산    : x * y, x / y, x % y
4) 더하기 연산    : x + y, x - y
5) 시프트 연산    : x << y, x >> y
6) 비교 연산     : x < y, x > y, x <= y, x >= y
7) 같음 연산     : x == y, x != y
8) 논리 AND 연산 : x & y, x && y
9) 논리 XOR 연산 : x ^ y
10) 논리 OR 연산 : x | y, x || y
11) 대입 연산   : x = y, x op= y

3. 조건문

조건에 따라 실행이 달라지게 할 때 사용하는 문장

3.1. if 조건문

조건식의 true, false에 따라 실행할 블록을 결정하는 조건문

  • if 조건문 기본
// 1. 기본
if (true)   // 조건이 true인 경우 바로 아래의 블록이 실행됨
{
	Console.WriteLine("실행되는 블록");
}
else
{
	Console.WriteLine("실행되지 않는 블록");
}


// 2. else 생략 (else일 때 아무것도 안 해도 될 시
if (true)
{
	Console.WriteLine("실행되는 블록");
}


// 3. else if
string input = "바위";

if (input == "가위")
{
	Console.WriteLine("가위를 입력");
}
else if (input == "바위")
{
	Console.WriteLine("바위를 입력");
}
else if (input == "보")
{
	 Console.WriteLine("보를 입력");
}
else
{
	Console.WriteLine("잘못된 값을 입력");
}

3.2 switch 조건문

조건값에 따라 실행할 시작지점을 결정하는 조건문

  • switch 조건문 기본
string cmd = "B";

switch (cmd)        // 조건값 지정
{
	case "A":       // 조건값이 일치하지 않아 넘어감
		Console.WriteLine("A가 일치하는 경우");
		break;
        
	case "B":       // 조건값이 일치하는 case부터 시작함
		Console.WriteLine("B가 일치하는 경우");
		break;      // break; 가 있는 지점에서 switch 블록을 빠져나감
        
	case "C":		// 조건값이 일치하는 case가 없는 경우 default 가 실행지점이 됨
		Console.WriteLine("C가 일치하는 경우");
		break;
}


// 조건값에 따라 동일한 실행이 필요한 경우 묶어서 사용 가능
char key = 'w';

switch (key)
{
	case 'w':
	case 'W':
	case 'ㅈ':
		Console.WriteLine("위쪽으로 이동");
		break;
	case 'a':
	case 'A':
	case 'ㅁ':
		Console.WriteLine("왼쪽으로 이동");
		break;
	case 's':
	case 'S':
	case 'ㄴ':
		Console.WriteLine("아래쪽으로 이동");
		break;
	case 'd':
	case 'D':
	case 'ㅇ':
		Console.WriteLine("오른쪽으로 이동");
		break;
	default:
		Console.WriteLine("이동하지 않음");
		break;
}

3.3 삼항연산자

간단한 조건문을 빠르게 작성

  • 삼항 연산자 기본
// 조건 ? true인 경우 값 : false인 경우 값
int big   = 20 > 10 ? 20 : 10;      // 조건이 true이니 20
int small = 20 < 10 ? 20 : 10;      // 조건이 false이니 10

4. 배열

동일한 자료형의 요소들로 구성된 데이터 집합
인덱스(index)를 통하여 배열 요소(Element)에 접근할 수 있음
배열의 처음 요소의 인덱스는 0부터 시작함

4.1 배열의 구현 원리

C#의 배열은 Array 클래스를 통해 구현되며, Array 클래스의 모든 특징을 가진다
Array 클래스의 정적 함수를 활용하여 다양한 기능 사용 가능

int[] array = { 1, 3, 5, 4, 2 };

int length = array.Length;   // 배열의 크기
int max = array.Max();       // 배열의 최대값
int min = array.Min();       // 배열의 최소값

Array.Sort(array);                      // 배열 정렬
Array.Reverse(array);                   // 배열 반전
int index = Array.IndexOf(array, 3);    // 배열 탐색

int[] shallow = array;                  // 배열의 얕은 복사 : 동일한 인스턴스를 참조
int[] deep = new int[array.Length];     // 배열의 깊은 복사 : 새로운 인스턴스를 생성하고 복사
Array.Copy(array, deep, array.Length);

array[0] = 0;
Console.WriteLine(array[0]);            // output : 0
Console.WriteLine(shallow[0]);          // output : 0
Console.WriteLine(deep[0]);             // output : 5

4.2 배열의 선언 및 초기화

배열을 만들기 위해 자료형과 크기를 정하여 생성

int[] array1;                           // 배열 변수 선언
array1 = new int[3];                    // 데이터를 3개 가지는 배열 생성
int[] array2 = new int[3] { 1, 2, 3 };  // 크기가 3인 배열을 선언하고 배열 요소들을 초기화
int[] array3 = new int[] { 1, 2, 3 };   // 배열의 요소들을 초기화 하는 경우 배열의 크기를 생략 가능
int[] array4 = { 1, 2, 3 };             // 배열의 요소들을 초기화 하는 경우 배열 생성을 생략 가능

4.3 배열의 요소에 접근하기 위해 [인덱스] 를 사용

인덱스
배열은 요소들을 메모리에 연속적으로 배치하는 원리로 구현
이를 이용하여 배열의 특정요소의 메모리주소를 계산할 수 있음
i번째 배열요소 메모리주소 == 배열 시작 메모리주소 + (i * 자료형의 크기)
이를 인덱스라고 표현함

int[] scores = new int[5];      // 크기 5의 배열 선언

scores[0] = 10;                 // 0번째 요소 저장
scores[1] = 20;                 // 1번째 요소 저장
scores[2] = 30;                 // 2번째 요소 저장
scores[3] = 40;                 // 3번째 요소 저장
scores[4] = 50;                 // 4번째 요소 저장

Console.WriteLine($"배열의 0번째 요소 : {scores[0]}");     // 0번째 요소 불러오기
Console.WriteLine($"배열의 1번째 요소 : {scores[1]}");     // 1번째 요소 불러오기
Console.WriteLine($"배열의 2번째 요소 : {scores[2]}");     // 2번째 요소 불러오기
Console.WriteLine($"배열의 3번째 요소 : {scores[3]}");     // 3번째 요소 불러오기
Console.WriteLine($"배열의 4번째 요소 : {scores[4]}");     // 4번째 요소 불러오기

4.4 다차원 배열

배열의 []괄호 안에 차원수만큼 ','를 추가
배열의 크기가 차원마다 동일함

int[,] matrix = new int[3, 4];          // 2차원 배열 선언
[0,0] [0,1] [0,2] [0,3]
[1,0] [1,1] [1,2] [1,3]
[2,0] [2,1] [2,2] [2,3]
matrix[2, 1] = 10;                      // 2차원 배열의 y:2 x:1 배열요소 저장
Console.WriteLine(matrix[2, 1]);        // 2차원 배열의 y:2 x:1 배열요소 불러오기
Console.WriteLine(matrix.GetLength(0)); // 2차원 배열의 y 크기
Console.WriteLine(matrix.GetLength(1)); // 2차원 배열의 x 크기

int[,,] cube = { { {1, 2}, {3, 4} }, { {5, 6}, {7, 8} } };      // 3차원 배열 선언 및 초기화

4.5 가변 배열

배열의 []괄호를 배열 갯수만큼 추가
배열의 크기를 각각 설정 가능
다차원 배열과 달리 배열 안의 배열 개념

int[][] jagged = new int[3][];          // 배열의 갯수 설정
jagged[0] = new int[5];
jagged[1] = new int[2];
jagged[2] = new int[3];
[0][0] [0][1] [0][2] [0][3] [0][4]
[1][0] [1][1]
[2][0] [2][1] [2][2]

4.6 배열과 반복

배열의 인덱스를 반복하여 증가시키며 사용하는 경우 배열의 모든 요소를 반복 수행하는데 용이함

int[] ints = { 1, 2, 3, 4, 5 };

for (int i = 0; i < array.Length; i++)
{
	Console.Write(ints[i]);
}

int[,] tile = { { 1, 2, 3 }, { 4, 5, 6 } };

for (int y = 0; y < tile.GetLength(0); y++)
{
	for (int x = 0; x < tile.GetLength(1); x++)
	{
		Console.Write(tile[y, x]);
	}
	Console.WriteLine();
}            

5. 함수 (Function)

미리 정해진 동작을 수행하는 코드 묶음
어떤 처리를 미리 함수로 만들어 두면 다시 반복적으로 사용 가능

5.1 함수 구성

// 일련의 코드를 하나의 이름 아래에 묶음
// 반환형 함수이름(매개변수목록) { 함수내용 }
int Plus(int left, int right)
{
	Console.WriteLine($"Input : {left}, {right}");
	int result = left + right;
	Console.WriteLine($"Output : {result}");
	return result;
}

5.2 반환형 (Return Type)

함수의 결과(출력) 데이터의 자료형
함수가 끝나기 전까지 반드시 return으로 반환형에 맞는 데이터를 출력해야 한다.
함수 진행 중 return을 만나는 경우 그 즉시 결과 데이터를 반환하고 함수가 종료된다.
함수의 결과(출력)이 없는 경우 반환형은 void이며 return을 생략할 수 있다.

int Return10()	// 반환형이 있는 함수
{
	// return 에 도달하기 전까지 코드가 순차적으로 진행
	Console.WriteLine("도달하는 코드");

	return 10;

	// return 이후 코드는 진행되지 않음
	Console.WriteLine("도달하지 못하는 코드");
}

void PrintProfile(string name, string phone)	// 반환형이 없는 함수
{
	if (name == "")
	{
		Console.WriteLine("이름을 입력해주세요.");
		return;     // void 반환형에서 return을 진행하는 경우 함수 종료의 역할을 함
	}

	Console.WriteLine($"이름 : {name}, Phone : {phone}");

// void 반환형의 함수는 return을 생략가능
}

void Main()
{
	int ret = Return10();   // Return10 함수에서 반환된 10이 결과로 나옴

	PrintProfile("", "010-1234-5678");          // void 반환형의 함수는 실행에 의미
	PrintProfile("홍길동", "010-1111-2222");
}

5.3 매개변수 (Parameter)

함수에 추가(입력)할 데이터의 자료형과 변수명
같은 함수에서도 매개변수 입력이 다름에 따라 다른 처리가 가능

int Minus(int left, int right)
{
	// 함수의 입력으로 넣어준 매개변수 left, right 에 따라 함수가 동작
	return left - right;
}

void Main()
{
	int value1 = Minus(20, 10);     // 매개변수 20, 10이 들어간 Minus 함수의 반환은 10
	int value2 = Minus(30, 10);     // 매개변수 30, 10이 들어간 Minus 함수의 반환은 20
}

5.5 함수 호출 순서

함수는 호출되었을 때 해당 함수블록으로 제어가 전송되며 완료되었을 때 원위치로 제어가 전송됨

int Func2()
{   /*4*/
	/*5*/ int ret = 1;
	/*6*/ return ret;
}

int Func1()
{   /*2*/
	/*3*/ int ret = Func2(); /*7*/
	/*8*/ return ret + 1;
}

void Main()
{   /*시작*/
	/*1*/ int ret = Func1(); /*9*/
}   /*종료*/

5.6 함수 오버로딩

같은 이름의 함수를 매개변수를 달리하여 다른 함수로 재정의하는 기술
같은 이름의 함수를 호출하여도 매개변수의 자료형에 따라 함수를 달리 호출할 수 있음

int    Multi(int    left, int    right) { return left * right; }
float  Multi(float  left, float  right) { return left * right; }
double Multi(double left, double right) { return left * right; }

void Main5()
{
	int    result1 = Multi(5,    3   );     // Multi(int, int)가 호출됨
	float  result2 = Multi(5.1f, 3.3f);     // Multi(float, float)가 호출됨
	double result3 = Multi(5.1,  3.3 );     // Multi(double, double)가 호출됨
}

6. 열거형

기본 정수 숫자의 형식의 명명된 상수 집합에 의해 정의되는 값 형식
열거형 멤버의 이름으로 관리되어 코드의 가독성적인 측면에 도움이 된다.

6.1 열거형 기본사용

enum 열거형이름 { 멤버이름, 멤버이름, ... }

enum Direction { Up, Down, Left, Right }    // 열거형 정의 : 열거형이름과 멤버이름 작성
void Main()
{
	Direction dir = Direction.Up;           // 열거형 변수 : 열거형의 값을 가지는 변수
	switch (dir)
	{
		case Direction.Up:                  // 정수자료형보다 코드 가독성이 좋음
			Console.WriteLine("위쪽 방향으로 이동");
			break;
		case Direction.Down:
			Console.WriteLine("아래쪽 방향으로 이동");
			break;
		case Direction.Left:
			Console.WriteLine("왼쪽 방향으로 이동");
			break;
		case Direction.Right:
			Console.WriteLine("오른쪽 방향으로 이동");
			break;
	}
}

6.2 열거형 변환

enum Season
{
	Spring, // 0    // 열거형 멤버에 정수값을 지정하지 않을 경우 0부터 시작
	Summer, // 1    // 열거형 멤버에 정수값을 지정하지 않을 경우 이전 맴버 +1 값을 가짐
	Autumn = 20,    // 정수값을 직접 할당 가능
	Winter  // 21   // 정수값을 직접 할당한 경우에도 이전 멤버 +1 값을 가짐
}

void Main()
{
	Season season1 = Season.Autumn;
	Console.WriteLine($"{season1}의 정수값은 {(int)season1} 입니다.");  // 열거형변수를 int로 형변환

	Season season2 = (Season)0;     // 정수에서 열거형변수로 형변환
	Console.WriteLine(season2);     // Spring
}

7. 구조체 (Struct)

데이터와 관련 기능을 캡슐화할 수 있는 값 형식 >> 깊은 복사를 함
데이터를 저장하기 보관하기 위한 단위용도로 사용한다.

7.1 구조체 구성

struct 구조체이름 { 구조체내용 }
*구조체 내용으로는 변수와 함수가 포함 가능

struct StudentInfo
{
	public string name;
	public int math;
	public int english;
	public int science;

	public float Average()
	{
		return (math + english + science) / 3f;
	}
}

void Main()
{
	StudentInfo info;            // 구조체 변수 선언
	info.name = "Kim";           // 구조체내 변수에 접근하기 위해 구조체에 .을 붙여 사용
	info.math = 10;
	info.english = 20;
	info.science = 100;

	float average = info.Average();   // 구조체내 함수에 접근하기 위해 구조체에 .을 붙여 사용
}

7.2 구조체 초기화

반환형이 없는 구조체이름의 함수를 초기화라 하며 구조체 변수들의 초기화를 진행하는 역할로 사용한다.
매개변수가 있는 초기화를 정의하여 구조체 변수의 값을 설정하기 위한 용도로 사용한다.
구조체의 초기화는 new 키워드를 통해서 사용한다.

struct Point
{
	public int x;
	public int y;

	public Point(int x, int y)
	{
		// 초기화에서 모든 구조체 변수를 초기화하지 않으면 error 발생
		this.x = x;     // this : 자기 자신을 가리킴
		this.y = y;
	}
}

void Main4()
{
	Point point1;
	point1.x = 1;
	Console.WriteLine($"{point1.x}");	

	Point point2 = new Point();                     // 기본 초기화 사용
	Console.WriteLine($"{point2.x}, {point2.y}");   // output : 0, 0

	Point point3 = new Point(1, 2);                 // 추가로 구현한 초기화 사용
	Console.WriteLine($"{point3.x}, {point3.y}");   // output : 1, 2

	Point point4 = new Point() { x = 3, y = 4 };    // 초기화 및 값대입
	Console.WriteLine($"{point4.x}, {point4.y}");   // output : 3, 4
}

8. 클래스 (class)

데이터와 관련 기능을 캡슐화할 수 있는 참조 형식으로, 객체지향 프로그래밍에 객체를 만들기 위한 설계도이다. 클래스를 통해만들어진 객체는 인스턴스라 한다.
참조 : 원본을 가리키고 있음 == 원본의 주소를 가지고 있음 >> 얕은 복사

8.1 클래스 구성

class 클래스이름 { 클래스내용 }
*클래스 내용으로는 변수와 함수가 포함 가능

class Student
{
	public string name;
	public int math;
	public int english;
	public int science;

	public float GetAverage()
	{
		return (math + english + science) / 3f;
	}
}

void Main()
{
	Student kim = null;     // 지역변수를 생성하고 null(아무것도 없음) 참조
	kim = new Student();    // 클래스 인스턴스를 생성하고 지역변수가 인스턴스를 참조함
	kim.name = "Kim";       // 인스턴스의 변수에 접근하기 위해 참조한 변수에 . 을 붙여 사용
	kim.math = 10;
	kim.english = 20;
	kim.science = 100;

	float average = kim.GetAverage();   // 클래스내 함수에 접근하기 위해 참조한 변수에 . 을 붙여 사용
}

8.2 클래스 생성자

반환형이 없는 클래스이름의 함수를 생성자라 하며 클래스의 인스턴스를 만들 때 호출되는 역할로 사용한다. 인스턴스의 생성자는 new 키워드를 통해서 사용한다.

class Car
{
	public string name;
	public string color;

	// 기본생성자는 다른 생성자를 포함하지 않은 경우에만 자동 생성됨
	/*public Car()
	{

	}*/

	public Car(string name, string color)
	{
		this.name = name;
		this.color = color;
	}
}

void Main()
{
	Car car;
	// car.name = "차";         // error : 클래스 지역변수는 인스턴스 없이 사용불가

	Car truck = new Car("트럭", "파란색");
	Console.WriteLine($"{truck.name}, {truck.color}");    // output : 트럭, 파란색

	// Car taxi = new Car();    // error : 기본생성자는 다른생성자를 포함하지 않은 경우에만 자동생성됨
}

9. 객체지향 프로그래밍 (Object Oriented Programming)

프로그램 설계방법론이자 개념의 일종으로, 프로그램을 서로 상호작용하는 객체를 기본 단위로 구성하는 방식이다.

9.1 객체지향의 장단점

  • 장점 :
    객체단위로 관리하기 때문에 디버깅이 유리함,
    클래스 단위로 모듈화 시켜 관리하므로 대규모 프로젝트에 적합
    코드의 재사용성이 좋음

  • 단점 :
    설계에 시간이 많이 소비되며 신중해야함

9.2 객체지향 4특징

  • 캡슐화 : 객체를 상태와 기능으로 묶는 것을 의미하며, 객체의 내부 상태와 기능을 숨기고, 허용한 상태와 기능만의 액세스를 허용한다.
  • 다형성 : 부모클래스의 함수를 자식클래스에서 재정의하여 자식클래스의 다른 반응을 구현한다.
  • 추상화 : 관련 특성 및 엔터티의 상호 작용을 클래스로 모델링하여 시스템의 추상적 표현을 정의한다.
  • 상속 : 부모클래스의 모든 기능을 가지는 자식클래스를 설계하는 방법이다.

9.3 객체설계 5원칙(SOLID 원칙)

  • (S)단일 책임 원칙 : 객체는 오직 하나의 책임을 가져야 함
  • (O)개방 폐쇄 원칙 : 객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 함
  • (L)리스코프 치환 원칙 : 자식클래스는 언제나 자신의 부모클래스를 대체할 수 있어야 함
  • (I)인터페이스 분리 원칙 : 인터페이스는 작은 단위들로 분리시켜 구성하며, 사용하지 않는 함수는 포함하지 않아야 함
  • (D)의존성 역전 원칙 : 객체는 하위클래스(상위클래스를 구현한 객체)보다 상위클래스(추상성이 높은 상위 개념)에 의존해야함

9.4 캡슐화

멤버 변수 및 기능을 묶는 것

class Capsule
{
	int variable;           // 멤버변수 : 객체의 정보를 표현
	void Function() { }     // 멤버함수 : 객체의 기능을 표현
}
  • 접근제한자

외부에서 접근이 가능한 멤버변수와 멤버함수를 지정하는 기능
접근제한자를 지정하지 않는 경우 기본접근제한자는 private로 지정된다.

public : 외부에서도 접근가능
private : 내부에서만 접근가능
protected : 상속한 클래스에서 public, 그외에는 private

접근 제한자를 이용하면 정보 은닉의 효과가 있다.

-객체 구성에 있어서 외부에서 사용하기 원하는 기능과 사용하기 원하지 않는 기능을 구분하기 위해 사용한다.
-사용자가 객체를 사용하는데 있어서 필요한 기능만을 확인하기 위한 용도이며 외부에 의해 영향을 받지 않길 원하는 기능을 감추기 위한 용도이기도 하다.

9.5 다형성

부모클래스의 멤버를 자식클래스가 상황에 따라 여러가지 형태를 가질 수 있는 성질

class Car
{
	protected string name;
	protected int speed;

	public void Move()
	{
		Console.WriteLine($"{name} 이/가 {speed} 의 속도로 이동합니다.");
	}
}

class Truck : Car
{
	public Truck()
	{
		name = "트럭";
		speed = 30;
	}
}

class SportCar : Car
{
	public SportCar()
	{
		name = "스포츠카";
		speed = 100;
	}
}

void Main()
{
	Car car1 = new Truck(); 
	Car car2 = new SportCar();

	car1.Move();    // 트럭 이/가 30 의 속도로 이동합니다.
	car2.Move();    // 스포츠카 이/가 100 의 속도로 이동합니다.
}
  • 가상함수와 오버라이딩
    가상함수 : 부모클래스의 함수 중 자식클래스에 의해 재정의 할 수 있는 함수를 지정
    오버라이딩 : 부모클래스의 가상함수를 같은 함수이름과 같은매개변수로 재정의하여 자식만의 반응을 구현
class Skill
{
	public virtual void Execute()       // 가상함수
	{
		Console.WriteLine("스킬 재사용 대기시간을 진행시킴");
	}
}

class FireBall : Skill
{
	public override void Execute()      // 오버라이딩
	{
		base.Execute();      // base : 부모클래스를 가리킴
		Console.WriteLine("전방에 화염구를 날림");
	}
}

class Dash : Skill
{
	public override void Execute()
	{
		Console.WriteLine("전방에 근거리를 빠르게 이동");
	}
}

void Main2()
{
	Skill fireBall = new FireBall();
	fireBall.Execute();     // 자식클래스의 함수가 호출됨
	// 스킬 재사용 대기시간을 진행시킴
	// 전방에 화명구를 날림

	Skill dash = new Dash();
	dash.Execute();         // 자식클래스의 함수가 호출됨
	// 전방에 근거리를 빠르게 이동
}

다형성은 아래와 같은 효과가 있다.
-새로운 클래스를 추가하거나 확장할 때 기존 코드에 영향을 최소화함
-새로운 클래스를 만들 때 기존의 소스를 수정하지 않아도 됨
-클래스 간의 의존성을 줄여 확장성은 높임

9.6 추상화 (Abstraction)

클래스를 정의할 당시 구체화 시킬 수 없는 기능을 추상적 표현으로 정의

  • 추상클래스 (abstract class)

하나 이상의 추상함수를 포함하는 클래스
클래스가 추상적인 표현을 정의하는 경우 자식에서 구체화시켜 구현할 것을 염두하고 추상화 시킴
추상클래스에서 내용을 구체화 할 수 없는 추상함수는 내용을 정의하지 않음
추상클래스를 상속하는 자식클래스가 추상함수를 재정의하여 구체화한 경우 사용가능

abstract class Item                 // 추상클래스 : 하나 이상의 추상함수를 포함하는 클래스
{
	public abstract void Use();     // 추상함수 : 클래스에서 구현을 진행하지 않고 선언만 진행
}

class Potion : Item
{
	public override void Use()
	{
		Console.WriteLine("포션을 사용하여 체력을 회복합니다.");
	}
}

class Herb : Item
{
	public override void Use()
	{
	Console.WriteLine("해독초를 사용하여 독을 해제합니다.");
	}
}

void Main1()
{
	// Item item = new Item();      // error : 추상클래스는 인스턴스 생성불가

	// 추상클래스를 구체화한 자식클래스는 인스턴스 생성이 가능하며 관련 기능을 사용가능
	Item potion = new Potion();
	potion.Use();

	Item herb = new Herb();
	herb.Use();
}

추상화는 객체들의 공통적인 특징을 도출하고, 구현을 구체화하기 어려운 상위클래스를 설계하기 위한 수단으로 사용한다.
추상적인 기능을 구체화시키지 않은 경우 인스턴스 생성이 불가하므로, 자식클래스에게 순수가상함수의 구현을 강제하여 실수를 줄이는 역할도 한다.

9.7 상속 (Inheritance)

부모클래스의 모든 기능을 가지는 자식클래스를 설계하는 방법

is-a 관계 : 부모클래스가 자식클래스를 포함하는 상위개념일 경우 상속관계가 적합함

class Monster
{
	protected string name;
	protected int hp;

	public void Move()
	{
		Console.WriteLine($"{name} 이/가 움직입니다.");
	}

	public void TakeHit(int damage)
	{
		hp -= damage;
	Console.WriteLine($"{name} 이/가 {damage} 를 받아 체력이 {hp} 이 되었습니다.");
	}
}

class Dragon : Monster
{
	public Dragon()
	{
		name = "드래곤";
		hp = 100;
	}

	public void Breath()
	{
		Console.WriteLine($"{name} 이/가 브레스를 뿜습니다.");
	}
}

class Slime : Monster
{
	public Slime()
	{
		name = "슬라임";
		hp = 5;
	}

	public void Split()
	{
		Console.WriteLine($"{name} 이/가 분열합니다.");
	}
}

void Main1()
{
	Dragon dragon = new Dragon();
	Slime slime = new Slime();

	// 부모클래스 Monster를 상속한 자식클래스는 모두 부모클래스의 기능을 가지고 있음 		dragon.Move();
	slime.Move();

	// 자식클래스는 부모클래스의 기능에 자식만의 기능을 더욱 추가하여 구현 가능
	dragon.Breath();
	slime.Split();
 }
  • 업캐스팅 : 자식클래스는 부모클래스 자료형으로 묵시적 형변환 가능
Monster monster = new Dragon();
player.Attack(monster);
  • 다운캐스팅 : 부모클래스는 자식클래스 자료형으로 명시적 형변환 가능 (단, 가능할 경우)
Dragon d = (Dragon)monster;     
Slime s = (Slime)monster;            // error : 인스턴스가 Slime이 아니기 때문에 변환시 오류

따라서 다운캐스팅할 때는 형변환이 가능한지 확인하도록 한다.

if (monster is Dragon)                  // 형변환이 가능한지 확인
{
	Dragon isDragon = (Dragon)monster;
}

Dragon asDragon = monster as Dragon;    // 형변환이 가능하다면 형변환

상속을 진행하는 경우 부모클래스의 소스가 자식클래스에서 모두 적용된다.
부모클래스와 자식클래스의 상속관계가 적합한 경우 부모클래스에서의 기능 구현이 자식클래스에서도 어울리게 되어 코드의 반복작성을 줄인다.
업캐스팅을 통해 자식클래스는 부모클래스로 형변환이 가능하며 요구하는 곳에서 동일한 기능을 수행할 수 있다.

10. 인터페이스 (Interface)

인터페이스는 맴버를 가질 수 있지만 직접 구현하지 않는다. (정의만 가짐)
인터페이스를 가지는 클래스에서 반드시 인터페이스의 정의를 구현해야 한다

Can-a 관계 : 클래스가 해당 행동을 할 수 있는 경우 적합함

public interface IEnterable
{
	void Enter();
}

public interface IOpenable
{
	void Open();
}

public class Door : IEnterable, IOpenable
{
	public void Enter()
	{
		Console.WriteLine("문에 들어갑니다.");
	}
	public void Open()
	{
	Console.WriteLine("문을 엽니다.");
	}
}

public class Town : IEnterable
{
	public void Enter()
	{
		Console.WriteLine("마을에 들어갑니다.");
	}
}

public class Box : IOpenable
{
	public void Open()
	{
		Console.WriteLine("상자를 엽니다.");
	}
}

public class Player
{
	public void Enter(IEnterable enterable)
	{
		Console.WriteLine("플레이어가 대상에 들어가기 시도합니다.");
		enterable.Enter();
	}

	public void Open(IOpenable openable)
	{
		Console.WriteLine("플레이어가 대상을 열기 시도합니다.");
	openable.Open();
	}
}

void Main()
{
	Player player = new Player();

	Door door = new Door();
	Box box = new Box();
	Town town = new Town();

	player.Enter(door);
	player.Enter(town);

	player.Open(box);
	player.Open(door);

	IEnterable enterable;
}

11. 일반화 클래스

클래스에 필요한 자료형을 일반화하여 구현하는 방법이다.
이후 클래스 인스턴스를 생성할 때 자료형을 지정하여 사용할 수 있다.

public class SafeArray<T>
{
	private T[] array;

	public SafeArray(int size)
	{
		array = new T[size];
	}

	public void Set(int index, T value)
	{
		if (index < 0 || index >= array.Length)
		return;

		array[index] = value;
	}

	public T Get(int index)
	{
		if (index < 0 || index >= array.Length)
		return default(T);      // default : T 자료형의 기본값

		return array[index];
	}
}
  • 일반화 자료형 제약

일반화 자료형을 선언할 때 제약조건을 선언하여, 사용 당시 쓸 수 있는 자료형을 제한하는 것이다.

class StructT<T> where T : struct { }           // T는 구조체만 사용 가능
class ClassT<T> where T : class { }             // T는 클래스만 사용 가능
class EnumT<T> where T : Enum { }               // T는 열거형만 사용 가능
class NewT<T> where T : new() { }               // T는 매개변수 없는 생성자가 있는 자료형만 사용 가능

class ParentT<T> where T : Parent { }           // T는 Parent 파생클래스만 사용 가능
class InterfaceT<T> where T : IComparable { }   // T는 인터페이스를 포함한 자료형만 사용 가능

일반화 자료형에 제약조건이 있다면 포함가능한 자료형의 기능만의 사용으로, 의도치 않은 오용을 방지한다.

12. 대리자 (Delegate)

특정 매개 변수 목록 및 반환 형식이 있는 함수에 대한 참조로, 대리자 인스턴스를 통해 함수를 호출 하는 것을 말한다.

기본형
delegate 반환형 델리게이트이름(매개변수들);

public float Plus(float left, float right) { return left + right; }
public float Minus(float left, float right) { return left - right; }
public float Multi(float left, float right) { return left * right; }
public float Divide(float left, float right) { return left / right; }
public void Message(string message) { Console.WriteLine(message); }

oid Main1()
{
	DelegateMethod1 delegate1 = new DelegateMethod1(Plus);  // 델리게이트 인스턴스 생성
	DelegateMethod2 delegate2 = Message;                    // 간략한 문법의 델리게이트 인스턴스 생성

	delegate1.Invoke(20, 10);   // Plus(20, 10);            // Invoke를 통해 참조되어 있는 함수를 호출
	delegate2("메세지");        // Message("메세지");       // 간략한 문법의 델리게이트 함수 호출

	delegate1 = Plus;
	Console.WriteLine(delegate1(20, 10));       // output : 30
	delegate1 = Minus;
	Console.WriteLine(delegate1(20, 10));       // output : 10
	delegate1 = Multi;
	Console.WriteLine(delegate1(20, 10));       // output : 200
	delegate1 = Divide;
	Console.WriteLine(delegate1(20, 10));       // output : 2

	// delegate2 = Plus;        // error : 반환형과 매개변수가 일치하지 않은 함수는 참조 불가
}
  • 델리게이트 체인

델리게이트 변수에 여러 개의 함수를 참조하는 방법

+=, -= 연산자를 통해 할당을 추가하고 제거할 수 있음
= 연산자를 통해 할당할 경우 이전의 다른 함수들을 할당한 상황이 사라짐

void Main()
{
	Action action;
	action = Func2;     // 델리게이트 인스턴스를 Func2 로 초기화
	action += Func1;    // 델리게이트 인스턴스에 Func1 추가 참조
	action += Func3;    // 델리게이트 인스턴스에 Func3 추가 참조
	action();           // Func2, Func1, Func3 이 호출됨

	action -= Func1;    // 델리게이트 인스턴스에 Func1 참조 제거
	if (action != null) // 델리게이트 인스턴스에서 참조를 제거할 경우 참조하고 있는 함수가 없는 경우를 조심
		action();       // Func2, Func3 이 호출됨

	action += Func2;    // 같은 함수를 여러번 참조한 경우 여러번 호출됨
	action += Func2;
	action();           // Func2 3회, Func3 1회 호출됨

	action -= Func1;    // 델리게이트 인스턴스에 참조되지 않은 함수를 제거하는 경우 해당 작업이 무시됨

	action = Func1;     // 델리게이트 인스턴스에 = 을 통해 할당할 경우 이전의 참조된 상황이 사라짐
	action();           // Func1 이 호출됨
}

void Func1() { Console.WriteLine("Func1"); }
void Func2() { Console.WriteLine("Func2"); }
void Func3() { Console.WriteLine("Func3"); }
  • 일반화 델리게이트

개발과정에서 많이 사용되는 델리게이트의 경우 일반화된 델리게이트를 사용

-Func 델리게이트
반환형과 매개변수를 지정한 델리게이트

Func<매개변수1, 매개변수2, ..., 반환형>

-Action 델리게이트
반환형이 void 이며 매개변수를 지정한 델리게이트

Action<매개변수1, 매개변수2, ...>

-Predicate 델리게이트
반환형이 bool, 매개변수가 하나인 델리게이트

bool IsSentence(string str) { return str.Contains(' '); }

  • 무명메서드와 람다식

델리게이트 변수에 할당하기 위한 함수를 소스코드 구문에서 생성하여 전달
전달하기 위한 함수가 간단하고 일회성으로 사용될 경우에 간단하게 작성

-무명메서드
간단한 표현식을 통해 함수를 즉시 작성하여 전달하는 방법

Func<int, int, int> pow = delegate (int n, int x)
{
     int result = 1;
     for (int i = 0; i < x; i++)
     {
         result *= n;
     }
     return result;
};

-람다식
무명메서드의 간단한 표현식

pow = (n, x) =>
{
    int result = 1;
    for (int i = 0; i < x; i++)
    {
        result *= n;
    }
    return result;
};

13. 이벤트 (Event)

일련의 사건이 발생했다는 사실을 다른 객체에게 전달하는 용도로, 델리게이트의 일부 기능을 제한하여 이벤트의 용도로 사용한다.

이벤트 선언
델리게이트 변수 앞에 event 키워드를 추가하여 이벤트로 선언

이벤트가 선언된 델리케이트 변수는 += , -= 연산자를 통해서만 함수를 추가, 제거할 수 있다.
또한 외부에서의 호출도 제한하여 이벤트로 선언된 델리케이트 변수가 오용되는 걸 막는다.

14. 기타 C# 언어

14.1 Named Parameter

함수의 매개변수 순서와 무관하게 이름을 통해 호출

void Profile(int id, string name, string phone) { }

void Main()
{
	// 함수 호출시 이름을 명명하고 순서와 상관없이 호출 가능
	Profile(phone: "010-1111-2222", id: 1, name: "홍길동");
	Profile(name: "홍길서", phone: "010-1234-5678", id: 2);
}

14. 2 Optional Parameter

함수의 매개변수가 초기값을 갖고 있다면, 함수 호출시 생략하는 것을 허용하는 방법

void AddStudent(string name, string home = "서울", int age = 8) { }   // 초기값이 있는 경우 미리 할당
void AddStudent(int age = 8, string home = "서울", string name) {} 

void Main()
{
	AddStudent("철수");               // AddStudent("철수", "서울", 8);
	AddStudent("영희");               // AddStudent("영희", "서울", 8);
	AddStudent("민준", "인천");       // AddStudent("민준", "인천", 8);
	AddStudent("미영", age: 7);       // AddStudent("미영", "서울", 7);
}

14. 3 Params Parameter

매개변수의 객수가 정해지지 않은 경우, 매개변수의 갯수를 유동적으로 사용하는 방법

int Sum(params int[] values)
{
	int sum = 0;
	for (int i = 0; i < values.Length; i++) sum += values[i];
	return sum;
}

void Main()
{
	Sum(1, 3, 5, 7, 9);
	Sum(3, 5, 7);
	Sum();
}

14. 4 in Parameter

매개변수를 입력전용으로 설정하여 함수의 처음부터 끝까지 동일한 값을 보장하게 된다.

int Plus(in int left, in int right)
{
// left = 20;      // error : 입력전용 매개변수는 변경 불가
return left + right;
}

void Main()
{
int result = Plus(1, 3);
Console.WriteLine($"{result}");     // output : 4
}

14.5 out Parameter

매개변수를 출력전용으로 설정한다. 함수의 반환값 외에 추가적인 출력이 필요할 경우 사용한다.

void Divide(int left, int right, out int quotient, out int remainder)
{
	quotient = left / right;
	remainder = left % right;

	// 함수의 종료전까지 out 매개변수에 값이 할당 안되는 경우 오류
}

void Main()
{
	int quotient;
	Divide(5, 3, out quotient, out int remainder);
	Console.WriteLine($"{quotient}, {remainder}");  // output : 1, 2
}

14. 6 ref Parameter

매개변수를 원본참조로 전달한다. 매개변수가 값형식인 경우에도 함수를 통해 원본값을 변경하고 싶을 경우 사용한다.

void Swap(ref int left, ref int right)
{
	int temp = left;
	left = right;
	right = temp;
}

14. 7 Getter Setter

맴버변수가 외부객체와 상호작용하는 경우 Get & Set 함수를 구현해 주는 것이 일반적이다.

  1. Get & Set 함수의 접근제한자를 설정하여 외부에서 맴버변수의 접근을 캡슐화함
  2. Get & Set 함수를 거쳐 맴버변수에 접근할 경우 호출스택에 함수가 추가되어 변경시점을 확인 가능
public int GetHP()
{
	return hp;
}
private void SetHP(int hp)
{
	this.hp = hp;
}
  • 속성 (Property)
    Get & Set 함수의 선언을 간소화
private int mp;
public int MP                       // mp 맴버변수의 Get & Set 속성
{
	get { return mp; }              // get : Get함수와 역할동일
	set { mp = value; }             // set : Set함수와 역할동일, 매개변수는 value
}

public int AP { get; set; }         // AP 맴버변수를 선언과 동시에 Get & Set 속성
public int DP { get; private set; } // 속성의 접근제한자를 통한 캡슐화
public int SP { get; } = 10;        // 읽기전용 속성(상수처럼 사용가능)
public int HP => GetHP();           // 읽기전용 속성(람다처럼 사용가능)

14.8 Partial Type

클래스, 구조체, 인터페이스를 분할하여 구현하는 방법으로, 대규모 프로젝트에서 작업하는 경우 분산하여 구현에 유용

// 전투담당자 Player 소스
public partial class Player
{
	private int hp;

	public void Attack() { }
	public void Defense() { }
}

// 아이템담당자 Player 소스
public partial class Player
{
	private int weight;

	public void GetItem() { }
	public void UseItem() { }
}

14.9 Nullable 타입

값형식의 자료형들은 null을 가질 수 없음
값형식에도 null을 할당할 수 있는 Nullable 타입을 지원

bool? b = null;
int? i = 20;
if (b != null) Console.WriteLine(b);    // b 값이 null
if (i.HasValue) Console.WriteLine(i);   // i 값이 있으므로 20 출력
  • Null 조건연산자
    ? 앞의 객체가 null 인 경우 null 반환
    ? 앞의 객체가 null 이 아닌경우 접근
NullClass instance = null;
instance.Func();                     // 예외발생 : instance가 null 이므로 접근할 객체가 없음
Console.WriteLine(instance?.value);     // instance?.value는 null 반환
instance?.Func();                       // instance?.Func()은 null 반환

instance = new NullClass();
Console.WriteLine(instance?.value);     // instance?.value는 5 반환
instance?.Func();                       // instance?.Func()은 함수 호출

14.10 연산자 재정의

기본연산자의 연산을 함수로 재정의하여 기능을 구현
기본연산자를 호환하지 않는 사용자정의 자료형에 기본연산자 사용을 구현함

public struct Point
{
	public int x;
	public int y;

	public Point(int x, int y)
	{
		this.x = x;
		this.y = y;
	}

	// 연산자 재정의를 통한 기본연산자 사용 구현
	public static Point operator +(Point left, Point right)
	{
		return new Point(left.x + right.x, left.y + right.y);
	}
}

14.11 인덱서 자료형

this[]를 속성으로 정의하여 클래스의 인스턴스에 인덱스 방식으로 접근 허용

public enum Parts { Head, Body, Feet, Hand, SIZE }
public class Equipment
{
	string[] parts = new string[(int)Parts.SIZE];

	public string this[Parts type]
	{
		get
		{
			return parts[(int)type];
		}
		set
		{
			parts[(int)type] = value;
		}
	}
}

void Main()
{
	Equipment equipment = new Equipment();

	equipment[Parts.Head] = "낡은 헬멧";
	equipment[Parts.Feet] = "가죽 장화";

	Console.WriteLine($"착용하고 있는 신발 : {equipment[Parts.Feet]}");
}
profile
게임 만들러 코딩 공부중

0개의 댓글