-
Section 1 ~ Section: 7👩🏻💻 c# 2023. 8. 1. 07:34
Section 1: data
- 4 main types: int, float, string, bool
- 변수: 선언시 RAM 에 자리 할당
- 선언, 할당, read
- Int
- int: 32 bit / 4 byte 크기 (short: 2 byte)
- 최상의 비트로 부호표현
- Float: 숫자에 f를 붙어야 함.
- 근사치를 표현 (그렇기에 정수 사용할 수 있는 경우에는 int를 사용하도록)
- double (8 byte, 더 정밀 표현, f 붙이지 않는다)
- String
- "" 큰 따옴표 붙여 사용. 여러개의 문자열
- char : 작은 따옴표 ('') 사용. 하나의 문자
- 정수로 치환되어 저장된다 (아스키코드)
- Bool : true / false
- 캐스팅: 형식을 변환
- 예시:
int a = 100; short b = (short)a;
- 크기가 큰 데이터를 작은 곳에 넣을 때는 에러메세지: 꼭 명시적으로 표현 해야한다
- 스트링 포맷
int a = 100; string b = (string) a; //불가. string은 클래스여서. 이전과는 다른 경우이다 int c = 80; //string -> int int number = int.Parse(input) //int -> string //1st method string.Format("Your number is {0}", a); string.Format("Your number is {0}{1}", a, c); //2nd method string message = $"Your number is {a}"; // 요 방법 추천. 순서 명시 필요 없음
- 산술연산: +, -, *, /, % (홀짝 구분시)
- 증감연산: a++, a+= 5, ++a;
- 비교연산: < <= > >= == !=
- 논리연산: && || ! / 예시: bool result = (isTall && isSmart);
- var
- 만능형. 자동추론. 하지만 직접 명시가 직관적이어서 더 좋음.
Section 2: 코드 흐름의 제어
- if / else 문
- switch
- switch(choice) -> 에서 choice 는 꼭 정수나 문자열이어야 한다
- case a: 에서 a는 변수 일수 없다. (변수 일시 앞에 const 붙여서 상수화 해야함)
- 삼항 연산자:
- 취향 차이. 가독성 문제
int number = 24; bool isPair; //isPair = ( (조건) ? 맞을때; 틀릴때); isPair = ( (number % 2) == 0 ? true; false);
- 상수와 열거형
- 숫자 하드코딩 하지 말자 -> 가독성 떨어짐
- 열거형
int SCISSORS = 0; //상수는 대문자로 int ROCK = 1; int PAPER = 2; //보다는 열거형을 쓰자. 가독성 올라간다 enum Choice { Rock, Paper, Scissors } //숫자를 지정하지않으면 순서대로 0... assign 된다 enum Choice2 { Rock = 2, Paper = 1, Scissors = 3 } //요래 명시 할수도 있음 switch(choice) { case (int)Choice.Scissors: //요렇게 쓰면된다 ... }
- while, for 문
- break와 continue 로 코드의 흐름을 제어.
- continue: 루프가 끝난걸로 가정하고 그 다음 iteration으로 넘어간다.
함수 (method)
- 정의방법
한정자 반환형식 이름(매겨변수 목록) { } static void HelloWorld() { Console.WriteLine("hello"); }
⭐️ 참조 (진짜) / 복사 (짝퉁)
static void AddOne(ref int number) //ref : 참조 값을 받겠다고 명시적 { number += 1; } int a = 0; Program.AddOne(ref a); //복사가 아니라 참조 값을 보낸다. 이때도 ref 키워드를 붙여서 -> 즉 실제 a 의 값이 변경괴게 된다.
- ref, out
- 자주 사용하지는 않는다
- ref 안 쓴 버전이 더 좋다 (반환을 받아서 이 값을 저장하는 방식으로)
- 물론 ref를 썼을 때 더 편한 경우도 있다. 예를 들면 두 숫자를 서로 스왑하는 경우
- out은 함수에서 여러 값을 반환하고 싶을 때 쓴다
static void Divide(int a, int b, out int res1, out int res2) //out 붙이기 { res1 = a/b; res2 = a%b; } static void Main(string[] args) { int num1 = 10; int num2 = 3; int res1; int res2; Divide(10, 3, out res1, out res2); ////out 붙이기 } //out은 여러 개를 반환 하고싶을 때 쓴다 요렇게
오버로딩 (과적) // 이름의 재사용
- 매개변수 타입이나 갯수가 달라야 한다
static int Add1(int a, int b){ } static int Add1(int a, int b, int c){ } //팁. static int Add(int a, int b, int c = 0){ } // 마지막 매개 변수: 옵션 -> 오버로딩없이 이 함수 여러 상황에서 쓸수 있게 된다 Add(1,2); Add(1,2,3);
Section 3: TextRPG
- 구조체 (복사 o, 참조 x)
struct Player { int hp; int attack; }
Section 3: 객체 지향
- 함수 기반
- 절차 지향. 순서 중요 하다.
- 유지보수 어려움
- 직관적 장점
- 객체 지향 (OOP)
- 클래스: 속성, 필드 & 기능
- 특징: 은닉성, 상속성, 다형성
클래스
- 동적 할당
class Knight //Knight 라는 틀을 만드는 것 { public int hp; public int attack; public void move() { } } Knight knight = new Knight(); //동적 할당 (Heap) knight.hp = 100; knight.Move();
- 복사와 참조
- struct는 복사. 클래스는 참조!
- 깊은 복사 (deep copy)
public Knight Clone() { Knight knight = new Knight(); knight.hp = hp; knight.attack = attack; return knight; }// 요런식으로 참조가 아니라 복사를 할 수도 있다
생성자
class Knight { //skip public Knight() //한정자 + 클래스 이름 + () { hp = 100; attack = 10; } //하나의 생성자 이상을 가질수 있다. public Knight(int hp) { this.hp = hp; attack = 10; } }
- 클래스의 속성이 너무 많으면 일일이 생성자로 assign 하기 불편. 그래서 생성자에: this() ⭐️
class Knight() { public Knight(int hp): this() //빈 생성자. 즉 Knight() 먼저 호출 { this.hp = hp; attack = 10; } }
⭐️⭐️⭐️Static 의 정체
- 클래스에서 오로지 한개만 존재
- 즉 인스턴트들이 이 한개의 값을 공유한다
- 클래스에 종속적이기 때문에 유일성을 보장한다
- 클래스 함수들도 static 선언 가능하다
- static으로 선언된 함수는 그 클래스의 필드에 접근 불가능하다
- 오로지 static 필드만 접근 가능하다
- 호출할때는 클래스이름.method이름(); 으로 (보통은 인스턴스이름.method()이다)
- 예시: Console.WriteLine() 도 클래스이름.method()로 한거
상속성
- 부모-자식 / 계층관계 정리
class Player //부모, 기반 클래스 { } class Mage: Player //자식, 파생 클래스 { }
- 여기서 Mage 인스턴스를 만들면 부모 생성자 먼저 호출 -> 후 자식 생성자 호출 된다
- 이때 부모의 기본 생성자가 아닌 다른 생성자를 호출하고 싶다면 base() 를 활용하여 명시한다.
class Mage: Player { public Mage() : base(10) //부모의 생성자중 인자를 1개 받는 생성자 호출 { base.hp = 100; //부모의 필드에 접근할때도 base를 써서 } }
- 그렇다면 함수는?
- 부모의 method도 상속되고, 자식은 마치 자기 method 인 것 처럼 쓰면 된다. (static 함수는 안되겠지)
은닉성 (보안레벨)
- 접근 한정자
- public: 아무나 접근
- protected: 외부 접근불가 but 자식 클래스는 접근 가능
- private: 클래스 내부에서만 접근 가능. 자식 클래스도 노노.
- 지정 하지않으면 자동적으로 private 으로 간주 된다
- 쓰는 이유: 여러사람이 같은 게임 개발 하게되서 파일 복잡해져서 보안
class Knight { private int hp; public void SetHp(int hp) { this.hp = hp; } } Knight knight = new Knight(); // knight.hp = 3 -> 자 요렇게 하지말고 따로 클래스에서 SetHp 같은 함수를 활용해서 !! //이유 아래 설명
- SetHp 같은 함수를 쓰는 이유:
- 장점: 이 방식을 쓰면 누가 언제 hp 를 고쳤는지 찾기 쉬워진다.
- 안쓰면 knight.hp 찾아야하는데 더 힘듦.
- 그래서 hp 를 private 선언해놓고 public 선언된 setter 를 써서 수정한다
클래스 형식변환
class Player { } class Mage: Player { } static void EnterGame(Player player){ } static void Main(string[] args) { Mage mage = new Mage(); EnterGame(mage); // EnterGame 함수는 Player 인스턴스를 인자로 받지만 //이것을 상속받은 Mage 인스턴스도 가능하다 }
- 요렇게 받았을 때 EnterGame 에서 확인 하는 방법
- player is Mage
- player as Mage
- 요 방법을 더 많이 쓴다. 더 깔끔해서
static void EnterGame(Player player) { //1번 방법 bool isMage = (player is Mage); //2번 방법 Mage mage = (player as Mage); //player 가 Mage 아니면 mage 는 null인 상태가 된다 } static void Main(string[] args) { Mage mage = new Mage(); EnterGame(mage); }
다형성 ( Polymorhism)
- 부모 & 자식이 같은 이름의 함수를 가지고 있다면?
- virtual / override 키워드를 사용하여 가상 함수를 만든다 (오버로딩과 헤깔리지 말자)
- 언제 문제인가? 윗 상황에서도 문제 볼수 있다.
- 만약 Player와 Mage 클래스 둘다에서 Move 라는 함수가 있다면, EnterGame 에서 player 인자 받아서 player.Move() 호출하면 Player 클래스의 Move 가 호출 되는 것일까 Mage의 Move 가 호출되는 것일까? ---> 부모의 Move 가 호출되게 됨
- 해결:
class Player { public virtual void Move(){ } } class Mage: Player { public override void Move() { } }
- 이제 EnterGame 함수안에서 player.Move() 하면 자식의 Move() 가 호출된다
- 부모 함수를 대체하고 싶지는 않고 기능만 추가 하고싶을 때에는 base 키워드를 써서 추가하자 (아래 예시)
class Player { public virtual void Move(){ } } class Mage: Player { public override void Move() { base.Move(); //부모의 move함수를 먼저 호출한다 //내용 추가 } }
- virtual 에는 성능 부하가 있다는 것을 염두
- 자식의 override를 막으려면 Sealed 라는 키워드를 쓴다 (하지만 거의 안쓰임)
문자열 둘러보기
string name = "hello"; //찾기 name.Contains("Hel"); // true, false name.indexOf("h"); //0, 없으면 -1 //변형 name + " world"; //추가 name.ToLower(); name.ToUpper(); name.Replace("h", "k"); //분할 name.Split(new char[]{' '}); name.Substring(3) //3부터 잘라 보관
Section 5: TextRPG2
- 클래스는 따로 빼서 다른 파일로 저장한다
- include 등 이런거 없이 다른 파일에서 사용할수 있다 (namespace 가 같다는 가정하에)
Section 6: 자료 구조 맛보기
배열
- 동적. 즉 참조 타입
int[] a; int[] b = new int[5] // 5개 짜리 정수형 배열 //세가지 방법 int[] scores = new int[5] {10, 20, 30, 40, 50}; //요거 추천 int[] scores = new int[]{10, 20, 30, 40, 50}; int[] scores = {10, 20, 30, 40, 50}; //iterate for (int i = 0; i < 5; i++ ) { } //자칫 index out of range //그러니 요거나 for (int i = 0; i < b.Length; i++) { } //요거 foreach(int score in b){ }
- 다차원 배열
int[,] arr = new int[2,3]; // [2차원 크기, 1차원 크기] /// 요런 모양 ///[x,x,x] ///[x,x,x] int[,] array = new int[2,3]{{1, 2, 3}, {1, 2, 3}};
- 가변 배열: int[][] a = new int[2][]; (배열의 배열)
List
- 배열의 아쉬운 점: 고정 크기 -> 그래서 리스트 (동적 배열)
using Systems.Collections.Generic List<int> list = new List<int>(); //클래스. 참조 형태 list.Add(1); // 추가방법. list[0] = 2 따위는 안된다. //list.Count 로 크기 알 수 있다. //삽입 삭제 list.Insert(index, item); list.Remove(item); list.RemoveAt(index); list.Clear();
- Insert, remove, removeAt 은 효율성 떨어진다 -> 왜냐면 뒤에 아이템들 모두 이동 시켜야 하니까.
Dictionary
- list 의 단점: 찾기 힘들다
- 사전으로 특정 키-> 벨류 빠르게 찾을 수 있다: hashtable 써서 구현되어 있기 때문이다 (메모리 희생시키고 성능 올린것)
Dictionary<int, Player> dic = new Dictionary<int, Player>(); dic.Add(key, value); dic[5] = new Player(5); //찾기 dic.TryGetValue(key, out player); // true, false dic.Remove(key); dic.Clear();
Section 7: 알아두면 유용한 기타 문법
Generic (일반화)
- 우선 오브젝트 vs Var
- 오브젝트는 타입 자체가 object
- var 는 컴파일러가 알아서 바로바로 변환시킨다
- string, int,... 가 모두 object를 상속 받기에 가능한것이다.
- object는 느리다는 단점. 왜냐면 참조 타입 (heap 에 메모리 할당)
Object obj = 3; Object obj2 = "hello"; int num = (int)obj; // 꺼낼시 캐스팅 필요
- 일반화
- 클래스의 일반화 'T'를 써서:
- 각 타입마다 클래스 선언 하지 않아도 된다.
- 클래스의 일반화 'T'를 써서:
class MyList<T> { T[] arr = new T[10]; } static void Main(string[] args) { MyList<int> myList = new MyList<int>(); MyList<short> myList = new MyList<short>(); MyList<Player> myList = new MyList<Player>(); }
- 함수도 일반화 가능하다
static void Test<T>(T input) { }
- 일반화 타입에 조건 추가 가능
class MyList<T> where T: struct { }
Abstract & Interface
- Abstract: 최상 클래스. abstract 을 붙인 클래스이다 (인스턴스를 만들 수 없다)
- 함수 틀만 잡고 내용은 정의 하지않고 상속받은 멤버들이 override 를 써서 무조건 정의 하게 한다
- 문제점: 다중상속 불가 하다
- c#에서는 다중 상속이 불가 하지만 인터페이스는 여러개 넘길수 있다(마치 다중 상속처럼)
- 다중 상속 불가한 이유: 죽음의 다이아몬드 같은 문제, 즉 같은 함수가 있을시 호출 충돌 문제 때문에 불가 하게됨.
absract class Monster { public abstract void Shout(); //abstract 때문에 함수 내용은 정의하지 않는다. } class Orc: Monster { public override void Shout(){//정의} //상속받은 멤버는 꼭 Override 로 무조건 정의 해야한다. } class Skeleton: Monster { public override void Shout(){//정의} //강요. 강제 }
- 해결법: 인터페이스 문법을 사용하여 다중상속처럼 쓰도록 한다.
- interface 키워드 사용, 또 I 를 이 클래스 이름 앞에 붙여 다른 클래스와 구분하도록 한다
interface IFlyable { void Fly(); } class FlyableOrc: Orc, IFlyable // 마치 다중 상속처럼 { //fly 함수 꼭 정의 필요하다 }
- Abstact 보다 interface 가 더 유연하다-> 여러개 인터페이스 동시 사용 가능하니까
- 왜 자식의 함수 정의 강요 필요한가? 왜 인터페이스 필요한가?
- 인터페이스를 상속? 받은 애들을 묶음으로 활용 가능
- 왜 자식의 함수 정의 강요 필요한가? 왜 인터페이스 필요한가?
IFlyable flyable = new FlyableOrc(); //라던가 static void doFly(Iflyable flyable){ } //라던가 활용 방법 많음
⭐️프로퍼티 (객체지향의 은닉성과 관련있다)
public int HP { get { } set { } } //위에랑 요거랑 똑같은 것. 더 간소화 해서 쓸 수 있음 //private, protected 도 붙여 쓸수 있다 public int HP { get { return hp; } set { this.hp = value; } } knight.hp = 100 // 이때 set 이 사용된다 int hp = knight.hp // 이때 get 이 사용된다
- ⭐️⭐️⭐️⭐️⭐️ 더 간결한 방법: 자동 완성 프로퍼티
public int Hp {get; set;} //c# 7.0 부터 사용 가능해졌다 //풀이 private int Hp; public int GetHp() { return Hp; } public int SetHp(int val) { Hp = val; }
⭐️⭐️Delegate ( 대리자) ⭐️⭐️
- 사용 빈도, 중요도 높음!
- 함수 자체를 인자로 넘기는 형식 (함수가 아님): callback방식
- 함수를 수정할 수 없을때 (내가 만든 함수가 아닐때) 도 유용하다
- delegate chaining 가능
delegate int OnClicked(); //delegate 형, 반환: int, 형식의 이름, 입력: void //다시한번...함수가 아니고 형식!!!!!!! static void ButtonPressed(OnClicked clickedFunction) { clickedFunction(); } static int TestDelegate() { Console.WriteLine("test"); } ButtonPressed(TestDelegate); //TestDelegate 는 델리게이트와 같은 입력, 반환 형식이어야 한다.
⭐️⭐️Event⭐️⭐️
- delegate의 문제: 아무나 호출 가능하다 (설계적 문제)
- wrap 하는 방법-> event (구독 신청만 가능하다)
- 예시) 사용자가 'A'키를 눌렀을때 그 정보가 필요한 함수들에게 눌렸다고 알려주는 형식 (정보를 뿌려주는 형태)
- Oberserver 패턴:
class InputManager { public delegate void OnInputKey(); public event OnInputKey InputKey; public void Update() { if (Console.KeyAvailable == false) return; ConsoleKeyInfo info = Console.ReadKey(); if (info.Key == ConsoleKey.A) { InPutKey(); // 구독 신청자에게 모두에게 알려준다. } } } InputManager inputManger = new InputManager(); inputManager.InputKey += OnInputTest; // 구독신청 while (true) { inputManager.Update(); }
Lambda : 일회용 함수
- delegate keyword 를 사용해서 할수 있다 (무명, 익명 함수)
- 람다식이 더 간략
Exception
- try...catch....finally
Reflection
- x-ray: 런타임떄 클래스의 정보를 빼올수 있다
- using Reflection; 추가해야함
Nullable
Bonus: shortcuts and more
- Naming Convention (업계)
- 클래스 인자/필드 들은 클래스 안에서만 쓸떄 _(언더바)나 m_을 앞에 붙여 구분하기도 한다 ex) _value
- 밖에서도 필요한 필드는 대문자 시작으로 : ex) Value
- 인터페이스 클래스: I 를 이 클래스 앞에 붙어 구분하도록 한다 ex) IFlyable
- Shortcuts (Editor)
- cw + tab + tab 으로 Console.WriteLine() 바로 작성 // Console.Write() 은 줄바꿈없이
- Shortcuts (Debugging, Mac)
- Step into: shift + cmd + I / F11 (window)
- Step over: shift + cmd + o
- Run: cmd + enter
- quit run: cmd + shift + enter
- Build: cmd + b
- Go to definition: cmd + o
- Random #
Random rand = new Random(); int randValue = rand.Next(0,3) // 0 에서 2 사이의 랜덤값 int choice = Convert.ToInt32(Console.ReadLine()); //사용자 입력값 int 로 치환해서 저장
- 그 외:
- #region / #endregion: 으로 가독성 높이기
'👩🏻💻 c#' 카테고리의 다른 글
Server (1) 2023.09.01 자료구조와 알고리즘 (0) 2023.08.01 - 4 main types: int, float, string, bool