C# 기초 - 데이터와 연산

이도희·2022년 6월 6일
0

C#

목록 보기
1/9

데이터 타입

변수는 다양한 데이터 타입을 가지고 있다. 타입에 따라 크기가 다르기에 프로그램의 메모리와 직접적인 연관이 있다. 따라서 타입 결정을 신중히 할 필요가 있다. 특히, 네트워크 통신이 필요한 데이터의 경우 최대한 용량을 아껴서 네트워크 부하를 줄일 필요가 있다.

선언

타입이름 변수이름

// ex
int hp;
string name;

* 정수형 타입

정수형 타입은 크기에 따라 다음으로 나뉜다.

  • byte (1byte: 0~255)
  • short (2byte: -3만~3만)
  • int (4byte: -21억~21억)
  • long (8byte)
  • sbyte (signed, -128~127)
  • ushort (unsigned, 0~6만)
  • uint (0~43억)
  • ulong
int hp;
short level;
long id;

hp = 100;
level = 10;

Console.WriteLine("Number {0}", hp); // 출력

> 일반적으로 대부분의 경우 int type을 많이 사용한다.
> long type은 주로 id를 정의할 때 사용한다.

👉 유지 보수 측면에서 굉장히 오래 관리된 프로그램, 게임 등의 경우 유저 id의 수가 많이 생성되었을 것이다. int로 id가 정의되었다면 21억명이 넘는 id가 생성되었을 때 큰 문제를 직면할 수 있다. 그래서 일반적으로 id의 경우 long으로 정의한다.

* 실수형 타입

소수를 표현하기 위해서는 정수형을 나타내는 것과 내부적으로 다른 계산을 사용한다. (연산이 비싼편이므로 잘 고려해서 사용해야한다.)

  • float(4 byte)
  • double(8 byte)
float f = 3.14f; // 기본적으로 소수를 double로 인식하므로, f를 붙여 4 byte임을 명시한다.
double d = 3.14;

* 문자형 타입

char(2 byte) -> 하나의 문자 표현
string(character의 집합) -> 문자열

char c = 'a';
string str = "Hello World!"; // 더블 quote(") 사용

* boolean 타입

true / false를 나타내는 형식 (1 byte)

👉 참과 거짓은 1 bit로 표현이 가능한데 1 byte를 사용하는 이유는 컴퓨터 연산시 1 byte로 처리하는 것이 더 빠르기 때문이다.

* var 타입

var은 명시적으로 타입을 알려주지 않고 컴파일러가 타입을 찾도록 맡기는 것이다.
(하지만, 읽는 사람 입장에서는 변수가 많이 선언되어 있을 때 명확히 읽히지 않을 수 있다는 단점이 있어서 남용하지는 않는 것이 좋을 것이다.)

다음과 같이 프로그램을 작성하고 변수명에 마우스를 올려두면 각각 int, string 타입이라고 나오는 것을 확인할 수 있다.

var num = 3;
var num2 = "Hello World";

형식 변환

특정 타입으로 저장되어 있는 데이터를 다른 타입으로 변환하는 것을 몇가지 케이스로 나누어 보고자 한다.

1. 데이터의 크기가 다른 경우

먼저, 데이터 크기가 더 큰 타입을 작은 데이터 크기를 가지는 타입으로 변환하는 경우이다. 이 경우 일부 데이터가 분실되는 경우가 생길 수 있다. 따라서, casting을 통해 명시적 변환을 한다.

  • casting: 위험한 일인줄 알지만, 필요하기 때문에 컴퓨터에게 큰 데이터 타입을 작은 데이터 타입쪽으로 대입 요청
int a = 0x0FFFFFFF;
short b = (short) a; // -1, 0x0FFF FFFF 

위의 예제를 살펴보면 short 타입은 2 byte만 저장할 수 있다. 그래서, 뒤만 저장하게 되면서 0xFFFF 값인 -1이 결과로 출력된다. 이는 처음 a가 의도했던 값과 다르다는 것을 알 수 있다. 따라서, casting을 할 때는 굉장히 유의할 필요가 있다.

하지만, 작은 데이터 크기를 가지는 타입에서 큰 쪽으로 넘어가는 것은 큰 문제가 되지 않는다. 따라서, 이 경우는 따로 casting이 필요하지 않다.

short c = 100;
int d = c;

2. 데이터의 크기가 같지만, 부호가 다른 경우

(해당 부분은 컴퓨터에서의 정수, 실수, 진법 등의 개념을 알아야 쭉 읽히는 내용이라 추후 자세히 다루고자 한다.)

byte b = 255;
sbyte sb = (sbyte) b; // -1	

0xFF = 0b11111111
sbyte의 측면에서 보면, -1이다. 즉, 같은 데이터에 대해 서로 인식하는 숫자가 다르기 때문에 이러한 결과 값을 보이게 된다.

  • overflow: 메모리 표현 범위를 초과하는 수를 저장할 때 발생
  • underflow: 메모리 표현 범위보다 작은 수를 저장할 때 발생

+) 예전에 게임 중에서 간디 버그라는 것이 발생했다. 이는 간디 캐릭터에 대해 난폭한 정도를 굉장히 낮은 수치를 설정했는데, 난폭한 정도를 감소시키면서 문제가 발생한 사건이다. (0에서 -1을 시키면서 underflow가 발생해서 255 값이 되어버려 간디 캐릭터가 굉장히 난폭해졌던 사건이다.)

3. 소수 변환

소수의 경우 인접한 값을 표현하는 경우가 많다.
double 타입과 float 타입 간의 casting은 정확히 일치하지 않는 경우가 대부분이다.

👉 따라서, 소수끼리 변화할 경우 정확히 일치하는 지 확인할 것이 아니라 오차를 두고 비교를 해야 예측하는 일반적인 결과가 나올 수 있다! (즉, 값 비교 연산 등을 진행할 때 [float 변수 == 3.14 (소수)] 으로 확인 시도를 하면 해당 조건에 거의 걸리지 않는다고 보면 된다.)

float f = 3.1414f;
double d = f;

이건 컴퓨터가 소수를 표현하는 방식이 정수와 다르기 때문인데, 해당 부분은 추후 다른 포스트에서 자세히 다뤄보고자 한다.

데이터 연산

1. 산술 연산

+, -, *, /, %
(연산자 우선순위가 적용된다.)

2. 증감 연산

++,--

다음과 같이 증감 연산자의 위치에 따라 실행되는 시점의 차이가 발생한다. 앞의 두 케이스는 먼저 +1을 실행하는 것이고, 뒤의 케이스들은 해당 줄이 끝난 다음 +1을 실행한다. 즉, 시점의 차이는 있으나 결과는 같음을 알 수 있다!

// 먼저 +1, -1
++hp
--hp
// 끝나고 +1, -1
hp++
hp--

3. 비교 연산

<, <=, >, >=, ==, !=

int hp = 100;
bool isAlive = (hp > 0); // 오른쪽 연산 결과를 boolean type 변수에 저장

int level = 50;
bool isHighLevel = (level >= 40);

4. 논리 연산

비트 연산자랑 유사한 작동을 하는 연산자들이 있어 헷갈릴 수 있지만, 논리 연산자는 boolean type의 결과를 가지고, 비트 연산자는 비트에 대한 연산임을 기억하자!

  • &&(and)
  • ||(or)
  • !(not)
bool check = isAlive && isHighLevel; // check =  살아있는 고렙 유저인가요? (둘다 참) -> AND
bool check2 = isAlive || isHighLevel; // 살아있거나, 고렙 유저이거나, 둘 중 하나인가요? (둘 중 하나만 참) ->  OR
bool check3 = !isAlive; // 죽은 유저인가요? (<-> 살아있음) -> NOT

5. 비트 연산

비트 연산자의 종류는 다음과 같다.

  • << (left shift): 모든 비트 한칸씩 왼쪽으로 이동
  • >> (right shift): 모든 비트 한칸씩 오른쪽으로 이동
  • & (and): 둘다 1인 경우만 1
  • | (or): 1이 하나라도 있으면 1
  • ^ (xor): 두 비트가 같으면 1, 다르면 0
  • ~ (not) : 1과 0을 서로 뒤집음

👉 주의할 점은 음수의 경우 unsigned로 생각하고 한 칸씩 이동 후 부호를 붙이는 방식으로 연산을 해야 한다! (즉, 부호를 살려야한다.)

❓ 비트 연산자는 실제 게임 프로그래밍에서 언제 사용이 될까?

=> 비트 연산자는 id를 만들 때 사용될 수 있다!

int형으로 id를 만든다고 생각해보자. id의 경우 유저나 아이템, 몬스터 등 다양한 것들에 부여될 수 있다. 그리고 int는 32bit로 굉장히 널널한 편이다. 그래서, 실제 id를 부여할 때는 상위 4 bit는 user, item 등 type 종류, 그 다음 4 bit에는 위치한 지역 등등 다양한 값으로 사용할 수 있다.

여기서 첫 4 bit에 값을 부여한다. 3번이 NPC type인 경우 0011을 작성한 후 shift 연산을 사용해 가장 최상위 비트쪽으로 민다. 이러한 방식으로 조립해서 id를 만들 수 있다!

❗ xor의 사용
xor은 암호학에서 자주 사용된다. (정보 보호론에서 키를 다룰 때 나오는 내용..!)

int id = 123;
int key = 401;
int a = id^key; // 490
int b = a^key; // 123 (id가 그대로 나온 것을 확인할 수 있다.)

다음과 같이 xor을 2번 연산하게 되면 같은 값이 나오게 되는 것을 확인할 수 있다.
id의 경우 서버와 클라이언트에서 모두 알아야하는 값이다. 이걸 네트워크에 그냥 전송해버리면 해킹의 위험이 있다! 그래서, 키를 이용해서 암호화한다음 보내면 받은 사람은 공유된 동일한 키를 대입하여 해당 값을 찾게 된다.

할당(대입) 연산

할당 연산자로는 '='이 사용되며 오른쪽의 값을 왼쪽에 대입한다는 의미를 가진다.

a = a + 1 
a += 1

b = b - 1
b -= 1

c = c & 1
c &= 1

다음과 같이 산술연산자와 할당 연산자를 간소화하여 작성할 수 있다. (비트 연산자 등에도 적용할 수 있다!) 연산의 경우 복합적으로 사용하는 경우가 자주 있기에 괄호로 우선순위를 명시적으로 표시해주는 것이 좋다. (아무래도 모든 연산자 우선순위를 외우고 있기에는 힘들기 때문!)

profile
하나씩 심어 나가는 개발 농장🥕 (블로그 이전중)

0개의 댓글