클래스나 배열은 동적할당을 한다고 배웠다.
즉, 유저가 공간을 빌려달라고 요청하면 그때 그때 임의의 공간이 할당되고, 실행 도중에도 상황에 따라 저장된 위치가 내부적으로 바뀌기도 한다.
동적의 반대의 개념인 정적도 존재한다.
정적은 움직이지 않고 계속 유지된다는 뜻 이다.
static 키워드를 통해
static 필드
static 참조형 필드
static 프로퍼티
static 함수
static 클래스 에 대해 배웠고, 복습용으로 한번 정리를 해보았다.
먼저 static 필드(변수)부터 알아보자
static 필드(변수)
using System;
namespace ToTest
{
class Hunter
{
int _deathCount;
int _healthPoint;
int _money;
}
internal class Program
{
static void Main(string[] args)
{
Hunter hunta1 = new Hunter();
Hunter hunta2 = new Hunter();
Hunter hunta3 = new Hunter();
}
}
}
위의 코드는 static 필드 가 없는 상태이다 .
이상태에선 저장공간이 익히알듯이 스택영역에선 주소값을 가진 변수가 공간을 배정받고, 그 주소값이 가르키는 위치에 있는 힙공간의 저장공간이 있다.
대충 이렇게 되있다 한다.
주소값은 임의로 써둔거라 한다.
아무튼 이게 일반적인 경우이고, static 을 사용하면 좀 묘하게 변한다.
using System;
namespace ToTest
{
internal class Program
{
class Hunter
{
static int _deathCount; //여기 봐주세여!!!
int _healthPoint;
int _money;
}
static void Main(string[] args)
{
Hunter hunta1 = new Hunter();
Hunter hunta2 = new Hunter();
Hunter hunta3 = new Hunter();
}
}
}
위의 Hunter클래스에 static 필드를 선언해줬다.
static 필드의 경우는, 그 정적맴버변수가 속한 클래스에서 만들어진 모든 객체가 하나의 정적 맴버변수를 공유한다.
사용될 수 있는 상황으로는, Hunter 클래스로 만들어진 4명의 플레이어가 한 게임을 진행하는데 다 같이 목숨을 공유한다던가 (몬헌 수레카운트, 로아 데카 등등) 하는 경우에 쓰인다고 한다.
static 필드에 생성될때마다 증가식을 넣어주면 클래스가 호출되어서 생성될때마다 카운트를 세서 몇번 생성되었는지도 알수있게된다.
이 코드는 다시한번 싹 정리해서 만들어본 코드이다.
저렇게 클래스를 정해주고
메인에 쓴다음 실행한 테스트 코드이다.
그리고 우리는 public 를 변수 앞에 쓰면 다른 함수에서 변수값을 가져올수 있었는데, 앞에 스테틱을 선언하면 조금 다르게 불러와야한다.
public int hp;
public static int _createCount;
위와 같이 public 를 선언해줬는데
Console.WriteLine(enemy.hp); //적어보면 문제가 없음
Console.WriteLine(enemy._createCount); //적어보면 에러가 남
여기서는 사용이안된다. 일반 변수는 사용이 가능한데 반해 static 변수는 에러가 났다.
그래서 아래같이 사용을 해줘야한다.
Console.WriteLine(Enemy._createCount);
정적맴버변수는, 해당 클래스로 만들어진 객체가 단 하나도 없더라도 열람 및 수정이 가능하다.
이는 static 맴버변수는 힙과 스택이 아닌 static들만의 공간인 static영역에 저장되어있기 때문이다.
타 언어에선 Data영역이라고도 부른단다.
프로그램이 실행되고 static 관련 맴버변수, 프로퍼티, 메소드등은 최초로 호출되는 순간 이 Data영역에 공간이 확보가 되고 프로그램이 끝날때까지 자리잡고 있다고 들었다.
static 참조형 필드
맴버변수로 static int asd;
이런식으로 만들면 해당 asd를 사용한 그 시점부터 메모리에 항시 상주하게 된다.
그렇다면 참조형 필드는 어떨까?
class Reward
{
int _gold;
public int Gold
{
get { return _gold; }
set { _gold = value; }
}
}
리워드 클래스를 하나 추가해줬다.
에너미 클래스에서 static Reward 을 가지고 있게 되면.
public static Reward rwd;
이런식으로 선언 해줬을 것이다.
어느 Enemy 객체라도 이 rwd 를 사용 가능하고 Enemy로 rwd을 접근 가능긴 하지만 주의할 점이 있다.
static 참조형 Reward rwd 경우, Enemy가 정적으로 보유중인 것은 Reward 의 실체가 아닌 바로가기(주소)일 뿐이다.
에너미가 보유중인 rwd 는 참조형이기에 정적 Reward 가 아닌 정적 Reward 바로가기 였던것이다.
rwd 에 new 할당을 해 주어야 비로소 정상작동 한다.
class Enemy
{
public static Reward rwd;
public Enemy()
{
rwd= new Reward();
}
}
이런식으로 말이다.
하지만 여기도 함정이 있다.
생성자에서 할당을 해준다는것은 클래스를 생성해줄때만 들어오기 때문에 만약 생성안해주고 바로 사용할시에 저 필드를 호출할경우 null값이 호출되며 터진다.
예를들어서 만든 아래 코드들을 보자
Enemy Behemoth = new Enemy();
Enemy.rwd.Gold = 25000;
Console.WriteLine(Enemy.rwd.Gold);
이 코드로 실행할땐 생성자가 호출되어서 저장장소 할당이 되어 정상 작동을 할것이다.
그러나
//Enemy Behemoth = new Enemy();
Enemy.rwd.Gold = 25000;
Console.WriteLine(Enemy.rwd.Gold);
이런식으로 사용할 경우 할당되지 않았기 때문에 바로 터진다.
더 황당한건 이런 경우다.
Enemy Behemoth = new Enemy();
Enemy.rwd.Gold = 18000;
Console.WriteLine(Enemy.rwd.Gold);
Enemy Behemoth2 = new Enemy();
Console.WriteLine(Enemy.rwd.Gold);
이게 뭐가문젠지 처음 봤을땐 몰랐다.
다만 두번째값이 0이 나오길래 자세히 살펴보니 static 필드를 할당할떄 초기화가 일어나는 거였다!
새로운 객체를 하나씩 생성할때마다, 각 객체별로 생성자가 호출되는데 여기서 new Reward()가 있기 때문 이다.
모든 객체가 같은 바로가기를 쓰고 있지만, 생성될때마다 rwd는 새로운 공간의 Reward 공간을 할당받아 기존 바로가기가 가리키던 대상을 갈아치우게 되어있는것 이다.
처음 이걸보고 가끔가다 0이나 잘못된값이 들어갈때 뭔가 했더니 이렇게 잘못쓴 경우에 발생하는 일이였다.
이걸 해결하기 위한 방법이 있다.
public static Reward awd = new Reward ();
바로 이 방법이다.
static 선언및 초기화가 일어나는 줄은 객체가 몇개이든 최초 호출되는 시점에서 단 한번만 불리고 다시 일어나지 않는다.
이를 이용해서 이렇게 선언해주면 static 참조변수를 편리하게 쓸수있게된다!
사실 아무생각없이 저렇게 선언한적도 많았는데 왜 저게 되는지 모르고 쓰고있었던 것이다.
이제와 알았다고 해서 크게 달라질건 없지만 이제 저 코드는 왜 저렇게썼는데? 하는 질문엔 답할수 있게 되었다.
'IT > 학습' 카테고리의 다른 글
Static -3. static 함수 (0) | 2024.12.11 |
---|---|
Static -2 static 프로퍼티 (1) | 2024.12.11 |
클래스Class (0) | 2024.12.10 |
Git 버전관리 (1) | 2024.12.06 |
재귀함수 (0) | 2024.11.29 |