강께르의 개발일지

[수업] 20210914_2일차 본문

프로그래밍/C#

[수업] 20210914_2일차

강께르 2021. 9. 14. 19:51

1. 배열

Array 클래스 데이터형

수치데이터형들에 대해 정렬이 가능하다.

하지만 사용자 정의 데이터는 기준을 정해줘야 정렬이 가능하다.

 

배열은 고정 사이즈를 정해줘야 한다.

이 점 때문에 장점이 있다.

 

연속된 데이터형을 일렬로 메모리에 로드할 수 있는데

이 때, 일렬로 늘어놓은 메모리에 대하여 배열은 인덱스를 알고 있으면 바로 접근이 가능하기에

알고리즘에서 말하는 시간 복잡도면에서 빠른 속도를 지닌 편이다.

즉, 랜덤 엑세스가 가능하다고 이야기한다.

 

접근하는 방법으로는 (시작 배열 인덱스) + sizeof(elem) * n이며,

이는 상수 복잡도를 가진 배열의 장점을 잘 나타내준다,

 

단점은 일렬로 메모리에 늘어놓은 데이터의 연속이라는 점에서 삽입과 삭제면에서 치명적으로 작용한다.

중간 혹은 앞의 인덱스가 삭제가 된다면 뒤의 인덱스를 삭제된 인덱스만큼 땡겨와야하기 때문

만약 맨 앞의 요소를 지우면 그 배열의 크기만큼 처리를 해야하는 번거로움이 있다.

 

그리고 순서가 의미 있는 자료구조들인 리스트, 배열, 벡터 등에 대해 나타나는 특징들로

탐색에 약하다고 할 수 있다. 원하는 요소 값이 나올 때까지 인덱스 처음부터 끝까지 탐색해야하기 때문이다.

 

여담) 자료구조를 이해하는데에는 구현하는 것도 좋다.

하지만 실무에서는 각 자료구조만의 장단점을 특정한 케이스에 맞게 사용하는 것이 중요하기에 장단점을 파악하고 언제 사용할지 생각해는 것이 좋을 것 같다.

 

오브젝트 풀에 대한 예시)

삽입 삭제를 반복하면 비용 크니까 게임 시작할 때, 미리 싹 만들어 놓고 비활성화하여 사용할 때마다 활성화하고 갖고와서 사용하며 다 쓰고나서 비활성해서 다시 저장하는 그런 상황도 있을 수 있는데 그런 점을 고려해서 자료구조를 선택할 수 있어야 한다.

여담 끝)

 

배열도 참조형식이다. 그렇기에 대입 연산을 하면 배열의 값이 복사되는 것이 아닌 배열의 참조값을 복사하는 것이다.

배열은 고정 사이즈이기 때문에 첫 인덱스와 마지막 인덱스를 특정, 일반화할 수 있다.

첫 인덱스는 0, 마지막 인덱스는 Length - 1이기 때문이다. 단, 사이즈 1개 이상일 때만 유효하다.

 

초기화하는 방법

new char[5];

new char[5] { ‘a’, ‘e’, ‘i’, ‘o’, ‘u’ };

new char[] { ‘a’, ‘e’, ‘i’, ‘o’, ‘u’ };

{ ‘a’, ‘e’, ‘i’, ‘o’, ‘u’ };

 

생성자를 이용하는 초기화

new int[1000];

이렇게 생성하면 쓰레기값이 할당될 것 같지만?

C#에서는 default value가 존재한다. 메모리가 잡히는 순간 기본으로 0으로 할당한다.

참조형 변수의 default value는 NULL이다.

 

구조체 필드의 메모리 합이 만약 크다고 한다면, 구조체 배열의 사이즈를 크게 잡아버려 메모리를 그만큼 잡아 먹는 상황이 있을 수 있다. 그 점을 유의해야한다.

 

구조체의 필드가 값형식이라면 default value로 0이 잘 할당된다.

하지만 클래스의 필드가 값형식이어도 NullReferenceException을 출력한다. 왜?

클래스의 필드가 값형식이기 이전에 클래스가 참조형식이어서 NULL을 할당하기 때문이다.

 

배열만의 기능이지만 유니티에서는 제공하지 않는 기능...

Index first = 0;

Index last = ^1;

인덱스 구조체를 따로 구현되어있다.

^ 연산자는 뒤에서 몇번째 인덱스라는 것을 의미하고

.. 연산자는 범위를 지정하는 것을 의미한다. 아래에 예시가 있다.

char[] vowels2 = new char[] { 'a', 'e', 'i', 'o', 'u' };

char[] firstTwo = vowels2[..2]; // 'a', 'e'

char[] lastThree = vowels2[2..]; // 'i', 'o', 'u'

char[] middleOne = vowels2[2..3]; // 'i'

char[] lastTwo = vowels[^2..]; // 'o', 'u'

 

2.. 2개 인덱스 다음 값들

..2 뒤 2개 인덱스 이전 값들

2..3 인덱스 2와 3 사이의 값

 

인덱스 접근 에러 = IndexOutOfRangeException

 

다차원 배열

다차원 배열 데이터형 선언 예시)

int[,] ( int[][] 과는 다른 것이다)

int[][] (int[]의 배열)

 

다차원 배열의 메소드

Rank : 해당 배열이 몇차원 배열인지?

Length : 다차원이던 단차원이던 총 인덱스의 수를 반환

GetLength(0) : 0번째 배열의 길이

 

다차원 배열이어도 일렬로 메모리가 잡힌다.

foreach문에서도 모두 순차적으로 순회해서 접근이 가능하다.

 

다차원 배열을 실무에서 어떤 경우에 자주 쓰이는가? : 이미지(색상), map 자료구조, texture 등

new int [,]
{
	{ ... },
	{ ... },
	{ ... }
}

 

배열의 배열

int[][] matrix = new int[][]
{
	new int[] { ... },
	new int[] { ... },
	new int[] { ... }
}

-> int[]의 []라는 느낌으로 이해해야한다. 

배열인데 배열의 요소의 데이터형이 또다른 배열이다!

만약 인스턴스만 선언한다면 앞의 []에 고정사이즈를 할당해 줘야한다.

 

2. 변수

변수는 메모리와 함께 생각해야한다.

변수의 역할은 프로그래머가 메모리를 접근할 수 있도록 돕는 것이다.

 

중괄호 내에 선언된 변수는 지역변수라고 한다.

만약 두 변수가 중괄호 내에 선언되어있다고 하자.

int a = new int();

object obj = new object();

두 지역변수는 메모리의 어느 부분에 로드되는가?

값 형식의 인스턴스 : 스택 영역

참조 형식의 인스턴스 : 힙 영역(실제 데이터)

값 형식의 변수 : 값 형식의 인스턴스와 같다 - 자동으로 할당

참조 형식의 변수 : 스택 영역(참조값)

번외) 매개변수 : 스택 영역

 

지역변수는 중괄호가 종료됨으로 메모리를 해제한다.

가비지 컬렉터의 수거 기능은 스택 영역에 대해 굳이 관리할 필요가 없다. 알아서 삭제되기 때문이다.

매개변수의 경우는 return 시에 해제된다.

 

힙 영역

일반 메소드가 끝나면 참조값을 가지는 스택영역은 삭제되는데

힙 영역에 할당된 인스턴스는 바로 삭제되지 않고 메모리가 차고나서 삭제된다.

 

그렇기에 게임 프로그래밍에서는 자동 메모리 관리에 대해 프레임과 연관되어 있기 때문에 골치가 아프다.

메모리가 차고나서 삭제한다는 것은 그 시점의 프레임 드랍이 일어날 수 밖에 없기 때문이다.

 

그래서 최대한 new를 하지 않아 인스턴스 생성을 막고 참조값도 항상 들고 있으려고 한다.

빈번한 new는 성능 저하를 초래할 수 있다.

 

각 변수와 인스턴스가 어디에 로딩되는지만 파악하자.

 

변수의 기본값이라는 의미인 default 키워드가 있다.

default(int) : 0 / int a = default; 0 대입

위와 같은 기능을 제공한다.

 

default 키워드는 나중에 배울 일반화 프로그래밍(C++에서의 템플릿 프로그래밍과 유사)에

사용된다.

모든 데이터형들에 대해 default 키워드가 적용 가능한데 그 값은 아래와 같다.

모든 참조형 null

모든 수치와 열거형 0

문자 ‘\0’

bool false

 

레퍼런스형 지역변수

int [] numbers = { ... };

ref int numRef = ref numbers[2];

 

이미 존재하는 인스턴스를 레퍼런스형 변수를 통해서 똑같이 접근 가능하게 만드는 것이다.

만약 그 레퍼런스형 변수에 연산을 하면 원래 인스턴스에도 영향을 준다.

왜냐면 같은 인스턴스를 접근하기 때문이다.

 

이 변수 또한 지역변수로 선언되기에 중괄호가 끝나면 해제된다.

 

매개변수

메소드가 수행되기 이전에 먼저 수행되어야할 것이 있다.

그것은 매개변수를 전달 받는 것이다.

매개변수를 전달받으면 스택영역에 메모리를 로드한다.

 

매개변수와 실제 변수의 값 변동에 대한 문제

콜 스택을 따라가면서 메모리가 언제 어떻게 잡히고 사라지는지를 생각해보면 쉬운 문제이다.

메소드에 매개변수를 넘길 때 값을 넘긴다 : Call by value

C#은 기본적으로 매개변수를 전달할 때, Call by value로 전달한다.

데이터형이 참조형이냐 값형이냐 따지기 이전에 무조건 Call by value로 매개변수를 전달한다.

return도 무조건 Call by value이다.

 

참조 형식 매개변수일 때는 어떨까?

string 클래스로 코드를 돌렸을 때, 메소드 내에서 변경으로 영향이 미친 모습을 확인할 수 있었다.

Call by value로 전달되며 참조값을 넘겨 스택 영역에 저장되는 것이다.

 

Call by reference

매개변수에 ref 키워드를 추가해줘야 한다.

넘길 때나 받을 때나 두 곳 모두에 추가해줘야하는 것이다.

 

문자열일 때는 어떻게 전달해야할까?

문자열이면 참조형이기 때문에 참조값이 Call by value로 넘어가서 원하는 동작을 얻을 수 있지 않을까?

하지만 문자열은 참조형이지만 값형처럼 동작한다. 그렇기에 매개변수를 전달할 때, 문자열 값 복사를

수행하고 넘기기때문에 ref 키워드를 사용해 Call by reference로 전달해야 한다.

 

out 키워드와 in 키워드(매개변수 한정자), ref 키워드

Call by referenec로 매개변수를 전달할 때,

out : 무조건 메소드 안에서 한 번 할당이 되어야 한다.

in : 전달받은 참조값을 읽는 것만 가능하고 쓰려고하면 컴파일 오류 발생(너무 큰 애를 전달하는 상황)

ref : 뭘하든 상관없다.

제약 사항을 걸어두어 특정 상황에서 사용하는 것으로 제한 걸어두는 것이다.

 

함수의 선언, 시그니처, 프로토타입은 같은 이름이다.

 

가변길이 매개변수

함수 선언 시 매개변수에 params 키워드를 추가하면 정해지지 않은 매개변수 갯수를 전달할 수 있다.

이 때 전달해주는 매개변수의 데이터형은 통일 되어야 한다.

 

디폴트 매개변수 / 선택적 매개변수

메소드 호출 시 매개변수를 할당하지 않고 기본으로 설정해놓은 값을 할당하고 싶다면 사용.

디폴트 매개변수와 기본 생성자 중에서 기본 생성자가 선택에 우선순위를 갖지만 이런 상황은 지양해야한다.

 

네임드 파라미터

Foo(int x, int y)가 있다고 가정하자.

Foo(y: 2, x: 1);   이런 파라미터 할당이 가능하도록 기능을 제공한다.

Foo(x:1, 2);

Foo(1, y:2);

Foo(y:2 ,1); 모호성 발생, 안됨

 

3. 식(expressions)

연산자와 피연산자로 이루어진 것들

피연산자만 있어도 식이라 한다.

메소드를 호출하고 난 리턴값

리턴 값이 void여도 식이다.

 

그 식에 세미콜론이 추가되면 문장이라고 한다.

 

할당이 일어나는 식은 할당식이라고 한다.

 

연산 결과가 bool형으로 떨어지는 식은 조건식이다.

 

연산자 우선순위 및 결합 방향

대입은 오른쪽붙

사칙연산은 곱, 나눗셈부터

무시하고자 하는 것은 소괄호로 묶어서

그 이외는 MSDN 참고

 

NULL 병합 연산자

?? 연산자 -> NULL을 체크해주고 그에 따른 처리를 수행하는 연산자

string s2 = s1 ?? “nothing”;

널이 아니면 / 널이면

 

??= 널이 아니면 본인 / 널이면 오른쪽 피연산자 대입

 

NULL 조건 연산자

?.(메소드) -> 호출한 변수가 NULL이면 메소드를 호출하지 않는다.

반환 받아야할 데이터형이 수치 데이터형이면 호출할 메소드나 NULL값으로 할당할 수 없기에 참조형만 가능

하지만 따로 리턴하여 할당하는 것 없이 단독 쓰는 애라면 사용 가능

 

4. 흐름 제어문

복합문장 : 중괄호로 묶인 문장들

문장이 들어가는 자리에 복합문장으로 대치하는 흐름제어문

 

if

조건식이 필수로 있어야하는 흐름제어문

bool형이 연산 결과로 나오는 조건식을 필요로 한다.

 

switch

스위치문은 break를 만나면 끝난다.

case에는 상수가 항상 와야한다.

C#에서는 정수형, 열거형말고 또 있다 : 문자열, 오브젝트형(오브젝트가 참조하고 있는 데이터형) where 키워드로 필터링이 가능하다.

 

반복문

몇 번반복할지 알면 for

돌면서 언제끝나리 알 것 같다 while

 

foreach

순회가능한 녀석은 iEnumerator를 상속받아야한다.

반복 변수는 쓰기용도로 사용할 수 없어. -> for문을 사용해야한다.

 

break

반복문을 끝낸다. 가장 가까운.

 

continue

반복문을 진행하다가 만나면 뒤 반복문 나머지를 수행 안하고 다음 반복문을 수행

 

return

메소드를 마무리

 

5. namespace

그룹화하는 것

네임스페이스 안에는 클래스, 구조체, 타입 정의하는 것 모두 여기에 있을 수 있다.

 

using으로 생략이 가능하고

using으로 생략하면 하위 메소드를 멤버 메소드처럼 호출 가능하다.

 

typedef처럼 별명을 붙여 사용할 수 있다.

'프로그래밍 > C#' 카테고리의 다른 글

[수업] 20210916_4일차  (0) 2021.09.17
[문제 풀기] 클래스에 관하여  (0) 2021.09.16
[수업] 20210915_3일차  (0) 2021.09.16
[문제 풀기] 문자열에 대해서  (0) 2021.09.14
[수업] 20210913_1일차  (0) 2021.09.14