신규 블로그를 만들었습니다!

2020년 이후부터는 아래 블로그에서 활동합니다.

댓글로 질문 주셔도 확인하기 어려울 수 있습니다.

>> https://bluemiv.tistory.com/

1.변수 (variable)

값(data)을 잘 다루는 것은 매우 중요하다.

잘 다루기 위해서는 변수(variable)를 잘 이해하고 활용하는것이 중요하다.

1.1. 변수(variable)란?

수학에서 변수는 "변하는 수"라고 정의한다. 프로그래밍 언어에서는 값을 저장할 수 있는 메모리상의 주소 공간을 의미한다.

 

이 공간에 저장된 값은 변경 될 수 있다.

변수란? 단 하나의 값을 저장할 수 있는 메모리 상의 공간

변수에는 하나의 값만 저장 할 수 있기 때문에, 새로운 값을 저장하면 이전 값은 지워진다.

1.2. 변수의 선언과 초기화

변수를 사용하려면 선언을 먼저해야한다.

변수타입 변수이름;
int age;
String name;
double rate;

변수 타입: 변수가 무슨 타입을 가지는지 정의한다.

  • 정수형, 실수형, 문자형 등 다양한 타입을 제공한다.

변수 이름: 변수의 이름. 메모리상의 공간에 이름을 붙여주는 것.

 

변수를 선언하면, 메모리의 빈 공간에 변수 타입에 맞는 크기의 저장공간이 확보된다. 그리고 변수이름을 가지고 이 메모리 공간을 사용할 수 있게 된다.

1.2.1 변수의 초기화

변수는 선언한 이후부터 사용이 가능하지만, 반드시 초기화(initialization)를 해야한다. 메모리 공간은 여러 프로그램이 같이 사용하기 때문에 쓰레기 값이 남아 있을 수 있다.

 

변수에 값을 지정할 때는 대입 연산자(=)를 이용한다. 프로그래밍에서는 대입 연산자는 양변이 같다는 뜻이 아니라 왼쪽의 변수에 오른쪽 값을 저장한다는 뜻이다.

 

여러변수를 한 줄에 선언하기도 한다.

// 한 줄에 하나씩 변수 선언
int a;
int b;

int x = 0;
int y = 0;

// 한 줄에 여러 변수 선언
int a, b;
int x = 0, y = 0;

변수의 초기화: 변수를 사용하기 전에 처음으로 값을 지정하는 것

1.2.1.1. 예제

public class VarEx1 {

    public static void main(String[] args) {
        int year = 0;
        int age = 27;

        System.out.println(year);
        System.out.println(age);

        year = age + 2000;
        age = age + 1;

        System.out.println(year);
        System.out.println(age);
    }
}

1.2.2. 두 변수의 값 교환하기

int x = 10;
int y = 20;
int tmp; // 임시로 값을 저장할 변수를 선언

tmp = x;
x = y;
y = tmp;

System.out.println(x);
System.out.println(y);

1.3. 변수의 명명규칙

프로그래밍에서 사용하는 모든 이름을 "식별자(identifier)" 라고 한다. (예를들어 변수명과 같은)

 

식별자를 작성할 때는 다음과 같은 규칙이 있다.

 

  1. 대소문자가 구분되며 길이에 제한이 없다.
  2. 예약어(Keyword)를 사용해서는 안된다.
  3. 숫자로 시작해서는 안된다.
  4. 특수문자는 '_'와 '$'만을 허용한다.

예약어란?

예약어는 키워드(Keyword) 또는 Reserved word 라고 한다. 프로그래밍의 구문에서 사용되는 단어를 말한다.

Java keyword

그 외에 필수는 아니지만 자바 프로그래머들에게 권장하는 규칙들이 있다.

 

  1. 클래스 이름의 첫 글자는 항상 대문자로 한다.
    • class Hello { ... }
  2. 반대로, 변수와 메소드의 첫 글자는 항상 소문자로 한다.
  3. 여러 단어로 이루어진 이름은 단어의 첫 글자를 대문자로 한다. (캐멀 케이스)
    • lastIndexOf, StringBuffer
  4. 상수의 이름은 모두 대문자로 한다. 여러 단어로 이루어진 경우는 '_'로 구분한다.
    • PI, MAX_NUMBER

필수는 아니라서 반드시 지켜야하는 것은 아니지만, 코드를 보다 이해하기 쉽게 하기 위한 자바 개발자들 사이의 암묵적인 약속이다.

 

마지막으로 변수의 이름을 지을때는 길더라도 의미를 파악 할 수 있는 이름으로 작성하는 것이 좋다.

2. 변수의 타입

주로 사용하는 데이터(data)의 종류(type)은 크게 '문자'와 '숫자'로 나눌 수 있다. 그리고, 숫자는 '정수'와 '실수'로 나눌 수 있다.

 

데이터의 타입에 따라 저장될 공간의 크기와 형식을 정의한 것을 자료형(datatype)이라 한다.

2.1. 기본형과 참조형

자료형은 크게 '기본형', '참조형' 두 가지로 나눠진다.

 

  • 기본형(primitive type): 실제 값(data)을 저장하는 자료형
    • 문자형: char
    • 정수형: byte, short, int, long
    • 실수형: float, double
  • 참조형(reference type): 값이 들어가있는 메모리 상의 주소(memory address)를 저장하는 자료형

자바는 C언어와 다르게 참조형 변수 간의 연산을 할 수 없기 때문에, 실제 연산에 사용되는 것은 모두 기본형이다.

 

참조형 변수의 타입은 클래스 이름을 사용한다. (그래서 새로운 클래스를 작성하면 새로운 참조형 자료형을 생성하는 것과 같다)

Date today = new Date();

2.2. 기본형 (primitive)

기본형에는 8개 타입의 자료형이 있다.

 

  • 논리형(boolean): true 또는 false 중 하나의 값을 가진다. 조건식과 논리 계산에 사용된다.
  • 문자형(char): 문자를 저장하는데 사용한다. 하나의 문자만 저장할 수 있다.
  • 정수형(byte, short, int, long): 정수를 저장할때는 주로 int를 사용한다. byte는 이진 데이터를 다룰때 사용한다. short는 C언어와 호환을 위해 추가됐다.
  • 실수형(float, double): 실수를 저장하는데 사용한다. 주로 double을 사용한다.

 

문자형은 문자를 유니코드(정수)로 저장하기 때문에 정수형과 별반 다를게 없다. 따라서 논리형(boolean)을 제외한 나머지 7개 자료형은 서로 연산과 변환이 가능하다.

 

정수를 표현할때 보통은 4bytes 크기의 int를 많이 사용한다. 만약 메모리를 절약하고 싶다면, byte나 short를 사용한다.

 

자료형의 크기

자료형의 크기

각 자료형이 가질 수 있는 값의 범위는 -2^(n-1) ~ 2^(n-1) - 1 이다. (부호 구분하는 경우)

따라서, int의 경우는 약 -20억 ~ 20억 정도의 수까지 저장할 수 있다.

2.3. 상수와 리터럴(constant & literal)

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

상수를 만들때는 final 이라는 키워드를 이용한다.

final int MAX_SPEED = 1;

상수는 선언과 동시에 초기화 작업을 해줘야한다. 선언만 한다고해서 오류가 발생하는건 아니지만, 상수의 값을 변경하는 것은 불가능하기 때문에 어떠한 값도 할당하지 못하게된다.

final int MAX_SPEED;
final int MAX_VALUE = 100;
MAX_VALUE = 200; // 오류 발생

2.3.1. 리터럴(literal)

원래 상수는 12, 40, 3.14, 'A' 등 과 같은 값이 상수이지만, 프로그래밍에서는 상수를 한번 저장하면 변경하지 못하는 저장공간으로 정의 했기 때문에 이와 구분하기 위해 '리터럴'이라는 용어를 사용한다.

정리

  • 변수(variable): 하나의 값을 저장하기 위한 공간
  • 상수(constant): 값을 한번만 저장할 수 있는 공간
  • 리터럴(literal): 그 자체로 값을 의미하는 것
int year = 2019;
// year: 변수
// 2014: 리터럴

final int MAX_VALUE = 100;
// MAX_VALUE: 상수
// 100: 리터럴

2.3.2. 상수가 필요한 이유

그냥 리터럴을 사용하면 되지 않을까 싶지만, 값을 여러곳에서 사용하거나 의미를 부여하기 위해 상수를 사용한다.

예를들어,

final int WIDTH = 20;
final int HEIGHT = 10;

int triangleArea = (WIDTH * HEIGHT) / 2;
int rectangleArea = WIDTH * HEIGHT;

만일 값을 가로 세로 길이를 수정한다고 하면 상수 WIDTH, HEIGHT만 수정하면 다른 부분도 모두 수정이 되기 때문에 효율적이다.

20과 10이라는 리터럴만 봐서는 무슨 뜻을 가진 값인지 알 수 없기 때문에 문맥을 파악하거나 추측해야하는 불편함이 있다.

즉, 상수는 코드를 쉽게 이해할 수 있도록 해준다.

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

접미사

  • 논리형 false, true: 없음
  • 정수형 123, 0b0101, 077, 0xFF, 100L: L
  • 실수형 3.14, 3.0e8, 1.4f, 0x1.0p-1: f, d
  • 문자형 'A', '1', '\n': 없음
  • 문자열 "ABC", "123", "A", "true": 없음

정수형과 실수형에는 여러 타입이 존재하므로, 리터럴에 접미사를 붙여서 구분한다.

정수형의 경우는 long 타입의 'l' 또는 'L'을 붙이고, 접미사가 없다면 int 타입의 리터럴이다. byte와 short 타입의 리터럴은 별도로 존재하지 않는다.

10진수와 2, 8, 16진수를 표현할때는 접두사를 이용한다.

int octNum = 010;
int hexNum = 0x10;
int binNum = 0b10;

JDK 1.7 이상부터는 정수형 리터럴의 중간에 '_' 구분자를 넣을 수 있게 됐다.

long big = 100_000_000_000L;
long hex = 0xFFFF_FFFF_FFFF_FFFFL;

참고로, l 접두사를 사용할 떄는 1과 헷갈릴 수 있으므로, 대문자 'L'을 사용하길 권장한다.

float는 'f' 접미사를 double은 'd' 접미사를 붙인다.

float pi = 3.14f; // 또는 3.14F
double rate = 1.618d; // 또는 1.618D

double은 실수형에서 기본 자료형이라서 d를 생략할 수 있다.

float pi = 3.14; // 오류 발생 f 접미사를 붙여야한다.
double rate = 1.618; // double은 d 접미사를 생략 가능하다.

자주 쓰이지는 않지만 기호 p는 제곱(pow)을 뜻한다.

2.3.4. 타입의 불일치

리터럴의 타입과 저장될 변수의 타입이 일치하는것이 보통이지만, 타입이 달라도 저장범위가 넓은 타입에 좁은 범위를 가지는 타입을 저장할 수 있다.

int i = 'A'; // int(4bytes) = char(2bytes) 가능하다.
long l = 123; // long(8bytes) = int(4bytes) 가능하다.
double d = 3.14f; // double(8bytes) = float(4bytes) 가능하다.

반대로 좁은 범위를 가지는 타입에 큰 범위 타입을 저장하려고하면 (범위를 벗어나는 경우) 컴파일 에러가 발생한다.

int i = x123456789; // 에러. int의 범위를 벗어나는 값을 저장하는 경우
float f = 3.14; // 에러. float(bytes)에 double(bytes)을 저장하는 경우

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

'A'와 같이 작은 따옴표로 문자 하나를 감싼 것을 문자 리터럴이라 한다. 두 문자 이상은 큰 따옴표("")를 이용하여 감싸야한다. 이를 문자열 리터럴이라고 한다.

char ch = 'J'; // 문자
String name = "Java"; // 문자열

char 타입은 문자 하나만 저장할 수 있으므로, 여러 문자열을 저장하려면 String 타입을 사용해야 한다.

문자열 리터럴에는 "" 과 같이 아무 문자도 넣지 않는 것을 허용하는데, 이를 빈 문자열(empty string)이라고 한다. 반대로 char의 경우는 '' 안에 하나의 문자가 무조건 있어야한다. 하지만 공백문자는 가능하다.

String a = ""; // 가능
char b = ''; // 컴파일 에러 발생
char c = ' '; // 공백문자는 가능

String 타입은 클래스이기 때문에 new 키워드를 사용하는게 맞지만, 특별히 아래와 같이 생략하는 것이 가능하다.

String name = new String();
String name2 = "";

보통은 new를 사용하지 않는 방식으로 선언 및 초기화를 한다.

 

2.3.5.1. 덧셈 연산자를 이용한 문자열 결합

덧셈 연산자를 이용하여 문자열을 결합할 수 있다.

String name = "Ja" + "va";
System.out.println(name); // Java

String version = name + "8.0";
System.out.println(version); // Java8.0

기본형과 참조형 구별 없이 어떤타입의 변수도 문자열과 덧셈연산을 하게되면, 결과는 문자열이 된다.

단, 컴파일러는 덧셈 연산을 수행할 때, 왼쪽에서 오른쪽으로 수행하기 때문에 숫자 + 숫자 + 문자열과 같은 연산을 하면 앞의 숫자 2개는 "정수 합"과 같이 연산이 된다.

예를들어,

System.out.println(7 + 7 + "7"); // 7 + 7 + "7" => 14 + "7" => "147"
System.out.println("7" + 7 + 7); // "7" + 7 + 7 => "77" + 7 => "777"
System.out.println(7 + "7" + 7); // 7 + "7" + 7 => "77" + 7 => "777"

System.out.println(true + ""); // "true"

위와 같이 빈 문자열을 뒤에 덧셈해줌으로서 문자열로 변환된다는 것을 알 수 있다.

2.4. 형식화된 출력 - printf()

화면에 출력할 때 println()을 사용했는데, 이는 변수 그대로 출력하기 때문에 값을 변환하지 않고 다른 형식을 출력할 수 없다.

소수점 둘째 자리까지 출력하거나, 16진수 또는 8진수와 같은 형태로 출력하고 싶을때는 printf()를 사용하는 것이 편리하다.

int age = 20;
System.out.printf("age: %d%n", age); // age: 20

int year = 2019, month = 9;
System.out.printf("%d-%d", year, month); // 2019-9

printf()는 출력 후 줄바꿈을 하지 않기 때문에 %n을 따로 넣어준다.

/n이 아닌 %n을 넣어주는 이유는 OS마다 줄바꿈 문자가 다를 수 있기 때문에 %n을 사용하는 것이 안전하다.

2.4.1. 지시자 종류

%b: 불리언(boolean) 형식으로 출력

boolean myBool = true;
System.out.printf("bool: %b%n", myBool); // bool: true

%d: 10진수(decimal) 정수 형식으로 출력

byte myByte = 1;
int myInt = 10;
System.out.printf("int: %d %d%n", myByte, myInt); // decimal: 1 10

%o: 8진수(octal) 정수 형식으로 출력

byte myByte = 1;
int myInt = 10;
long myLong = 011110L;
System.out.printf("octal: %o %o %o%n", myByte, myInt, myLong); // octal: 1 12 11110

%x, %X: 16진수(hexa-decimal) 정수 형식으로 출력

byte myByte = 1;
int myInt = 10;
long myLong = 011110L;
long myLong2 = 0xFFFF_FFFF_FFFF_FFFFL;
System.out.printf("hexa-decimal: %x %x %x %x%n", myByte, myInt, myLong, myLong2); // hexa-decimal: 1 a 1248 ffffffffffffffff

%f: 부동 소수점(floating-point)로 출력

float myFloat = 3.14F;
System.out.printf("floating-point: %f", myFloat); // floating-point: 3.140000

%e, %E: 지수(exponet) 표현식의 형식으로 출력

float myFloat2 = 123_123_000_000F;
System.out.printf("exponent: %e%n", myFloat2); // exponent: 1.231230e+11

%c: 문자(character) 형식으로 출력
%s: 문자열(String)으로 출력

char myChar = 'A';
String myString = "Java";
System.out.printf("char, string = %c, %s%n", myChar, myString); // char, string = A, Java

%뒤에 숫자를 지정하여 출력될 값이 차지할 공간을 지정할 수 있다.

System.out.printf("[%5d]%n", 1); // [    1]
System.out.printf("[%-5d]%n", 1); // [1    ]
System.out.printf("[%05d]%n", 1); // [00001]

%x와 %o 에서 앞에 %와 x(또는 o) 사이에 #을 붙여주면 0x(또는 0)이 앞에 각각 붙는다.

System.out.printf("%#x%n", 0xFFF_FFF_FFF_FFFL); // 0xffffffffffff
System.out.printf("%#o%n", 0xFFF_FFF_FFF_FFFL); // 07777777777777777

10진수를 2진수로 출력해주는 지시자는 없기 때문에 "Integer.toBinaryString(int i)" 메소드를 사용한다.

int binName = 100;
System.out.printf("binName: %s%n", Integer.toBinaryString(binName)); // binName: 1100100

그리고 char 타입을 정수로 출력할 때는 int 형태로 형변환을 해야한다.

char c = 'A';
System.out.printf("c: %c, d: %d%n", c, (int)c); // c: A, d: 65

2.4.2. 실수형 값 출력

실수 형태의 값을 출력할때는 보통 %f, %e, %g 를 사용한다.

  • %f: 소숫점을 표현할때 사용
  • %e: 지수로 표현할때 사용
  • %g: 간략하게 표현할때 사용
float myFloat = 1.234f;

System.out.printf("f: %f%n", myFloat); // f: 1.234000
System.out.printf("f: %e%n", myFloat); // f: 1.234000e+00
System.out.printf("f: %g%n", myFloat); // f: 1.23400

%f 이용하여 출력할때 기본적으로 6자리까지만 출력해준다. 그리고 7번째 자리에서 반올림을 한다.

float myFloat2 = 1.234567890f;
System.out.printf("f2: %f%n", myFloat2); // f2: 1.234568

만약 소숫점을 더 표현하고 싶다면 아래와 같이 사용한다.

// %[전체자리수].[소수점자리수]f
float myFloat2 = 1.234567890f;
System.out.printf("f3: [%20.3f]%n", myFloat2); // f3: [               1.235]
System.out.printf("f3: [%-20.3f]%n", myFloat2); // f3: [1.235               ]
System.out.printf("f3: [%020.3f]%n", myFloat2); // f3: [0000000000000001.235]

이때 소수점도 전체 자리 수에 포함된다.
마이너스(-) 부호를 넣으면 좌측 정렬이된다.

그리고 앞에 0을 붙이면, 빈 공간이 공백 문자가 아닌 0으로 채워진다.

2.5. 화면에서 입력받기 - Scanner

화면에 출력하는 것이 아닌 입력을 받을 수 있다.

Scanner sc = new Scanner(System.in);

// 문자열 입력받기
String str = sc.nextLine();
System.out.println(str); // taehong

// 문자열로 받아서 int로 파싱
String strNum = sc.nextLine(); // "40"
System.out.println(Integer.parseInt(strNum)); // 40

사실 Scanner에서는 nextInt(), nextFloat()와 같이 바로 정수, 실수를 받는 메소드가 있어서 위와 같이 파싱을 따로 할 필요가 없지만, 이 메소드들은 연속적으로 값을 입력받기가 힘들어서 nextLine()을 이용하여 입력받고 적절히 파싱하는 것이 좋다.

// 정수형 입력받기
int num = sc.nextInt();
System.out.println(num); // 100

2.5.1. BufferedReader를 이용한 입력받기

Scanner가 없기 전에 사용하던 다른 방법으로 Scanner를 사용하는 것보다 더 빠르다.

// BufferedReader 를 이용한 입력받기
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
    System.out.println(br.readLine());
} catch(IOException e) {
    e.printStackTrace();
}

3. 진법

3.1. 10진법과 2진법

대부분의 컴퓨터는 2진 체계로 설계되었기 때문에 2진법을 알아야 컴퓨터의 동작 원리나 데이터 처리 방식을 알 수 있다.

10진수 25는 16 + 8 + 1 이므로 2진법으로 "1 1001" 이 된다.

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

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

8비트를 묶어서 1바이트(byte)라 부른다. 바이트 단위로 정의해서 데이터의 기본 단위로 사용한다.

3.2.1. 워드 (word)

워드(word)는 CPU가 한번에 처리 할 수 있는 크기를 의미한다. 그래서 워드의 크기를 4바이트(32bits)라 했을때, CPU는 한번에 32비트를 처리할 수 있다. 워드의 크기가 8바이트(64비트)인 경우 한번에 64비트를 처리 할 수 있다.

따라서 CPU(32bits 또는 64bits)에 따라 처리할 수 있는 비트 수(성능)가 다르다.

3.2.2. n 비트로 처리할 수 있는 10진수의 범위

n비트로 표현할 수 있는 10진수의 개수는 2^n개이다.

  • 값의 개수: 2^n
  • 값의 범위: 0 ~ 2^n -1

3.3. 8진법과 16진법

2진수는 0과 1로 표현을 하다보니 숫자가 커지면 자리수가 매우 길어진다는 단점이 있다.
8진수는 2진수의 3자리를 표현할 수 있고, 16진수는 2진수의 4자리를 표현할 수 있다.

16진수의 경우는 10부터 15까지의 수를 표현할 수 없기 때문에, 알파벳 a ~ f를 사용한다.

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

2진수를 8진수로 바꾸는 방법은 2진수를 3자리씩 끊어서 8진수로 계산하면된다.
그리고, 2진수를 16진수로 바꾸는 방법은 2진수를 4자리씩 끊어서 16진수로 계산하면 된다.

예를들어, 010100011010을 변환해보면

  • 8진수: 010 100 011 010 = 2432
  • 16진수: 0101 0001 1010 = 519

3.4. 정수의 진법 변환

3.4.1. 10진수를 n진수로 변환

10진수를 n진수로 변환할 때는 나누기를 하면된다.

예를들어, 46을 2진수로 변경한다고 하면

46 / 2 = 23 (몫) ... 0 (나머지)
23 / 2 = 11 ... 1
11 / 2 = 5 ... 1
5 / 2 = 2 ... 1
2 / 2 = 1 ... 0

따라서 46의 2진수는 10 1110 이다.

3.4.2. n진수를 10진수로 변환

반대로 n진수를 10진수로 변환 할때는 곱하면 된다.

10 1110 2진수를 10진수로 변경하면

(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (1 * 2^2) + (1 * 2^1) + (0 * 2^0)
= 32 + 8 + 4 + 2
= 46

3.5. 실수의 진법 변환

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

정수와 반대로 곱하기를 하면된다.

예를들어, 0.625 10진수를 2진수로 변경한다면

0.625 * 2 = 1.25 -> 1 (정수값)
0.25 * 2 = 0.5 -> 0
0.5 * 2 = 1

정수값만 밑에서부터 차례대로 가져오면
101이 되고, 마지막으로 앞에 값에 0. 을 붙이면 0.101 이 된다.

따라서, 10진수 0.625는 2진수로 0.101 이다.

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

2진수 0.101을 10진수로 바꾸면

(1 * 2^(-1)) + (0 * 2^(-2)) + (1 * 2^(-3))
= 1/2 + 1/8
= 0.5 + 0.125
= 0.625 가 된다.

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

음수를 표현할 때는 2의 보수를 사용한다.

3.6.1. 2의 보수법

프로그래밍에서는 음수를 표현하기 위해 부호 비트를 설정했는데, 맨 앞자리 비트를 부호비트로 사용한다.

4자리 비트를 예를들면
0000 = 0
0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7
1000 = -8
1001 = -7
1010 = -6
1011 = -5
1100 = -4
1101 = -3
1110 = -2
1111 = -1

쉽게 첫번째 비트만 1로 변경해서 음수를 만들면 편하겠지만, 그렇게 되면 0000과 1000이 모두 0이라는 숫자로 표현할 수 있는 수가 1개가 부족해진다. 따라서 2의 보수를 사용한다.

양수와 음수를 모두 표현하기 위해서는 2의 보수를 알아야한다.
2의 보수란 더해서 1가 되는 수를 뜻한다.

2진수에서 2의 보수란 더해서 10이 되는 수인데, 결국은 자리올림이 발생하고 1의 자리의 수가 0이되는 수를 말한다.

0101을 예를들면,
"0101"(5)이 "1 0000"이 되기 위해서는 "1011"(-5)의 값을 더해야 한다.

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

-5를 예를들면,

-5 --(절대값)--> 5 --(2진수)--> 0101 --(2의 보수)--> 1011

3.6.3. 2의 보수 구하기

쉽게 2의 보수 구하기

2의보수 = 1의 보수 + 1

(1의 보수는 단순히 0을 1로 바꾸고, 1은 0으로 바꾸면 된다)

4. 기본형 (primitive type)

4.1. 논리형 - boolean

논리형을 표현하는 boolean형 변수는 true와 false밖에 없다. 기본값은 false

2가지 형태밖에 없어서 대답(yes/no), 스위치(on/off) 등의 논리 구현에 많이 사용된다.

자바에서 boolean 변수를 사용할때는 true(참), false(거짓)와 같이 소문자로 작성해야한다.

4.2. 문자형 - char

문자형에는 char 한 가지 자료형밖에 없다. 사실 char에는 문자가 저장되는거 같지만 사실은 문자의 유니코드 정수 값이 저장된다.

예를들어, 'A'와 같은 문자를 담으면 65 값이 저장이된다.

참고로 어떤 문자의 유니코드를 알고 싶다면 int로 타입 형변환을 하면된다.

public static void main(String[] args) {
    // 영어
    char ch = 'A';
    System.out.printf("%d%n", (int) ch);

    // 한글
    char hch = '가';
    int int_hch = (int) hch;
    System.out.printf("%d, %x%n", int_hch, int_hch);
}

4.2.1. 특수 문자 다루기

특수문제를 다룰때는 아래와 같이 사용한다.

  • tab: \t
  • backspace: \b
  • form feed: \f
  • new line: \n
  • carriage return: \r
  • back slash: \
  • 작은 따옴표: '
  • 큰 따옴표: "
  • 유니코드(16진수)문자: \u유니코드
System.out.println("\'"); // ' 를 표현
System.out.println("abc\t123\b456"); // \t에 의해 빈공간이 생기고, \b에 의해 3이 지워진다.
System.out.println("개\n행"); // new line 이 생성된다.
System.out.println("\"Hello\""); // "Hello"
System.out.println("c:\\"); // c:\

4.2.2. char 타입의 표현형식

char 타입의 크기는 2byte라서 16비트 까지 표현이 가능하다.
'A'(65)를 예를들면 "0000 0000 0100 0001" 와 같이 표현이 가능하다.

char 타입도 결국은 정수 값을 저장하지만 정수형 타입과 차이점은 음수가 필요없기 때문에 숫자의 범위가 다르다는 점이다.

char와 short 모두 2byte로 동일한 크기를 가지지만 표현할 수 있는 수의 범위는 다르다.

  • short: -2^15 ~ 2^15 - 1
  • char: 0 ~ 2^16 - 1

똑같이 65 값을 출력을 해보면 둘이 서로 다른 값을 출력하는데, char 타입일때는 유니코드 문자로 해석하여 출력하기 때문에 'A'라고 출력한다.

char ch = 65;
short sh = 65;
System.out.println(ch); // 'A'
System.out.println(sh); // 65

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

인코딩은 문자를 코드로 바꿔주는 것을 말한다. 반대로 디코딩은 코드를 문자로 바꿔주는 것을 말한다.

예를들어, 'A'를 인코딩 하면 65, 반대로 65를 디코딩하면 'A'라고 할 수 있다.

인코딩은 '~을 코드화하다' 라는 뜻을 가졌다고 생각하면 된다.

4.2.4. 아스키(ASCII)

American Standard Code for Information Interchange 의 약자로 미국 표준 코드라는 뜻이다. 아스키는 7비트로 이루어져있다.

4.2.5. 확장 아스키(Extended ASCII)와 한글

일반적으로 데이터는 바이트 단위로 다루는데, 아스키의 경우는 7비트로 이루어져있어서 1비트가 남는다. 남는 1비트를 활용해서 문자를 추가로 정의한 것이 "확장 아스키" 이다.

확장 아스키로도 표현할 수 있는 문자의 개수가 제한적이어서, 2개의 문자코드로 한글을 표현하는 방법이 생겼다. 이렇게 문자 인코딩 CP949 만들어졌고, 한글에서 기본적으로 사용하는 인코딩이 되었다.

4.2.6. 코드 페이지(code page, cp)

지역이나 국가에 따라 여러 버전의 "확장 아스키"가 필요했다. 이것을 Code Page라 하여 CP라 했고, 각 코드 페이지에 'CP xxx' 와 같이 이름이 붙여졌다.

 

한국 윈도우는 CP 949 를 사용하고, 영문 윈도우는 CP 437 을 사용한다.

 

참고할 만한 링크

https://ko.wikipedia.org/wiki/%EC%BD%94%EB%93%9C_%ED%8E%98%EC%9D%B4%EC%A7%80_949

 

코드 페이지 949 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

4.2.7. 유니코드 (Unicode)

인터넷이 발달하면서 서로 다른 언어를 사용하는 컴퓨터 간의 문서교환이 활발히 이루어지기 시작했다. 이로인해 인코딩 문제가 발생하기 시작했다.

 

이런 어려움을 해소하기 위해 전 세계 모든 문자를 하나의 통일된 문자 집합으로 표현하고자 유니코드를 만들었다.

 

참고. 유니코드 종류를 한번에 볼 수 있는 사이트

https://home.unicode.org/

 

Home

News 🗓 Unicode Standard Releases📝 Public Review Issues

home.unicode.org

 

유니코드는 처음에는 2bytes를 이용해서 표현하려 했지만 표현 할 수 있는 문자가 부족하여, 21비트로 확장되었다.

새로 추가된 문자들을 보충 문자(supplementary character)라 하는데, 이 문자를 표현할 때는 char가 아닌 int 타입을 사용해야 한다.

 

유니코드 인코딩 방법에는 여러가지가 존재한다. UTF-8, UTF-16, UTF-32 등이 있고 자바에서는 UTF-16을 사용한다.

 

UTF-16은 모든 문자를 2바이트 코정 크기로 표현하고, UTF-8은 하나의 문자를 1 ~ 4바이트 가변 크기로 표현한다.

보통 인터넷에서는 문서의 크기가 작을수록 유리하고, 전송속도가 중요하여 UTF-8 인코딩으로 작성한다.

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

정수형에는 모두 4개의 자료형이 있다. byte(1바이트), short(2바이트), int(4바이트), long(8바이트)

기본 자료형은 int이다.

4.3.1. 정수형의 표현형식과 범위

리터럴 변수에 저장을 하게되면, 2진수로 바뀌어 저장이된다. 맨 앞의 비트는 부호비트로 사용된다.

n 비트로 표현 할 수 있는 정수의 개수는 2^n개가 된다.

  • 음수 2^(n-1)개 + 양수 2^(n-1)개 = 총 2^n개

n 비트로 표현할 수 있는 정수의 범위

  • -2^(n-1) ~ 2^(n-1) - 1

2^(n-1)에 -1을 빼는 이유는 0을 포함하고 있기 때문이다.

4.3.2. 정수형의 선택기준

변수에 저장하려는값의 범위에 따라 4개 중 하나를 선택하여 저장할 수 있다.

 

byte나 short를 사용하면 메모리를 절약할 수 있겠지만, 저장 할 수 있는 범위가 작은 편이라 연산 시에 범위를 넘어 잘못된 값을 얻기 쉽다.

 

그리고, JVM의 피연산자 스택(operand stack)이 피연산자를 4바이트 단위로 저장하기 때문에 크기가 4바이트 보다 작은 자료형(byte, short)의 값을 계산할 때는 4바이트로 변환한다. 결국은 int를 사용하는 것이 더 효율적이다.

 

따라서 정수형 변수를 선언할때는 int를 사용하고, int의 범위를 벗어나는 (약 20억) 값에 대해서 long 타입을 이용한다. byte나 short는 성능이 아닌 저장공간을 절약할때 사용하는 것이 좋다.

4.3.3. 정수형의 오버플로우

'1111'에 1을 더하게 되면 4비트의 범위를 넘어서는 값이 된다. 에러가 발생하는 것은 아니지만 자신이 예상한 값과 다른 결과가 나온다.

 

1111 + 0001 = 1 0000 이 되는데 4비트를 넘어서기 때문에 앞에 '1' 비트는 사라진다. 결국 0000이된다. 우리는 16이라는 값을 기대했겠지만 0이라는 값을 얻게 된다.

 

이렇게 표현할 수 있는 범위를 벗어나서 원하지 않는 값이 나오는 것을 오버 플로우(Overflow)라 한다.

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

부호 있는 정수는 부호비트가 0에서 1이 될때 오버플로우가 발생한다.

@Test
public void testSignedVarOverflow() {
    short sMin = -32768;
    short sMax = 32767;
    // short는 2바이트이지만 자바에서는 연산을 할때 4바이트로 치환하기 때문에
    // short로 형변환을 다시 해줘야 한다.
    assertEquals(sMax, (short) (sMin - 1), 0); // sMax == sMin - 1
    assertEquals(sMin, (short) (sMax + 1), 0); // sMin == sMax + 1

    char cMin = 0;
    char cMax = 65535;
    // char는 양수만 존재하기 때문에 연산을 하게되면 음수값이 된다.
    // char로 다시 형변환을 해줘야 함
    assertEquals(cMax, (char) (cMin - 1), 0); // cMax == sMin - 1
    assertEquals(cMin, (char) (cMax + 1), 0); // cMin == cMax + 1
}

4.4. 실수형 - float, double

  • float의 표현 가능 범위: 1.4 * 10^(-45) ~ 3.4 * 10^38 (정밀도: 7자리)
  • double의 표현 가능 범위: 4.9 * 10^(-324) ~ 1.8 * 10^308 (정밀도: 15자리)

표현할 수 있는 범위가 넓을수록 좋긴하지만, 실수의 경우는 0에 얼마나 가깝게 표현할 수 있는지도 중요하다.

4.4.1. 실수형도 정수형처럼 저장 할 수 있는 범위를 벗어나게 되면 오버플로우가 발생하나?

실수 또한 표현범위를 벗어나면 오버플로우가 발생하지만, 정수형과 달리 무한대(infinity)가 된다.

그리고, 언더플로우(underflow)가 있는데 실수형으로 표현할 수 없는 아주 작은 값을 뜻한다. 이때 변수의 값은 0이 된다.

4.4.2. float와 double

float의 정밀도는 7자리라서 그 이상의 소숫점을 표현하려면 double을 사용해야한다.

그래서 보통 double을 사용하는 이유는 보다 높은 정밀도를 얻기 위해서이다.

 

결론은 연산속도 및 메모리 절약을 위해서는 float를 사용하고, 보다 큰 값 또는 높은 정밀도를 필요로 하는 경우 double을 사용한다.

4.4.3. 실수형의 저장형식

실수형은 정수형과 표현형식이 달라서 값을 부동 소수 점수의 형태로 저장한다.

부동소수점수는 부호(Sign), 지수(Exponent), 가수(Manissa)로 이루어져있다.

 

float

  • S(1) + E(8) + M(23)

double

  • S(1) + E(11) + M(52)

4.4.3.1. 부호 (Sign bit)

S는 부호비트(sign bit)를 의미하며 1bit이다.

  • 0 양수
  • 1 음수

정수형과 달리 2의 보수를 사용하지 않고, 그냥 부호비트를 0에서 1로 바꾼다.

 

4.4.3.2. 지수 (Exponent)

float의 경우 8비트를 지수로 가지기 때문에 -127 ~ 128까지 표현이 가능하다. 여기서 양끝의 수 -127과 128은 숫자 아님(NaN, Not a Number)이나 무한대(POSITIVE_INFINITY 또는 NEGATIVE_INFINITY)로 예약되어 있다. 그래서, 실제 표현 가능 수는 -126에서 127이다.

 

즉, float의 표현 가능 범위는 2^(-126) ~ 2^127 또는 10^(-45) ~10^38 이다.

 

4.4.3.3. 가수 (Mantissa)

float는 23비트를 표현하고, double은 52비트를 표현할 수 있다. 즉 double 이 float보다 약 2배 정도 더 정밀하게 값을 표현할 수 있다.

4.4.4. 부동소수점의 오차

실수에는 무한소수가 존재하는데, 이로인해 실수를 저장할때는 오차가 발생한다. 게다가 2진수로 저장을 하기 때문에 2진수로 변환하면 무한소수가 되는 경우도 있다.

 

예를들어, 9.1234567 10진수는 2진수로 표현하면 1001.00011111100110... 무한소수가 된다.

즉, 2진수로는 이 값을 정확히 표현하지 못한다.

 

2진수로 변환된 실수를 저장할때 정규화 과정을 거치는데 "1.xxxx * 2^n" 과 같은 형태를 말한다.

그래서 맨 앞의 1을 제외한 23자리의 2진수만 저장이 되고 나머지 부분은 잘려나간다.

@Test
public void testFloatToBin() {
    float f = 9.1234567f;
    int i = Float.floatToIntBits(f);
    System.out.printf("%f%n", f);
    // 원래는 ...1101 10..로 이어지기 때문에 4111f9AD 가 나와야 하지만,
    // 반올림이 되면서 잘려나가기 때문에 ...1110이 되어 4111f9AE가 된다.
    System.out.printf("%x%n", i);
}

5. 형변환

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

프로그래밍을 하다보면 타입이 다르거나 다른 타입의 연산을 하게 되는 경우가 생긴다.

이럴때, 연산을 하기 전에 타입을 맞춰야 하는데 이를 형변환(Casting)이라고 한다.

5.2. 형변환 방법

타입을 캐스팅 할때는 괄호와 타입 이름을 명시해주면 된다.

"(타입) 피연산자" 와 같이 표현한다.

double d = 85.4;
int score = (int) d;

System.out.println(d); // 85.4
System.out.println(score); // 85

primitive 타입에서 boolean 을 제외하고는 서로간의 형변환이 가능하다.

5.3. 정수형 간의 형변환

큰 타입에서 작은 타입으로 변환할때는 크키의 차이만큼 잘려나가게 된다. 예를들어, int에서 byte로 의 변환하는 경우.

그러다보니 값 손실(loss of data)이 발생한다.

int i = 300;
byte b = (byte) i;

System.out.println(i); // 300
System.out.println(b); // 44

반대로 작은 타입을 큰 타입으로 변한하는 경우는 잘려나가는 일이 없어서 값 손실이 없다. (나머지 자리는 부호 비트와 같은 값으로 채워진다.)

5.4. 실수형 간의 형변환

실수형에서도 정수형처럼 작은 타입에서 큰 타입으로 변환하는 경우, 빈 공간을 0으로 채운다.

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

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

정수를 2진수로 변환하고 정규화하여 저장하면된다. 주의할점은 int는 10자리의 수를 표현하지만 float는 소수점 자리가 존재하여 7자리까지 표현이 가능하다.

 

그래서, 숫자가 큰 경우 오차가 발생한다. 이때는 double로 형변환을 한다.

5.5.2. 실수형을 정수형을 변환

실수형도 소수점 이하 값을 버리고 저장하면되는데, 정수의 값이 저장 범위를 넘어서는 경우 오버플로우가 발생한다.

5.6. 자동 형변환

원래는 캐스팅을 통해 형변환을 하는 것이 원칙이지만, 편의상 생략이 가능한 경우도 있다.

float f = 1234; // float f = (float) 1234; 와 같다.

서로 다른 두 타입간의 연산을 할 때는 더 큰 범위를 가지는 타입으로 캐스팅하여 계산한다.

double d = 1.0 + 3;

5.6.1. 자동 형변환의 규칙

컴파일러가 자동으로 캐스팅을 할때, 기준이 있는데

 

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

char와 short의 경우는 둘 다 2byte이지만, char의 경우는 양수의 값만 가지다보니 서로 범위가 달라 어느쪽으로도 형변환에 의한 값 손실이 발생할 수 있다.

 

정리

  1. boolean을 제외한 7개의 primitive 자료형은 서로 casting이 가능하다.
  2. 기본형과 참조형은 서로 캐스팅이 불가능하다.
  3. 캐스팅을 해주는게 원칙이지만, 작은 범위의 타입을 큰 범위의 타입으로 캐스팅 할때는 생략할 수 있다.
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기