프로그래밍 관련/C#

C# 강좌 3편. 변수, 데이터 형식, 상수 [최근 수정 2017.10.29]

LAYER6AI 2018. 1. 12. 14:30


1. 변수(Variable)

변수(variable)란 무엇일까요? 이미 익히 들어보신 분도 계실 거라 생각합니다. 변수는 쉽게 말하면 값을 담아 두는 상자와 같습니다. 변수는 하나의 데이터 값을 가질 수 있으며, 한번 정해진 값은 고정되어 있는 게 아니라 계속해서 변할 수 있습니다. 마치 수학의 변수와 비슷하죠!


변수는 값을 담아 두는 상자와 같다


또한, 이 변수라는 녀석은 담기는 음식에 따라 그릇이 달라지듯 담기는 데이터에 따라 변수의 자료형(data type)이 결정됩니다. 예를 들어, 문자는 문자를 담을 수 있는 char 자료형이, 정수는 정수를 담을 수 있는 int라는 자료형 등과 같이 말이죠. 그럼 우리는 어떻게 해서 이러한 변수를 만들 수 있을까요?


아래와 같이 자료형과 변수명을 가지고 변수를 만들 수 있습니다. 지금부터는 '변수를 만든다'가 아닌 '변수를 선언한다'와 같이 설명할 것입니다. 여기서의 선언(declaration)은 컴파일러에게 어떤 대상의 이름을 알려주는 것을 의미합니다. 컴파일러가 임의의 메모리 공간에 이름을 붙여주면, 우리는 변수의 이름을 가지고 그 메모리의 위치에 접근할 수 있습니다.

자료형 변수명;

그럼 아래와 같은 변수 선언은 어떻게 해석할 수 있을까요?

int a;

위의 코드는 '10진수 정수형 변수를 메모리 공간에 할당하고 그 공간에 a라는 이름을 붙여줘!'라는 말과 같습니다. 여기서 int는 자료형에 해당하고, a는 변수명에 해당하게 됩니다. 그리고 마지막에는 문장의 끝을 알리는 세미콜론(;)이 항상 붙습니다.


<10진수 정수형 변수 메모리 할당>


그러고는, 컴파일러가 임의의 공간에다 메모리 공간을 할당해주고 이 공간에 a라는 이름을 만들어주죠. 그럼 우린 이 공간을 사용할 수 있게 된겁니다. 이제 이 공간에다 값을 넣어볼까요?

a = 1000;

위의 코드는 '변수 a에 1000이란 값을 대입해!'라는 말과 같습니다. 그러면 변수 a가 할당된 공간에 1000이란 값이 기록되는거죠. 참고로, 위에서 쓰인 '='은 수학에서 같음을 나타내는 '='와는 의미가 다릅니다. '할당' 혹은 '대입'의 의미를 가지고 있습니다. 등호를 기준으로, 우측의 값을 좌측으로 넘기는 역할을 합니다.


이제 직접, 우리가 코드를 작성하고 컴파일을 해보도록 합시다. 우선 변수만 하나 선언하고 1000이란 값을 대입하여 그 변수의 값을 출력하는 예제를 보겠습니다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a;
            a = 1000;

            Console.WriteLine(a);
        }
    }
}
결과:
1000
계속하려면 아무 키나 누르십시오 . . .

이미 결과를 예상하신 분들도 있겠지만, 12~13행을 보시면 정수형 변수 a를 선언하고 나서 1000이란 값으로 변수를 초기화시키고 있습니다. 여기서 초기화는 최초로 데이터를 할당하는 것을 말합니다. 아래와 같이 변수 선언과 초기화를 동시에 진행할 수도 있습니다.
int a = 1000;
그리고, 자료형이 같다면 그 형식의 변수들을 한 줄에 여러 개 선언할 수 있습니다. 예를 들면,
int a = 1000;
int b = 2000;
int c = 3000;
위의 코드는 아래의 코드와 동일한 역할을 수행하는 코드입니다.
int a = 1000, b = 2000, c = 3000;
위의 두 코드는 같은 역할을 수행합니다. 대신 한 줄에 여러 개의 변수를 선언할 경우에는 변수명 사이에 콤마(,)를 붙여 구분해 주어야 합니다.

그런데, int형으로 변수를 선언하게 되면 오로지 정수만 넣을 수 있습니다. 실수라던가, 문자열 등은 넣질 못합니다. 당연히 C#에선 int형만 지원하는게 아니라, 각각의 형식에 맞는 다양한 자료형들을 지원합니다. 그 예로, char, long, short, byte 등을 예로 들 수 있죠. 이제 이 자료형에 대해 알아볼까 합니다.

2. 기본 자료형(Primitive Types)
C#에서 제공하는 기본 자료형들 중 하나인 정수 형식, 실수 형식, 참과 거짓을 가리는 논리 형식 등을 아래의 표에 정리해 보았습니다.

구분

데이터 형식

크기(Byte)

값의 범위 

정수

byte

1

0~255

정수

sbyte 

-128~127 

정수

short 

-32,768~32,767 

정수

ushort 

0~65535 

정수

int 

-2,147,483,648~2,147,483,647 

정수

uint

0~4,294,967,295 

정수

long 

-922,337,203,685,477,508~922,337,203,685,477,507

정수

ulong 

0~18,446,744,073,709,551,615

문자

char 

 

실수

float 

-3.402823e38~3.402823e38

실수

double

-1.79769313486232e308~1.79769313486232e308 

실수

decimal 

16 

±1.0x10e-28~±7.9x10e28 

문자열

string 



논리

bool 

true, false 

객체

object 


 

정수 자료형:

우선 정수 자료형부터 다뤄보도록 합시다. 아래 예제는 변수를 각각 byte, sbyte, short, ushort, int, uint, ulong 타입으로 선언하여 값을 담고 그 값을 출력시키는 예제입니다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            byte a = 200;
            sbyte b = -126;
            short c = 30000;
            ushort d = 40000;
            int e = 100000000;
            uint f = 1000000000;
            long g = 1000000000000000000;
            ulong h = 10000000000000000000;

            Console.WriteLine("a={0}, b={1}, c={2}, d={3}, e={4}, f={5}, g={6}, h={7}", a, b, c, d, e, f, g, h);
        }
    }
}
결과:
a=200, b=-126, c=30000, d=40000, e=100000000, f=1000000000, g=100000000000000000
0, h=10000000000000000000
계속하려면 아무 키나 누르십시오 . . .

위 예제를 보시면 long이나 ulong 같은 경우는 어마어마한 값을 저장할 수 있습니다. 자료형 앞에 u가 붙으면 이것은 unsigned의 줄임말로, 부호가 없다는 것을 의미합니다. 간단하게 '음수의 범위만큼 양수의 범위를 확장했다!'라고 생각해두시면 됩니다. 이 부분에 대해서는 비트를 다룰 때 자세히 다룰 예정입니다.

실수 자료형:
이번에는 실수 자료형을 다룹니다. 아래 예제는 float, double, decimal 형식으로 변수를 선언하여 값을 담고 그 값을 출력하는 예제입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            float a = 123.567891011f;
            double b = 1234.567878910111213;
            decimal c = 1234.56789101112131415161718m;

            Console.WriteLine("a={0}, b={1}, c={2}", a, b, c);
        }
    }
}
결과:
a=123.5679, b=1234.56787891011, c=1234.56789101112131415161718
계속하려면 아무 키나 누르십시오 . . .

유의하셔야 할 것은 float나 decimal 형식 같은 경우에는 맨 뒤에 f나 m같은 접미사가 붙는다는 겁니다. 만약, 위의 코드에서 접미사를 빼버린다면 아래와 같은 에러가 발생하게 됩니다.

9행에서 오류 CS0664: double 형식의 리터럴을 암시적으로 'float' 형식으로 변환할 수 없습니다. 이 형식의 리터럴을 만들려면 'F' 접미사를 사용하세요.
11행에서 오류 CS0664: double 형식의 리터럴을 암시적으로 'decimal' 형식으로 변환할 수 없습니다. 이 형식의 리터럴을 만들려면 'M' 접미사를 사용하세요.

왜 f나 m과 같은 접미사를 붙여주어야 하는 것일까요? C# 사양(C# specification)에 따르면, 기본적으로 소수점이 들어간 실수 상수를 double형으로 인식합니다. 그렇기 때문에, '이것은 float 형식의 데이터입니다.'를 나타내기 위해 뒤에 'f'와 같은 접미사를 붙여주어야 합니다.

문자, 문자열 자료형:
다음 예제는 문자를 다루는 char, 문자열을 다루는 string 형식에 대한 예제입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            char a = '안';
            string b = "녕하세요";

            Console.WriteLine("{0}{1}", a, b);
        }
    }
}
결과:
안녕하세요
계속하려면 아무 키나 누르십시오 . . .

여기서 알아두셔야 할게 있다면, 문자는 작은따옴표(')로 감싸주어야 하고 문자열은 큰따옴표(")로 감싸주어야 합니다. 여기서 문자열은 하나 이상의 문자들로 구성된 녀석들을 말합니다. 

논리 자료형:
다음 예제는 참과 거짓을 다루는 bool 형식에 대한 예제입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            bool a = true;
            bool b = false;
            
            Console.WriteLine("a={0}, b={1}", a, b);
        }
    }
}
결과:
a=True, b=False
계속하려면 아무 키나 누르십시오 . . .

bool 형식은 참(true), 거짓(false)만을 다룰 수 있습니다. 이 논리 자료형은 나중에 배울 if문에서 많이 만나보게 될 것입니다.

객체 자료형:
다음 예제는 객체(object)를 다루는 object 형식에 대한 예제입니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            object a = 12345678910;
            object b = 12345.67891011;
            object c = true;
            object d = "안녕하세요";
            
            Console.WriteLine("a={0}, b={1}, c={2}, d={3}", a, b, c, d);
        }
    }
}
결과:
a=12345678910, b=12345.67891011, c=True, d=안녕하세요
계속하려면 아무 키나 누르십시오 . . .

위 결과를 보고 한가지 의문점이 생길것인데, 도대체 object 형식이 뭐길래 정수든, 실수든, 논리든, 문자열이든 모든 데이터를 담고 처리할수 있는 걸까요? 그것은 바로 나중에 배우게 될 "상속 관계" 때문입니다. 우리가 알고있는 상속 그대로 부모가 유산을 자식에게 물려주는 것과 같습니다. 부모에게 유산(데이터, 메소드)를 물려받은 자식은 부모가 가지고 있던 기능을 똑같이 수행할 수 있습니다. 

이 object는 모든 자료형의 최상위 부모 클래스, 즉 C#에서 모든 객체들은 object 클래스로 부터 상속을 받습니다. 한마디로 말하자면, '모든 자료형의 조상은 object다!'라고 말할 수 있습니다. 객체나 클래스에 대해서는 나중에 좀 더 자세하게 다룰 예정입니다.

3. 서식 지정자(Format Specifier)
서식 지정자는 우리가 지정한 형식대로 문자열을 출력할 수 있도록 도와줍니다. 예를 들어, 서식 지정자를 통해 출력 너비를 정하거나, 정수를 16진수 표기로 나타낼 수 있습니다. 위에서 본 {0}이나 {1}과 같은 녀석들을 서식 항목이라 하며, 서식 항목은 아래와 같은 형식으로 작성할 수 있습니다. 참고로, 대괄호로 둘러싸인 것은 선택적인 요소입니다.
{인덱스[, 출력 너비][:서식 문자열]}
여기서 인덱스(index)는 0부터 시작하는데, 이는 아래의 "a, b, a + b"와 같이 뒤에 열거된 값의 순서라고 생각하셔도 됩니다.



예를 들어서, 아래와 같은 코드에서는 각각 어떠한 값을 출력해 낼까요?

int a = 81, b = 27;

Console.WriteLine("{0} + {1} = {2}", a, b, a + b); // 81 + 27 = 108
Console.WriteLine("{1} - {0} = {2}", a, b, b - a); // 27 - 81 = -54

이제 좀 감이 오시나요? 이번에는 다양한 서식 문자열을 사용하여 출력해 보도록 하겠습니다. 아래는 수치 서식 문자열을 정리해둔 표입니다. 

서식

종류

사용 예

출력 예 

c / C

통화

Currency

{0:c}

\12,345

d / D

10진법

Decimal

{0:d}

12345

e / E

지수 표기법

Exponential Notation

{0:e}

1.2345000e+004

f / F

고정 소수점

Fixed Point

{0:f}

12345.00

g / G

일반

General

{0:g}

12345

n / N

숫자

Number

{0:n}

12,345.00

x / X

16진법

Hexadecimal

{0:x}

3039

p / P

백분율

Percentage

{0:p}

23.45%

바로 예제를 보도록 합시다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 12345678;
            double b = 12.345678;

            Console.WriteLine("통화 (C) . . . : {0:C}", a);
            Console.WriteLine("10진법 (D) . . : {0:D}", a);
            Console.WriteLine("지수 표기법 (E): {0:E}", b);
            Console.WriteLine("고정 소수점 (F): {0:F}", b);
            Console.WriteLine("일반 (G) . . . : {0:G}", a);
            Console.WriteLine("숫자 (N) . . . : {0:N}", a);
            Console.WriteLine("16진법 (X) . . : {0:X}", a);
            Console.WriteLine("백분율 (P) . . : {0:P}", b);
        }
    }
}
결과:
통화 (C) . . . : \12,345,678
10진법 (D) . . : 12345678
지수 표기법 (E): 1.234568E+001
고정 소수점 (F): 12.35
일반 (G) . . . : 12345678
숫자 (N) . . . : 12,345,678.00
16진법 (X) . . : BC614E
백분율 (P) . . : 1,234.57%
계속하려면 아무 키나 누르십시오 . . .

이어서 사용자 지정 수치 서식 문자열(Custom Numeric Format Strings)에 대해 정리해둔 표를 보도록 하겠습니다. 

서식

종류

사용 예

출력 예

0

0 자리 표시

{0:00.0000}

12345.1200

#

10진수 자리 표시

{0:#.##}

12345.12

.

소수점

{0:0.0}

12345.12

,

천 단위 자릿수 표시

{0:0,0}

12,345

%

백분율 자리 표시

{0:0%}

1234512% 

e

지수 표기법

{0:00e+0}

12e+3

참고로, 위에서 0과 #의 차이를 비교하면 이렇습니다. 0 자리 표시(0)는 서식 문자열에서 0이 있는 위치에 숫자가 있다면 숫자를 그대로 출력하고, 그렇지 않다면 0을 출력합니다. 10진수 자리 표시(#)는 서식 문자열에서 #이 있는 위치에 숫자가 있다면 숫자를 그대로 출력하는 것은 같지만, 숫자가 없을 경우에는 아무것도 표시하지 않습니다.


아래의 예제를 통해 직접 확인해 보도록 합시다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 1234;
            double b = 12.345678;

            Console.WriteLine("0 자리 표시 (0) . . . .: {0:00000}", a);
            Console.WriteLine("10진수 자리 표시 (#) . : {0:#####}", a);
            Console.WriteLine("소수점 (.) . . . . . . : {0:0.00000}", b);
            Console.WriteLine("천 단위 자릿수 표시 (,): {0:0,0}", a);
            Console.WriteLine("백분율 자리 표시 (%) . : {0:0%}", b);
            Console.WriteLine("지수 표기법 (e) . . . .: {0:0.000e+0}", b);
        }
    }
}
결과:
0 자리 표시 (0) . . . .: 01234
10진수 자리 표시 (#) . : 1234
소수점 (.) . . . . . . : 12.34568
천 단위 자릿수 표시 (,): 1,234
백분율 자리 표시 (%) . : 1235%
지수 표기법 (e) . . . .: 1.235e+1
계속하려면 아무 키나 누르십시오 . . .

이번에는 출력 너비를 지정하고, 값을 왼쪽 정렬하거나 오른쪽 정렬하는 예제를 살펴보도록 하겠습니다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 12345;

            Console.WriteLine("|{0,15}|", a);
            Console.WriteLine("|{0,-15}|", a);
            Console.WriteLine("|{0,15:N0}|", a);
            Console.WriteLine("|{0,-15:N0}|", a);
        }
    }
}
결과:
|          12345|
|12345          |
|         12,345|
|12,345         |
계속하려면 아무 키나 누르십시오 . . .

위의 코드에서 11~12행을 살펴보시면 출력 너비를 그대로 적었을 때는 오른쪽 맞춤되어 나오고, 출력 너비 앞에 -를 적었을 때에는 왼쪽 맞춤되어 나오는 것을 보실 수 있습니다. 13~14행의 코드는 출력 너비 지정과 서식 문자열을 같이 사용한 것입니다.

4. 형식 변환(Type Conversion)
말 그대로, 형식 변환이란 특정 형식의 값을 우리가 원하는 형식의 값으로 변환하는 것을 말합니다. 여기에서는, 정수와 실수 간의 형식 변환, 실수와 정수를 문자열로 표현하는 방법, 역으로 문자열을 실수와 정수로 변환하는 예제를 차례대로 보도록 하겠습니다.

정수와 실수 간의 형식 변환:
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 500;
            float b = a; // 암시적 변환으로 별도의 구문이 필요하지 않음 

            Console.WriteLine("a={0}, b={1}", a, b);

            double c = 123.45;
            int d = (int)c;

            Console.WriteLine("c={0}, d={1}", c, d);
        }
    }
}
결과:
a=500, b=500
c=123.45, d=123
계속하려면 아무 키나 누르십시오 . . .

우선 10행과 같이 정수를 실수로 변환하는 것은 데이터 손실이 없으므로 암시적 변환(Implicit conversions)이 이루어 집니다. 여기서 암시적 변환이란, 일반적으로 형식 변환이 안전하고 데이터 손실의 우려가 없을 경우 컴파일러에 의해 수행되는 변환을 말합니다. 암시적 변환과 관련된 표는 이곳에서 확인하실 수 있습니다.

반대로, 15행은 실수를 정수로 바꾸는 과정에서 소수점 이하는 모두 버려지면서 데이터 손실이 일어나게 됩니다. 손실 여부와는 상관없이 형식 변환을 하고 싶은 경우에는, 명시적 변환(Explicit conversions)을 사용하여 실수를 정수로 바꾸어야 합니다. 여기서 명시적 변환이란, 프로그래머가 컴파일러에게 "이 형식으로 변환해줘!"라고 분명하게 의도를 내비치는 것입니다. 이 경우에는 결과에서 확인할 수 있듯이 데이터 손실이 일어난 것을 확인할 수 있습니다.

실수와 정수를 문자열로 변환, 문자열을 실수나 정수로 형식 변환:
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 500;
            float b = 60.44f;
            string c = a.ToString();
            string d = b.ToString();
            
            Console.WriteLine("a={0}, b={1}, c={2}, d={3}", a, b, c, d);

            string e = "123";
            string f = "123.456";
            int g = int.Parse(e);
            float h = float.Parse(f);

            Console.WriteLine("e={0}, f={1}, g={2}, h={3}", e, f, g, h);
        }
    }
}
결과:
a=500, b=60.44, c=500, d=60.44
e=123, f=123.456, g=123, h=123.456
계속하려면 아무 키나 누르십시오 . . .

주목하셔야 할 부분은 14~15행과 21~22행에 쓰인 ToString()이란 메소드와 Parse()란 메소드입니다. ToString() 메소드는 특정 형식의 값을 문자열 형식으로 바꿔주는 역할을 합니다. 그리고 Parse()라는 메소드를 통해 문자열을 정수나 실수로 바꿔줄 수도 있습니다. 


5. 상수(Constant)

우리가 수천 줄에서 수만 줄이 넘어가는 프로그램을 만들게 될 때, 수많은 변수를 선언하여 코드를 작성하게 됩니다. 그러나, 이 수많은 변수 중 값이 도중에 변경되어도 프로그램엔 영향을 미치지 않는 것과 도중에 변경되면 심각한 오류를 일으킬 수 있는 것들이 있습니다. 만약에, 코드를 작성하다가 나도 모르게 값을 변경하지 말아야 할 변수를 건드려서 버그가 발생할 수 있습니다. C# 프로그래밍은 이 문제를 쉽게 해결해주는 const라는 키워드가 존재합니다.


이 const 키워드를 사용하게 되면 한번 초기화 된 값은 절대 변하지 않습니다. 상수를 정의하는 방법은 변수의 선언방법과 유사합니다. 아래는 상수의 선언 예입니다.

const double PI = 3.141592;
const int month = 12;

예제를 직접 보도록 하겠습니다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            const int a = 50;

            Console.WriteLine(a);
        }
    }
}
결과:
50
계속하려면 아무 키나 누르십시오 . . .

위의 예제를 보아선 const 키워드의 특징을 파악할 수 없습니다. 만약 상수 a의 값을 변경하게 되면 어떻게 될까요?
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            const int a = 50;

            a = 60; // 오류 발생!

            Console.WriteLine(a);
        }
    }
}
11행에서 오류 CS0131: 할당식의 왼쪽은 변수, 속성 또는 인덱서여야 합니다.

상수의 값은 변경이 불가능한 것을 확인할 수 있습니다. 그렇다면 아래와 같이 상수의 선언과 초기화를 분리하면 어떻게 될까요?
...
            const int a;

            a = 60; // 오류 발생!
...
11행에서 오류 CS0145: const 필드에 값을 입력해야 합니다.

위와 같은 에러가 발생합니다. 여기서 const 키워드의 특징을 정리해 보자면, 선언과 동시에 초기화를 하여야 하며 초기화가 된 후로부터는 값의 변경이 불가능한 것을 알 수 있습니다.

변수와 데이터 형식, 그리고 상수에 대한 설명은 끝났습니다. 여기까지 읽느라 수고하셨습니다.

다음 강좌에서는 연산자(Operator)에 대해 알아보도록 하겠습니다.