Data type
데이터가 메모리에 저장되는 형태와 처리되는 방식을 명시하는 역할
bool : 논리자료형. 1byte => true, false
int : 부호가 있는 정수형. 4byte => -2,147,483,648 ~ 2,147,483,647
float : 부동소수점 실수. 4byte => 약 소수점 7자리
char : 유니코드 문자형. 2byte => 's'
string : 유니코드 문자열. X => "abcde"
variable
데이터를 저장하기 위해 프로그램에 의해 이름을 할방답은 메모리 공간. 저장된 값은 변경이 가능하다.
자료형 선언 후 뒤에 변수 이름을 작성하여 변수 선언을 한다.
선언함 변수에 값을 처음 할당하는 과정을 초기화라고 하며, 선언과 초기화 과정을 동시에 진행하는 것이 가능하다.
=(대입연산자) 좌변(좌측)에 변수를 배치한다.
데이터가 필요한 곳에 변수명을 배치한다.
동일한 이름을 중복해서 사용하는 것이 불가능하다.
숫자부터 시작하는 이름 및 띄어쓰기, 특수문자 사용하는 것이 불가능하다.
카멜 표기법을 사용한다 (valueAddress
)
Constant
프로그램이 실행되는 동안 변경할 수 없는 데이터로, 값이 변경되기를 원하지 않는 데이터가 있을 경우 사용한다.
변수 선언 앞에 const 키워드를 추가하여 상수 선언을 하며, 초기화 없이 선언하는 것은 불가능하다.
Casting
데이ㅓ를 선언한 자료형에 맞는 형태로 변환하는 작업. 다른 자료형의 데이터를 저장하기 위해 형변환 작업을 진행하며, 보관할 수 없는 데이터는 버려진다.
변환할 데이터 앞에 변환할 자료형을 괄호 안에 넣어 형변환을 진행한다.
int intValue = (int)1.2;
1.2를 int로 변환하는 과정 중 보관할 수 없는 소수점이 버려진다.
변수에 데이터를 넣는 과정에서 자료형이 더 큰 범위를 표현하는 경우 자동으로 형변환을 진행한다.
float floatValue = 3;
다만 일반적으로 변수의 형변환 같은 경우 자동 형변환이 가능하더라도 형변환을 적어준다.
아스키 코드 : 이진법을 사용하는 컴퓨터에서 문자를 표현하기 위해 정해둔 매칭표
유니 코드 : 영어만 표현이 가능했던 아스키코드에서 전 세계의 모든 문자를 다루도록 설계한 매칭표
문자옇은 단순형변환이 불가능하다. 따라서 각 자료형의 Parse
, TryParse
를 이용하여 문자열에서 자료형으로 변환한다. 자료형에서 문자열로 변환할 때는 ToString()
을 사용한다.
Array
동일한 자료형의 요소들로 구성된 데이터 집합으로 인덱스를 통하여 요소에 접근할 수 있다.
배열을 만들기 위해 자료형과 크기를 정하여 생성한다.
자료형[] 이름 = new 자료형[크기]{요소};
요소들을 초기화 하는 경우 배열의 크기와 new 키워드 생략이 가능하다.
Array 클래스로 구현되어 해당 클래스의 기능들을 모두 사용할 수 있다.
array.Length
array.Max
array.Min
array.Sort // 정렬
array.Reverse // 반전
Array.IndexOf // 탐색
int[] swallow = array // 얕은 복사
int[] deep = new int[array.Length];= // 깊은 복사
Array.Copy(array,deep,array.Length);
배열은 요소를 메모리에 메모리에 연속적으로 배치하는 원리로 구현되어 특정요소의 메모리주소를 계산할 수 있다. k번째 배열 요소 메모리 주소 : 배열 시작 메모리 주소 + k x (자료형의 크기)
배열의 [ ]괄호 안에 차원수만큼 ,를 추가하여 선언한다.
Operator
프로그래밍 언어에서는 일반적인 수학 연산과 유사한 연산자들이 지원된다.
+
-
/
%
++value
value++
=
x += y
: x = x + y
>
<
==
!=
&&
||
!
false && x
에서 x
의 연산을 무시연산자에 따라 우선순위가 있으며, 괄호( )를 사용하여 처리 순서를 정할 수 있다.
if
조건문조건식의 true, false에 따라 실행할 블록을 결정하는 조건문을 의미한다.
if(true) => 실행
else => 실행x
if(false) => 실행x
else => 실행
if(조건1)
else if(조건2)
else
switch
조건문조건값에 따라 실행할 시작지점을 결정하는 조건문이다.
switch(value)
{
case value1 :
case value2 :
func1;
break;
case value3 :
fucn2;
break;
default :
func3;
break;
}
int value = (parameter) switch;
{
1 => 1;
2 => 2;
_ => 3;
}
간단한 조건문을 빠르게 작성할 때 사용한다.
int big = 20 > 10 ? 20; 10;
int small = 20 < 10 ? 20; 10;
while
조건이 true인 동안 반복한다.
while(true)
{
// 반복 실행
}
do-while
먼저 한 번은 반드시 수행해야할 때 사용된다.
do
{
// 먼저 한 번의 실행 후 반복 실행
}
while(true)
for
초기화, 조건식, 증감연산으로 구성된 반복문이다.
for(int i = 0; i < 5; i++)
{
int sum += i;
}
foreach
반복가능한 데이터 집합의 처음부터 끝까지 반복한다.
int [] numArray = new int[3] { 1,2,3 };
foreach(var n in numArray)
{
sum += n;
}
프로그램의 순차적인 실행 중 다른 문으로 제어를 전송한다.
1. continue
2. break
3. return
미리 정해진 동작을 수행하는 코드 묶음으로, 어떤 처리를 미리 함수로 만들어 두면 다시 반복적으로 사용 가능하다.
반환형 함수 이름(매개변수) {함수 내용}
int Plus(int left, int right)
{
return left + right;
}
함수로 구성해둔 코드를 이름을 불러 사용한다.
static void Main(string[] args)
{
Plus(1,2);
}
함수의 결과 데이터의 자료형으로 함수가 끝나기 전까지 반드시 return으로 반환형에 맞는 데이터를 출력해야한다. 진행 중에 return을 만나는 경우 즉시 데이터를 반환하고 함수가 종료된다. 결과가 없는 경우 반환형은 void이며 return을 생략할 수 있다.
void Print(string message)
{
Console.WriteLine(message);
//(return;) => 생략 가능
}
함수에 추가할 데이터의 자료형과 변수명을 의미한다. 매개변수의 차이로 같은 함수에서도 다른 처리가 가능하도록 만들 수 있다.
int Minus(int left, int right)
{
return left - right;
}
int Minus()
{
return 20 - 10;
}
Minus(30, 10);
Minus()
20
10
함수 호출 스택이라는 용어를 활용하며, 스택처럼 함수를 쌓아 후입선출의 방식으로 함수를 호출하여 처리한다.
int Func1()
{
return Func2;
}
int Func2()
{
return 1;
}
void Main()
{
Func1;
}
위와 같이 Main에서 Func1을 호출하게 되면 Func1이 Func2를 호출하는데, Func2 -> Func1 -> Main 순서로 처리된다.
같은 이름의 함수를 매개변수를 달리하여 다른 함수로 재정의하는 것을 의미한다. 매개변수의 차이로 호출할 함수를 지정할 수 있는 것이다.
int Plus(int left, int right)
{
return left + right;
}
int Plus(float left, float right)
{
return left + right;
}
기본 정수 형식이 명명된 상수 집합에 의해 정의되는 값 형식을 의미한다. 멤버의 이름으로 관리되어 코드의 가독성적인 측면에 도움이 된다.
enum Direction { Up, Down, Left, Right }
void Main()
{
Direction dir = Direction.Up;
switch(dir)
{
case Direction.Up:
// 실행
break;
...
}
}
열거형 멤버에 정수값을 지정하지 않는 경우 0으로 시작하며, 지정할 경우 해당 정수값부터 시작한다. 따라서 열거형과 정수는 서로 형변환이 가능하다.
enum Season()
{
Spring, // 0
Summer, // 1
Autumn = 10, // 10
Winter // 11
}
데이터와 관련 기능을 캡슐화할 수 있는 값 형식으로 데이터를 저장하기 위한 단위 용도로 사용된다.
struct StudentInfo
{
public string name;
public int num;
}
void Main()
{
StudentInfo student1;
student1.name = "velog";
student1.num = 1;
}
구조체에 대입 연산자를 통해 구조체 내 모든 변수들의 값이 복사된다. 즉 값형식의 깊은 복사가 이루어진다. 구조체와 클래스의 가장 큰 차이 중에 하나로 클래스의 경우 참조형식의 복사인 얕은 복사로 이루어진다.
StudentInfo student2 = student1;
student1.num = 2;
student2.num; // = 1;
class StudentInfo
{
public string name;
public int num;
}
void Main()
{
StudentInfo student1 = new StudentInfo();
student1.name = "velog";
student1.num = 1;
StudentInfo student2 = student1;
student1.num = 2;
student2.num; // = 2;
}
구조체는 값형식의 데이터이며 클래스는 참조형식의 데이터이다. => Vector를 클래스가 아닌 구조체로 선언해야하는 이유
값 형식
깊은 복사 : 복사할 때 실제값이 복사된다.
호출 : 값 형식의 데이터가 복사되어 전달된다.
참조 형식
얕은 복사 : 복사할 때 객체의 주소가 복사가 된다.
호출 : 참조형식의 데이터가 전달되거나, 값 형식의 데이터가 ref, out 키워드로 전달되는 경우 원본 데이터가 전달된다.
OOP
객체지향 프로그래밍은 프로그램 설계방법론의 일종으로 상호작용하는 객체를 기본 단위로하여 프로그램을 구성하는 것이다.
기존 절차 지향의 방식으로는 복잡한 구조에 대한 설계가 힘들기에 이에 대한 대안으로 객체지향이 등장하게 되었다.
객체는 오직 하나의 책임을 가져야 한다.
객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다.
자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
인터페이스는 작은 단위들로 분리시켜 구성하며, 사용하지 않는 함수는 포함하지 않아야한다.
객체는 하위 클래스보다 상위 클래스에 의존해야한다.
객체를 상태와 기능으로 묶는 것. 허용한 상태와 기능만의 액세스만 허용하여 객체의 내부 상태와 기능을 숨긴다.
부모 클래스의 함수로 자식 클래스에서 재정의 하여 자식 클래스의 다른 반응을 구현하는 것
관련 특성 및 엔터티의 상호 작용을 클래스로 모델링하여 시스템의 추상적 표현을 정
부모 클래스의 모든 기능을 가지는 자식 클래스를 설계하는 방법
객체를 정보와 기능으로 묶어 멤버라고 표현하는데, 현실세계의 객체를 표현하기 위해 객체가 가지는 정보와 행동을 묶어 구현하고, 이를 통해 객체 간 상호작용을 표현한다.
class Capsule
{
private int variable; // 멤버 변수 : 객체의 정보
public void Function() {} // 멤버 함수 : 객체의 기능
}
외부에서 접근이 가능한 멤버 변수,함수를 지정하는 기능으로 기본접근제한자는 private이다.
객체 구성에 있어서 외부에서 사용하기 원하는 기능과 사용하기 원하지 않는 기능을 구분하기 위해 사용한다. 사용자가 객체를 사용하는데 있어서 필요한 기능만을 확인하기 위한 용도이며, 외부에 의해 영향 받지 않는 기능을 감추기 위한 용도이기도 하다.
부모 클래스의 모든 기능을 가지는 자식 클래스를 설계하는 방법이다.
is-a 관계 : 부모 클래스가 자식 클래스를 포함하는 개념인 경우의 관계이다.
is
: 형변환이 가능한지 확인as
: 형변환이 가능하면 형변환상속을 진행하는 경우 부모 클래스의 소스가 자식 클래스에서 모두 적용된다.
부모 클래스와 자식 클래스의 상속 관계가 적합한 경우 부모 클래스에서의 기능 구현이 자식 클래스에서도 적용된다.
업캐스팅을 통해 자식 클래스는 부모 클래스로 암시적 형변환이 가능하다.
자식 클래스는 부모 클래스를 요구하는 곳에서 동일한 기능을 수행할 수 있다.
부모 클래스의 멤버를 자식 클래스마다 여러가지 형태로 가질 수 있다.
부모 클래스에 virtual로 멤버 함수를 선언한 경우 자식 클래스마다 상황에 맞게 override로 이를 구현하여 다형성을 가질 수 있다.
클래스를 정의할 때 구체화시킬 수 없는 기능을 추상적 표현으로 정의한다.
하나 이상의 추상 함수를 포함하는 클래스를 의미하며, 클래스가 추상적인 표현을 정의하는 자식에서 구체화시켜 구현할 것을 염두하고 추상화시킨다.
추상클래스에서 내용을 구체화 할 수 없는 추상 함수는 내용을 정의하지 않고, 상속하는 자식 클래스가 이를 재정의하여 구체화하는 경우 사용 가능하다.
interface
인터페이스는 멤버를 가질 수 있지만 직접 구현하지 않는다. 다만 인터페이스를 가지는 클래스에서 반드시 인터페이스에서 정의한 멤버를 구현해야한다. 이를 줄여말하면, 인터페이스를 포함하는 클래스는 해당 기능을 구현했음을 보장하므로 사용이 가능한 것이다.
can - a 관계 : 클래스가 해당 행동을 할 수 있는 경우
일반적으로 인터페이스의 이름은 I로 시작하며, 인터페이스의 함수는 직접 구현하지않고 정의만 진행한다.
상속과 동일하게 클래스 : 뒤에 선언하여 사용한다. 인터페이스는 여러 개 포함이 가능하며, 여러 인터페이스의 함수를 모두 다 구현해야한다.
인터페이스는 추상클래스의 일종으로 특징이 동일하기에 둘의 구분이 애매해보인다. 하지만 추상클래스와 인터페이스를 통해 얻는 효과는 다르기에 다른 역할로 구분되며, 개발자는 차이를 알고 상황에 적합한 것을 선택하여 활용할 수 있어야한다.
함수에 대한 선언만 정의하고 이를 포함하는 클래스에서 구체화하여 사용
추상 클래스(A Is B 관계) : 상속으로부터 얻는 이점을 갖으며, 부모 클래스 기능에서 자식 클래스의 기능을 확장하는 경우 사용한다.
인터페이스(A Can B 관계) : 인터페이스에 정의된 함수를 클래스의 목적에 맞게 기능을 구현하는 경우 사용한다.
class Player
{
void Hit(Monster monster)
{
monster.Damage();
}
}
class Monster : Idamageable
{
void Damage() {}
}
interface Idamageable
{
void damage();
}
static
프로그램의 시작과 함께 할당되고 프로그램 종료시에 소멸되는 데이터로, 프로그램이 동작하는 항상 고정된 위치에 존재한다.
Generic
클래스 또는 함수가 코드에 의해 선언되고 인스턴스화될 때까지 형식의 지정을 연기하는 디자인을 의미한다. 기능을 구현한 뒤 자료형을 사용할 때 지정한다.
void Swap<T>(T left, T right)
{
T temp = left;
left = right;
right = temp;
}
일반화 자료형을 선언할 때 제약 조건을 선언하여, 사용 당시 쓸 수 있는 자료형을 제한하는 방법이다.
clase Class<T> Where T : class { } // T는 클래스만 사용이 가능
void Func<T>(T left, T right) where T : int { } // T는 int만 사용 가능
제약 조건이 있다면 포함 가능한 자료형의 기능을 사용할 수 있다.
T Bigger<T> (T left, T right) Where T : IComparable
{
if(left.CompareTo(right) > 0) // 제약이 있어서 CompareTo 사용 가능
{
return left;
}
else return right;
}
Deligate
특정 매개변수와 반환형이 존재하는 함수에 대한 참조를 의미하며, 델리게이트 인스턴스를 통해 참조한 함수의 호출이 가능하다.
반환형과 매개변수가 일치하는 함수를 델리게이트 변수에 할당하여 델리게이트 변수에서 참조한 함수를 대리자로써 호출한다.
public delegate float Mydel(float x, float y);
public float Plus(float left, float right) { return left + right; }
Mydel Foo = new Mydel(Plus);
Foo?.Invoke(20,10)
30
델리게이트를 이용하여 특정 조건에서 반응하는 함수를 구현하여, 직접 호출 받는 것이 아닌, 다른 함수의 호출로 간접적으로 실행되는 것을 의미한다.
void Main()
{
File file = new File();
Button saveButton = new Button();
Button loadButton = new Button();
saveButton.Click(file.Save); // output : 저장하기 합니다.
loadButton.Click(file.Load); // output : 불러오기 합니다.
}
public class Button
{
public Action callback;
public void Click(Action callback)
{
if (callback != null)
{
callback();
}
}
}
public class File
{
public void Save()
{
Console.WriteLine("저장하기 합니다.");
}
public void Load()
{
Console.WriteLine("불러오기 합니다.");
}
}
콜백함수를 쓰는 이유? → 추상화를 위함!
콜백함수를 사용하지 않아도 특정 클래스 멤버함수에서 다른 클래스를 참조하여 해당 클래스의 멤버함수를 호출하는 것이 가능하다.
하지만 이 경우 두 클래스 간의 결합도가 높아지기에 객체지향의 원칙에 어긋나며, 간단히 콜백함수로 추상화 과정을 통해 객체지향을 만족하며 구현할 수 있다.
즉, 콜백함수는 버튼 클릭 등의 이벤트 발생 시 실행될 메서드를 정의할 때, 실행할 메서드를 매개변수로 전달하여 유연한 코드 구조를 만들 때 활용하면 유용하다.
개발 과정에서 많이 사용되는 델리게이트의 경우 일반화된 델리게이트를 사용한다.
Func<반환형, parameter>
= delegate 반환형 Func(parameter)
Action<parameter>
= delegate void Action(parameter)
일련의 사건이 발생했다는 사실을 다른 객체에게 전달하는 것으로 델리게이트의 일부 기능을 제한하여 이벤트의 용도로 사용한다.
델리게이트 변수 앞에 event 키워드를 붙여 선언하며 2가지 기능을 제한한다.
1. =
사용 불가
대입 연산으로 인해 기존의 이벤트에 반응할 객체 상황이 초기화 위험
2. 외부 호출 불가
클래스 외부에서 이벤트를 발생시켜 원하지 않는 상황에서 이벤트 발생 위험
이벤트에 반응할 객체의 추가할 함수를 += 연산자로 추가
이벤트에 반응할 객체의 제거할 함수를 -= 연산자로 제거
이벤트의 경우 위에서 말했듯이 2가지 기능이 제한되어, 말 그대로(literally) 이벤트를 위한 기능이 된다. 델리게이트 체인은 2가지 위험이 있기에 이벤트의 쓰임을 원한다면 event
키워드를 붙이는 것이 좋다.
이벤트를 사용할 경우 클래스의 개방폐쇄원칙을 지킬 수 있으며, 일련의 사건이 발생한 타이밍에만 연산을 진행할 수 있다.
사건이 발생한 순간에 대상의 함수를 호출한다.
장점 : 불필요한 연산 없이 일련의 사건이 발생한 타이밍에 함께 처리 가능하다.
단점 : 클래스 간 결합도가 높아져, 개방폐쇄의 원칙을 위반한다.
일련의 사건 발생을 확인하기 위해 대상이 계속해서 변경사항을 확인한다.
장점 : 클래스 간 결합도가 낮아, 개방폐쇄의 원칙을 준수한다.
단점 : 변경사항이 없는 경우에도 계속 확인하는 불필요한 연산이 발생한다.
일련의 사건이 발생했을 때 반응할 대상을 참조하고 사건 발생시 호출하여 진행한다.
장점 : 개방폐쇄의 원칙이 지켜지며, 불필요한 연산을 필요로 하지 않음
단점 : 이벤트를 구성하기 위한 추가적인 소스를 작성해야한다.
ETC
Parameter Keyword
out
매개 변수를 출력 전용으로 설정하여, 반환값 이외의 결과값이 존재할 때 사용한다.
매개 변수를 원본 참조로 전달하여, 값형식인 경우에도 함수를 통해 원본값을 변경할 떄 사용한다.
Property
멤버 변수가 외부 객체와 상호작용하는 경우 Getter, Setter 함수를 구현해주는 것이 일반적이다.
각각의 getter, setter 함수에 접근제한자를 설정하여 멤버 변수에 대한 접근을 캡슐화하며, 함수를 거쳐 멤버 변수에 접근할 경우 호출 스택에 함수가 추가되어 변경 시점을 확인하는 것이 가능하다.
다만, 모든 필드에 함수를 구현하는 것이 힘든 작업이기에 속성을 추가하여 함수와 같은 기능을 수행하도록 만든다.
private int MP;
public int MP
{
get { return mp; }
set { mp = value; }
}
public int HP { get; set; }
public int DP { get; private set; }
public int SP { get; } = 10;
public int NP => GetNP();