Ch 2. 변수

kdkdhoho·2022년 10월 27일
0

자바의 정석

목록 보기
2/4

1. 변수

1.1 변수란?

값을 저장할 수 있는 메모리상의 공간

1.2 변수의 선언과 초기화

변수를 선언한 이후부터는 반드시 변수를 초기화해야 한다. 메모리는 여러 프로그램이 공유하는 자원이므로, 다른 프로그램에 의해 저장된 garbage value, 쓰레기값이 남아있을 수 있기 때문이다.

참고로, 지역변수는 사용되기 전에 반드시 초기화 해야하지만, 클래스변수와 인스턴스 변수는 초기화를 생략할 수 있다.

2. 변수의 타입

기본형과 참조형

자료형은 크게 기본형(primitive type)과 참조형(reference type)으로 나눌 수 있다.

기본형은 data를 저장하고, 참조형은 memory address를 값으로 가진다.

자바는 C언어와 달리 참조형 변수 간의 연산을 할 수 없으므로 실제 연산에 사용되는 것은 모두 기본형 변수이다.

참조형 변수를 선언할 때는 type으로 클래스의 이름을 사용하므로 클래스의 이름이 참조변수의 type이 된다.

2.1 기본형(primitive type)

  • 문자형인 char은 내부적으로 정수(유니코드)로 저장하기 때문에 정수형과 별반 다르지 않으며, 정수형 또는 실수형과 연산도 가능하다.

  • boolean은 다른 기본형과 연산이 불가능하다.

    즉, boolean을 제외한 나머지 7개의 기본형은 서로 연산과 변환이 가능하다.

  • 정수는 byte, short, int, long으로 총 4가지가 있다.

    일반적으로는 int를 많이 사용한다. 이유는, CPU가 가장 효율적으로 처리할 수 있는 타입이기 때문이다.

    효율적인 실행보다 메모리를 절약하려면, byteshort를 이용하자.

  • 실수형은 정수형과 저장형식이 달라서 같은 크기라도 훨씬 큰 값을 표현할 수 있으나 오차가 발생할 수 있다는 단점이 있다.

    그래서 정밀도(precision)가 중요한데, 정밀도가 높을수록 발생할 수 있는 오차의 범위가 줄어든다.

    float의 정밀도는 7자리이고, double의 정밀도는 15자리이다.

2.2 상수와 리터럴

상수는 변수와 마찬가지로 값을 저장할 수 있는 공간이지만, 변수와 달리 한번 값을 저장하면 다른 값으로 변경할 수 없다.

상수를 선언하는 방법은 변수와 동일하며, 단지 변수 타입 앞에 final을 붙여주면 된다.

상수는 반드시 선언과 동시에 초기화해야 하며, 그 뒤로는 상수의 값을 변경할 수 없다.

리터럴

원래 12, 123, 3.14, 'A' 같은 값들이 상수인데, 프로그래밍에서 상수를 값을 한 번 저장하면 변경할 수 없는 저장공간으로 정의하였기에 이와 구분하기 위해 리터럴이라고 부른다.

리터럴의 타입과 접미사

  • 정수형의 경우, long 타입의 리터럴에 접미사 'l' 또는 'L'을 붙이고, 접미사가 없으면 int 타입의 리터럴이다.

    'l'이 1과 헷갈릴 수 있으니 가능한 'L'을 사용하자.

    ex) long l = 10L;

    byte와 short 타입의 리터럴은 별도로 존재하지 않는다.

  • JDK1.7부터 정수형 리터럴 중간에 구분자 '_' 를 넣을 수 있게 되어 큰 수를 편하게 읽을 수 있다.

    ex) long big = 100_000_000_000L;

  • 10진수 외에도 2, 8, 16진수로 표현된 리터럴을 변수에 저장할 수 있다.

    • 16진수는 접미사 '0x' 또는 '0X'
    • 8진수는 '0'을 붙인다.
  • 실수형 리터럴에는 접미사를 붙여 타입을 구분한다.

    • float 타입에는 'f'
    • double 타입에는 'd' 를 붙인다.
    • 실수형에서의 기본 자료형은 double이기에 접미사 'd'는 생략 가능하다.

      실수형 리터럴인데, 접미사가 없으면 double 타입 리터럴이다.
  • 10의 제곱을 나타내는 기호 'E' 또는 'e', 그리고 접미사 'f', 'F', 'd', 'D'를 포함하면 실수형 리터럴로 간주한다.

    ex) float f = 3.14e3f; // 3140.0f && double d = 1e1; // 10.0 && double d = 1e-3; // 0.001

2.3 형식화된 출력 - printf()

  • printf()지시자를 통해 변수의 값을 여러 가지 형식으로 변환하여 출력할 수 있다.

    지시자의 전체 목록을 보려면, Formatter 클래스(java.util 패키지)를 참조하자.

  • 주로 출력하는 형태를 맞추기위해 사용된다.

    • 정수형의 경우, %d || %5d || %-5d || %05d 와 같이 출력할 수 있다.

    • %x%o#를 사용하면 접두사 0x0이 각각 붙는다.

      System.out.printf("hex=%x\n", hex); // hex = ffffffffffffffff
      System.out.printf("hex=%#x\n", hex); // hex = 0xffffffffffffffff
      System.out.printf("hex=%#X\n", hex); // hex = 0Xffffffffffffffff
    • 10진수를 2진수로 출력해주는 지시자는 없기 때문에, 정수를 2진 문자열로 변환해주는 Integer.toBinaryString(int i)를 사용해야 한다.

      정수형 매개변수를 2진수로 변환해서 문자열로 반환한다.

    • 실수형의 경우 일반적으로 %f가 사용되고, %e는 지수형태로 출력할 때, %g는 값을 간략하게 표현할 때 사용된다.

      %f의 경우 소수점 아래 6자리까지만 출력하기 때문에 소수점 아래 7자리에서 반올림한다.

      %전체자리.소수점아래자리f 와 같이 출력 형식을 지정할 수 있다.

      전체자리 앞에 '0'을 붙여 공백을 0으로 채울 수 있다.

    • 문자열의 경우

      • [%s]로 문자열 길이만큼 출력공간을 확보하거나
      • [%20s]로 최소 20글자 출력공간 확보(우측정렬)
      • [%-20s]로 좌측정렬
      • [%.8s]로 왼쪽에서 8글자만 출력할 수 있다.

2.4 화면에서 입력받기 - Scanner

Scanner scanner = new Scanner(System.in)으로 사용하자.

참고: 화면으로부터 입력받는 방법들은 근본적으로 모두 같으므로 차이를 비교할 필요는 없다. 그저 상황에 맞는 편리한 것을 선택해서 사용하면 된다.

3. 진법

3.1 10진법과 2진법

3.2 비트(bit)와 바이트(byte)

  • 한 자리의 2진수를 비트(bit)라고 하며, 1비트는 컴퓨터가 값을 저장할 수 있는 최소단위이다.

  • 1byte는 8bit이다.

  • wordCPU가 한 번에 처리할 수 있는 데이터의 크기이다.

참고로, niblle은 4bit를 저장할 수 있는 단위이다.

  • n비트는 2^n개의 값을 표현할 수 있다. 그리고 n비트로 10진수를 표현한다면, 표현가능한 숫자의 범위는 0 ~ (2^n)-1 이 된다.

3.3 8진법과 16진법

2진법으로만 수를 표현하면 자리수가 굉장히 길어지기에 8진법과 16진법을 사용한다.

8진법은 2진법 3자리를, 16진법은 2진법 4자리를 한 자리로 표현할 수 있다.

2진수를 8진수, 16진수로 변환

2진수를 8진수로 변환하려면, 2진수를 뒤에서부터 3자리 씩 끊어 그에 해당하는 8진수로 변환한다.

16진수로 변환하려면, 4자리씩 끊어 그에 해당하는 16진수로 변환한다.

3.4 정수의 진법 변환

10진수를 n진수로 변환

변환하려는 10진수를 변환하려는 n진수의 n으로 더이상 나눌 수 없을 때까지 나누며, 몫 옆에는 나머지를 적어준다.

더이상 나눌 수 없는 수에 도달하면 그 수부터 나머지를 차례로 올라가며 적어준다.

10진수를 2진수로 나눌 때를 생각해보자. 이것이 8진수와 16진수에도 똑같이 적용된다 !

n진수를 10진수로 변환

이번엔 n진수를 10진수로 변환하기이다.

어느 진법의 수라도 10진수로 변환하는 방법은 똑같다.

각 자리의 수에 해당 단위를 곱하여 모두 더해주면 된다.

2진수를 10진수로 변환할 때를 생각하자.

3.5 실수의 진법변환

10진 소수를 2진 소수로 변환하는 방법

10진 소수의 소수부분에 2를 계속 곱한다. 10진 소수의 소수부분이 0이 될 때까지 반복한다.

소수부분이 0이 되지 않고 무한히 반복되는 경우도 있다.

위 과정을 반복하는 과정에서 정수부만을 구한 순서대로 차례로 적어주면 된다.

예를 들어, 10진수 0.625를 2진수로 변환하는 방법은, 0.625 2 = 1.25 -> 0.25 2 = 0.5 -> 0.5 * 2 = 1.0
따라서 0.101(2) 가 된다.

2진 소수를 10진 소수로 변환하는 방법

2진 정수를 10진 정수로 변환하는 방법과 동일하다.

각 자리의 수에 각 자리의 단위를 곱하여 모두 더해주면 된다.

3.6 음수의 2진 표현 - 2의 보수법

앞에서 n비트의 2진수로 표현할 수 있는 값의 개수는 모두 2^n개이므로, 4비트의 2진수로는 모두 2^4=16개의 값을 표현할 수 있다.

이 값을 모두 0과 양수로만 표현하면 0부터 15까지의 정수를 나타낼 수 있다.

그러면 4비트의 2진수로, 음수는 어떻게 표현할까?

4비트의 2진수의 절반인 8개는 0으로 시작하고, 나머지 절반은 1로 시작하니까, 1로 시작하는 2진수를 음수표현에 사용하자.

이렇게하면, 왼쪽의 첫 번째 비트(MSB)가 0이면 양수, 1이면 음수이므로 첫 번째 비트만으로 부호를 알 수 있다.

그러면 앞자리가 1로 시작하는 수들을 -0부터 채워보자.

이렇게 배치할 경우 첫 번째 비트를 바꿈으로서 부호를 바꿀 수 있게 되었다.

하지만 문제가 생긴다. 5(0101)과 -5(1101)을 더했을 때 0(0000)이 나와야하지만 10010이 나와 값이 맞지 않다. 게다가 2진수가 증가할 때 10진수는 감소한다는 문제점이 생긴다.

그러나 2의 보수법으로 배치를 하면 모든 문제가 해결된다. 다만, 첫 번째 비트를 바꾸는 것만으로 값의 부호를 바꿀 수 없게 되었다.

2의 보수

어떤 수의 'n의 보수'는 더했을 때 n이 되는 수를 말한다.

7의 '10의 보수'는 3이고, 3의 '10의 보수'는 7이다. 3과 7은 '10의 보수 관계'에 있다고 말한다.

'2의 보수 관계' 역시, 더해서 2가 되는 수의 관계를 말하며 10진수 2는 2진수로 '10'이다. 2진수로 '10'은 자리올림이 발생하고 0이 되는 수를 뜻한다.

그래서 '2의 보수 관계'에 있는 수를 더하면 자리올림이 발생하고 0이 된다.

음수를 2진수로 표현하기

10진 음의 정수를 2진수로 표현하려면, 먼저 10진 음의 정수의 절대값을 2진수로 변환한다.

그 다음에 이 2진수의 '2의 보수'를 구하면 된다.

'2의 보수'는 위에서 봤듯이 더해서 2가 되어야 하고, 이를 2진수로 나타내면 자리올림이 발생하고 0이 되어야 한다.

그러기 위해서 10000에 구한 2진수를 빼주면 된다.

하지만 이 방법은 자리수가 길어지면 힘들다.

따라서, '1의 보수를 구한 다음 1을 더해주는 방법을 사용하자. 이렇게 구하면 간단히 2의 보수를 구할 수 있다.

'1의 보수'는 0을 1로, 1을 0으로 바꾸면 되므로 구하기 쉽다.

왜 '1의 보수 + 1'은 '2의 보수'인가?

어떤 2진수가 있을 때, 이 2진수의 1의 보수를 더하면 모든 자리가 1이 된다.

여기에 +1을 해주면 자리올림이 발생하고 0이 되므로, 2의 보수를 구한 값과 동일한 결과를 도출하게 된다.

4. 기본형(primitive type)

4.1 논리형 - boolean

논리형에는 boolean 하나밖에 없다. booleanfalse 혹은 true의 값만 가질 수 있다.

두 가지의 값만 표현하면 되므로 1bit만으로도 충분하지만, 자바에서는 데이터를 다루는 최소단위가 byte이기 때문에, boolean의 크기가 1byte이다.

4.2 문자형 - char

문자형도 char 하나밖에 없다. char 타입의 크기는 2byte이다.

char에는 문자가 저장되는 것 같지만, 사실 문자의 유니코드가 저장된다.

그래서 char ch = 65 처럼 직접 유니코드를 저장할 수 있다.

만약 어떤 문자의 유니코드를 알고 싶으면, char형 변수를 int형으로 변환하면 된다.

특수문자 다루기

이스케이프 문자에 대한 내용이다.

char타입의 표현형식

char타입의 크기는 2byte이므로 2^16(=65536)개 만큼 값을 나타낼 수 있다.

char타입은 사실상 정수가 저장된다. 하지만 정수형과 달리 음수를 나타낼 필요가 없으므로 표현할 수 있는 값의 범위가 다르다.

같은 2byte인 short는 범위의 절반을 음수표현에 사용하므로 -32768~32767을 범위로 가지지만, char0~65535의 범위를 가진다.

인코딩과 디코딩

문자 인코딩은 문자를 숫자로 변환하는 것이고, 문자 디코딩은 숫자를 문자로 변환하는 것이다.

인코딩에 사용된 코드표와 디코딩에 사용된 코드표가 다르면 엉뚱한 글자로 나타난다.

word파일을 한글로 열었을 때 글자가 깨지는 것을 생각하면 된다.

word에서 사용하는 코드표와, 한글에서 사용하는 코드표가 다르기때문에 발생하는 현상이다.

아스키(ASCII)

ASCII는 정보교환을 위한 '미국 표준 코드'라는 뜻이다.

아스키는 2^7(128)의 character set을 제공하는 7bit 부호로, 처음 32개 문자는 인쇄와 전송 제어용으로 사용되는 제어문자로 출력할 수 없고 마지막 문자 DEL을 제외한 33번째 이후의 문자들은 출력할 수 있는 문자들로, 기호와 숫자, 영문자로 이루어져있다.

4.3 정수형 - byte, short, int, long

byte(1byte) - short(2byte) - int(4byte) - long(8byte)

정수형의 표현형식과 범위

정수형 변수에 어떤 진법의 리터럴을 저장해도, 실제로는 2진수로 바뀌어 저장된다.

이 2진수가 저장되는 형식은 크게 정수형실수형이 있으며, 정수형최상위 1bit는 부호를, 나머지 n-1 bit는 값의 크기를 나타낸다.

모든 정수형은 부호가 있는 정수이므로, n비트의 정수형이 표현할 수 있는 값의 범위는 (2 ^ n-1) ~ ((2 ^ n-1) - 1) 이다.

정수형의 선택 기준

저장하려는 값의 크기에 따라 변수형을 정하면 되겠지만, 웬만하면 byte, short 대신, int 형을 쓰자.

이유 첫 번째로, byteshortint보다 크기가 작아서 연산 시에 overflow가 일어나기가 쉽다.

두 번째로, JVM의 피연산자 스택(operand stack)이 피연산자를 4 byte단위로 저장하기 때문에 크기가 4 byte보다 작은 자료형(byte, short)의 값을 계산할 때는 4 byte로 변환하여 연산이 수행된다.

따라서 오히려 int를 사용하는 것이 더 효율적이다.

때에 따라, int의 범위를 넘는 수를 다룰 때에는 long을 사용하거나, 효율보다 메모리 공간이 더 중요할 때에는 byteshort를 사용하자.

long의 범위를 벗어날 때에는 실수형 타입이나 BigInteger 클래스를 사용하자.

정수형의 오버플로우

만약 4bit 2진수의 최대값인 1111에 1을 더하면 어떤 결과가 발생할까?

2진수 1111에 1을 더하면 10000이 된다. 이때 표현하는 bit수는 4bit 뿐이므로, 맨 앞의 1은 사라지고 나머지 0000만이 남게된다.

따라서 가장 큰 수에서 가장 작은 수가 된다.

이렇게, 해당 타입이 표현할 수 있는 값의 범위를 넘어서는 것오버플로우라고 한다.

그러면 반대로 0000에 1을 빼면 어떻게 될까?

결과는 1111이 되어 가장 큰 값이 된다.

그래서 정수형 타입이 표현할 수 있는 최대값에 1을 더하면 최소값이 되고, 최소값에 1을 빼면 최대값이 된다.

부호있는 정수의 오버플로우

부호가 없는 정수와 부호가 있는 정수는 표현 범위, 즉 최대값과 최소값이 다르므로 오버플로우가 발생하는 시점이 다르다.

부호없는 정수는 2진수로 '0000'이 될 때 오버플로우가 발생하고, 부호있는 정수는 부호비트가 0에서 1이 될 때 발생한다.

4.4 실수형 - float, double

실수형의 범위와 정밀도

타입지정 가능한 값의 범위(양수)정밀도크기
float1.4 x 10^-45 ~ 3.4 x 10^387자리4byte
double4.9 x 10^-324 ~ 1.8 x 10^30815자리8byte

위 범위는 양수만 나타낸 것이므로, -를 붙이면 지정 가능한 음수 값의 범위가 된다.

즉, float타입의 표현범위는 '-3.4 x 10^38 ~ 3.4 x 10^38'이지만, '-1.4 x 10^-45 ~ 1.4 x 10^45' 범위(0은 제외)는 표현할 수 없다.

실수형은 소수점도 표현해야 하므로 '얼마나 큰 값을 표현할 수 있는가?'도 중요하지만, 얼마나 0에 가깝게 표현할 수 있는가?도 중요하다.

Q. 실수형도 정수형처럼 오버플로우가 발생할까?

실수형에서 오버플로우가 발생한다. 하지만 정수형과 달리, 오버플로우가 발생하면 값이 무한대가 된다.

그리고 정수형에는 없는 언더플로우가 있는데, 이는, 실수형으로 표현할 수 없는 아주 작은 값, 즉 양의 최소값(1.4 x 10^-45)보다 작은 값이 되는 경우이다. 이 때 변수의 값은 0이 된다.

그런데, 4byte 정수형은 '약 +-2 x 10^9'의 값밖에 표현하지 못하는데, 실수형은 '+-3.4 x 10^38'과 같이 큰 수를 표현할 수 있을까?

바로, 값을 저장하는 형식이 다르기 때문이다.

정수형은 32bit 중, 1bit를 부호를 위한 bit로 사용하고 나머지는 값을 나타내지만, 실수형은 1bit를 부호로, 8bit를 지수, 나머지 23bit를 가수를 표현하는 데에 사용된다.

따라서 '2의 제곱을 곱한 형태(+-M x 2^E)'로 저장하기 때문이다.

double은 1/11/52 = 64bit

하지만, 실수형은 오차가 발생할 수 있다는 치명적인 단점이 존재한다.

float의 정밀도는 7자리로, 7자리 10진수를 실수형으로 오차없이 저장할 수 있다는 의미이다.

만약, 7자리보다 큰 10진수를 오차없이 실수형으로 저장하기 위해선 double형을
사용해야한다.

높은 정밀도때문에 double형이 실수형의 default type이 되었다. 실수형에서는 '값의 범위'보다 '높은 정밀도'가 더 중요하기 때문이다.

결론: 연산속도의 향상이나 메모리 절약을 위해선, float >
더 큰 값의 범위나 높은 정밀도를 위해선, double을 사용하자.

실수형의 저장형식

실수형은 정수와 달리, 값을 부동소수점 형태로 저장한다.

부동소수점은 실수를 '+-M x 2^E'와 같은 형태로 표현하는 것을 의미한다.

부동소수점은 부호(S), 지수(E), 가수(M)로 이루어져 있다.

  1. 부호(Sign bit)
  • 값이 0이면 양수, 1이면 음수이다.
  • 정수형과 달리, '2의 보수법'을 사용하지 않기에 부호를 바꾸려면 그저 부호비트만 0에서 1로 바꿔주면 된다.
  1. 지수(Exponent)
  • 부호있는 정수를 의미한다.
  • 2^E개의 값을 저장할 수 있다.
  • Nan, POSITIVE_INFINITY, NEGATIVE_INFINITY와 같은 예약어가 있다.
  1. 가수(Mantissa)
  • 실제 값인 가수를 저장하는 부분이다.
  • 2^M개의 값을 저장할 수 있다.

부동소수점의 오차 (필요 시 추가 예정)

5. 형변환

5.1 형변환(캐스팅, casting)이란?

5.2 형변환 방법

기본형(primitive type)에서 boolean을 제외한 나머지 타입들은 서로 형변환이 가능하다.

기본형과 참조형간의 형변환은 불가능하다.

5.3 정수형간의 형변환

큰 type(int)에서 작은 type(byte)으로 형변환 시, 작은 type보다 큰 bit의 값들은 모두 잘려져나간다. 이를 값 손실(loss of data)이 일어난다고 한다.

반대로 작은 type에서 큰 type으로 형변환 시, 새롭게 생기는 bit 자리는 원래 값이 양수라면 0, 음수라면 1을 채운다.

5.4 실수형간의 형변환

실수형도 마찬가지로 작은 타입에서 큰 타입으로 변환 시, 빈 공간을 0으로 채운다.

double에서 float으로 변환 시, float의 범위를 넘는 값을 형변환하는 경우, '+-무한대' 또는 '+-0' 으로 결과를 얻는다.

5.5 정수형과 실수형 간의 형변환

정수형을 실수형으로 변환

정수를 2진수로 변환한 다음에 정규화해서 실수의 저장형식에 맞게 저장한다.

주의할 점은, 실수형의 정밀도의 제한으로 인한 오차가 발생할 수 있다.

예를 들어 int의 최대값은 약 20억으로 최대 10자리의 정밀도를 요구한다. 하지만 float은 약 7자리의 정밀도만을 제공하므로 오차가 발생할 수 있다.

실수형을 정수형으로 변환

실수형의 소수점이하 값은 버려진다.

그래서 실수형을 정수형으로 형변환할 때 반올림이 발생하지 않는다.

만일 실수의 소수점을 버리고 남은 정수가, 정수형의 저장범위를 넘는 경우에는 정수의 오버플로우가 발생한 결과를 얻는다.

5.6 자동 형변환

서로 다른 타입간의 대입이나 연산을 할 때, 형변환으로 타입을 일치시키는 것이 원칙이지만, 경우에 따라 편의상의 이유로 형변환을 생략할 수 있다.

이는 컴파일러가 생략된 형변환을 자동적으로 추가한다.

하지만, 변수가 저장할 수 있는 값의 범위보다 큰 값을 저장하려는 경우에 형변환을 생략하면 에러가 발생한다.

ex) byte b = 1000

하지만 char ch = (char)1000 같이 명시적으로 형변환 할 경우, 컴파일러는 프로그래머가 의도적으로 했다고 판단하여 에러를 발생시키지 않는다.

서로 두 타입간의 덧셈에서는 두 타입 중 표현범위가 더 넓은 타입으로 형변환하여 타입을 일치시킨 다음에 연산을 수행한다.

값손실의 위험이 더 적기 때문이다.

이를 '산술 변환'이라고 한다.

자동 형변환의 규칙

컴파일러는 기존의 값을 최대한 보존할 수 있는 타입으로 자동 형변환한다.

이게 무슨 말이냐면, 값손실을 최대한 방지하면서 자동 형변환한다는 뜻이다.

때문에 표현범위가 좁은 타입에서 넓은 타입으로 형변환하는 경우에는 표현범위가 더 넓은 쪽으로 형변환된다.

하지만 반대의 경우, 명시적으로 형변환을 해줘야한다.

charshort의 경우 2byte로 크기가 같지만, 앞서 이야기했듯이 표현범위가 다르기 때문에 자동 형변환이 수행되지 않는다.

profile
newBlog == https://kdkdhoho.github.io

0개의 댓글