[C#] 4.1 구조체

C#에서는 int 형식이나 bool 형식과 같은 모든 단순 형식들도 구조체입니다. 그리고 다른 많은 언어처럼 여러 멤버를 갖는 사용자 정의 구조체를 만들 수 있습니다.

4.1.1 정수

C#에서는 표현할 수의 범위에 따라 여러 종류의 정수 형식을 제공하고 있습니다.

대부분 키워드가 C언어나 C++언어와 같고 할당되는 메모리 크기와 표현 범위가 비슷합니다. 이러한 이유로 무의식적으로 C#에서도 할당되는 메모리 크기나 표현 범위가 같다고 생각하면서 프로그래밍할 수 있고 이 때문에 논리적 오류를 갖게 될 수 있습니다. C#에서 char는 C언어와 C++언어와 다르게 2바이트가 할당되며 유니코드로 표현할 수 있습니다. 예를 들어, C언어에서는 ASCII 코드로 표현하기 때문에 한글의 한 글자를 하나의 문자로 표현하지 못하고 문자열로 표현해야 했지만, C#에서는 유니코드로 표현하므로 하나의 문자로 표현할 수 있습니다. 그리고 long 형식은 C언어에서 4바이트였지만 C#에서는 8바이트(64비트)를 할당받아 int 형식보다 넓은 범위의 수를 표현할 수 있습니다.

형식메모리 크기표현 범위
sbyte1바이트-128~127
byte1바이트0~255
char2바이트0~65535(유니코드)
short2바이트-32768~32767
ushort2바이트0~65335
int4바이트-2147483648~2147483647
uint4바이트0~4294967295
long8바이트-9223372036854775808~9223373036854775807
ulong8바이트0 ~ 18446744073709551615

int 와 같은 기본 형식이 구조체라는 사실은 변수나 형식을 통해 보관된 값 이외에도 사용할 수 있는 멤버들이 있다는 것에서 확인할 수 있습니다.

int max = int.MaxValue;
int min = int.MinValue;
Console.WriteLine(max.ToString());
Console.WriteLine(min.ToString());

일반적으로 가장 많이 사용하게 될 멤버로는 최종 사용자가 입력한 값을 기본 형식으로 변환할 때 사용하는 정적 멤버 메서드인 Parse와 TryParse입니다. Parse와 TryParse 메서드는 사용하는 목적에 따라 입력 인자가 다르게 중복 정의(overload)되어 있습니다. 다음은 중복 정의되어 있는 목록 중에 하나입니다.

public static int Parse (string s);
public static bool TryParse (string s,out int result);

Parse 메서드는 문자열을 입력인자로 받아 문자열을 정수로 변환하여 반환합니다. 만약, 변환할 수 없는 문자가 포함되어 있을 때는 예외를 발생합니다. TryParse 메서드는 첫 번째 입력 인자로 전달된 문자열을 정수로 변환하여 output 전달 형식의 두 번째 매개 변수에 대입해 줍니다. 만약, 정상적으로 변환되면 true를 반환하고 변환할 수 없을 때는 false를 반환합니다.

int i1 = int.Parse("-123");
Console.WriteLine(i1.ToString());

int i2 = int.Parse("123");
Console.WriteLine(i2.ToString());

//int i3 = int.Parse("a-123"); 변환할 수 없는 문자가 있기 때문에 예외가 발생합니다.
//Console.WriteLine(i3.ToString());

//int i4 = int.Parse("123a"); 변환할 수 없는 문자가 있기 때문에 예외가 발생합니다.
//Console.WriteLine(i4.ToString());

만약, 다음과 같이 최종 사용자로부터 수를 입력받는 구문이 있다면 변환할 수 없는 문자가 포함된 문자열을 입력하면 예외가 발생합니다. 개발자가 테스트할 때 잘못 입력할 때를 생각하지 못하면 버그가 있는 프로그램을 만들게 됩니다.

Console.WriteLine("수를 입력하세요.");
int num = int.Parse(Console.ReadLine());
Console.WriteLine("입력한 수는 {0}입니다.", num);

이럴 때 개발자는 Parse 메서드를 대신하여 TryParse 메서드를 이용할 수 있습니다.

Console.WriteLine("수를 입력하세요.");
int num;
if (int.TryParse(Console.ReadLine(), out num))
{
    Console.WriteLine("입력한 수는 {0}입니다.", num);
}
else
{
    Console.WriteLine("잘못된 수를 입력하였습니다..");
}

char 형식은 문자 리터럴 상수 표현을 이용할 수 있습니다. 이는 C언어나 C++언어와 마찬가지로 표현할 문자를 콤마로 감싸 표현하는 것입니다. 앞에서 얘기했듯이 C#언어는 유니코드로 표현하므로 한글도 하나의 문자로 표현할 수 있습니다.

char c = '가';
Console.WriteLine(c.ToString());

그리고 char 형식은 묵시적으로 정수 형식과 형식 변환을 제공하고 있습니다.

int cval = c;
Console.WriteLine(cval.ToString());

4.1.2 부동 소수점 형식

C#에서는 실수를 표현하기 위해 float과 double 형식을 제공하고 있습니다. 그런데 실수는 0.1에서 0.2 사이에도 무한개의 실수가 존재하기 때문에 컴퓨터 프로그램에서는 정확한 수치를 저장하지 못하고 근사치를 보관합니다.

형식메모리 크기표현 범위
float4바이트±1.5e−45 ~ ±3.4e38
double8바이트±5.0e−324 ~ ±1.7e308

예를 들어, 0.0으로 초기화된 변수에 0.1씩 더해나가면 10번 반복했을 때 1.0이 될 것으로 생각할 수 있습니다. 하지만, 정확한 수치가 아닌 근사치이기 때문에 다음의 코드를 수행하면 반복문을 탈출하지 못합니다.

double d = 0.0;
while (d != 1.0) //무한 루프 - 0.1을 정확히 표현하지 못하기 때문에 1.0이 되지 못함
{
    Console.WriteLine(d.ToString());
    d = d + 0.1;
}

4.1.3 decimal

C#에서는 10진수 표현에 적합한 decimal 형식을 제공하고 있습니다. decimal도 근사치를 표현하지만 오차 범위내에서 10진수로 실수 표현을 할 때 효과적으로 프로그래밍할 수 있습니다. double 형식을 사용했을 때 무한루프에 빠졌던 예제 코드를 decimal 형식으로 바꾸면 원하는 결과를 얻을 수 있음을 알 수 있습니다.

decimal d = 0.0m;
while (d != 1.0m)
{
    Console.WriteLine(d.ToString());
    d = d + 0.1m;
}

10진수로 오차 범위(96비트 정수, 소수 자리수 10의 28)내에서 표현하면 부동 소수점 표현보다 정확하게 표현할 수 있습니다.

또한, C#에서는 수를 표현하는 다양한 형식들에 대해 표현 범위가 좁은 형식을 범위가 넓은 형식으로 암시적 형식 변환을 대부분 지원합니다.

형식변환 형식
sbyteshort, int, long, float, double, decimal
byteshort, ushort, int, uint, long, ulong, float, double, decimal
charushort, int, uint, long, ulong, float, double, decimal
shortint, long, float, double, decimal
ushortint, uint, long, ulong, float, double, decimal
intlong, float, double, decimal
uintlong, ulong, float, double, decimal
longfloat, double, decimal
ulongfloat, double, decimal
floatdouble

4.1.4 bool

C#에서는 true와 false를 값으로 표현할 수 있는 bool 형식을 제공하고 있습니다.

4.1.5 사용자 정의 구조체

C#에서는 사용자에 의해 프로그램에 필요한 형식을 정의할 수 있게 구조체와 열거형 및 클래스를 지원하고 있습니다. 특히, 구조체와 클래스는 여러 개의 멤버들을 하나의 형식으로 캡슐화하여 목적에 맞게 정의할 수 있으며 많은 부분에서 비슷한 문법을 제공합니다. 이 책에서는 구조체에 대한 문법 사항을 상세하게 얘기하지 않고 클래스와 공통적인 문법 사항을 OOP의 특징별로 설명하고 있습니다.

구조체를 정의하여 사용하는 방법은 클래스와 함께 다음 장부터 자세히 다루겠습니다.