3. 변수
Application은 기본적으로 필요한 Data를 Memory에서 다루기 위해 '변수'를 사용합니다. 데이터는 파일이나 DB혹은 외부의 사용자 입력 등 다양한 곳에서 들어올 수 있지만 이렇게 입력된 데이터를 처리하기 위해서는 어딘가에 해당 데이터를 저장해야 하고 이러한 수단에서 '변수'를 활용하는 것입니다.
다만 '변수'는 Application이 종료되면 Memory에서 제거되므로 이를 지속적으로 저장하기 위해서 파일이나 DB 등을 별도로 이용할 수 있습니다.
'변수'는 사용하고자 하는 type에 따라 Memory를 차지하는 크기가 달라집니다. 가능한 한 작은 type을 사용하면 그만큼 적은 Memory를 할당받게 되지만 그렇다고 해서 작은 크기의 '변수'가 그렇지 않은 '변수'보다 항상 처리가 더 빠르게 이루어지는 것은 아닙니다. 예를 들어 64비트 시스템에서 16비트 변수는 크기의 범위만 다를 뿐 64비트 변수와 비교하여 동일하게 처리됩니다.
● 네이밍 규칙과 변수 할당
'변수'나 '필드'같은걸 사용하기 위해서는 다음과 같이 해당 type의 이름을 식별자로 사용해야 합니다.
int i;
여기에서 int는 변수의 type이며 i가 식별자에 해당합니다. 이때 i과 같이 이름을 붙이는 경우에는 다음과 같은 규칙을 따라 결정하기를 권장합니다.
- Camel case : 지역변수(local variables)나 private field에 사용하는 방법으로 변수명이 하나의 단어로만 이루어져 있는 경우에는 'user'처럼 지정하고, 소문자로 2개 이상의 단어로 이루어져 있는 경우에는 첫 번째 단어만 소문자로 시작하고 그다음부터 각 단어의 시작 문자를 'userDetail'처럼 대문자로 시작합니다.
- Pascal case 또는 Title case : Type이나 non-private field 또는 Method와 같은 그 외의 멤버에서 사용하는 방법으로 'UserOfBirth'처럼 처음부터 대문자로 시작한다는 점만 다르고 나머지는 Camel case와 같습니다.
'변수'로의 값의 할당은 =문자로 이루어집니다. 따라서 다음과 같이 변수를 생성하고 값을 할당하고 필요한 경우 해당 변숫값을 사용자에게 보여줄 수 있습니다.
int userAge = 30;
Console.Write($"귀하의 나이는 {userAge}입니다.");
● 문자열 다루기
'a'와 같은 단문자의 경우에는 char형식을 사용하며 '문자를 사용해 할당합니다.
char c = 'a';
'abc'처럼 여러 문자들로 이루어진 문자열의 경우에는 string형식을 사용하며 "문자를 사용해 할당합니다.
string s = "abc";
문자열을 저장할 때는 Tab이나 New Line처럼 특수한 문자를 표현하기 위해서 \(백 슬래시) 문자를 사용할 수 있습니다.
string s = "abc\tdef"; //\t는 Tab문자
다만 특정 경로를 지정하는 경우에도 \문자가 사용되는데 경로(path) 지정의 경우 t로 시작하는 파일이나 폴더가 존재할 수 있으므로 맨 앞에 '@'문자를 붙여 해당 문자열 값에 사용되는 \문자를 정확히 경로를 표시하는 문자로 컴파일러가 해석할 수 있도록 만들어 줘야 합니다.
string path = @"C:\Users\Administrator\Downloads\top";
혹은 '\\'처럼 두 번 연속으로 사용해도 동일하게 처리할 수 있습니다.
● 정수 다루기
'숫자'라 함은 더하기나 곱하기처럼 특정 계산을 위해 사용되는 데이터를 의미합니다. 따라서 휴대폰 번호는 숫자 형식으로 취급될 수 없습니다. 그 외 11과 같은 자연수나 -11 같은 음수, 소수 등은 모두 숫자 형식에 해당하며 다음과 같이 할당합니다.
int userAge = 30;
사람은 평소 0부터 9까지 10개의 숫자를 가지는 10진수를 사용하는데 반해 컴퓨터에서는 내부적으로 정보를 0과 1로만 이루어진 2진수 형식으로 취급합니다. 따라서 10진수에서의 9는 2진수로 다음과 같이 표현할 수 있습니다.
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
위의 표에서 아래에 1로 표시된 숫자를 합해보면 9(8+1)가 됨을 알 수 있습니다.
C#7.0부터는 숫자를 다루기 위한 2개의 개선점이 추가되었는데 그중 하나는 _(언더바) 구분 기호를 사용하여 숫자의 가독성을 향상하는 것입니다. 이것으로 인해 2진수, 10진수, 16 진수 등을 구분하지 않고 각각의 단위 구분을 위해 어디든 _문자를 추가시킬 수 있습니다. 예를 들어 10진수의 경우 천 단위 구분을 위해 다음과 같은 표기법을 사용할 수 있습니다.
long i = 1_000_000_000;
다른 하나는 바이너리 리터럴(binary literals) 지원인데 2진수 표기법의 경우 0과 1만을 사용하여 0b로 시작하여 표현할 수 있고
int i = 0b_0000_1101;
Console.WriteLine(i); //13
16진수의 경우 0~9와 A~F를 사용하여 0x로 시작한 표현이 가능합니다.
int i = 0xF;
Console.WriteLine(i); //15
● 소수 다루기
C#에서 소수는 float이나 double 등의 부동소수점 형식을 사용해 소수를 표현합니다. 대부분의 프로그래밍 언어는 부동소수점 연산에서 IEEE표준으로 따르고 있으며 이중에서 IEEE 754는 IEEE(Institute of Electrical and Electronics Engineers)에 의해 1985년에 수립된 부동소수점연산에 관한 기술표준에 해당합니다.
32 | 16 | 8 | 4 | 2 | 1 | . | ½ | ¼ | ⅛ |
0 | 0 | 1 | 1 | 0 | 1 | . | 1 | 1 | 0 |
위 표는 소수 13.75가 어떻게 컴퓨터에서 2진수로 표현되는지를 나타낸 것으로 8+4+1은 13이 되고 ½과 ¼은 ¾이 되므로 13¾은 13.75로 나타낼 수 있고 따라서 2진수로는 1101.110이 되는 것입니다. 물론 모든 소수를 위와 같이 항상 정확하게만 표현할 수 있는 것은 아니며 일부 예외는 있지만 이 표는 대부분의 소수를 어떻게 취급하는지를 이해하는데 도움이 될 수 있습니다.
참고로 C#에서 수를 다루는 경우 특정 형식(type)을 사용하게 되는데 이때, 특정 형식이 메모리를 차지하는 크기를 byte단위로 확인하고 싶은 경우 sizeof() 메서드를 사용할 수 있습니다.
Console.WriteLine($"int size : {sizeof(int)}"); //4byte
또한 MinValue나 MaxValue속성을 통해 해당 형식이 표현할 수 있는 최소/최댓값을 확인할 수도 있습니다.
//Max : 2147483647, Min : -2147483648
Console.WriteLine($"int Max : {int.MaxValue}, Min : {int.MinValue}");
double과 decimal은 비교적 큰 수의 소수를 다룰 수 있는 형식이지만 해당 수를 표현하기 위한 범위에서 약간의 차이가 존재합니다.
double a = 0.1;
double b = 0.2;
if (a + b == 0.3)
{
Console.WriteLine($"{a} + {b}는 0.3");
}
else
{
//0.1 + 0.2는 0.30000000000000004
Console.WriteLine($"{a} + {b}는 {a + b}");
}
위 예제를 실행하면 주석 처리한 결과를 출력하게 되는데 우선 double의 경우에는 예제에서의 0.1과 같은 값은 부동소수점으로서 표현될 수 없기에 정밀도를 보증하지 않습니다. 따라서 정밀도가 중요하지 않은 경우에만 double을 사용해야 하며 2개의 변수에 대한 동일성을 비교하는 경우에는 더욱 그렇습니다.
이런 문제는 0.1의 값에서 .1을 아래와 같이 00011001100110011... 으로 무한대로 표현될 수 있기에 발생합니다.
2 | 1 | . | 1/2 | 1/4 | 1/8 | 1/16 | 1/32 | 1/64 | 1/128 | 1/256 | 1/512 |
0 | 0 | . | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
하지만 위 예제를 double대신 decimal로 바꾸면
decimal a = 0.1M;
decimal b = 0.2M;
if (a + b == 0.3M)
{
//0.1 + 0.2는 0.3
Console.WriteLine($"{a} + {b}는 0.3");
}
else
{
Console.WriteLine($"{a} + {b}는 {a + b}");
}
이번에는 값을 정확하게 판단함을 알 수 있는데 이것으로 decimal은 double에 비해서 상대적으로 정확한 소수의 표현이 가능하다는 것을 알 수 있습니다.
decimal은 수 자체를 큰 정수로 저장하고 소수점을 이동시키는 방법으로 소수를 저장합니다. 예를 들어 0.1의 경우에는 값을 1로 저장하고 소수점을 1의 왼쪽으로 이동시키게 되며 12.34와 같은 소수의 경우에도 1234로 저장한 후 소수점을 왼쪽으로 2칸을 이동시켜 저장하게 되므로 소수점 이하 값을 정확하게 표현하는 것입니다.
double은 소수 값을 다루기는 하지만 값의 동일성 여부를 판단해야 하는 경우에는 사용을 피하는 것이 좋습니다. 참고로 double은 double.NaN속성을 통해 'not-a-number'를 표현할 수 있고 double.Epsilon을 통해서는 저장 가능한 최소수를, double.PositiveInfinity 와 double. NegativeInfinity로는 무한대의 양수와 음수를 표현할 수 있습니다.
● Boolean
Boolean은 true와 false이 두 가지로 참 거짓만을 표현합니다. 대부분 조건에 따른 분기나 반복문에서 사용됩니다.
bool b1 = true;
bool b2 = false;
● object
object는 모든 데이터를 담아낼 수 있는 아주 특별한 type이지만 코드를 지저분하게 만들 수 있고 성능 또한 매우 떨어지는 type에 해당합니다. 따라서 특별한 경우가 아니면 사용을 피하는 것이 좋습니다.
object s = "abc";
object d = 12.34;
object b = true;
● dynamic
C# 4.0부터 소개된 dynamic은 object처럼 모든 데이터를 담아낼 수 있는 특별한 type입니다. 다만 object와는 달리 성능의 저하가 없으며 형 변환이 필요하지 않습니다.
dynamic d = "abcdefg";
//7
//object라면 (string)를 사용해 형변환을 해야만 Length속성을 사용할 수 있었음
Console.WriteLine($"{d.Length}");
참고로 Visual Studio Code나 Visual Studio 2022와 같은 도구에서 위 예제를 작성해 보면 IntelliSense가 작동하지 않는다는 사실을 확인할 수 있는데 이는 Compiler가 build단계에서 type을 확인하지 않고 runtime에서 CLR이 type을 확인하기 때문이며 따라서 dynamic에서는 InstelliSense기능을 사용할 수 없게 됩니다.
● 지역변수
method안에 선언된 변수를 의미하며 method의 실행이 완료되면 메모리에 할단된 변수는 곧 소거됩니다. 정확하게는 참조 타입이 가비지 컬렉션에 의해 소거되기를 기다리는 반면 값 타입은 사용이 종료되면 즉시 메모리에서 소거될 수 있는 것입니다.
● var와 타입 추론
변수를 선언할 때는 특정 형식을 명시적으로 지정하는 대신 var형식을 사용할 수도 있습니다.
int i = 10;
var j = 10; //int 대신 var사용
그러면 compiler는 할당된 값을 통해 type을 유추하게 됩니다.
숫자의 경우 일반적으로 소수점이 없을 때는 int형으로 취급하지만 아래와 같은 접미사를 붙여 특정 형식으로 유추되게끔 하는 것도 가능합니다.
L | long |
UL | ulong |
M | decimal |
D | double |
F | float |
해당 접미사에 따라 일반적인 경우 소수점이 있을 때는 double로 유추하지만 값뒤에 M을 붙이면 decimal로 유추됩니다.
var d = 11.23M;
그 외 문자열은 string으로 문자는 char로 true/false는 bool로 유추하여 처리합니다.
● target-typed new
C#9에서 소개된 target-typed new를 사용하면 처음 변수에 type이 지정되어 있을 때 인스턴스를 만들기 위한 new에서 해당 type을 다시 명시하지 않아도 됩니다.
class TestClass
{
static void Main(string[] args)
{
Person p = new();
}
}
class Person
{
}
● 형식의 기본값 설정 및 가져오기
일반적인 값 형식은 해당 변수에 값이 할당되어야 하는데 이 경우 default() 연산자를 통해 해당 형식의 기본값을 할당할 수 있습니다.
Console.WriteLine($"int의 기본값은 : {default(int)}"); //0
Console.WriteLine($"bool의 기본값은 : {default(bool)}"); //false
string과 같은 참조 타입은 내부에 실제 값 대신 값이 위치하는 메모리의 주소 값을 가집니다. 참조 타입이므로 기본적으로 null상태를 가지게 되는데 사실상 참조타입의 기본값인 셈입니다. 그리고 null은 아직 해당 참조타입이 어떠한 값도 참조하지 않고 있음을 의미합니다.
● Array
같은 형식의 값을 여러 건 다뤄야 하는 경우 가장 쉬운 방법으로 Array를 사용할 수 있습니다. 일반적으로 Array에서 각 값은 index로 구분하며 index는 특별한 경우가 아니면 0부터 시작하게 됩니다.
string[] s = new string[5];
s[0] = "abc";
s[1] = "def";
s[2] = "ghi";
s[3] = "jkl";
s[4] = "mno";
foreach (var item in s)
{
Console.WriteLine(item);
}
Array는 []를 통해 내부에 얼마큼의 요소가 필요한지를 지정해야 하는데 숫자로 그 크기를 지정하는 대신 값을 사용해 얼만큼의 크기를 확보해야 하는지 compiler에게 알려줄 수도 있습니다.
//값이 5개이므로 배열은 5의 크기를 가져야 함.
string[] s = new[] { "abc", "def", "ghi", "jkl", "mno" };
foreach (var item in s)
{
Console.WriteLine(item);
}
'.NET > C#' 카테고리의 다른 글
[C#] 연산자 (0) | 2022.06.24 |
---|---|
[C#] C# 개요 - 3. 기타 Console Application 관련 (0) | 2022.06.24 |
[C#] C# 개요 - 1. C#의 특징및 개요 (0) | 2022.06.24 |
[C#] C#과 .NET6 시작하기 - 3. Console App 만들어 보기 (0) | 2022.06.24 |
[C#] C#과 .NET6 시작하기 - 2. .NET 이해하기 (0) | 2022.06.24 |