C#

[C# 기본 정리 3] 형 변환, Nullable, var, 전역변수, 문자열

sunlight-dby 2025. 4. 4. 00:37

 

'이것이 C#이다 개정판'의 chapter 3을 정리한 글입니다.

 


형 변환 (타입 캐스팅)

형 변환은 자주 사용되지만, 사소한 부주의로 인해 데이터에 손상을 입힐 수 있기에 주의하여야 합니다.

 

[주의할 점]

  • 데이터 손실
    • 큰 데이터 타입에서 작은 데이터 타입으로 변환할 때, 값이 범위를 초과하면(오버플로우가 발생되면) 데이터 손실이 발생할 수 있습니다.
    • 언더플로우 상황에서도 동일한 상황이 발생할 수 있습니다.
  • 형 변환 오류
    • 잘못된 형 변환을 시도하면 예외가 발생할 수 있습니다.
  • 정확도 문제
    • 데이터 손실의 측면과 유사합니다. 부동 소수점 형식 간의 변환에 있어서 정확도가 떨어질 수 있습니다.
      • float에서 double로 변환할 때는 문제가 없을 수 있지만, 

[예방 방법]

  • 명시적 변환 사용
    • 가능한 경우 명시적 변환을 사용하여 변환의 의도를 명확히 하고, 변환이 안전한지 확인합니다.
int intVlaue = (int)doubleValue;  // 명시적 변환
  • 형 검사
    • is 또는 as 키워드를 사용하여 타입 체크를 수행하고, 변환이 안전한지 확인합니다.
    • is 연산자
      • 캐스팅이 가능하면 true, 캐스팅이 불가능하면 false를 리턴합니다.
    • as 연산자
      • 캐스팅에 성공하면 캐스팅 결과를 리턴하고, 캐스팅에 실패하면 null 값을 리턴합니다.
      • as 연산자는 참조 타입 간의 캐스팅에만 가능합니다. (값 타입은 불가능합니다.)
if (isObj is string strValue)
{
    // strValue 사용
}

object asObj = "Hello, World!";

// 참조 타입 간의 형 변환
string str = asObj as string;

if (str != null)
{
    // str 사용
}

// 값 형식 간의 형 변환 (잘못된 예시)
int number = asObj as int;  // 컴파일 오류 발생
  • Try-Catch 사용
    • 예외가 발생할 수 있는 변환을 시도할 때는 try-catch를 사용하여 오류를 처리합니다.
try
{
    int intValue  = (int)doubleValue;
}
catch (InvalidCastException ex)
{
    // 오류 처리
}
  • 범위 검증
    • 변환하기 전에 값이 변환할 타입의 범위 내에 있는지 확인합니다.
if (doubleValue >= int.MinValue && doubleValue <= int.MaxValue)
{
    int intValue = (int)doubleValue;
}

 

 

형 변환 : 크기가 서로 다른 부동 소수점 형식 사이의 변환

[float에서 double로의 형 변환]

부동 소수점 형식 사이에서 형 변환이 일어나게 되면, 10진수로 복원한 후, 다시 2진수로 변화해서 기록하게 됩니다.

 

위의 코드에서 살펴보면, 69.6575는 안전하게 형 변환이 이루어졌지만, 0.1은 그렇지 못했습니다.

먼저 0.1은 왜 안전하게 형 변환이 이루어지지 않았는지를 확인해보겠습니다.

 

float 타입으로 0.1을 저장할 때, 0.1은 이진수로 정확히 표현할 수 없어서 근사값으로 저장됩니다.

  • 0.1₍₁₀₎ = 0.0.000110011001100110011001100110011...₍₂₎
  • float 타입으로 저장할 수 있는 값은 0.1의 근사값인 0.10000000149011612₍₁₀₎로 저장됩니다.

float 타입의 0.1을 double 타입으로 형 변환을 시도할 시, 0.10000000149011612₍₁₀₎가 그대로 double로 표현이 되게 되고,

결국 (double)x를 저장한 double y는 0.10000000149011612₍₁₀₎의 값을 가지게 됩니다.

 

[예시]

using System;
using static System.Console;

namespace FloatConversion
{
    class MainApp
    {
        static void Main(string[] args)
        {
            // 크기가 서로 다른 부동 소수점 형식 사이의 변환
            float a = 69.6875f;
            WriteLine("a : {0}", a);  // a : 69.6575

            double b = (double)a;
            WriteLine("b : {0}", b);  // b : 69.6875
            WriteLine("69.6875 == b : {0}", 69.6875 == b); // 69.6875 == b : True

            float x = 0.1f;
            WriteLine("x : {0}", x);  // x : 0.1

            double y = (double)x;
            WriteLine("y : {0}", y);  // y : 0.10000000149011612
            WriteLine("0.1 == y : {0}", 0.1 == y);  // 0.1 == y : False

            double n = 3.141592653589793238d;
            WriteLine("n : {0}", n);  // n : 3.1415926535897934
            
            float  m = (float)n;
            WriteLine("m : {0}", m);  // m : 3.1415927
            WriteLine("3.141592653589793238 == m : {0}", 3.141592653589793238 == m);
            // 3.141592653589793228 == m : False
        }
    }
}

 

 

[부동 소수점 형식에서 오버 플로우와 언더 플로우]

부동 소수점 형식에서 또한 오버 플로우와 언더 플로우가 발생할 수 있다.

 

float의 양수 최대값에서 오버플로우가 발생하면 Infinty가, float의 양수 최소값에서 언더 플로우가 발생하면 0이 결과로 나오게 됩니다.

반대로, float의 음수 최대값에서 오버플로우가 발생하면 -Infinty가, float의 음수 최소값에서 언더 플로우가 발생하면 0이 결과로 나오게 됩니다.

 

[예시]

using System;
using System.Reflection.Metadata.Ecma335;
using static System.Console;

namespace FloatConversion
{
    class MainApp
    {
        static void Main(string[] args)
        {
            // 부동 소수점에서의 오버플로우와 언더플로우
            float maxSValue = 3.4e38f;
            float overflowMaxSResult = maxSValue * 2.0f;
            WriteLine("Result : {0}", overflowMaxSResult);
            // float의 양수 최대값에서 오버플로우 발생 -> Result : Infinity

            float minSValue = 1.4e-45f;
            float underflowMinSResult = minSValue / 10.0f;
            WriteLine("Result : {0}", underflowMinSResult);
            // float의 양수 최소값에서 언더 플로우 발생 -> Result : 0

            float maxUsValue = -3.4028235E+38f;
            float underflowMaxUsResult = maxUsValue - 1E+38f;
            WriteLine("Result : {0}", underflowMaxUsResult);
            // float의 음수 최대값에서 오버 플로우 발생 -> Result : -Infinity

            float minUsValue = 1.4e-45f;
            float underflowMinUsResult = minUsValue / 10.0f;
            WriteLine("Result : {0}", underflowMinUsResult);
            // float의 음수 최소값에서 언더 플로우 발생 -> Result : 0
        }
    }
}

 

 

[checked & unchecked]

오버 플로우와 언더 플로우에 대해 공부하면서 checked와 unchecked라는 키워드를 알게 되었습니다.

 

checked 키워드는 오버 플로우와 언더 플로우가 발생하는지를 확인하고, '처리되지 않은 예외'로 컴파일러에서 걸러주도록 하는 키워드입니다.

반대로, unchecked는 오버 플로우와 언더 플로우가 발생하더라도, 컴파일러에게 의도한 바니깐 예외로 처리하지 않아도 된다는 키워드입니다.

 

[예시]

using System;
using static System.Console;

namespace CheckOverflow
{
	class MainApp
    {
    	static void Main(string[] args)
        {
            byte n = 255;
            byte m = 0;
            
            unchecked
            {
            	n++;  // overflow
                m--;  // underflow
            }
            WriteLine("overflow : {0}", n);  // 0
            WriteLine("underflow : {0}", m); // 255
            
            byte a = 255;
            byte b = 0;
            checked
            {
            	a++;  // overflow
                b--;  // underflow
            }
            WriteLine("overflow : {0}", a);
            WriteLine("underflow : {0}", b);
            // Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
        }
	}    
}

 

※ checked를 사용할 때, 원래 a와 b를 구분하여 테스트하여야 합니다. 
    위의 코드대로 하면, b는 제대로 검사가 이루어지지 않습니다. 편의상 한번에 적어놨다고 생각하시면 되겠습니다.

 

※ 처리되지 않은 예외로 출력되는 메시지를 보면 오버 플로우 상황과 언더 플로우 상황 모두 overflow라고 표현이 됩니다.

    vs에서는 overflow, underflow 모두 overflow라고 표현을 하니, 참고하시면 되겠습니다.

 

형 변환 : 문자열과 숫자

문자열을 숫자로, 숫자를 문자열로 형 변환하기 위해서는 ToString ( ) 메서드가 필요합니다.

 

[ToString( ) 메서드]

정수 계열 데이터 형식이나 부동 소수점 데이터 형식을 문자열로 변환하는 메서드입니다.

object로 물려받은 ToString ( ) 메서드를 오버라이드(Override)하여 사용합니다.

 

[예시]

using System;
using static System.Console;

namespace StringNumberConversation
{
    class MainApp
    {
    	static void Main(string[] args)
        {
            int a = 123;
            string b = a.ToString();
            WriteLine(b);    // 123
            
            float c = 3.14f;
            string d = c.ToString();
            WriteLine(d);    // 3.14
            
            string e = "123456";
            int f = Convert.ToInt32(e);
            WriteLine(f);	// 123456
            
            string g = "1.2345";
            float h = float.Parse(g);
            WriteLine(h);	// 1.2345
        }
    }
}

 

 

Nullable

C# 환경에서, C# 컴파일러는 메모리 공간에 반드시 어떤 값이든 넣도록 강제합니다. 초기화를 강제한다는 소리입니다.

 

하지만, 프로그래밍을 하면서 어떤 값도 가지지 않는 변수가 필요할 때가 있습니다.

이를 위해, 변수에게 할당된 메모리 공간을 비워둘 수 있는 Nullable 형식이 존재합니다.

 

[선언 방식]

데이터 형식? 변수이름;

 

Nullable 형식을 사용할 수 있는 경우는 값 형식에 한해서입니다.

참조 형식은 사용할 수 없습니다.

int? a = null;
float? b = null;
double? c = null;

 

[HasValue & Value]

  • HasValue
    • 해당 변수가 값을 갖고 있는지 또는 그렇지 않은지를 나타내는 키워드입니다.
  • Value
    • 변수에 담겨 있는 값을 나타내는 키워드입니다.
int? a = null;

WriteLine( a.HasValue );  // a는 null이므로 False 출력

a = 37;
WriteLine( a.HasValue );  // a는 37의 값을 갖고 있으므로 True 출력
WriteLine( a.Value );     // 37을 출력

 

var

C#은 변수나 상수에 대해 깐깐하게 형식 검사를 하는 강력한 형식의 언어(Strong Typed Language)입니다.

C#은 강력한 형식 검사를 하는 언어이지만, var 키워드를 통해서 약한 형식 검사를 하는 언어의 편리함도 지원합니다.

 

var를 사용해서 변수를 선언하면 컴파일러가 자동으로 해당 변수의 형식을 지원해줍니다.

컴파일 시점에 컴파일러가 a에 적합한 데이터 형식을 파악해서 컴파일을 해주는 것입니다.

CLR이 var 타입의 변수가 포함된 코드를 실행할 때는, 그 변수가 var로 선언되어있는지 조차 파악하지 못하고 적합한 데이터 형식의 객체에 값을 담아 스택에 올리게 됩니다.

 

단, var 키워드를 이용해서 변수를 선언하려면 반드시 선언과 동시에 초기화를 해줘야 합니다. 그 이유는 컴파일러가 그 데이터를 보고 형식을 추론할 수 있기 때문입니다.

그리고, var 키워드는 지역 변수로만 사용할 수 있습니다.

 

var a = 20;
WriteLine("Type : {0}, Value : {1}", a.GetType(), a);  // Type : System.Int32, Value : 20

var b = 3.141592;
WriteLine("Type : {0}, Value : {1}", a.GetType(), a);  // System.Double, Value : 3.141592

var c ="Hello, World!";
WriteLine("Type : {0}, Value : {1}", a.GetType(), a);  // System.String, Value : Hello, World!

var d = new int[] { 10, 20, 30 };
WriteLine("Type : {0}, Value : {1}", a.GetType(), a);  // System.Int32[], Value : 10 20 30

for each (var e in d)
{
    Write("{0} ", e);
}

 

※ 참고로 c# 환경에서는 전역 변수를 지원하지 않습니다. 하지만, 유사한 기능을 구현할 수 있는 몇 가지 방법이 있습니다.

 

전역 변수

1. 정적 클래스와 정적 필드 사용

정적 클래스를 만들고 그 안에 정적 필드를 정의하여 전역 변수처럼 사용할 수 있습니다.

using System;
using static System.Console;

class GlobalVariables
{
    public static int GlobalValue = 100; // 전역 변수처럼 사용
}

class Program
{
    static void Main()
    {
        WriteLine(GlobalVariables.GlobalValue); // 100 출력

        GlobalVariables.GlobalValue = 200;
        WriteLine(GlobalVariables.GlobalValue); // 200 출력
    }
}

 

GlobalVariables 클래스에서 static 변수(GlobalValue)를 선언하면, 어디서든 GlobalVariables.GlobalValue로 접근할 수 있습니다.

하지만 C++이나 다른 언어에서 사용하는 전역 변수와는 다르게 C#은 반드시 클래스 내부에서 선언해야 합니다.

 

2. 싱글톤 패턴 사용

싱글톤 패턴을 사용하여 전역적으로 접근 가능한 인스턴스를 만들 수 있습니다.

public class GlobalState
{
    private static GlobalState instance;
    public int GlobalValue { get; set; } = 100;

    private GlobalState() { } // 외부에서 인스턴스 생성 방지

    public static GlobalState Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GlobalState();
            }
            return instance;
        }
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        GlobalState.Instance.GlobalValue = 500;
        Console.WriteLine(GlobalState.Instance.GlobalValue); // 500 출력
    }
}

 

싱글톤 패턴은 하나의 인스턴스만 생성되어 프로그램 전체에서 공유된다는 특징을 가지고 있습니다.

그렇기에, 전역 변수처럼 사용이 가능합니다.

 

 

문자열

[문자열의 탐색 메서드]

메서드 설명 반환 타입
   IndexOf(str)   현재 문자열 내에서 찾고자 하는 지정된 문자 또는 문자열(str)의 위치를 앞에서부터 찾습니다.   Integer
   LastIndexOf(str)   현재 문자열 내에서 찾고자 하는 지정된 문자 또는 문자열(str)의 위치를 뒤에서부터 찾습니다.   Integer
   StartsWith(str)   현재 문자열이 지정된 문자 또는 문자열(str)로 시작하는지를 평가합니다.   Boolean
   EndsWith(str)   현재 문자열이 지정된 문자 또는 문자열(str)로 끝나는지를 평가합니다.   Boolean
   Contains(str)   현재 문자열이 지정된 문자 또는 문자열(str)을 포함하는지를 평가합니다.   Boolean
   Replace(str1, str2)   현재 문자열에서 지정된 문자 또는 문자열(str1)이 다른 지정된 문자 또는 문자열(str2)
  모두 바뀐 새 문자열을 반환합니다.
  String

※ Replace( ) 메서드를 사용할 때, str1과 str2는 모두 문자이거나, 모두 문자열이어야 한다.

   문자와 문자열을 함께 사용하여 대체하는 것은 불가능하다.

 

[문자열의 변형 메서드]

메서드 설명
   str.ToLower( )   현재 문자열(str)의 모든 대문자를 소문자로 바꾼 새 문자열을 반환합니다.
   str.ToUpper( )   현재 문자열(str)의 모든 소문자를 대문자로 바꾼 새 문자열을 반환합니다.
   str1.Insert(n, str2)   현재 문자열(str1)의 지정된 인덱스(n)에 지정된 문자열(str2)이 삽입된 새 문자열을 반환합니다.
   str.Remove(n, m)   현재 문자열(str)의 지정된 인덱스(n)로부터 지정된 수(m)만큼의 문자가 삭제된 새 문자열을 반환합니다.
   str.Trim( )   현재 문자열(str)의 앞/뒤에 있는 공백을 삭제한 새 문자열을 반환합니다.
   str.TrimStart( )   현재 문자열(str)의 앞에 있는 공백을 삭제한 새 문자열을 반환합니다.
   str.TrimEnd( )
  현재 문자열(str)의 뒤에 있는 공백을 삭제한 새 문자열을 반환합니다.

 

[문자열의 분할 메서드]

메서드 설명
   str1.Split(str2 )   지정된 문자(str2)를 기준으로 현재 문자열(str1)을 분리한 다음, 분리한 문자열의 배열을 반환합니다.
   str.Substring(n, m)
   str.Substring(n)
  현재 문자열(str)의 지정된 위치(n)으로부터 지정된 수(m)만큼의 문자로 이루어진 새 문자열을 반환합니다.
  지정된 수(m)가 존재하지 않는다면, 지정된 수로부터 문자열의 저장된 값의 끝까지를 새 문자열로 반환합니다.
using System;
using static System.Console;

namespace StringSlice
{
    class MainApp
    {
        static void Main(string[] args)
        {
            string greeting = "Good moring.";
            
            WriteLine(greeting.Substring(0, 5));  // "Good"
            WriteLine(greeting.Substring(5));     // "morning"
            
            string[] arr = greeting.Split(
            	new string[] {" "}, StringSplitOptions.None);
            
            WriteLine("World Count : {0}", arr.Length);  // Word Count : 2
                
            foreach (string element in arr)
                WriteLine("{0}", element);  // Good
                                            // morning.
		}
    }
}

 

위의 코드를 보면, 표에서는 설명한 것처럼 간단하지 않게 Split을 사용한 것으로 보일 수 있습니다.

 

[ new string[ ] { " " } ]

먼저, new string[ ] { " " }라는 표현은 명시적으로 문자열 배열을 생성하여 공백을 구분자로 사용하는 것을 의미합니다.

만약 문자열 배열을 생성하지 않고, 문자열 " "(공백)을 직접 구분자로 사용하여 분할하려고 해도, C#에서는 " "가 자동으로 배열로 처리되게 됩니다.

string[] arr = greeting.Split(new string[] { " " }, StringSplitOptions.None);
string[] arr = greeting.Split(" ", StringSplitOptions.None);

// Good
// morning.

// 모두 동일한 결과를 출력한다.

 

그리고, Split에서 인자를 문자열 배열을 받는 이유는 C# 환경에서 그렇게 설정했기 때문입니다.

vs에서 제공하는 string.Split의 정보

[ StringSplitOptions ]

두 번째로, StringSplitOptions이라는 표현이 있습니다. 해당 표현은 열거형으로 두 가지 옵션을 제공합니다.

 

    1. None

        StringSplitOptions가 기본값으로, 구문에 사용하지 않아도 자동으로 포함됩니다.

        문자열을 분할할 때, 빈 항목을 포함하여 모든 부분을 반환합니다. 즉, 구분자가 연속으로 나타나더라도 빈 문자열을 결과 배열에           포함시킵니다.

 

    2. RemoveEmptyEntries

        이 문자열을 사용하면, 구분자에 의해 생성된 빈 항목을 결과 배열에서 제거합니다. 즉, 분할 후 빈 문자열을 포함하지 않습니다.

string testString = "aaVaaaVVaaaa";

string[] arr1 = testString.Split(new string[] { "V" }, StringSplitOptions.None);
// aa
// aaa
//
// aaaa

string[] arr2 = testString.Split(new string[] { "V" });
// aa
// aaa
//
// aaaa

string[] arr3 = testString.Split(new string[] { "V" }, StringSplitOptions.RemoveEmptyEntries);
// aa
// aaa
// aaaa

 

 

[문자열의 서식]

 

[ Format( ) 메서드 ]

문자열의 서식 항복 옵션은 아래와 같습니다.

 

{ 첨자, 맞춤: 서식 문자열}

  • 첨자 : 서식화할 값의 인덱스
  • 맞춤 : 값의 정렬 방향과 폭
string fmt = "{0, -20}{1, -15}{2, 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");

// Publisher           Author                                  Title
// Marvel              Stan Lee                             Iron Man
// Hanbit              Sanghyun Park                      This is C#
// Prentice Hall       K&R                The C Programming Language
  • 서식 문자열 : 값의 형식화 방법
// D : 10진수
WriteLine("10진수 : {0:D}", 123);   // 10진수 : 123
WriteLine("10진수 : {0:D5}", 123);  // 10진수 : 00123

// X : 16진수
WriteLine("16진수 : 0x{0:X}", 0xFF1234);   // 16진수 : 0xFF1234
WriteLine("16진수 : 0x{0:X8}", 0xFF1234);  // 16진수 : 0x00FF1234

// N : 숫자
WriteLine("숫자 : 0x{0:N}", 123456789);   // 숫자 : 123,456,789.00
WriteLine("숫자 : 0x{0:N0}", 123456789);  // 숫자 : 123,456,789  (자릿수 0은 소수점 이하를 제거함)

// F : 고정 소수점
WriteLine("고정 소수점 : 0x{0:F}", 123.456);   // 고정 소수점 : 123.45
WriteLine("고정 소수점 : 0x{0:F5}", 123.456);  // 고정 소수점 : 123.45600
WriteLine("고정 소수점 : 0x{0:F1}", 123.456);  // 고정 소수점 : 123.5 (반올림)

// E : 공학용
WriteLine("공학 : {0:E}", 123.456789);  // 공학 : 1.234568E+002
WriteLine("공학 : {0:E0}", 123.456789); // 공학 : 1E+002
WriteLine("공학 : {0:E2}", 123.456789); // 공학 : 1.23E+002
WriteLine("공학 : {0:E5}", 123.456789); // 공학 : 1.23457E+002 (반올림)
using System;
using System.Globalization;
using static System.Console;

namespace StringFormatDatetime
{
    class MainApp
    {
        static void Main(string[] args)
        {
            DateTime dt = new DateTime(2025, 04, 04, 00, 25, 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 ciKo = new CultureInfo("ko-KR");
            WriteLine();
            WriteLine(dt.ToString("yyyy-MM-dd tt hh:mm:ss (ddd)", ciKo));
            WriteLine(dt.ToString("yyyy-MM-dd HH:mm:ss (dddd)", ciKo));
            WriteLine(dt.ToString(ciKo));

            CultureInfo ciEn = new CultureInfo("en-US");
            WriteLine();
            WriteLine(dt.ToString("yyyy-MM-dd tt hh:mm:ss (ddd)", ciEn));
            WriteLine(dt.ToString("yyyy-MM-dd HH:mm:ss (dddd)", ciEn));
            WriteLine(dt.ToString(ciEn));
        }
    }
}

// 12시간 형식 : 2025-04-04 오전 12:25:22 (금)
// 24시간 형식 : 2025-04-04 00:25:22 (금요일)

// 2025-04-04 오전 12:25:22 (금)
// 2025-04-04 00:25:22 (금요일)
// 2025-04-04 오전 12:25:22

// 2025-04-04 AM 12:25:22 (Fri)
// 2025-04-04 00:25:22 (Friday)
// 4/4/2025 12:25:22 AM

 

[ 문자열 보간 ]

문자열 보간에서 사용되는 문자열 틀의 구조는 아래와 같습니다.

 

$ "텍스트{<보간식>[,길이] [:서식]}텍스트{...}..."

  • 보간식 : 보통 인스턴스의 이름을 지정하지만, 출력할 객체를 반환하는 식을 지정할 수도 있습니다.
  • 길이 : 길이 옵션, 서식 항복의 [맞춤]과 동일합니다.
  • 서식 : 서식 항복의 [서식 문자열]과 동일합니다.
string name = "철수";
int age = 27;
WriteLine($"{name,-10}, {age:D3}");

name = "민수";
age = 30;
WriteLine($"{name}, {age, -10:D3}");

name = "영희";
age = 19;
WriteLine($"{name}, {(age > 20 ? "성인" : "미성년자")}");

WriteLine($"{age.GetType()}");

// 철수        , 027
// 민수, 030
// 영희, 미성년자
// System.Int32