일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Team Fortress 2
- XR Interaction Toolkit
- 정렬
- 그래프
- 다익스트라
- 트리
- DFS
- 재귀
- 그리디 알고리즘
- BFS
- 유니티
- 유니온 파인드
- 누적 합
- 브루트포스
- 다이나믹 프로그래밍
- 백트래킹
- 투 포인터
- 백준
- VR
- 자료구조
- 시뮬레이션
- 수학
- 구현
- Unreal Engine 5
- c++
- 문자열
- 우선순위 큐
- 스택
- 알고리즘
- ue5
- Today
- Total
1일1알
4일차 : 객체지향 여행 본문
객체지향의 시작
절차지향 : 함수의 호출 순서대로 순차적으로 진행되는 프로그램, 유지보수가 어렵다는 단점이 있다.
객체지향 : 객체를 중심으로 진행되는 프로그램
class를 이용하여 객체를 만들 수 있다.
class Knight //객체를 만들기 위한 틀 (붕어빵 틀)
{
public int hp;
public void Move()
{
//move
}
}
Knight myknight1 = new Knight(); //객체1 (붕어빵 1)
Knight myknight2 = new Knight(); //객체2 (붕어빵 2)
만들어진 객체(붕어빵) 은 class (붕어빵 틀) 에 있는 속성들을 가지고 있다.
myknight1.hp = 100; //100의 체력을 가진 기사
myknight2.hp = 150; //150의 체력을 가진 기사
이런 식으로 객체의 속성을 변경할 수 있다.
복사(값)와 참조
struct : class와 비슷하지만, class는 참조로 작동하고 struct는 복사로 작동한다는 큰 차이점이 있다.
struct를 통해 객체를 생성할 때는 new를 사용하지 않아도 된다.
struct Mage
{
public int hp;
}
class Knight
{
public int hp;
}
static void KillMage(Mage mage)
{
mage.hp = 0;
}
static void KillKnight(Knight knight)
{
knight.hp = 0;
}
Mage mage;
mage.hp = 100;
KillMage(mage); // mage.hp=100
Knight knight = new Knight();
knight.hp = 100;
KillKnight(knight); // knight.hp=0
Killmage에서 mage가 복사가 되서 인자로 넘어가기 때문에 원본 mage.hp값은 변하지 않는다.
Killknight에서 Knight는 class 타입이기 때문에 인자가 참조로 넘어가기 때문에, 원본의 값이 변하게 된다.
static void KillMage(ref Mage mage)
{
mage.hp = 0;
}
Mage mage;
mage.hp = 100;
KillMage(ref mage); // mage.hp=0
ref를 이용하면 참조로 넘길 수 있긴 하다.
Mage mage;
mage.hp = 100;
Mage mage2 = mage;
mage2.hp = 0;
// mage.hp=100, mage2.hp=0
이런 식으로 struct를 이용하여 mage2를 만들게 되면, mage가 mage2로 복사가 되고, mage와 mage2는 다른 객체가 된다.
Knight knight = new Knight();
knight.hp = 100;
Knight knight2 = knight;
knight2.hp = 0;
// knight.hp=0, knight2.hp=0
하지만 class의 객체로 같은 작업을 하면 knight객체가 복사가 되는 것이 아니라, knight2가 knight의 주소를 가리키게 되어서 knight와 knight2가 같은 Knight의 객체를 가리키고 있는 상태가 된다. 그렇기 때문에 둘 중 하나의 값을 변경하면 값이 같이 바뀌게 된다.
class Knight
{
public int hp;
public Knight Clone()
{
Knight knight = new Knight();
knight.hp = hp;
return knight;
}
}
똑같은 원소를 가진 객체를 만들고 싶다면, 위의 코드와 같이 클래스 내에서 자신의 멤버변수들을 똑같이 설정한 객체를 반환하는 함수를 만들어주면 된다.
스택과 힙
스택 메모리 : 불완전하고 임시적으로 사용하는 메모리, 용량이 작고 속도가 빠름
힙 메모리 : 동적으로 할당되는 메모리, 용량이 매우 크고 스택 메모리보다는 속도가 느림
struct 타입은 stack에 객체의 원본 그대로 들어가고,
class 타입은 stack에 객체의 주소가 들어가게 되고, 그 주소는 힙 영역에 있는 객체의 원본을 가리킨다.
스택은 함수 단위로 쌓인다. 함수가 호출되면 쌓이고, 종료되면 제거되는 방식으로 동작한다.
c#은 자동으로 메모리가 해제되기 때문에 따로 delete를 해주지 않아도 된다.
생성자
생성자 : 객체의 생성과 동시에 멤버 변수의 값을 할당, class와 같은 이름으로 선언해야 함.
class Knight
{
public Knight()
{
hp = 100;
}
public int hp;
}
Knight knight = new Knight();
knight 객체 생성과 동시에 hp에 100이 들어간다.
class Knight
{
public Knight(int hp)
{
this.hp = hp;
}
public int hp;
}
Knight knight = new Knight(100);
인자를 받는 생성자를 만들어서 hp를 직접 지정할 수 있다.
this.hp는 class에 있는 hp이고, hp는 인자로 받는 hp이다.
class Knight
{
public Knight()
{
attack = 5;
}
public Knight(int hp) : this()
{
this.hp = hp;
}
public int attack;
public int hp;
}
this() 를 이용하여 다른 생성자를 호출할 수 있다.
Knight knight=new Knight(100); 을 통해 인자를 받는 생성자를 호출하면, this를 통해 인자를 받지 않는 생성자가 먼저 호출된 뒤 인자를 받는 생성자가 호출된다.
static
static이 붙지 않았을 때의 필드는 각 객체마다 각각 가지고 있기 때문에 다를 수 있다.
static이 붙으면 class 자체에 종속적인 필드가 된다. class에서 파생된 객체가 모두 공유한다.
static 함수에서는 static 변수만 접근할 수 있다.
static 함수는 class이름.함수이름 으로 호출할 수 있다.
상속성
상속 : 여러 개의 클래스에 공통된 필드가 있을 때, 같은 필드들을 묶어서 부모 클래스로 만들고, 그것을 상속받은 자식 클래스는 부모 클래스의 필드 외에 필요한 필드들을 추가해서 중복되는 공통되는 필드들을 없앤다.
class Player //부모 클래스
{
public int id;
public int hp;
public int attack;
}
class Mage : Player //자식 클래스
{
public int mana;
}
class Knight : Player //자식 클래스
{
public int shield;
}
Mage는 Player을 상속받아서 id, hp, attack, mana 의 필드를 가지고 있다.
Knight도 Player을 상속받아서 id, hp, attack, shield 의 필드를 가지고 있다.
자식 클래스의 생성자를 호출하면 부모 클래스의 생성자가 먼저 호출된다.
base를 이용하여 호출되는 부모 클래스의 생성자의 버전을 정할 수 있다.
은닉성
은닉성 : 정보를 숨겨서 외부에서 맘대로 수정할 수 없게 한다. 중요한 정보를 숨길 수 있다.
접근 한정자
public : 어디서든 접근할 수 있음
protected : 자식 클래스에서만 접근할 수 있음
private : 접근 불가
접근 한정자를 쓰지 않았을 때에는 기본으로 private가 적용됨
클래스 형식 변환
부모 클래스를 인자로 받는 함수에 자식 클래스를 인자로 넘길 수 있다.
자식 -> 부모 타입 변환 가능
부모 -> 자식 타입 변환 불가 (명시적 형변환은 가능 but 위험하다) (Knight -> Player)로 변환한 것을 (Mage)player 라고 명시적 형변환을 하면 문법에 오류는 없지만 프로그램을 실행하면 크래시가 난다.
static void EnterGame(Player player)
{
bool isKnight = (player is Knight);
if (isKnight)
{
Knight knight = (Knight)player;
knight.shield = 10;
}
}
이것을 방지하는 첫 번째 방법은 is 문법을 사용해서 player가 Knight 타입인지 확인하고 맞다면 명시적으로 형변환을 해서 위험을 없앨 수 있다.
static void EnterGame(Player player)
{
Knight knight = (player as Knight);
if (knight != null)
{
knight.shield = 10;
}
}
두 번째 방법은 as 문법을 사용하는 것이다. player가 Knight 타입이라면 knight에 (Knight)player이 할당이 되고 아니라면 null이 할당된다. 이렇게 두 가지 방법으로 부모 -> 자식 형변환을 할 수 있다.
대부분의 경우에서 as를 사용한다.
다형성
class Player
{
public int id;
public int hp;
public int attack;
public void Move()
{
Console.WriteLine("Player 이동");
}
}
class Knight : Player
{
public int shield;
public void Move()
{
Console.WriteLine("Knight 이동");
}
}
static void EnterGame(Player player)
{
player.Move(); //Player 이동
}
Knight myKnight = new Knight();
myKnight.Move(); //Knight 이동
EnterGame(myKnight);
위 코드에서 문제는 myKnight는 Knight 타입이지만 EnterGame에서 Player 타입으로 인자로 들어가기 때문에
EnterGame 안에서 Move함수를 호출하면 Player 클래스의 Move 함수가 호출된다.
class Player
{
public int id;
public int hp;
public int attack;
public virtual void Move()
{
Console.WriteLine("Player 이동");
}
}
class Knight : Player
{
public int shield;
public override void Move()
{
Console.WriteLine("Knight 이동");
}
}
virtual 키워드를 붙여서 가상 함수를 만들 수 있다. 만들어진 가상 함수를 오버라이딩 할때는 override 키워드를 붙이면 된다. 이렇게 수정하고 EnterGame에서 Move를 호출하면 Knight 클래스의 Move 함수가 호출된다.
Move 함수가 가상함수가 아닐 때에는 타입을 컴파일 시간에 정하기 때문에 인자로 넘어온 player의 타입을 따로 검사하지 않고 넘어온 타입의 Move함수를 호출한다. EnterGame에 넘어온 타입은 Player 타입이기 때문에 Player 클래스의 Move가 호출된다.
Move 함수가 가상함수 일 때는 타입을 실행 시간에 검사하게 되고, 그 타입에 맞는 함수를 호출하게 된다.
EnterGame에서 Knight 타입의 객체가 Player 타입으로 넘어왔지만, 본질적으로 Knight 타입이기 때문에 Knight 클래스의 Move함수가 호출된다.
오버라이딩 : 타입에 따라 다르게 작동하도록 부모 클래스의 함수를 자식 클래스에서 재정의하는 것?
sealed 키워드를 붙이면 그 함수는 더 이상 오버라이딩 할 수 없다.
virtual을 붙이면 성능이 조금 떨어지기 때문에 남용하는 것은 좋지 않은 선택이다.
문자열 둘러보기
string name = "Harry Potter";
//name에 해당 문자열이 있는지 검사, bool타입 반환
bool found = name.Contains("Harry");
//해당 문자가 name의 몇번째 index에 있는지 검사, int타입 반환, 없으면 -1 반환
int index = name.IndexOf("P");
//문자열 추가
name = name + " Junior";
//소문자로 변환
string lowerName = name.ToLower();
//대문자로 변환
string upperName = name.ToUpper();
//name의 첫번째 문자를 두번째 문자로 전부 바꿈
string newName = name.Replace('r', 'l');
// name을 ''에 들어오는 문자를 기준으로 분할, 문자열 배열로 반환
string[] names = name.Split(new char[] { ' ' });
// name의 ()에 오는 숫자의 인덱스부터 시작하는 문자열 반환
string subStringName = name.Substring(5);
'스터디 > C#' 카테고리의 다른 글
알아두면 유용한 기타 문법 (1) | 2021.11.09 |
---|---|
자료구조 맛보기 (1) | 2021.11.08 |
2일차 : 코드의 흐름 제어 (1) | 2021.11.02 |
1일차 : 데이터 갖고 놀기 (1) | 2021.11.01 |