[이것이 C#이다] 3. 데이터 보관

ssu_hyun·2022년 4월 5일
0

C#

목록 보기
7/22

Key point

  • 데이터 형식의 종류
  • 값 형식 vs 참조 형식
  • 기본 데이터 형식의 사용 방법
  • 상수
  • Nullable
  • C# 데이터 형식, 공용 형식 시스템의 관계

  • cpu에게는 데이터 종류에 따라 명령을 내려야한다. cpu는 데이터 타입을 스스로 판단할 수 있는 능력이 없기 때문이다. 다행히 컴파일러가 이 작업을 도와준다.
  • 데이터 형식(Data Types)
    • 데이터의 유형(정수/부동소수점/문자열/이미지)과 크기(byte) 지정
    • 기본 데이터 형식
      • 정수 형식(char/uchar, signed byte/byte, short/ushort, int/uint...)
      • 부동 소수형식(float, double)
    • 복합 데이터 형식
      • 클래스
      • 구조체
      • 인터페이스
  • 변수(Variable)
    • 데이터를 저장할 수 있는 메모리 공간
    • 공간에는 유형과 크기를 지정해야 함
    • 코드에서 이름(식별자, Identifier)을 붙여 사용
      ex) int age = 33;
  • C#에서 사용하는 메모리
    • 스택(stack)
      • 데이터를 쌓아 올리는 구조의 메모리
      • 나중에 쌓인 데이터를 먼저 제거
      • 처음에 쌓인 데이터를 나중에 제거
      • 쌓인 순서의 역순으로 필요 없는 데이터를 자동으로 제거(자동메모리)
    • 힙(Heap)
      • 자유롭게 데이터를 저장할 수 있는 메모리
      • 별명 : 자유 저장소(Free Store)
  • 값 형식(Value Type)
    • 메모리에 값을 직접 담는 데이터 형식
    • 스택에 할당 (즉, 자동으로 제거됨)
    • 기본 데이터 형식과 구조체가 여기에 해당
  • 참조 형식(Reference Type)
    • 메모리에 다른 변수의 주소를 담는 데이터 형식
    • 힙에 할당(가비지 콜렉터에 의해 제거됨)
    • 복합 데이터 형식과 클래스 등이 여기에 해당
  • 기본 데이터 형식(Primitive Types)
    • C#이 제공하는 기본 데이터 형식
      • 수 형식
        • 정수 형식(int/uint, short/ushort, byte/sbyte...)
        • 부동 소수점 형식(float, double, decimal)
      • 논리 형식(bool)
      • 문자열 형식(string)
      • object 형식
    • 복합 데이터 형식은 기본 데이터 형식을 바탕으로 만들어짐
  • 박싱(Boxing), 언박싱(Unboxing)
    • 박싱 : 값 형식을 object 형식에 담아 힙에 올리기
    • 언박싱 : 힙에 올라가 있는 데이터를 object에서 꺼내 값 형식으로 옮기기
      ex) object a=20;
  • 상수(Constant)
    • 변수 : 變(변할 변)경이 가능한 수
    • 상수 : 항常(늘 상) 최초의 상태를 유지하는 수
    • const 키워드를 이용해 선언 : const 데이터형식 식별자(이름) =
      ex) const int MAX_INIT_BIT = 32
  • 열거형식(Enumerated Type)
    • 하나의 이름 아래 묶인 상수들의 집합
    • enum 키워드를 이용하여 선언
  • var 키워드
    • var 키워드로 선언한 변수는 컴파일러가 리터럴을 분석해 자동으로 형식 추론
    • 지역 변수에 대해서만, 메소드 안에서만 사용 가능
 var a = 3;  // a는 int 형식
 var b = "Hello";  // b는 string 형식

3.1 데이터 형식(Data Type)

  • 데이터 형식 = 기본 데이터 형식 + 복합 데이터 형식

    복합 데이터 형식(Complex Data Type)

    • 기본 데이터 형식을 부품으로 삼아 구성 (이미지나 소리 등의 데이터)
    • 종류 : 구조체, 클래스, 배열 등
  • 데이터 형식은 기본과 복합으로 분류하는 동시에, 값 형식과 참조 형식으로도 분류가 가능하다. 값 형식과 참조 형식은 기본 데이터 형식과 복합 데이터 형식 각각 모두 존재한다.

3.2 변수(Variable)

  • 데이터를 담는 일정 크기의 메모리 공간

    • 여기서 일정 크기는 데이터 형식에 따라 결정
    • 변수를 선언한다 = 컴파일러에게 "이 변수에 필요한 메모리 공간을 예약해줘"라고 알리는 것
    • 공간에는 유형과 크기를 지정해야 한다.
  • 변수 선언

    • 데이터 형식 식별자(변수의 이름) ; 의 형태로 컴파일러에게 선언→ 컴파일러는 int형식을 위해 메모리 공간을 할당하고 이 공간을 x라는 식별자가 사용할 수 있도록 준비

    • 선언된 변수 x에 대입 연산자를 통해 데이터 입력!→ 이 코드가 실행되고 나면 x를 위해 할당된 메모리 공간에 데이터 100이 기록됨

    • 어떤 변수는 태어나는 시점부터 특정한 값을 갖고 있어야 하는 경우가 있는데 이런 경우 선언과 데이터 할당을 동시에 할 수 있다.

      // 선언과 데이터 할당을 별도로 하는 경우
      int x;
      x = 100;
      
      // 선언과 초기화를 한번에 하는 경우
      int x = 100;
      
      // 변수 여러 개를 동시에 선언하는 경우
      int a, b, c;  // 단, 동시에 선언하는 변수들은 데이터 형식이 같아야함
      int x=30, y=40, z=50; // 선언과 초기화를 여러 개 하는 것도 가능
  • 초기화(Initialization)
    • 변수에 최초의 데이터를 할당하는 것
    • C, C++에서 변수 선언 후 데이터를 입력하지 않을 경우 쓰레기 데이터가 들어가 소프트웨어가 엉뚱하게 동작하는 문제를 미연에 방지하기 위해 C#은 초기화를 강제한다.
    • C#은 초기화되지 않은 변수를 사용하면 컴파일러가 에러 메시지를 내면서 실행 파일을 만들어주지 않는다.

  • 리터럴(Literal)
    • 고정값을 나타내는 표기법
    int a = 100;              // 변수 a, 리터럴 : 100
    int b = 0x200;            // 변수 b, 리터럴 : 0x200
    float c = 3.14f;          // 변수 c, 리터럴 : 3.14f
    double d = 0.12345678;    // 변수 d, 리터럴 : 0.12345678
    string s = "가나다라마바사"; // 변수 e, 리터럴 : "가나다라마바사"

3.3 값 형식과 참조 형식

두 형식은 변수의 값, 데이터를 스택에 넣느냐 힙에 넣느냐에 따라 다르다.

  • 스택 : 변수의 생명 주기가 다 하면 자동으로 데이터 제거

  • 힙 : 데이터를 참

  • 값 형식(Value Types)

    • 변수가 을 담는 데이터 형식
    • 변수의 값, 즉 데이터를 스택(Stack)에 저장
      • 코드 블럭을 여는 괄호 { 실행 후 코드 순서대로 값 형식의 변수가 스택에 쌓였다가 블록을 닫는 괄호 }를 만나면 모든 값 형식의 변수들은 역순으로 메모리에서 제거된다.
        { // 코드 블록 시작
        	int a = 100;
            int a = 200;
            int a = 300;
         } // 코드 블록 끝
  • 참조 형식(Reference Types)

    • 변수가 값 대신 값이 있는 곳의 위치(참조)를 담는 데이터 형식
    • 힙(Heap)스택(Stack) 메모리 영역 함께 이용
    • 변수의 값, 즉 데이터를 힙(Heap)에 저장
      • 힙 영역에는 데이터를, 스택 영역에는 데이터가 저장된 힙 메모리의 주소를 저장(데이터를 직접 저장하는 대신 실제 데이터가 저장된 메모리의 주소를 "참조"하므로 참조 형식!)
        { 
            object a = 10;  // 스택에 a가 저장된 힙 메모리 주소 저장
            object b = 20;  // 스택에 b가 저장된 힙 메모리 주소 저장
         } // 스택에 쌓인 a와 b 사라짐 
      • CLR의 가비지 컬렉터(Garbage Collector)는 프로그램 뒤에 숨어 동작하면서 힙에 더 이상 사용하지 않는 객체가 있으면 그 객체를 쓰레기로 간주하고 수거하는 기능을 한다. (더 이상 데이터를 참조하는 곳이 없을 때 가비지 컬렉터가 데이터를 치워주는 구조)
      • 데이터 유지가 힘든 스택 구조의 한계를 보완하기 위해 데이터를 살릴 수 있는 다른 메모리 영역을 CLR이 제공하는 것

가비지 컬렉션과 참조 카운트
프로그래밍 언어의 종류는 다음의 두 가지가 있다.

  • unmanaged 언어 : 메모리를 프로그래머가 직접 관리 (C/C++)
  • managed 언어 : 언어단에서 관리 (C#, Java)
    대부분의 매니지드 언어에선 자동으로 메모리 관리를 하기 위해 다음 중 하나의 기법을 사용한다.
  • Garbage collection (가비지 컬렉션 또는 쓰레기 수집)
  • Reference counting (레퍼런스 카운팅 또는 참조 횟수)


    가비지 컬렉션의 경우 힙에 할당된 메모리 중 이를 가리키는 포인터 또는 참조 변수(스택의 지역변수)가 사라지면(지역변수가 속한 함수를 빠져나가면 스택 메모리의 지역변수는 정리된다.) 이로 인해 메모리 누수 문제(=쓰레기 메모리)가 발생하게 되어 가비지 컬렉터는 이를 방지하기 위해 사용되지 않는 힙 메모리를 추적해 해제해준다.

3.4 기본 데이터 형식(Primitive Types)

  • 총 15가지
    • 참조 형식 : 문자열 형식, 오브젝트 형식
    • 값 형식 : 숫자 형식, 논리 형식

3.4.1 숫자 데이터 형식(Numeric Types)

  • C#은 15가지 기본 자료 형식 중 12가지를 숫자 데이터 형식으로 제공한다.
  • 12가지 형식 - 정수 계열, 부동 소수 계열, 소수 계열

정수 계열 형식(Integer Types)

  • 정수 데이터를 담기 위해 사용

  • 12가지의 숫자 데이터 형식 중 9가지 해당

    • 메모리를 효율적으로 쓰기 위해 각각 크기(바이트)와 담을 수 있는 값의 범위를 달리함
  • 예제 프로그램

    /* IntegralTypes */
    
     using System;
    
    namespace IntegralTypes
    {
        class MainApp
        {
            static void Main(string[] args)
            {
                sbyte a = -10;
                byte b = 40;
    
                Console.WriteLine($"a={a}, b={b}");
    
                short c = -30000;
                ushort d = 60000;
    
                Console.WriteLine($"c={c}, d={d}");
    
                int e = -1000_0000; // 0이 7개 , 자릿수 구분자(_) 사용
                uint f = 3_0000_0000; // 0이 8개 
    
                Console.WriteLine($"e={e}, f={f}");
    
                long g = -5000_0000_0000; // 0이 11개 
                ulong h = 200_0000_0000_0000_0000; // 0이 18개 
    
                Console.WriteLine($"g={g}, h={h}");
             }
         }
     }

    바이트(byte)

    • 컴퓨터가 데이터를 다루는 기본 단위
    • 요즘 거의 모든 컴퓨터는 8 비트(bit) = 1바이트(byte) 로 취급
    • 1바이트(byte) : 8개의 0과 1로 구성되는 데이터 덩어리
      129(10진수) = 1000 0001(2진수)
    • 비트가 아닌 바이트를 사용하는 이유
      : 대부분의 데이터 크기가 바이트 이상이므로 비트 단위로 잘게 나눠서 처리(밥알을 하나씩 젓가락으로 집어 먹는 것)하기 보다 한번에 처리(숟가락으로 밥을 잔뜩 퍼서 입에 떠 넣는 것)하는 것이 더 효율적이므로

    비타민 퀴즈

    sbyte a = -5000_0000_0000;

    sbyte 코드 부분을 위와 같이 수정 후 컴파일하면 아래와 같은 error가 뜸


2진수, 10진수, 16진수 리터럴

  • 2진수 접두사 : 0b (숫자 0과 알파벳 b)
  • 16진수 접두사 : 0X or 0x
  • 10진수 접두사 : 아무것도 붙이지 않음
/* IntegralLiterals */

using System;

namespace IntegralLiterals
{
    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}");
         }
     }
 }


부호 있는 정수, 부호 없는 정수

  • 부호 : 음(-), 양(+)

    • 부호 있는 정수 : 음의 영역까지 다룬다
    • 부호 없는 정수 : 0과 양의 영역만 다룬다
  • 컴퓨터가 음수를 표현하는 방법, 2의 보수법(2's Complement)

    • 컴퓨터 시스템에서는 음수를 표현하기 위해 첫 번째 비트를 부호를 표현하는 데 사용하며 이를 부호 비트(Sign Bit)라고 한다. (부호 비트가 0일 때는 양수, 1일 때는 음수)

    • 부호와 절댓값(Sign-and-magnitude) 방식은 부호 비트를 순수하게 음과 양을 나타내는 데 사용하고 나머지 비트도 순수하게 수를 나타내는 데만 사용하여 수를 나타내는 방식인데 이는 0의 표현이 +0(0000 0000)-0(0000 0000) 두 가지가 존재한다는 문제가 발생해 이것 대신 2의 보수법(2's Complement)이라는 알고리즘을 채택해 음수를 표현한다.

    • 2의 보수법을 통한 음수 표현 방법
      1) 먼저 수 부분 비트를 채운다
      2) 전체 비트를 반전시킨다
      3) 반전된 비트에 1을 더한다

/* SignedUnsigned */

using System;

namespace SignedUnsigned
{
    class MainApp
    {
        static void Main(string[] args)
        {
            byte a = 255;  // 1111 1111 (byte 형식)
            sbyte b = (sbyte)a;  // (sbyte) : sbyte 형식으로 변환하는 연산자

            Console.WriteLine(a);
            Console.WriteLine(b);
        }
    }
}

// byte에서 1111 1111은 255
// sbyte에서 1111 1111은 -1


오버플로(Overflow), 언더플로(Underflow)

  • 오버플로 : 변수에 데이터 형식의 크기를 넘어선 값을 담으면 넘치는 현상

    /* Overflow */
    
     using System;
    
      namespace Overflow
      {
          class MainApp
          {
              static void Main(string[] args)
              {
                  uint a = uint.MaxValue;  // uint의 최대값
    
                  Console.WriteLine(a);  // 4294967295
    
                  a = a + 1; // uint의 최대값 + 1
    
                  Console.WriteLine(a);  // 0
               }
           }
       }

    • Overflow 되면 0이 되는 이유위와 같은 일은 모든 정수 계열 형식에서도 똑같이 발생한다.
  • 언더플로우 : 최저값보다 작은 데이터 저장하면 생기는 현상

비타민 퀴즈
Overflow 예제에서 uint를 int로 바꾸어 코드를 실행시키면 나오는 결과

using System;
namespace Overflow
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int a = int.MaxValue;
            Console.WriteLine(a);
            a = a + 1;
            Console.WriteLine(a);
        }
    }
}

=> int값의 가장 최저값으로 바뀜 = 0이 되므로 그 데이터 형식의 값의 범위 중 가장 작은 값이 되는 것


char

정수 계열 형식




부동 소수점 형식(Floating Point Types)

  • 소수점이 고정되어 있지 않고 움직이면서 수를 표현한다는 뜻

    • 소수점을 이동시켜 수를 표현하면 고정시켰을 때보다 더 제한된 비트를 이용해 훨씬 넓은 범위의 값을 표현할 수 있다.
  • 부동 소수점 형식이 정수 형식을 대체하지 못하는 이유

    • 소수점을 표현하기 위해 일부 비트를 사용하기 때문에 (부호도 표현해야 함) 같은 크기의 정수 계열 형식과 같은 크기의 수를 표현할 수 없다.
    • 산술 연산 과정이 정수 계열 형식보다 복잡해서 느리다.
  • float, double

    • IEEE754라는 표준 알고리즘에 기반한 데이터 형식

      • 4바이트(32비트) float형식의 수
        • 1비트 - 부호 비트
        • 8비트 - 지수부(소수점 위치)
        • 23비트 - 가수부(수)
      /* FloatingPoint */
      
      using System; 
      
      namespace FloatingPoint 
      { 
          class MainApp 
          { 
              static void Main(string[] args) 
              { 
                  float a  = 3.1415_9265_3589_7932_3846f; 
                  Console.WriteLine(a); // 3.1415927
      
                  double b = 3.1415_9265_3589_7932_3846; 
                  Console.WriteLine(b); // 3.141592653589793
      
               } 
           } 
       } 

    • 정밀도

      • 부동 소수점 형식(float, double)은 각각 자신의 가수부가 담을 수 있는 부분까지만 저장하고 나머지는 버리고 표현한다.
      • float, double은 한정된 정밀도를 가진다.
      • double(복수 정밀도, Double Precision) 형식은 float(단일 정밀도 Single Precision)에 비해 두 배의 메모리(8바이트)를 사용하여 float보다는 정밀도가 높다.
  • decimal

    • 실수를 다루는 데이터 형식

    • 부동 소수점과는 다른 방식으로 소수를 다루며 정밀도가 훨씬 높다

      /* Decimal */
      
      using System;
      
       namespace Decimal
       {
           class MainApp
           {
               static void Main(string[] args)
               {
                   float a = 3.1415_9265_3589_7932_3846_2643_3832_79f; // f : float
                   double b = 3.1415_9265_3589_7932_3846_2643_3832_79; // 아무것도 없으면 : double
                   decimal c = 3.1415_9265_3589_7932_3846_2643_3832_79m; // m : decimal
       
                   Console.WriteLine(a);
                   Console.WriteLine(b);
                   Console.WriteLine(c);
                }
            }
        }

    • decimal 형식도 표현 범위의 한계가 있다.

    • 정밀도는 float < double < decimal 순으로 높다.


3.4.2 문자 형식, 문자열 형식

  • char : 개별 문자 표현, 작은 따옴표(')로 표시
  • string : 문자열 표현, 큰 따옴표(")로 표시
/* Char */

using System; 
 
namespace Char 
{ 
    class MainApp 
    { 
        static void Main(string[] args) 
        { 
            char a = '안'; 
            char b = '녕'; 
            char c = '하'; 
            char d = '세'; 
            char e = '요'; 
 
            Console.Write(a); // Console.Write() : 데이터 출력 후 줄바꿈 X
            Console.Write(b); 
            Console.Write(c); 
            Console.Write(d); 
            Console.Write(e); 
            Console.WriteLine(); // Console.Write() : 데이터 출력 후 줄바꿈 O 
        } 
    } 
} 

/* String */

using System; 
 
namespace String 
{ 
    class MainApp 
    { 
        static void Main(string[] args) 
        { 
            string a = "안녕하세요?"; 
            string b = "박상현입니다."; 
 
            Console.WriteLine(a); 
            Console.WriteLine(b);                 
        } 
    } 
} 


3.4.3 논리 형식

데이터 형식설명크기(바이트)범위
bool논리 형식1(8비트)true, false
  • bool형식은 1바이트 크기의 데이터 형식
  • 컴퓨터가 기본적으로 다루는 데이터의 크기가 바이트 단위이기 때문에 1비트만 저장하려 해도 한 바이트가 통째로 사용됨
/* Bool */

using System; 
 
namespace Bool 
{ 
    class MainApp 
    { 
        static void Main(string[] args) 
        { 
            bool a = true; 
            bool b = false; 
 
            Console.WriteLine(a); 
            Console.WriteLine(b); 
        } 
    } 
} 


3.4.4 object 형식

  • 어떤 물건(데이터)든지 다룰 수 있는 데이터 형식 ("상속"의 효과)
  • C#은 모든 데이터 형식(기본 데이터 형식뿐 아니라 모든 복합 데이터 형식, 심지어 프로그래머들이 만드는 데이터 형식마저도)이 자동으로 object형식으로부터 상속받게 함
/* Object */

using System;

namespace Object
{
    class Program
    {
        static void Main(string[] args)
        {
            object a = 123;
            object b = 3.141592653589793238462643383279m;
            object c = true;
            object d = "안녕하세요.";

            Console.WriteLine(a);
            Console.WriteLine(b);
            Console.WriteLine(c);
            Console.WriteLine(d);
        }
    }
}


3.4.5 박싱, 언박싱

  • 박싱(Boxing)
    • 값 타입(Value Type)의 객체를 참조 타입(Reference Type)으로 변환하는 작업 (값 형식의 데이터를 힙에 할당하는 것. 따라서 스택 메모리에는 힙 메모리에 할당되어 있는 값 형식의 데이터의 주소가 저장되어있다.)
    • 값 형식의 데이터를 할당하려는 시도가 이루어지면 object 형식은 박싱을 통해 해당 데이터를 힙에 할당
      object a = 20;

  • 언박싱(Unboxing)
    • 참조 타입을 값 타입으로 변환하는 작업
    • 힙 메모리에 박싱된 값을 꺼내 값 형식 변수에 저장하는 과정
      object a = 20; // a는 20이 박싱되어 저장된 힙 참조
       int    b = (int)a; // b는 a가 참조하고 있는 메모리로부터 값 복사
/* BoxingUnboxing */

using System;

namespace BoxingUnboxing
{
    class MainApp
    {
        static void Main(string[] args)
        {
            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);
        }
    }
}


3.4.6 형식 변환(Type Conversion)

변수를 다른 데이터 형식의 변수에 옮겨 담는 것

  • 값 형식과 참조 형식 간의 형식 변환
    • 박싱, 언박싱
  • 크기(표현 범위)가 서로 다른 정수 형식 사이의 변환
    • 용량의 차이 때문에 문제 발생 (ex. 오버플로)
using System;

namespace IntegralConversion
{
    class MainApp
    {
        static void Main(string[] args)
        {
            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, double→float)변환하려면 10진수로 복원한 후, 다시 2진수로 변환해서 기록하게 된다. 문제는 2진수로 표현하는 소수가 완전하지 않다는 것
using System;

namespace FloatConversion
{
    class MainApp
    {
        static void Main(string[] args)
        {
            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);
        }
    }
}

위 코드 결과에서 69.6875와 0.1의 결과가 다른 이유
2진 배수의 끝자리는 손실이 적다.
1 2 4 8 이나
0.5 0.25 0.125 0.0625 는 손실이 없다.

부동소수는 큰 값이 될수록 오차가 커진다.
float의 경우 2^23 부터 소수를 표현할 수 없게된다.

0.1은 2진으로 볼때
1 / 2^n을 해서 떨어지지 않는다.

때문에 실제로는 float에 0.100000001490116119384765625 로 저장되고 float은 오차를 감안하여 이 값을 0.1에 아주 근접한(자기가 표현하기에) 값으로 표현하게 된다.
그러나 이를 double로 변환했을때 기존 오차 값이 그대로 남게되고, double 입장에선 이 오차값은 아주아주 큰 오차값이므로 실제 값이라 판단해 출력값은 0.1보다는 조금 크거나 작은 값이 실제로 출력되는 것이다.

0.100000001490116119384765625는 float이 보기에 오차가 거의 없는 완벽히 0.1로 인식될 수 있는 값이지만 double이 볼땐 1.490116119e-9 만큼의 오차가 있는것으로 보고 그 결과가 반영되는 것이다.

  • 부호 있는 정수 형식과 부호 없는 정수 형식 사이의 변환
using System;

namespace SignedUnsignedConversion
{
    class MainApp
    {
        static void Main(string[] args)
        {
            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);
        }
    }
}

  • 부동 소수점 형식과 정수 형식 사이의 변환
    • 부동 소수점 형식의 변수를 정수 형식으로 변환하면 데이터에서 소수점 아래는 버리고 소수점 위의 값만 남김
using System;

namespace FloatToIntegral
{
    class MainApp
    {
        static void Main(string[] args)
        {
            float a = 0.9f;
            int b = (int)a;
            Console.WriteLine(b);

            float c = 1.1f;
            int d = (int)c;
            Console.WriteLine(d);
        }
    }
}

  • 문자열과 숫자 사이의 변환
    • Parse() : 문자열 → 숫자
      int a = int.Parse("12345");
       float b = float.Parse("123.45");
    • ToString() : 숫자 → 문자열
       int c = 12345;
        string d = c.ToString();
       
        float e = 123.45;
        string f = e.ToString();
using System;

namespace StringNumberConversion
{
    class MainApp
    {
        static void Main(string[] args)
        {
            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.2345";
            float h = float.Parse(g);
            Console.WriteLine(h);
        }
    }
}


3.5 상수와 열거 형식

프로그래머의 실수로 인한 프로그래머의 버그를 줄이기 위해 담긴 데이터를 절대 바꿀 수 없는 상수와 열거 형식을 사용

3.5.1 상수(Constants)

  • 선언 형식
    const 자료형 상수명 =;
  • 변수와 똑같이 사용할 수 있으나 선언 후에는 데이터를 변경할 수 없다.
using System;

namespace Constant
{
    class MainApp
    {
        static void Main(string[] args)
        {
            const int MAX_INT = 2147483647; 
            const int MIN_INT = -2147483648;

            Console.WriteLine(MAX_INT);
            Console.WriteLine(MIN_INT);
        }
    }
}

상수의 값을 바꾸려고 할 경우 : 컴파일러가 에러 메시지 출력


3.5.2 열거 형식(Enumerator)

  • 여러 개의 상수 정리

  • 선언 형식

    enum 열거 형식명 : 기반자료형 { 상수1, 상수2, 상수3, ··· }
    // 기반자료형은 정수 계열만 사용할 수 있다. (byte, sbyte, short, ushort, int, uint, long, ulong, char)
    // default : int
  • 열거 형식의 각 요소는 기본적으로 컴파일러에서 값을 자동으로 할당받는다. (0부터 선언된 순서에 따라 1씩 증가된 값을 컴파일러가 자동으로 할당)

    using System;
    
     namespace Enum
     {
          class MainApp
         {
             enum DialogResult { YES, NO, CANCEL, CONFIRM, OK }
    
             static void Main(string[] args)
             {
                 Console.WriteLine((int)DialogResult.YES);
                 Console.WriteLine((int)DialogResult.NO);
                 Console.WriteLine((int)DialogResult.CANCEL);
                 Console.WriteLine((int)DialogResult.CONFIRM);
                 Console.WriteLine((int)DialogResult.OK);
             }
         }
     }
    

  • 열거 형식의 변수를 선언하여 값을 대입하는 예

 using System; 
 
 namespace Enum2 
 { 
     class MainApp 
     { 
         enum DialogResult { YES, NO, CANCEL, CONFIRM, OK } 
 
         static void Main(string[] args) 
         { 
             DialogResult result = DialogResult.YES; 
 
             Console.WriteLine(result == DialogResult.YES); 
             Console.WriteLine(result == DialogResult.NO); 
             Console.WriteLine(result == DialogResult.CANCEL); 
             Console.WriteLine(result == DialogResult.CONFIRM); 
             Console.WriteLine(result == DialogResult.OK); 
         } 
     } 
 } 

  • 열거 형식에 프로그래머가 원하는 값을 직접 대입할 수도 있다.
    enum 열거형식명 { 상수1 =1 + 상수2 =2, 상수3 =3, ···}
    • 열거 형식을 선언할 때 프로그래머가 어떤 값도 할당하지 않은 요소에는 컴파일러가 앞 요소에 이어 자동으로 +1된 값을 할당한다.
	using System;

    namespace Enum3
    {
         class MainApp
      {
          enum DialogResult { YES = 10, NO, CANCEL, CONFIRM = 50, OK }
  
          static void Main(string[] args)
          {
              Console.WriteLine((int)DialogResult.YES);
              Console.WriteLine((int)DialogResult.NO); // 컴파일러 : 자동으로 앞 요소 +1
              Console.WriteLine((int)DialogResult.CANCEL);
              Console.WriteLine((int)DialogResult.CONFIRM);
              Console.WriteLine((int)DialogResult.OK);
          }
      } 
   }


3.6 Nullable 형식

  • C# 컴파일러는 메모리 공간에 반드시 어떤 값이든 넣어야 에러를 발생시키지 않는다.

  • 어떤 값도 가지지 않는 변수가 필요할 때는 변수에게 할당된 메모리 공간을 비워둘 수 있도록 Nullable 형식을 사용한다.

    데이터형식? 변수이름;
  • 속성

    • HasValue : 해당 변수가 값을 갖고 있는지 or 그렇지 않은지
    • Value : 변수에 담겨 있는 값
    int? a = null;
    
    Console.WriteLine(a.HasValue); // a는 null이므로 False 출력
    
    a = 37;
    Console.WriteLine(a.HasValue); // a는 37을 갖고 있으므로 True 출력
    Console.WriteLine(a.Value); // 37 출력
/* Nullable */

using System; 
     
namespace Nullalble 
{ 
    class MainApp 
    { 
        static void Main(string[] args) 
        { 
            int? a = null; 
     
            Console.WriteLine(a.HasValue); 
            Console.WriteLine(a != null); 
                             
            a = 3; 
     
            Console.WriteLine(a.HasValue); 
            Console.WriteLine(a != null); 
            Console.WriteLine(a.Value); 
        } 
    } 
} 


3.7 var

  • C#은 강력한 형식 검사를 하는 언어지만, var키워드를 통해 약한 형식 검사를 하는 언어의 편리함도 지원
  • var를 사용해 변수를 선언하면 컴파일러가 자동으로 해당 변수의 형식 지정
  • var를 통해 변수를 선언하려면 선언과 동시에 초기화를 해야 함
    var a = 3;  // a는 int형식
     var b = "Hello";  // b는 string형식
  • var는 지역 변수로만 사용 가능
using System;

namespace UsingVar
{
	class MainApp
    {
    	static void Main(string[] args)
        {
        	var a = 20; // var로 선언하는 변수는 반드시 선언과 동시에 초기화
            Console.WriteLine("Type:{0}, Value: {1}", a.GetType(), a);
            
            var b = 3.1414213;
            Console.WriteLine("Type: {0}, Value: {1}, b.GetType(), b);
            
            var c = "Hello, World!";
            Console.WriteLine("Type: {0}, Value: {1}", c.GetType(), c);
            
            var d = new int[] {10, 20 30};
            Console.Write("Type: {0}, Value: ", d.GetType());
            foreach (var e in d)
            	Console.Write("{0} ", e);
            
            Console.WriteLine();
        }
    }
}

object vs var

object a = 20;

위 코드가 실행되면 CLR은 20을 박싱해 힙에 넣어놓고 a가 힙을 가리키도록 만듦

var a =20;

컴파일 시점에 컴파일러가 a에 적합한 데이터 형식을 파악해 int a =20;으로 바꿔 컴파일. CLR이 해당 코드를 실행할 때는 a가 var로 선언되었는지조차 눈치채지 못하고 int형식의 객체 a에 20을 담아 스택에 올리는 것.


3.8 공용 형식 시스템 (CTS, Common Type System)

  • C#의 데이터 형식 체계는 CTS 표준(.NET 형식 체계 표준)을 따르고 있다.
    = C#이 갖고 있는 기본 데이터 형식과 복합 데이터 형식, 값 형식과 참조 형식을 아우르는 이 모든 체계는 공용 형식 시스템으로부터 왔다.
  • 공용 형식 시스템의 형식은 각 언어에서 코드에 그대로 사용할 수 있다.
using System;

namespace CTS
{
    class MainApp
    {
        static void Main(string[] args)
        {
            System.Int32 a = 123;  // int
            int b = 456;

            // GetType() : 해당 객체의 실제 형식 표시
            // ToString() : 변수의 데이터를 문자열로 표시
            Console.WriteLine("a type:{0}, value:{1}", a.GetType().ToString(), a);
            Console.WriteLine("b type:{0}, value:{1}", b.GetType().ToString(), b);

            System.String c = "abc";  // string
            string d = "def";

            Console.WriteLine("c type:{0}, value:{1}", c.GetType().ToString(), c);
            Console.WriteLine("d type:{0}, value:{1}", d.GetType().ToString(), d);
        }
    }
}


3.9 문자열 다루기

3.9.1 탐색 메소드

using static System.Console;

namespace StringSearch
{
    class MainApp
    {
        static void Main(string[] args)
        {
            string greeting = "Good Morning";

            WriteLine(greeting);
            WriteLine();

            // IndexOf() : 문자 또는 문자열 앞에서부터 몇번째에 있는지 탐색
            WriteLine("IndexOf 'Good' : {0}", greeting.IndexOf("Good"));
            WriteLine("IndexOf 'o' : {0}", greeting.IndexOf('o'));

            // LastIndexOf() : 문자 또는 문자열 뒤에서부터 몇번째에 있는지 탐색
            WriteLine("LastIndexOf 'Good' : {0}", greeting.LastIndexOf("Good"));
            WriteLine("LastIndexOf 'o' : {0}", greeting.LastIndexOf("o"));

            // StartsWith() : 현재 문자열이 지정 문자열로 시작하는가 (bool)
            WriteLine("StartsWith 'Good' : {0}", greeting.StartsWith("Good"));
            WriteLine("StartsWith 'Morning' : {0}", greeting.StartsWith("Morning"));

            // EndsWith() : 현재 문자열이 지정 문자열로 끝나는가 (bool)
            WriteLine("EndsWith 'Good' : {0}", greeting.EndsWith("Good"));
            WriteLine("EndsWith 'Morning' : {0}", greeting.EndsWith("Morning"));

            // Contains() : 현재 문자열이 지정 문자열을 포함하는가 (bool)
            WriteLine("Contains 'Evening' : {0}", greeting.Contains("Evening"));
            WriteLine("Contains 'Morning' : {0}", greeting.Contains("Morning"));

            // Replace() : 문자열 바꾸기
            WriteLine("Replaced 'Morning' with 'Evening': {0}",
                greeting.Replace("Morning", "Evening"));
        }
    }
}


3.9.2 변형 메소드

// StringModify //
using static System.Console;

namespace StringModify
{
    class MainApp
    {
        static void Main(string[] args)
        {
            WriteLine("Lower() : '{0}'", "ABC".ToLower()); // 대문자 → 소문자
            WriteLine("ToUpper() : '{0}'", "abc".ToUpper()); // 소문자 → 대문자

            WriteLine("Insert() : '{0}'", "Happy Friday!".Insert(5, " Sunny")); // 지정 위치에 문자열 삽입
            WriteLine("Remove() : '{0}'", "I Don't Love You.".Remove(2, 6)); // 지정 위치의 문자열 제거

            WriteLine("Trim() : '{0}'", " No Spaces ".Trim());  // 앞,뒤 공백 제거
            WriteLine("TrimStart() : '{0}'", " No Spaces ".TrimStart());  // 앞 공백 제거
            WriteLine("TrimEnd() : '{0}'", " No Spaces ".TrimEnd());  // 뒤 공백 제거
        }
    }
}


3.9.3 분할 메소드

// ![](https://velog.velcdn.com/cloudflare/ssu_hyun/85393572-0aae-4ab1-be2e-6441478e149b/image.png)
 //

using System;
using static System.Console;

namespace StringSlice
{
    class MainApp
    {
        static void Main(string[] args)
        {
            string greeting = "Good morning.";

            WriteLine(greeting.Substring(0, 5)); // "morning"
            WriteLine(greeting.Substring(5)); // "Good"
            WriteLine();

            string[] arr = greeting.Split(
                new string[] { " " }, StringSplitOptions.None); // 공백 기준 문자열 분리한 문자열의 배열
            WriteLine("Word Count : {0}", arr.Length); // arr 배열 길이

            foreach (string element in arr)  // arr 배열 loop
                WriteLine("{0}", element);
        }
    }
}

3.9.4 문자열 서식

서식 : 문자열이 일정한 틀과 모양을 갖추는 것

Format()

  • string.Format( 문자열 틀, 문자열 틀 안에 집어넣을 데이터 );

    • Console.WriteLine() 메소드는 내부적으로 string.Format() 메소드를 사용한다.
      • Console.WriteLine("제목 : {0}", "이것이 C#이다.");
  • 서식 항목(Format Item) : 문자열 틀에 입력하는 {} 부분

    • 서식 항목 추가 옵션

      왼쪽/오른쪽 맞춤

      숫자로 서식 항목이 차지할 공간의 크기 선택
      +,-로 오른쪽,왼쪽 정렬 선택

      • string.Format("{0,-10}DEF", "ABC");
        • 0 : 0번째 지정 데이터
        • 10 : 10의 공간 생성
        • - : 왼쪽부터 데이터 채우기!
      • string.Format("{0,10}DEF", "ABC");
        • 0 : 0번째 지정 데이터
        • 10 : 10의 공간 생성
        • (+) : 오른쪽부터 데이터 채우기
      // StringFormatBasic //
      using System;
      using static System.Console;
      namespace StringFormatBasic
      {
        class MainApp
        {
          static void Main(string[] args)
          {
            string fmt = "{0,-20}{1,-15}{2,30}";
            // {0,-20} : 첫번째 지정 데이터, 20 공간에서 왼쪽부터 데이터 채우기
            // {1,-15} : 두번째 지정 데이터, 15 공간에서 왼쪽부터 데이터 채우기
            // {2,30} : 세번째 지정 데이터, 30 공간에서 오른쪽부터 데이터 채우기
            WriteLine(fmt, "Publisher", "Author", "Title");
            WriteLine(fmt, "Marvel", "Stan Lee", "Iron Man");
            WriteLine(fmt, "Hanbit", "Sanghyun Park", "This is C#");
            WriteLine(fmt, "Prentice Hall", "K&R", "The C Programming Language");
          }
        }
      }

      숫자 서식화

      • 다양한 형태로 수를 서식화하는 기능 지원
      • MSDN의 표준 숫자 서식 문자열

      • 서식 지정자
        • 자릿수 지정자(Precision Specifier)
          • 서식 지정자 자릿수 지정자(0~99)
          • D5 : 10진수 5자리
        // StringFormatNumber //
        using System;
        		   using static System.Console;
        namespace StringFormatNumber
        {
          class MainApp
          {
            static void Main(string[] args)
            {
               // D : 10진수
               WriteLine("10진수: {0:D}", 123); // 10진수 서식화
               WriteLine("10진수: {0:D5}", 123); // 10진수 서식화 5자리
               // X : 16진수 
               WriteLine("16진수: 0x{0:X}", 0xFF1234);  // 16진수 서식화
               WriteLine("16진수: 0x{0:X8}", 0xFF1234);  // 16진수 서식화 8자리
               // N : 숫자
               WriteLine("숫자: {0:N}", 123456789); // 입력된 수 콤마로 구분해 출력
               WriteLine("숫자: {0:N0}", 123456789); // 자릿수 0은 소숫점 이하 제거
               // F : 고정소수점
               WriteLine("고정소수점: {0:F}", 123.45);  // 고정 소수점 형식 서식화
               WriteLine("고정소수점: {0:F5}", 123.456);  // 소수점 아래 5자리
               // E : 공학용
               WriteLine("공학: {0:E}", 123.456789);  // 지수 표기로 서식화
             }
           }
         }

      날짜 및 시간 서식화

      • 날짜 및 시간 서식 지정자
        *MSDN의 사용자 지정 날자 및 시간 서식 문자열
      • CultureInfo : 문화권 정보 표시
        *문화권 이름 - MSDN의 Product Behavior
        using System;
        using System.Globalization;
        using static System.Console;
        namespace StringFormatDatetime
        {
          class MainApp
          {
            static void Main(string[] args)
            {
              // 2018년 11월 3일 23시 18분 22초
              DateTime dt = new DateTime(2018, 11, 3, 23, 18, 22);
              // 국가 및 지역 설정에 따라 다른 결과 출력
              WriteLine("12시간 형식: {0:yyyy-MM-dd tt hh:mm:ss (ddd)}", dt);
              WriteLine("24시간 형식: {0:yyyy-MM-dd HH:mm:ss (dddd)}", dt);
              // CultureInfo 클래스를 통해 문화권 정보 표시
              // 한국
              CultureInfo ciKo = new CultureInfo("ko-KR");
              WriteLine();
              WriteLine(dt.ToString("yyyy-MM-dd tt hh:mm:ss (ddd)", ciKo));  // 12시간 형식
              WriteLine(dt.ToString("yyyy-MM-dd HH:mm:ss (dddd)", ciKo));  // 24시간 형식
              WriteLine(dt.ToString(ciKo));  // 한글 기본 형식
              // 미국
              CultureInfo ciEn = new CultureInfo("en-US");
              WriteLine();
              WriteLine(dt.ToString("yyyy-MM-dd tt hh:mm:ss (ddd)", ciEn));  // 12시간 형식
              WriteLine(dt.ToString("yyyy-MM-dd HH:mm:ss (dddd)", ciEn));  // 24시간 형식
              WriteLine(dt.ToString(ciEn));  // 미국(영어) 기본 형식
            }
          }
        }

문자열 보간(interpolation)

  • 보간(補間, interpolation) : 비거나 누락된 부분을 채운다
  • 문자열 틀 앞에 $ 붙이기 + 서식항목에 첨자가 아닌 식
  • 문자열 보간에서 사용되는 문자열 틀의 구조
  • 가독성, 코드의 양 부분에서 문자열 보간이 훨씬 나음
  • C# 6.0 이상이 지원되는 개발환경이라면 문자열 보간을 사용하는 것이 합리적
// StringInterpolation //

using System;
using static System.Console;

namespace StringInterpolation
{
    class MainApp
    {
        static void Main(string[] args)
        {
            string name = "김튼튼";
            int age = 23;
            WriteLine($"{name,-10}, {age:D3}");
            // {name,-10} : name, 10공간에서 왼쪽부터 채우기
            // {age:D3} : age, 10진수 3자리로 표현

            name = "박날씬";
            age = 30;
            WriteLine($"{name}, {age,-10:D3}");
            // {age,-10:D3} : age, 10공간에서 왼쪽부터 채우기, 10진수 3자리로 표현

            name = "이비실";
            age = 17;
            WriteLine($"{name}, {(age > 20 ? "성인" : "미성년자")}"); // 조건문
        }
    }
}



연습 문제

  1. 다음과 같이 사용자로부터 사각형의 너비와 높이를 입력받아 넓이를 계산하는 프로그램을 완성하세요. 다음 코드 중 주석 부분을 바꾸면 됩니다.
사각형의 너비를 입력하세요.
30
사각형의 높이를 입력하세요.
40
사격형의 넓이는 : 1200
using System;

namespace RectArea
{
    class MainApp
    {
        public static void Main()
        {
            Console.WriteLine("사각형의 너비를 입력하세요.");
            string width = Console.ReadLine();

            Console.WriteLine("사각형의 높이를 입력하세요.");
            string height = Console.ReadLine();
			
            
            Console.WriteLine($"사각형의 넓이는 : {int.Parse(width) * int.Parse(height)}");
            // int area = int.Parse(width) * int.Parse(height);
            // Console.WriteLine($"사각형의 넓이는: {area}");
        }

    }
}

  1. 다음 코드에서 잘못된 부분을 찾고, 그 이유를 설명하세요.
int a = 7.3;
// int는 정수형 데이터 타입이므로 소수 7.3을 int형 변수 a에 할당하는 것은 잘못되었다. int를 float으로 바꿔주거나 (int)를 7.3앞에 붙여주어 해결할 수 있다.
float b = 3.14;
double c = a * b;
char d = "abc";
// char은 문자 1개를 저장하며 변수를 정의할 때 작은 따옴표(')를 사용한다.
string e = '한';
// string은 문자열(여러개의 문자)을 저장하며 변수를 정의할 때 큰 따옴표(")를 사용한다.
  1. 값 형식과 참조 형식의 차이는 무엇인가요?
    값 형식과 참조 형식의 차이는 변수의 값, 즉 데이터를 어디에 담느냐에 있다. 값 형식은 데이터를 스택에 저장하고 참조 형식은 데이터를 힙에 저장한다.

  2. 박싱과 언박싱을 설명하세요.
    박싱은 값 형식의 데이터를 힙에 할당하는 것을 말한다. 따라서 스택 메모리에는 힙 메모리에 할당되어 있는 값 형식의 데이터의 주소가 저장되어있다.
    언박싱은 힙 메모리에 저장되어있는 값 형식의 데이터를 값 형식의 변수에 저장하는 과정이다.

  3. 다음 코드를 컴파일한 후의 a와 b는 각각 어떤 데이터 형식이겠습니까?

var a = 2020;  // int
var b = "double";  // string

0개의 댓글