본문 바로가기
Book/Java의 정석

[Chapter 2] 변수

by slchoi 2022. 4. 1.
728x90
SMALL

'자바의 정석 3rd Edition'을 공부하며 정리한 내용입니다.

1. 변수(Variable)


1.1 변수(variable)란?

  • 의미: 단 하나의 값을 저장할 수 있는 메모리 공간
  • 하나의 변수에 단 하나의 값만 저장할 수 있으므로, 새로운 값을 저장하면 기존의 값은 사라짐

1.2 변수의 선언과 초기화

1. 변수의 선언

int          age     ;
(변수타입)   (변수이름)
  • 변수타입: 변수에 저장될 값이 어떤 타입(type)인지를 지정하는 것
    • 저장하고자하는 값의 종류에 맞게 변수의 타입을 선택해서 작성
    • 정수형, 실수형, 문자형 등 다양한 타입을 제공
  • 변수이름: 변수에 붙인 이름
    • 이름을 이용해 저장공간(변수)에 값을 저장하고, 저장된 값을 읽어옮
    • 같은 이름의 변수가 열 개 존재해서는 안됨. 서로 구별될 수 있어야하기 때문
  • 변수를 선언하면, 메모리의 빈 공간에 변수타입에 알맞는 크기의 저장공간이 확보되고, 이 저장공간은 변수이름을 통해 사용할 수 있게 됨

2. 변수의 초기화

  • 변수를 선언한 이후부터는 변수를 사용할 수 있으나, 그 전에 반드시 변수를 '초기화(initialization)'해야 함

    • 이유: 메모리는 여러 프로그램이 공유하는 자원이므로 전에 다른 프로그램에 의해 저정된 알 수 없는 값이 남아있을 수 있기 때문
  • 변수에 값을 저장할 때는 대입 연산자 '='를 이용. 오른쪽의 값을 왼쪽(변수)에 저장하라는 의미

      int age = 25; // 변수 age를 선언하고 25로 초기화
  • 변수는 한 줄에 하나씩 선언하는 것이 일반적이지만, 타입이 같은 경우 콤마를 구분자로 여러 변수를 한 줄에 선언하기도 함

      int a, b;
      int x = 0, y = 0;
  • 변수의 종류에 따라 변수의 초기화를 생략할 수 있는 경우도 있지만, 사용되기 전에 적절한 값으로 초기화하는 것이 좋음

    • 지역변수는 사용되기 전에 초기화를 반드시 해야 하지만 클래스변수와 인스턴스변수는 초기화를 생략할 수 있음 (자세한 내용은 6장에서)

3. 변수 계산 과정

age = age + 1;
-> age = 14 + 1; // 변수 age에 저장된 값을 읽어옮
-> age = 15; // 변수 age에 15를 저장
  • 변수에 값을 저장하는 대입연산은 우변의 모든 계산이 끝난 후에 제일 마지막에 수행됨

4. 두 변수의 값 교환하기

  • 변수 x, y가 있을 때, 두 변수에 담긴 값을 서로 바꾸려면 어떻게 해야 할까?

    int x = 10;
    int y = 20;
  • 변수를 하나 더 선언해 x의 값을 위한 임시 저장소로 사용

    int x = 10;
    int y = 20;
    int tmp;
  • 아래와 같은 순서로 값 옮기기

      1. 변수 x에 저장된 값을 변수 tmp에 저장
      tmp = x;
    
      2. 변수 y에 저장된 값을 변수 x에 저장
      x = y;
    
      3. 변수 tmp에 저장된 값을 변수 y에 저장
      y = tmp;

1.3 변수의 명명규칙

  • 프로그래밍에서 사용하는 모든 이름을 '식별자(identifier)'라고 함

  • 식별자는 같은 영역 내에서 서로 구분될 수 있어야 함

  • 식별자 규칙

    1. 대소문자가 구분되며 길이에 제한이 없다.
      • True와 true는 서로 다른 것으로 간주
      1. 예약어를 사용해서는 안된다.
      • true는 예약어라서 사용할 수 없지만, True는 가능
      1. 숫자로 시작해서는 안 된다.
      • top10은 허용하지만, 7up은 허용되지 않음
      1. 특수문자는 '_'와 '$'만을 허용한다.
      • $harp은 허용되지만, S#arp은 허용되지 않음
  • 권장하는 규칙

    1. 클래스 이름의 첫 글자는 항상 대문자로 한다
      • 변수와 메서드의 이름의 첫 글자는 항상 소문자로 함
      1. 여러 단어로 이루어진 이름은 단어의 첫 글자를 대문자로 한다.
      • LastIndexOf, StringBuffer
      1. 상수의 이름은 모두 대문자로 한다. 여러 단어로 이루어진 경우 '_'로 구분한다.
      • PI, MAX_NUMBER
  • 자바에서는 모든 이름에 유니코드에 포함된 문자들을 사용할 수 있지만, 적어도 클래스 이름은 ASCII 코드(영문자)로 하는 것이 좋음. 유니코드를 인식하지 못하는 운영체제도 있기 때문

  • 변수의 이름은 짧을수록 좋지만, 약간 길더라도 용도를 알기 쉽게 '의미있는 이름'으로 하는 것이 좋음. 변수 선언문에 주석으로 변수에 대한 정보를 주는 것도 좋음

      int curPos = 0;    // 현재 위치(current position)
      int lastPos = 0;   // 마지막 위치(last position)

2. 변수의 타입


  • 자료형(data tpye): 값(data)의 종류(type)에 따라 값이 저장될 공간의 크기와 저장형식을 정의한 것
    • 종류: 문자형(char), 정수형(byte, short,int, long), 실수형(float, double) 등
  • 변수를 선언할 때는 저장하려는 값의 특성을 고려해 가장 알맞은 자료형을 변수의 타입으로 선택

기본형과 참조형

  • 기본형 변수: 실제 값(data)를 저장
    • 종류: 논리형(boolean), 문자형(char), 정수형(byte, short, int, long), 실수형(float, double)
  • 참조형 변수: 어떤 값이 저장되어 있는 주소(memory address)를 값으로 가짐
    • 메모리 주소: 메모리에는 1 byte단위로 일련번호가 붙어있는데, 이 번호를 메모리 주소 또는 주소라고 함. 객체의 주소는 객체가 저장된 메모리 주소를 의미
    • 종류: 8개의 기본형을 제외한 나머지 타임
    • 참조형 변수를 선언할 때는 변수의 타입으로 클래스의 이름을 사용. 클래스의 이름이 참조변수의 타입이 됨. 새로운 클래스를 작성한다는 것은 새로운 참조형 변수를 추가하는 것
    • 참조형 변수 선언 방법: 기본형 변수와 같이 변수 이름 앞에 타입을 적어주는데 참조변수의 타입은 클래스의 이름
        클래스이름 변수이름; // 변수의 타입이 기본형이 아닌 것들은 모두 참조 변수
    • 참조 변수는 null 또는 객체의 주소를 값으로 가지며 참조변수의 초기화는 아래와 같음
        Date today = new Date() // Date 객체를 생성해서, 그 주소를 today에 저장
  • 자바는 참조형 변수 간의 연산을 할 수 없으므로 실제 연산에 사용되는 것은 모두 기본형 변수

참고) 자료형(data type) vs 타입(type)

  • 기본형은 저장할 값(data)의 종류에 따라 구분되므로 기본형의 종류를 얘기할 때는 '자료형(data type)'이라는 용어를 사용
  • 참조형은 항상 '객체의 주소(4 byte 정수)'를 저장하므로 값(data)이 아닌, 객체의 종류에 의해 구분되므로 참조형 변수의 종류를 구분할 때는 '타입(type)'이라는 용어를 사용
  • 타입(type)이 자료형(data type)을 포함하는 보다 넓은 의미의 용어이므로 굳이 구분하지 않아도 됨

2.1 기본형(primitive type)

종류/크기 1 byte 2 byte 4 byte 8 byte
논리형 boolean
문자형 char
정수형 byte short int long
실수형 float double
  • 논리형(boolean)

    • true와 false 중 하나의 값을 가지며 조건식과 논리적 계산에 사용
    • 다른 기본형과의 연산이 불가능. boolean을 제외한 나머지 7개의 기본형은 서로 연산과 변환이 가능
  • 문자형(char)

    • 문자를 저장하는데 사용되며 변수에 하나의 문자만 저장할 수 있음
    • 내부적으로 정수(유니코드)로 저장하기 때문에 정수형과 다르지 않으며, 정수형 또는 실수형과 연산도 가능
  • 정수형(byte, short, int, long)

    • 주로 int를 사용(CPU가 가장 효율적으로 처리할 수 있는 타입). byte는 이진 데이터를 다룰 때 사용되며, short는 C언어와의 호환을 위해 추가됨
    • 효율적인 실행보다 메모리를 절약하려면 byte나 short를 선택
    • int 타입은 대략 10자리 수의 값을 저장할 수 있음. 7~9자리의 수를 계산할 때는 넉넉하게 long 타입(약 19자리)로 선언하는 것이 좋음
  • 실수형(float, double)

    • 실수를 저장하는데 사용. 주로 double를 사용함
    • 정수형과 저장형식이 달라서 같은 크기라도 훨씬 큰 값을 표현할 수 있으나 오차가 발생할 수 있다는 단점이 있음. 따라서 정밀도가 중요!
      • 정밀도가 높을수록 발생할 수 있는 오차의 범위가 줄어듦. float이 정밀도는 7자리인데, 이것은 10진수로 7자리의 수를 오차없이 저장할 수 있다는 것을 의미
    • float는 큰 값을 저장할 수 있지만, 정밀도가 7자리 밖에 되지 않으므로 높은 정밀도가 필요한 경우에는 변수 타입으로 double을 선택

2.2 상수와 리터럴(constant & literal)

1. 상수(constant)

  • 값을 저장할 수 있는 공간 but 한 번 값을 저장하면 다른 값으로 변경 불가

  • 상수 선언 방법은 변수와 동일. 변수의 타입 앞에 키워드 'final'을 붙여주면 됨

      final int MAX_SPEED = 10 // 상수 MAX_SPEED를 선언 & 초기화
  • 상수는 반드시 선언과 동시에 초기화해야 하며, 그 후부터는 상수의 값을 변경할 수 없음

      final int MAX_SPEED;        // 에러. 상수는 선언과 동시에 초기화해야 함
      final int MAX_VALUE = 100;    // OK
      MAX_VALUE = 200;        // 에러. 상수의 값은 변경할 수 없음
  • 상수의 이름은 모두 대문자로 하는 관례. 여러 단어로 이루어져 있는 경우 '_'로 구분

2. 리터럴(literal)

  • 12, 133, 3.14, 'A'와 같은 값들이 '상수'인데, 프로그래밍에서는 상수를 '값을 한 번 저장하면 변경할 수 없는 저장공간'으로 정의하였기 때문에 이와 구분해 상수를 다른 이름으로 불러야 했음
  • 따라서 상수 대신 리터럴이라는 용어를 사용

변수(variabl) 하나의 값을 저장하기 위한 공간
상수(costant) 값을 한번만 저장할 수 있는 공간
리터럴(literal) 그 자체로 값을 의미하는 것

3. 상수가 필요한 이유

    int triangleArea = (20 *10) / 2 ;     // 삼각형의 면적을 구하는 공식
    int rectangleArea = 20 * 10 ;        // 사각형의 면적을 구하는 공식
  • 위 코드에서 10과 20이 아닌 다른 값을 이용해 결과를 얻고 싶다면 여러 곳을 수정해야 함
    final int WIDTH = 20;    //폭
    final int HEIGHT = 10    // 높이

    int triangleArea = (WIDTH *HEIGHT) / 2 ;     // 삼각형의 면적을 구하는 공식
    int rectangleArea = WIDTH * HEIGHT ;        // 사각형의 면적을 구하는 공식
  • 이전 코드에 비해 면적을 구하는 공식의 의미가 명확해짐

  • 다른 값으로 계산할 경우에도 여러 곳을 수정할 필요없이 상수의 초기화만 다른 값으로 해주면 됨

  • 상수는 리터럴에 '의미있는 이름'을 붙여 코드의 이해와 수정을 쉽게 만듦

4. 리터럴의 타입과 접미사

종류 접미사
논리형 없음
정수형 L
실수형 f, d
문자형 없음
문자열 없음
  • 정수형과 실수형에는 여러 타입이 존재하므로 리터럴에 접미사를 붙여 타입을 구분
  • 정수형인 경우, long 타입의 리터럴에 접미사 'l' 또는 'L'을 붙이고, 접미사가 없으면 int 타입의 리터럴
    • byte와 short 타입의 리터럴은 별도로 존재하지 않으며 byte와 short 타입의 변수에 값을 저장할 때는 int 타입의 리터럴을 사용
  • 10진수 외에도 2, 8, 16진수로 표현된 리터럴을 변수에 저장할 수 있으며, 16진수라는 것을 표시하기 위해 리터럴 앞에 접투사 '0x' 또는 '0X'를, 8진수의 경우에는 '0'을 붙임
  • 실수형에서는 float 타입의 리터럴에 접미사 'f' 또는 'F'를 붙이고, double 타입의 리터럴에는 접미사 'd' 또는 'D'를 붙임
    • 정수형에서는 int가 기본 자료형인 것처럼 실수형에서는 double이 기본 자료형이라서 접미사 'd'는 생략이 가능
  • 리터럴에 소수점이나 10의 제곱을 나타내는 기호 E 또는 e, 그리고 접미사 f,F,d,D를 포함하고 있으면 실수형 리터럴로 간주

5. 타입의 불일치

  • 리터럴의 타입은 저장될 변수의 타입과 일치하는 것이 일반적이지만, 타입이 달라도 저장범위가 넓은 타입에 좁은 타입의 값을 저장하는 것은 혀용

      int    i = 'A'       // OK. 문자 'A'의 유니코드인 65가 변수 i에 저장됨
      long   l = 123;       // OK. int보다 long 타입이 더 범위가 넓음
      double d = 3.14f   // OK. float보다 double 타입이 더 범위가 넓음
  • 리터럴의 값이 변수의 타입의 범위를 넘어서거나, 리터럴의 타입이 변수의 타입보다 저장범위가 넓으면 컴파일 에러 발생

      int   i = 0x123456789;    // 에러. int 타입의 범위를 넘는 값을 저장
      float f= 3.14        // 에러. float 타입보다 double 타입의 범위가 넓음
  • byte와 short 타입의 리터럴은 따로 존재하지 않으므로 int 타입의 리터럴을 사용. 단, short 타입의 변수가 저장할 수 있는 범위에 속한 것이어야 함

      byte  b = 65;        // OK. byte 타입에 저장 가능한 범위의 int 타입 리터럴
      short s = 0ㅌ1234;    // OK. short 타입에 저장 가능한 범위의 int 타입 리터럴

6. 문자 리터럴과 문자열 리터럴

  • 문자 리터럴: 'A'와 같이 작은 따옴표로 문자 하나를 감싼 것
  • 문자열 리터럴: 두 문자 이상을 큰 따옴표로 감싼 것
      char    ch = 'J' ;
      String  name = "JAVA"
  • char 타입의 변수는 단 하나의 문자만 저장. 문자열을 저장하기 위해서는 String 타입을 사용
  • 문자열 리터럴은 ""안에 아무런 문자도 넣지 않는 것을 허용 => 빈 문자열(empty string)
  • 문자 리터럴은 반드시 ''안에 하나의 문자가 있어야 함
  • 원래 String은 클래스이므로 객체를 생성하는 연산자 new를 사용해야 하지만 위와 같은 표현도 허용
      String name = "JA" + "VA";
      String str = name + 8.0;
  • 덧셈 연산자를 이용해 문자열을 결합할 수 있음
    • 덧셈 연산자는 피연산자가 모두 숫자일 때는 두 수를 더하지만, 피연산자 중 어느 한쪽이 String이면 나머지 한 쪽을 먼저 String으로 변환한 다음 두 String을 결합
    • 기본형과 참조형 구별 없이 어떤 타입의 변수도 문자열과 덧셈연산을 수행하면 그 결과는 문자열이 됨
    • 덧셈 연산자는 왼쪽에서 오른쪽의 방향으로 연산을 수행하기 때문에 결합순서에 따라 결과가 달라짐
    • 기본형 타입의 값을 문자열로 변환할 때 빈 문자열("")을 더해주면 됨

2.3 형식화된 출력 - printf()

  • println()은 사용하기엔 편하지만 변수의 값을 그대로 출력하므로, 값을 변환하지 않고는 다른 형식으로 출력할 수 없음
  • printf()는 '지시자(specifier)'를 통해 변수의 값을 여러 가지 형식으로 변환하여 출력
    • 지시자(specifier)은 값을 어떻게 출력할 것인지를 지정해주는 역할
      • 정수형 변수에 저장된 값을 10진 정수로 출력할 때는 지시자 '%d'를 사용
      • 변수의 값을 지정된 형식으로 변환해서 지시자 대신 넣음
        ```java
        System.out.printf("age: %d", age);
      • System.out.printf("age: %d", 14);

      • System.out.printf("age: 14"); // "age: 14"가 화면에 출력
        ```

  • 출력하려는 값이 2개라면, 지시자도 2개를 사용해야하며 출력될 값과 지시자의 순서는 일치해야 함 (개수 제한 없음)
  • printf()println()과 다르게 출력 후 줄바꿈을 하지 않음. 줄바꿈을 하려면 지시자 '%n'을 넣어줘야 함
  • 자주 사용되는 지시자 (지시자 전체 목록은 Java API에서 Formatter 클래스를 찾으면 됨)
지시자 설명
%b 불리언(boolean) 형식으로 출력
%d 10진(decimal) 정수의 형식으로 출력. C언어와 다르게 char 타입은 사용 불가
%o 8진(octal) 정수의 형식으로 출력
%x, %X 16진(hexa-decimal) 정수의 형식으로 출력
%f 부동 소수점(floating-point)의 형식으로 출력. 소수점 아래 6자리까지만 출력. 자리수 지정 가능
%e, %E 지수(exponent) 표현식의 형식으로 출력
%c 문자(character)로 출력
%s 문자열(string)로 출력
  • 10진수를 2진수로 출력해주는 지시자는 없기 때문에, 정수를 2진 문자열로 변환해주는 Integer.toBinarySting(int i)를 사용해야 함

2.4 화면에서 입력받기 - Scanner

  • Scanner 클래스를 사용하려면 한 문장을 추가해줘야 함

      import java.util.*;    // Scanner 클래스를 사용하기 위해 추가
  • Scanner 객체 생성

      Scanner scanner = new Scanner(System.in);    // Scanner 클래스의 객체를 생성
  • nextLine()이라는 메서드를 호출하면, 입력 대기 상태에 있다가 입력을 마치고 Enter를 누르면 입력한 내용이 문자열로 반환됨

      String input = scanner.nextLine();    // 입력받은 내용을 input에 저장
      int num = Integer.parseInt(input);    // 입력받은 내용을 int 타입의 값으로 변환
  • Scanner 클래스에는 nextInt(), nextFloat()와 같이 변환없이 숫자로 바로 입력받을 수 있는 메서드가 있고, 이 메서드를 사용하면 문자열을 숫자로 변환해주지 않아도 됨

      int num = scanner.nextInt();    // 정수를 입력받아서 변수 num에 저장
  • 위의 메서드는 화면에서 연속적으로 값을 입력받아 사용하기 어렵기 때문에 모든 값을 nextLine()으로 입력받아 적절히 변환하는 것이 더 나음

  • 숫자가 아닌 문자 또는 기호를 입력하면, 입력받은 문자열을 숫자로 변환하는 과정인 Integer.parseInt()에서 에러가 발생. 공백을 입력하지 않도록 주의!

3. 진법


3.1 10진법과 2진법

  • 1946년 개발된 컴퓨터인 에니악(ENIAC)은 10진법을 사용하도록 설계되었으나 전기회로는 전압이 불안정해서 접압을 10단계로 나누어 처리하는 데 한계가 있었음
  • 1950년에 개발된 에드박(EDVAC)은 단 두 가지 단계, 전기가 흐르면 1, 흐르지 않으면 0만으로 동작하도록 설계
  • 2진법은 0과 1로만 데이터를 표현하기 때문에 10진법에 비해 많은 자리수를 필요로 함
  • 자리수가 많아지긴 해도 2진수는 10진수를 온전히 표현할 수 있고 덧셈이나 뺄셈 같은 연산도 10진수와 동일

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

'bit' vs 'byte' vs 'word'

  • bit: 한 자리의 2진수. 1bit는 컴퓨터가 값을 저장할 수 있는 최소 단위
  • byte: 1bit 8개를 묶은 단위. 데이터의 기본 단위로 사용
  • word: CPU가 한 번에 처리할 수 있는 데이터의 크기. 크기는 CPU의 성능에 따라 달라짐
    • 32bit CPU에서 1word는 32bit(4byte)이고, 64bit CPU에서는 64bit(8byte)
  • n bit로 $2^n$개의 값을 표현할 수 있음
    • n bit로 10진수를 표현한다면, 표현 가능한 10진수의 범위는 $0$ ~ $2^n-1$

3.3 8진법과 16진법

  • 2진법은 0, 1 두개의 기호만으로 값을 표현하기 때문에, 2진법으로 값을 표현할 경우 자리수가 길어진다는 단점이 있음. 단점을 보완하기 위해 2진법 대신 8진법이나 16진법을 많이 사용
  • 8진수는 2진수 3자리를, 16진수는 2진수 4자리를 각각 한자리로 표현할 수 있기 때문에 자리수가 짧아져 알아보기 쉽고 서로 간의 변환벙법도 간단
    • 8진법은 값을 표현하는데 8개의 기호가 필요하므로 0~7의 숫자를 기호로 사용
    • 16진법은 16개의 기호가 필요한데 09의 숫자만으로는 부족하므로 6개의 문자(AF)를 추가로 사용

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

  • 8진수 변환: 2진수를 뒤에서부터 3자리씩 끊어서 그에 해당하는 8진수로 변환
  • 16진수 변환: 2진수를 뒤에서부터 4자리씩 끊어서 그에 해당하는 16진수로 변환

3.5 실수의 진법 변환

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

  • 10진 소수점수에 2를 계속 곱함
  • 과정
    1. 10진 소수에 2를 곱함
    2. 위의 결과에서 소수부만 가져와 다시 2를 곱함
    3. 1, 2번 과정을 소수부가 0이 될 때까지 반복 => 소수가 0이 되지 않고 무한히 반복될 수도 있음
    4. 위의 결과에서 정수부만을 위에서 아래로 순서대로 적고 '0.'을 앞에 붙이기

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

  • 자리수에 맞춰 2^(-1), 2^(-2),...를 곱해줌

cf) 정수부가 있는 소수점수는 정수부와 소수점부를 따로 변환한 다음에 더하면 됨

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

  • 4비트의 2진수로 양수, 음수를 모두 표현하기 위해 MSB(왼쪽의 첫 번째 비트)가 0이면 양수, 1이면 음수 표현을 사용
  • '2의 보수법'에 의해 음수를 배치하면, 절댓값이 같은 양수와 음수를 더했을 때 2진수로도 0을 결과로 얻으므로 부호를 신경쓰지 않고 덧셈 가능

1. 2의 보수법

  • n의 보수: 더했을 때 n이 되는 수
    • 7의 '10의 보수'는 3이고, 3의 '10의 보수'는 7 => 3과 7은 '10의 보수의 관계'
  • 2의 보수 관계: 더해서 2가 되는 두 수의 관계
    • 10진수 2는 2진수로 '10'
    • 2진수로 '10': 자리올림이 발생하고 0이 되는 수를 의미
    • '2의 보수 관계'에 있는 두 2진수를 더하면 '(자리올림이 발생하고) 0이 됨'

      사진 출처: 자바의 정석 3rd Edition p.52

    • 2진수 '0101', '1011'은 서로 '2의 보수 관계'. 두 2진수를 더하면 0이 됨
      • 이 덧셈이 10진수로도 0이 되려면, 2진수 '0101'가 10진수로 5이므로 2진수 1011'은 10진수로 -5여야 함
  • 2의 보수법: 2의 보수 관계에 있는 두 2진수로 절대값이 같고 부호가 다른 두 10진수를 표현하는 것

2. 음수를 2진수로 표현하기

  • 10진 음의 정수의 절대값을 2진수로 변환한 후 '2의 보수'를 구하면 됨

3. 2의 보수 구하기

  • 2의 보수 = 1의 보수 + 1
    • 1의 보수: 0을 1로, 1을 0으로 바꾼 것

4. 음수의 2진 표현 구하는 방법 정리

    1. 음수의 절대값을 2진수로 변환
    1. 1번에서 구한 2진수의 1을 0으로 0은 1로 바꾸기 (1의 보수 구하기)
    1. 2번의 결과에 1을 더함 (2의 보수 구하기, 1의 보수 + 1)

4. 기본형 (primitive type)


4.1 논리형 - boolean

  • 논리형에는 boolean 한 가지만 존재

  • boolean형 변수에는 true와 false 중 하나를 저장. 기본값은 false

  • 논리구현에 주로 사용 (ex. 대답(yes/no), 스위치)

  • 크기: 1byte

      boolean power = true;
      boolean checked = False; // 에러. 대소문자 구분해야 함
  • 자바에서는 대소문자가 구별되기 때문에 TRUE와 true는 다른 것으로 간주됨

4.2 문자형 - char

  • 문자형에는 char 한 가지만 존재

  • 문자를 저장하기 위한 변수를 선언할 때 사용

  • char 타입의 변수는 단 하나의 문자만 저장 가능

      char ch = 'A' // 문자 'A'를 char 타입의 변수 ch에 저장
      char ch = 65; // 문자의 코드를 직접 변수 ch에 저장
  • 실제론 문자가 아닌 '문자의 유니코드(정수)'가 저장됨. 컴퓨터는 숫자밖에 모르기 때문에 모든 데이터를 숫자로 변환하여 저장

    • 문자 리터럴 대신 문자의 유니코드를 직접 저장할 수 있음
  • 어떤 문자의 유니코드를 알고 싶으면 char형 변수에 저장된 값을 정수형으로 변환하면 됨

      int code = (int)ch;

1. 특수 문자 다루기

  • 영문자 이외에 특수 문자 저장도 가능
특수 문자 문자 리터럴
tab \t
backspace \b
corm feed \f
new line \n
carriage return \r
역슬래쉬() \
작은 따옴표 '
큰 따옴표 "
유니코드(16진수) 문자 \u유니코드 (ex. char a = '\u0041')

2. char 타입의 표현형식

  • 크기: 2byte (=16bit)
  • 16자리의 2진수로 표현할 수 있는 정수의 개수인 65536개의 코드를 사용할 수 있으며 범위 내의 코드 중 하나를 저장할 수 있음
  • char 타입은 문자를 저장할 변수를 선언하기 위한 것이지만, 실제론 문자가 아닌 '문자의 유니코드(정수)'가 저장되고 표현형식 역시 정수형과 동일
    • 정수형과 달리 음수를 나타낼 필요가 없으므로 표현할 수 있는 값의 범위는 다름
    char ch = 'A'
    short s = 65;
  • 위의 두 변수에는 똑같은 2진수가 저장됨. 하지만 출력해보면 결과가 다름
    • 이유: println()은 변수의 타입이 정수형이면 변수에 저장된 값을 10진수로 해석하여 출력하고, 문자형이면 저장된 숫자에 해당하는 유니코드 문자를 출력하기 때문

인코딩과 디코딩(encoding & decoding)

  • 문자 인코딩: 문자를 코드(숫자)로 변환하는 것. 문자를 저장할 때 수행
  • 문자 디코딩: 코드(숫자)를 문자로 변환하는 것. 저장된 문자를 읽어올 때 수행

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

  • 크기 순 정렬: byte(1byte) < short(2byte) < int(4byte) < long(8byte)

1. 정수형 저장 형식과 범위

  • 모든 정수형은 부호가 있으므로 왼쪽의 첫 번째 비트를 부호 비트(sign bit)로 사용하고 나머지는 값을 표현하는데 사용
  • n비트로 표현할 수 있는 정소의 개수: $2^n$개(= 2^(n-1)개 + 2^(n-1)개)
  • n비트로 표현할 수 있는 부호있는 정수의 범위: (-2)^(n-1) ~ 2^(n-1) - 1
    • 최댓값에서 1을 빼는 이유는 범위에 0이 포함되기 때문

2. 정수형의 선택기준

  • byte, short 보다는 int를 사용. byte와 short는 int보다 크기가 작아 메모리를 조금 더 절약할 수 있지만, 저장할 수 있는 값의 범위가 작은 편이라 연산 시에 범위를 넘어 잘못된 결과를 얻기 쉬움
  • JVM의 피연산자 스택이 피연산자를 4byte 단위로 저장하기 때문에 4byte보다 작은 자료형의 값을 계산할 대는 4byte로 변환하여 연산을 수행. 따라서 int를 사용하는 것이 더 효율적
  • 정수형 변수를 선언할 때는 int 타입으로 하고, int의 범위(약 -20억 ~ +20억)를 넘어서는 수를 다뤄야할 때는 long을 사용
  • byte나 short는 성능보다 저장공간을 절약하는 것이 더 중요할 때 사용

3. 정수형의 오버플로우

  • 오버플로우: 타입이 표현할 수 있는 값의 범위를 넘어서는 것
    • Example) 1111 + 0001 = 0000
      • 1111에 0001를 더할 경우 10000이지만, 4bit로는 4자리의 2진수만 저장할 수 있기 때문에 0000이 됨
  • 오버플로우가 발생해도 에러가 발생하지는 않지만 예상했던 결과를 얻지 못함. 오버플로우가 발생하지 않게 충분한 크기의 타입을 선택해서 사용해야 함
  • 오버플로우가 사용되는 예시: 자동차 주행표시기, 계수기
    • 네 자리 계수기라면 0000 ~ 9999까지 표현 가능. 9999 다음의 숫자는 0000이 됨
    • 0000에서 1을 감소시키면 어떻게 될까?
      • 0에서 1을 뺄 수 없으므로 저장되지 않은 1이 있다고 가정하고 뺄셈을 진행. 결과는 네 자리로 표현할 수 있는 최댓값이 됨
  • 정리
    • 최댓값 + 1 => 최솟값
    • 최솟값 - 1 => 최댓값
    • 이 특징 때문에 2진수의 경우 값을 무한히 1씩 증가시켜도 0000과 1111의 범위를 계속 반복하게 됨

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

  • 부호없는 정수와 부호있는 정소는 최댓값 최솟값이 다르기 때문에 오버플로우가 발생하는 시점이 다름
  • 부호없는 정수는 2진수로 0000이 될 때 오버플로우가 발생하고, 부호있는 정수는 부호비트가 0에서 1이 될 때 오버플로우가 발생
    • 최댓값에서 최솟값이 되는 경우 오버 플로우 발생
  • 부호없는 정수의 경우 표현범위가 015이므로 이 값이 계속 반복. 부호있는 정수의 경우 표현범위가 -87이므로 이 값이 무한히 반복 됨
  • short와 char의 크기는 모두 16bit이므로 표현할 수 있는 값의 개수가 같음. 하지만 short는 이 중 절반을 음수를 표현하는데 사용하고 char는 전체를 양수와 0을 표현하는데 사용

4.4 실수형 - float, double

1. 실수형의 범위와 정밀도

사진 출처: 자바의 정석 3rd Edition p. 68

  • 표의 범위는 양의 범위만 적은 것. - 부호를 붙이면 음의 부호가 됨

사진 출처: 자바의 정석 3rd Edition p. 68

  • 중간 부분(0 제외)은 표현할 수 없음
  • 실수형은 소수점수도 표현해야 하므로 얼마나 큰 값을 표현할 수 있는가뿐만 아니라 얼마나 0에 가깝게 표현할 수 있는가도 중요

Q) 실수형도 정수형처럼 저장할 수 있는 범위를 넘게 되면 오버플로어가 발생하나요?
실수형에서도 변수의 값이 표현범위의 최댓값을 벗어나면 오버플로우가 발생. 실수형에서 오버플로우가 발생하면 변수의 값은 무한대가 됨.
언더플로우도 존재. 언더플로우는 실수형으로 표현할 수 없는 아주 작은 값, 즉 양의 최소값보다 작은 값이 되는 경우를 의미. 이 때 변수의 값은 0이 됨

  • 같은 4byte이지만 float형이 정수형보다 큰 값을 표현할 수 있는 이유
    • 값을 저장하는 형식이 다르기 때문
    • int 타입: 부호(S, 1bit)와 값(31bit)로 구성
    • float 타입: 부호(S, 1bit), 지수(E, 8bit), 가수(M, 23bit)로 구성
      • 2의 제곱을 곱한 형태(+-$M$ X $2^E$)로 저장하기 때문에 큰 범위의 값을 저장하는 것이 가능
  • 실수형은 오차가 발생할 수 있다는 단점이 있어 정밀도(precision)가 중요
    • float 타입의 정밀도는 7자리. 7자리의 10진수를 오차없이 저장할 수 있다는 의미
    • 7자리 이상의 정밀도가 필요하다면, 변수의 타입을 double로 해야함
  • 연산속도의 향상이나 메모리를 절약하려면 float를 선택하고, 더 큰 값의 범위 혹은 더 높은 정밀도를 필요호 한다면 double을 선택

2. 실수형의 저장 형식

  • 값을 부동소수점수(floating-point)의 형태로 저장. 부동소수점수는 부호(Sign), 지수(Exponent), 가수(Mantissa) 부분으로 이루어짐
    • float: 1(S) + 8(E) + 23(M) = 32(4byte)
    • double: 1(S) + 11(E) + 52(M) = 64(8byte)
  • 부호(Sign bit)
    • 0이면 양수, 1이면 음수를 의미
    • 2의 보수법을 사용하지 않기 때문에 양의 실수를 음의 실수로 바꾸려면 부호비트만 0에서 1로 변경
  • 지수(Exponent)
    • 지수는 부호있는 정수이고 8bit이므로 -127~128의 값이 저장됨
    • -127과 128은 'NaN(숫자 아님)' 이나 '양의 무한대(POSITIVE_INFINITY)', '음의 무한대(NEGATIVE_INFINITY)'와 같이 특별한 값의 표현을 위해 예약되어 있어 실제 사용 가능한 범위는 -126~127
  • 가수(Mantissa)
    • 실제 값인 가수를 저장하는 부분
    • 2진수 23자리 저장 가능. 2진수 23자리로는 약 7자리의 10진수를 저장할 수 있는데 이것이 float의 정밀도가 됨
      • double의 경우 기수를 저장할 수 있는 공간이 52자리로 float의 약 2배. double이 float보다 약 2배의 더 좋은 정밀도를 가짐

3. 부동소수점의 오차

  • 실수 중에는 무한소수가 존재하므로 저장할 때 오차가 발생할 수 있음. 2진수로 저장하기 때문에 10진수로는 유한소수이더라도 2진수로 변환하면 무한소수가 되는 경우도 있음
  • 2진수로는 10진 소수를 정확히 표현하기 어렵고 2진수로 유한소수이더라도 가수를 저장할 수 있는 자리수가 한정되어 있으므로 저장되지 못하고 버려지는 값들이 있으면 오차가 발생
  • 정규화: 2진수로 변환된 실수를 저장할 때 '1.xxx X $2^n$'의 형태로 변환하는 과정
    • 정규화된 2진 실수는 항상 '1.'으로 시작하기 때문에 이를 제외한 23자리의 2진수가 가수로 저장되고 이후는 잘림
    • 지수는 기저법(부호있는 정수를 저장하는 방법, 저장할 때 특정값을 더했다가 읽어올 때 다시 뺌)으로 저장
  • Float 클래스의 floatToIntBits()는 float 타입의 값을 int 타입의 값으로 해석해서 반환

5. 형변환


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

  • 서로 다른 타입간의 연산을 수행해야하는 경우 연산을 수행하기 전에 타입을 일치시켜야 함
  • 형변환(casting): 변수나 리터럴의 타입을 다른 타입으로 변환하는 것
    • ex) int 타입의 값과 float 타입의 값을 더하는 경우, 먼저 두 값을 같은 타입, 즉 둘 다 float 타입으로 변환한 다음 연산 수행

5.2 형변환 방법

  • 형변환하고자 하는 변수나 리터럴의 앞에 변환하고자 하는 타입을 괄호와 함께 붙여주기만 하면 됨

    (타입) 피연산자

    • 괄호 ()는 '캐스트 연산자' 또는 '형변환 연산자'라고 함
    • 형변환을 '캐스팅(casting)'이라고도 함
    double d = 85.4;
    int score = (int) d; // double 타입의 변수 d를 int 타입으로 형변환
  • 두 번째 줄의 연산 과정을 단계별로 살펴보면

         int score = (int) d;
      => int score = (int) 85.4; // 변수 d의 값을 읽어 와서 형변환
      => int score = 85;      // 형변환의 결과인 85를 score에 저장
  • 피연산자인 변수 d의 값은 형변환 후에도 변화 없음

  • 기본형에서 boolean을 제외한 나머지 타입들은 서로 형변환 가능 but 기본형과 참조형간의 형변환은 불가능

변환 수식 결과
int -> char (char) 65 'A'
char -> int (int) 'A' 65
float -> int (int) 1.6f 1
int -> float (float) 10 10.0f
  • float 타입의 값을 int 타입으로 변환할 때 소수점 이하의 값은 반올림이 아닌 버림으로 처리됨

5.3 정수형간의 형변환

  • 큰 타입에서 작은 타입으로의 변환(ex. int 타입의 값을 byte 타입으로 변환)하는 경우는 크기의 차이만큼 잘려나감 => 값 손실(loss of value) 발생
  • 작은 타입에서 큰 타입으로 변환(ex. byte 타입의 값을 int 타입으로 변환)하는 경우는 저장공간의 부족으로 잘려나가는 일이 없음 => 값 손실이 발생하지 않음
    • 나머지 빈 공간은 0 또는 1로 채워짐
    • 값을 채우고 남은 빈 공간은 보통 0으로 채우지만, 변환하려는 값이 음수인 경우 빈 공간을 1로 채움. (이유: 형변환 후에도 부호를 유지할 수 있도록 하기 위해서)
  • Integer.toBinaryString(int i): 10진 정수를 2진 정수로 변환한 문자열을 출력

5.4 실수형 간의 형변환

  • 작은 타입에서 큰 타입으로 변환하는 경우 빈 공간을 0으로 채움

  • float 타입의 값을 double 타입으로 변환하는 경우

    • 지수는 float의 기저인 127을 뺀 후 double의 기저인 1023을 더해서 변환하고, 가수는 float의 가수 23자리를 채우고 남은 자리를 0으로 채움
  • double 타입에서 float 타입으로 변환하는 경우

    • 지수는 double의 기저인 1023을 뺀 후 float의 기저인 127을 더하고 가수는 double의 가수 52자리 중 23자리만 저장되고 나머지는 버려짐
    • 형변환할 때 가수의 24번째 자리에서 반올림이 발생할 수 있음. 24번째 자리의 값이 1이면, 반올림이 발생해 23번째 자리의 값이 1 증가함
  • float 타입의 범위를 넘는 값을 float로 형변환하는 경우는 '+-무한대' 또는 '+-0'을 결과로 얻음

      double d = 1.0e100;    // float의 최댓값보다 큰 값을 d에 저장
      float f = (float) d;    // d의 값을 float로 형변환해서 f에 저장. f는 무한대가 됨
    
      double d = 1.0e-50;    // float의 최소값보다 작은 값을 d에 저장
      float f = (float) d;    // f의 값은 0이 됨

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

1. 정수형을 실수형으로 변환

  • 정수를 2진수로 변환한 다음 정규화를 거쳐 실수의 저장형식으로 저장
  • 실수형의 정밀도의 제한으로 인한 오차가 발생할 수 있음
    • int의 최대값은 약 20억으로 최대 10자리의 정밀도를 요구하지만 float는 10진수로 약 7자리 정밀도만 제공하므로, int를 float로 변환할 때 정밀도 차이에 의해 오차가 발생할 수 있음
    • 10진수로 8자리 이상의 값을 실수형으로 변환할 때는 float가 아닌 double 형태로 형변환해야 오차가 발생하지 않음

2. 실수형을 정수형으로 변환

  • 실수형을 정수형으로 변환하면 소수점 이하 값은 버려짐. 정수형의 표현 형식으로 수수점 이하의 값은 표현할 수 없기 때문
    • 실수형을 정수형으로 형변환할 때 반올림이 발생하지 않음
  • 실수의 소수점을 버리고 남은 정수가 정수형의 저장범위를 넘는 경우 정수의 오버플로우가 발생한 결과를 얻음

5.6 자동 형변환

  • 컴파일러가 자동으로 생략된 형변환을 추가

      float f = 1234;    // 형변환 생략. float f = (float) 1234;와 같음
  • 변수가 저장할 수 있는 값의 범위보다 더 큰 값을 저장하려는 경우에 형변환을 생략하면 에러 발생

      byte b = 1000;    // 에러. byte 범위(-128~127)를 넘는 값을 저장
    
      // 에러 메시지: incompatible types: possibly lossy conversion from int to byte
      // 큰 타입에서 작은 타입으로의 형변환은 값 손실이 발생할 수 있다는 의미
  • 명시적으로 형변환을 해줬을 경우 컴파일러는 에러를 발생시키지 않음

  • 계산식에서 자주 형변환이 생략

    • 서로 다른 두 타입의 연산에서 먼저 타입을 일치시킨 다음 연산을 수행해야하므로, 연산과정에서 형변환이 자동으로 이루어짐
    • 두 타입 중 표현범위가 더 넓은 타입으로 형벼환하여 타입을 일치시킨 후 연산 수행
      • 이유: 값손실의 위험이 더 적어 올바른 결과를 얻을 확률이 높기 때문
    • 산술 변환: 연산과정에서 자동적으로 발생하는 형변환

자동 형변환 규칙

  • 자동 형변환 기준: 기존의 값을 최대한 보존할 수 있는 타입으로 자동 형변환

  • 자동 형변환이 가능한 방향

    사진 출처: https://velog.io/@yoopark/java-implicit-conversion

    • 왼쪽에서 오른쪽으로의 변환은 형변환 연산자를 생략해도 자동 형변환되며, 반대 방향으로의 변환은 반드시 형변환 연산자를 써줘야 함

형변환 정리

  1. boolean을 제외한 나머지 7개의 기본형은 서로 형변환이 가능
  2. 기본형과 참조형은 서로 형변환할 수 없음
  3. 서로 다른 타입의 변수간의 연산은 형변환을 하는 것이 원칙이지만, 값의 범위가 작은 타입에서 큰 타입으로의 형변환은 생략 가능

Chapter 2 변수(Variable) 끝!!!

728x90
LIST

댓글