## 목차
1) 다양한 데이터의 종류
2) 변수
3) 값 형식과 참조형식
4) 기본 데이터 형식
#1 다양한 데이터의 종류
C#은 기본 데이터 형식을 부품으로 삼아 구성되는 '복합 데이터 형식'을 지원합니다.
복합 데이터 형식의 종류에는 구조체, 클래스, 배열 등이 있습니다.
기본 데이터 형식은 기본 데이터 형식과 복합 데이터 형식으로 분류하는 동시에 값 형식과 참조 형식으로도 분류 할 수 있습니다.
#2 변수
변수의 의미를 크게 두 가지로 나눌 수 있습니다.
코드에서 보면, 값을 대입시켜 변화시킬 수 있는 요소
메모리에서 보면, 데이터를 담는 일정 크기의 공간
변수를 선언 할 때 컴파일러에게 선언 합니다. = (이 의미는 즉슨) 컴파일러에게 "이 변수에 필요한 메모리 공간을예약해줘" 라고 알리는 것입니다.
(데이터 형식)int x(식별자-변수의 이름);
선언된 변수 x에 연산자를 통해 데이터를 입력 할 수 있습니다.
(식별자)x =(대입 연산자) 100(데이터);
선언과 데이터 할당을 동시에 할 수 있습니다.
int x; // 선언과
x = 100; // 데이터 할당을 별도로 할 수도 있지만
int x = 100; // 선언과 초기화를 한 번에 할 수도 있습니다.
변수 여러 개를 동시에 선언 할 때, 변수들은 데이터 형식이 같아야 하며 각 식별자를 콤마(,)로 구분해줘야 합니다.
int a, b, c; // 같은 형식의 변수들은 동시에 선언 할 수 있습니다.
int x = 30, y = 40, z = 50 // 선언과 초기화를 한 번에 하는 것도 여전히 가능합니다.
#3 값 형식과 참조 형식
값 형식은 변수가 값을 담는 데이터 형식을 말하고, 참조 형식은 변수가 값 대신 값이 있는 곳의 위치(참조)를 담는 데이터 형식을 말합니다.
값 형식을 이해하려면 두 메모리 영역에 대해서 알고 있어야 합니다.
스택 : 값 형식과 관련이 있는 것
힙 : 참조 형식과 관련이 있는 것
1. 스택과 값 형식
스택은 마치 게으른 프로그래머의 책상 위에 쌓여 있는 책 더미와 같은 구조로 생긴 메모리 입니다.
{ // 코드 블록 시작
int a = 100;
int b = 200;
int c = 300;
} // 코드 블록 끝
위와 같이 선언된 변수 a,b,c는 차례대로 스택에 쌓였다가 코드 블록이 끝나면서 스택에서 걷혀 제거됩니다.
2. 힙과 참조 형식
힙은 저장된 데이터를 스스로 제거하는 메커니즘을 갖고 있지 않습니다.
그 대신, 청소부를 따로 고용하고 있는데 CLR의 가비지 컬렉터가 바로 그것 입니다.
가비지 컬렉터는 프로그램 뒤에 숨어 동작하면서 힙에 더 이상 사용하지 않는 객체가 있으면 그 객체를 쓰레기로 간주하고 수고하는 기능을 합니다.
굳이 가비지 컬렉터가 필요한 힙 영역을 사용하는 이유는?
스택에 쌓인 데이터들은 자신이 태어났던 고향이 사라지는 시점에 함께 제거됩니다. 코드 블록이 끝나는 시점과 상관없이 데이터를 유지하고 싶을 때는 스택의 구조가 발목을 잡는 것입니다. 그래서 프로그래머가 원한다면 언제까지라도 데이터를 살릴 수 있는 또 다른 메모리 영역을 CLR이 제공하는 것입니다.
#4 기본 데이터 형식
기본 데이터 형식에는 모두 15가지가 있는데, 이들은 크게 참조 형식에 문자열 형식과 오브젝트 형식, 그 외에(숫자 형식, 논리 형식 등)는 모두 값 형식 입니다.
1. 숫자 데이터 형식
C#은 15가지 기본 데이터 형식 중 12가지를 숫자 데이터 형식으로 제공합니다.
이 12가지 형식은 다시 정수 계열, 부동 소수 계열, 소수 계열 이렇게 3가지로 나뉩니다.
정수 계열 형식
정수 데이터를 담기 위해 사용합니다.
12가지의 숫자 형식 중 9가지가 정수 계열 형식입니다.
9가지나 되는 정수 형식은 각각 크기와 담을 수 있는 데이터의 범위가 다른데, 프로그래머가 코드에 사용될 데이터가 어느 정도의 범위에 있는지 판단한 뒤 적절한 데이터 형식을 선택함으로써 메모리를 효율적으로 사용 할 수 있습니다.
2진수, 10진수, 16진수 리터럴
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IntegerLiterals
{
internal class MainApp
{
static void Main(string[] args)
{
byte a = 240; // 10진수 리터럴
Console.WriteLine($"a={a}");
byte b = 0b1111_0000; // 2진수 리터럴
Console.WriteLine($"b={b}");
byte c = 0XF0; // 16진수 리터럴
Console.WriteLine($"c={c}");
uint d = 0x1234_abcd; // 16진수 리터럴
Console.WriteLine($"d={d}");
}
}
}
실행 결과 :
부호 있는 정수와 부호 없는 정수
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignedUnsigned
{
internal class MainApp
{
static void Main(string[] args)
{
byte a = 255;
sbyte b = (sbyte)a;
Console.WriteLine(a);
Console.WriteLine(b);
}
}
}
실행 결과 :
데이터가 넘쳐 흘러요
변수에 데이터 형식의 크기를 넘어선 값을 담으면 넘치게 되는데 이런 현상을 오버플로(Overflow)라고 합니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Overflow
{
internal class MainApp
{
static void Main(string[] args)
{
uint a = uint.MaxValue;
Console.WriteLine(a);
a = a + 1;
Console.WriteLine(a);
}
}
}
실행 결과 :
왜 이러한 현상이 발생되는지, 정수형 byte로 이해해보겠습니다.
byte의 최대값은 255 입니다. 255는 2진수로 바꾸면 1111 1111 입니다. 2진수 1111 1111에 1을 더하면 1 0000 0000 이 됩니다. 하지만 byte는 1바이트 즉, 8개 비트만 담을 수 있으므로 넘쳐 흐른 왼쪽의 비트는 버리고 오른쪽 8개 비트만 보관합니다. 그래서 최대값을 가진 byte 형식 변수가 오버플로되면 '0'이 되는 것입니다. 다른 모든 정수 계열 형식에 똑같이 적용되는 예시입니다.
각 데이터 형식의 최대값을 넘어가는 데이터를 저장할 때는 오버플로가 일어나지만, 최저값보다 작은 데이터를 저장하면 언더플로(Under)가 일어납니다.
2. 부동 소수점 형식
부동 소수점이라는 이름은 소수점이 고정되어 있지 않고 움직이면서 수를 표현한다는 뜻에서 지어진 이름입니다.
소수점을 가만두지 않고 움직이는 이유는 소수점을 이동해 수를 표현하면 고정했을 때보다 더 제한된 비트를 이용해서 훨씬 넓은 범위의 값을 표현할 수 있기 때문입니다.
또한, 부동 소수점 형식은 정수뿐 아니라 유리수를 포함하는 실수 영역의 데이터를 다룹니다. 정수가 아닌 3.14, 11.08과 같은 소수를 다뤄야 할 때는 바로 이 부동 소수점 형식을 이용하면 됩니다.
그렇다면 정수 형식을 쓰지 않고 부동 소수점 형식만 사용하면 되지 않을까? 라고 저도 생각했었습니다.
하지만 다음 두 가지 이유에서 정수 형식을 대체하지 못합니다.
1. 부동 소수점 형식은 소수점을 표현하기 위해 일부 비트를 사용하기 때문에 (게다가 부호도 표현해야 합니다.) 같은 크기의 정슈 계열 형식과 같은 크기의 수를 표현할 수 없습니다.
2. 부동 소수점 형식은 산술 연산 과정이 정수 계열 형식보다 복잡해서 느립니다.
이러한 이유로 숫자 데이터를 다룬다고 무조건 부동 소수점 형식을 가져다 쓰는 것은 현명하지 못한 생각입니다.
데이터 형식 | 설명 | 크기(바이트) | 범위 |
float | 단일 정밀도 부동 소수점 형식 (7개의 자릿수만 다룰 수 있음) |
4(32비트) | -3.402823e38 ~ 3.402823e38 |
double | 복수 정밀도 부동 소수점 형식 (15~16개의 자릿수를 다룰 수 있음) |
8(64비트) | -1.79769313486232e308 ~ 1.79769313486232e308 |
decimal | 29자리 데이터를 표현할 수 있는 소수 형식 | 16(128비트) | ±1.0 X 10e - 28~ ±7.9 X 10e28 |
C#의 float와 double은 IEEE754라는 표준 알고리즘에 기반한 데이터 형식입니다.
IEEE754에 따르면 4바이트(32비트) 크기의 float 형식은 수를 표현할 때 1비트를 부호 전용으로 사용하고, 가수부 23비트를수를 표현하는데 사용하고, 나머지 지수부 8비트를 소수점의 위치를 나타내기 위해 사용합니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.FloatingPoint
{
class FloatingPoint
{
static void Main(string[] args)
{
float a = 3.1415_9265_3589_7932_3846f;
Console.WriteLine(a);
double b = 3.1415_9265_3589_7932_3846;
Console.WriteLine(b);
}
}
}
입력한 값과 출력된 값이 다르게 나왔는데 그 이유는 a와 b가 각각 자신이 가수부가 담을 수 있는 부분까지만 저장하고 나머지는 버렸기 때문입니다.
decimal 형식
decimal도 실수를 다루는 데이터 형식입니다. 다만 앞에서 살펴본 부동 소수점과는 다른방식으로 소수를 다루며 정밀도가 훨씬 높습니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.FloatingPoint
{
class Decimal
{
static void Main(string[] args)
{
float a = 3.1415_9265_3589_7932_3846_2643_3832_79f;
double b = 3.1415_9265_3589_7932_3846_2643_3832_79;
decimal c = 3.1415_9265_3589_7932_3846_2643_3832_79m;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
}
}
decimal 형식도 표현 범위의 한계가 있어 다 담지는 못했지만, 훨씬 더 높은 수준의 정밀도를 보여줍니다.
3. 문자 형식과 문자열 형식
char형식은 정수를 다루는 데이터 형식 출신이지만, 수가 아닌 '가', '나', '다', '라', 'a', 'b', 'c'와 같은 문자 데이터를 다룹니다.
char형식 변수에 데이터를 담는 방법도 다른 정수 계열 형식과는 다르게 따옴표(' ')로 문자를 감싸줘야 합니다.
'안', '녕', '하', '세', '요' char 형식의 5개 문자 여러개를 하나의 실로 주르륵 묶어 처리하는 방식이 string 입니다.
string 형식은 정해진 크기나 담을 수 있는 데이터의 범위가 따로 정해져 있지 않습니다. 변수가 담는 텍스트의 양에 따라 그 크기가 달라지기 때문입니다.
string 형식의 변수는 문자열 데이터를 큰따옴표(" ")로 묶어 담습니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.String
{
class StringExam
{
static void Main(string[] args)
{
string a = "안녕하세요?";
string b = "반갑습니다!";
Console.WriteLine(a);
Console.WriteLine(b);
}
}
}
char 형식은 개별 문자를 표현하기 위해, string 형식은 문자열을 표현하기 위해 사용합니다.
문자열 하나에 여러 줄을 담으려면 어떻게 해야 할까요?
다음과 같이 실습에서 확인해보겠습니다.
[실습]
using System;
namespace ThisIsCSharpExam.StringExam
{
class MultiLineExam
{
static void Main(string[] args)
{
string multiline = @"
별 하나에 추억과
별 하나에 사랑과
별 하나에 쓸쓸함과
별 하나에 동경과
별 하나에 시와
별 하나에 어머니, 어머니
";
Console.WriteLine(multiline);
}
}
}
책에는 큰따옴표 세 개 붙여 쓰면("""...""") 된다고 나와있습니다.
그러나 다중 행 문자열 리터럴 구문은 C# 9.0 버전 이상에서만 지원됩니다. 그러나 현재 사용 중인 .NET 버전이 C# 9.0 이하인 경우 이 구문을 사용하면 컴파일 오류가 발생합니다.
여기서 @"와 " 사이에 있는 문자열은 여러 줄을 포함할 수 있습니다. 이 구문은 줄 바꿈 문자 \n과 같은 이스케이프 시퀀스를 사용하지 않고도 여러 줄의 텍스트를 유지할 수 있게 해줍니다.
4. 논리 형식
논리 형식이 다루는 데이터는 '참(True)', '거짓(False)' 두 가지 입니다.
데이터 형식 | 설명 | 크기(바이트) | 범위 |
bool | 논리 형식 | 1(8비트) | true, false |
어떤 작업이 성공했는지 또는 실패했는지를 판단할 때, 두 비교 데이터가 같은지 또는 다른지를 판단할 때 사용합니다.
저도 궁금했던 사항인데 true, false를 8비트나 써서 표현해야하는지 궁금했습니다.
참(true:1)과 거짓(false:0)만을 다루기 1비트만으로 표현이가능하겠지만, 컴퓨터가 기본적으로 다루는 데이터의 크기가 바이트 단위이기 때문에 1비트만 저장하려 해도 한 바이트가 통째로 사용됩니다.
5. object 형식
object는 물건, 객체라는 뜻입니다. 그래서 object 형식은 어떤 물건(데이터)이든지 다룰 수 있는 데이터 형식이라 말할 수 있습니다. 그런데 object 형식이 다른 형식의 데이터를 담을 수 있는 이유는 바로 '상속'의 효과 덕분입니다.
부모로부터 데이터와 메소드를 물려받은 자식은 부모와 똑같이 동작할 수 있습니다. 이런 효과 덕에 컴파일러는 자식을 부모로 간주할 수 있게 됩니다.
C#은 object가 모든 데이터를 다룰 수 있도록 특별한 조취를 취했습니다. 모든 데이터 형식(기본 데이터 형식뿐 아니라 모든 복합 데이터 형식, 심지어 프로그래머가 만드는 데이터 형식마저도)이 자동으로 object 형식으로부터 상속받게 한 것입니다. 다시 말해 object 형식이 모든 데이터 형식의 조상이 된 겁니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.ObjectExam
{
class ObjectExam
{
static void Main(string[] args)
{
object a = 123;
object b = 3.14384123592345712394823m;
object c = true;
object d = "안녕하세요";
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);
}
}
}
6. 박싱과 언박싱
object형식은 참조 형식이기 때문에 힙에 데이터를 할당합니다. int, double 형식은 값 형식이기 때문에 스택에 데이터를 할당합니다. 그런데 위의 예시 같이 값 형식의 데이터를 object 형식 객체에 담았습니다. 이 경우에는 어느 메모리에 데이터가할당되는 것일까요?
object 형식은 값 형식의 데이터를 힙에 할당하기 위한 '박싱(boxing)' 기능을 제공합니다. object 형식에 값 형식의 데이터를 할당하려는 시도가 이루어지면 object 형식은 박싱을 수행해서 해당 데이터를 힙에 할당합니다.
반면, 힙에 있던 값 형식 데이터를 값 형식 객체에 다시 할당해야 하는 경우가 있습니다.
object a = 20;
int b = (int)a;
a는 20이 박싱되어 저장된 힙을 참조하고 있습니다. b는 a가참조하고 있는 메모리로부터 값을 복사하려고 하는 중이고요. 이때 박싱된 값을 꺼내 값 형식 변수에 저장하는 과정을 일컬어 '언박싱(unboxing)'이라고 합니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam
{
class BoxingUnboxing
{
public void Main()
{
int a = 123;
object b = (object)a; // a에 담긴 값을 박싱해서 힙에 저장
int c = (int)b; // b에 담긴 값을 언방식해서 스택에 저장
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
double x = 3.1414213;
object y = x; // x에 담긴 값을 박식해서 힙에 저장
double z = (double)y; // y에 담긴 값을 언박싱해서 스택에 저장
Console.WriteLine(x);
Console.WriteLine(y);
Console.WriteLine(z);
}
}
}
7. 데이터 형식 바꾸기
변수를 다른 데이터 형식의 변수에 옮겨 담는 것을 '형식 변환' 이라고 합니다.
박싱과 언박싱도 값 형식과 참조 형식 간의 형식 변환이라고 할 수 있습니다.
아래 5가지 형식 변환에 대해서 알아보며 해당 포스팅을 마무리 하겠습니다.
- 크기(표현 범위)가 서로 다른 정수 형식 사이의 변환
- 크기(표현 범위)가 서로 다른 부동 소수점 형식 사이의 변환
- 부호 있는 정수 형식과 부호 없는 정수 형식 사이의 변환
- 부동 소수점 형식과 정수 사이의 변환
- 문자열과 숫자 사이의 변환
크기가 서로 다른 정수 형식 사이의 변환
작은 정수 형식의 변수에 있는 데이터를 큰 정수 형식의 변수로 옮길 때는 문제가 없지만, 그 반대의 경우 원본 변수의 데이터가형식 변환하려는 대상 변수의 용량보다 큰 경우에는 '오버플로'가 발생합니다. 이러한 현상의 '용량'의 차이 때문입니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam
{
class Exam_1
{
public void Main() {
sbyte a = 127;
Console.WriteLine(a);
int b = (int)a;
Console.WriteLine(b);
int x = 128; // sbyte의 최대값 127보다 1 큰 수
Console.WriteLine(x);
sbyte y = (sbyte)x; // 오버플로 발생!
Console.WriteLine(y);
}
}
}
크기가 서로 다른 부동 소수점 형식 사이의 변환
부동 소수점 형식의 특성상 오버플로가 존재하지 않기 때문에 위의 예제와 같이 오버플로가 발생 하지 않을겁니다.
그러나, 정밀성에 손상을 입게 되는 다른 차원의 문제가 발생합니다.
float, double은 소수를 2진수로 메모리에 보관합니다.
이것을 다른 형식으로(float -> double, or double -> float)변환하려면 10진수로 복원한 후, 다시 2진수로 변환해서 기록하게 됩니다. 문제는 2진수로 표현하는 소수가 완전하지 않는다는 데 있습니다.
1/3 같은 수는 0.33333...의 무한 소수가 되는데 정밀한 수를 다뤄야 하는 프로그램에서 float와 double사이의 형식 변환을 시도할 때는 주의를 기울여야 합니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam
{
class Exam_2
{
public void Main()
{
float a = 69.6875f;
Console.WriteLine("a : {0}", a);
double b = (double)a;
Console.WriteLine("b : {0}", b);
Console.WriteLine("69.6875 == b : {0}", 69.6875 == b);
float x = 0.1f;
Console.WriteLine("x : {0}", x);
double y = (double)x;
Console.WriteLine("y : {0}", y);
Console.WriteLine("0.1 == y : {0}", 0.1 == y);
}
}
}
부호 있는 정수 형식과 부호 없는 정수형식 사이의 변환
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam
{
class Exam_3
{
public void Main()
{
int a = 500;
Console.WriteLine(a);
uint b = (uint)a;
Console.WriteLine(b);
int x = -30;
Console.WriteLine(x);
uint y = (uint)x; // 언더플로
Console.WriteLine(y);
}
}
}
부동 소수점 형식과 정수 형식 사이의 변환
부동 소수점 형식의 변수를 정수 형식으로 변환하면 데이터에서 소수점 아래는 버리고 소수점 위의 값만 남깁니다. 0.1을 정수 형식으로변환하면 0이 되지만, 0.9도 정수 형식으로 변환하면 0이 됩니다.
반올림과 같은 자비는 이 둘의 형식 변환에서는 찾아 볼 수 없습니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam
{
class Exam_4
{
public void Main()
{
float a = 0.9f;
int b = (int)a;
Console.WriteLine(b);
float c = 1.1f;
int d = (int)c;
Console.WriteLine(d);
}
}
}
문자열을 숫자로, 숫자를 문자열로
(int) 형식 변환연산자는 다른 숫자 형식 데이터를 int 형식으로변환하는 방법(그리고 object 형식을 언박싱하는 방법도 함께)을 갖고 있긴 하지만, string을 비롯한 여타 형식으로의 형식 변환 방법은 갖고 있지 않습니다. int 형으로 변환할 방법이 없는 것은 (string)도 마찬가지입니다.
C#은 정수 계열 형식, 부동 소수점 형식 모두에게 'Parse()'라는 메소드를 넣어줬습니다. 이 메소드에 숫자로 변환할 문자열을 넘기면 숫자로 변환해줍니다.
그리고 숫자 데이터 형식을 문자열로 바꾸는 방법도 마련해놓았습니다.
object로부터 물려받은 'ToString()' 메소드를 재정의 했습니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam
{
class Exam_5
{
public void Main()
{
int a = 123;
string b = a.ToString();
Console.WriteLine(b);
float c = 3.14f;
string d = c.ToString();
Console.WriteLine(d);
string e = "123456";
int f = Convert.ToInt32(e);
Console.WriteLine(f);
string g = "1.23456";
float h = float.Parse(g);
Console.WriteLine(h);
}
}
}
다음 블로그에 이어서 상수와 열거 형식부터 chapter3 마무리 스터디 해보겠습니다.
'ZAION > C#' 카테고리의 다른 글
[이것이 C#이다]Ch.04 데이터를 가공하는 연산자 (0) | 2024.01.29 |
---|---|
[이것이 C#이다]Ch.03 데이터 보관하기 - 2 (1) | 2024.01.25 |
[이것이 C#이다]Ch.08 인터페이스와 추상 클래스 (0) | 2024.01.22 |
[C#][인프런]C# 프로그래밍 기초(4~5강) (0) | 2024.01.08 |
[C#][인프런]C# 프로그래밍 기초(1~3강) (0) | 2024.01.08 |