본글은 『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트입니다.
산술 연산자
산술연산자는 숫자 연산을 하는 연산자이다.
연산의 결과 타입은 각항의 타입과 항상 같아야한다.
var a int64 = 10
var b float64 = 32.2433
var c float64
c = a + b // 각항의 타입이 달라서 계산 불가
비트연산자
비트 단위로 연산하는 비트 연산자다. 정수로만 연산이 가능하다.
각 비트마다 논리연산을 하는 것이다.
&^비트클리어 연산자
특정 비트를 0으로 바꾸는 연산자이다.
a&^n 이라고 한다면 a의 n번째 비트의 값을 0으로 바꾸는 연산이다.
처음에 ^연산을 이용해서 반전을 수행하고 &연산을 수행한다.
예를 들어서 10&^2를 해보자
10을 2진수로 바꾸면 0000 1010이다. (8비트로 표현했다.)
2에 ^연산을 수행하면 이진법으로는 1111 1101이 된다.
그런다음 &계산을 해주면
0000 1000이 된다.
결과적으로 10의 2번째 비트에서 1이 0으로 변한 것을 볼 수 있다.
시프트 연산자
시프트 연산자는 비트를 오른쪽으로 밀거나 당긴다.
- 왼쪽 시프트 왼쪽으로 우변값만큼 밀어버린다. 이때 오른쪽 피연산자는 반드시 양의 정수여야한다. 남아있는 비트 공간은 0으로 채워진다. 예제를 살펴보자
package main
import (
"fmt"
)
func main() {
var x int8 = 4
var y int8 = 64
fmt.Printf("x:%08b x<<2:%08b x<<2:%d\n" ,x,x<<2,x<<2)
fmt.Printf("x:%08b x<<2:%08b x<<2:%d\n" ,y,y<<2,y<<2)
}
출력값
x:00000100 x<<2:00010000 x<<2:16
x:01000000 x<<2:00000000 x<<2:0
왜 64에 2번만큼 왼쪽으로 밀면 0이 될까? 바로 범위를 벗어나기 때문이다.
64는 8비트라는 공간을 가진다. 시프트 연산으로 인해 공간의 범위를 벗어난 값은 사라진다. 비트의 값이 모두 보전된다면 2의 우변값 제곱만큼 곱한 것과 같을 수 있다.
- 오른쪽 시프트 (>>) 비트를 오른쪽으로 민다. 오른쪽 값은 항상 양의 정수여야한다. 이 때 오른쪽으로 밀어서 남아있는 공간은 전체값이 양수였다면 0 음수라면 1로 채워진다. 양수일 때는 이해가 가지만 음수일 때는 이해하기 어려울 수 있는데 만약 8비트 음수에서 가장 작은 값은 -1000 0000 이다. 이 수는 -128이다. 처음 값은 부호를 나타내는 값이다. 1이면 음수 0이면 양수이다. 1000 0000부터 1000 0001이 되면 -127이 되면서 늘어나가는 형식이다. 예제를 살펴보자
package main
import (
"fmt"
)
func main() {
var a int8 = -128
var b int8 = 127
fmt.Printf("a : %08b, a >> 2 : %08b, a >> 2 : %d\n" ,uint8(a),uint8(a>>2),(a>>2))
fmt.Printf("b : %08b, b >> 2 : %08b, b >> 2 : %d" ,uint8(b),uint8(b>>2),(b>>2))
}
출력값
a : 10000000, a >> 2 : 11100000, a >> 2 : -32
b : 01111111, b >> 2 : 00011111, b >> 2 : 31
이 부근에서 궁금한 것이 생겨서 tucker님한테 질문을 했다.
질문은 대충 이랬다.
uint8의 범위는 0부터 255까지라 배웠는데
이 예제에서는 음수인 -128을 어떻게 uint8로 변환 할 수 있을까?
컴퓨터는 int8인 -128을 비트 형태로 받아들인다. (1000 0000)
따라서 uint8 로 변환 될 때도 비트 형태로 받아들이기 때문에 음수인지 양수인지가 중요하지 않다.
package main
import (
"fmt"
)
func main() {
var a int8 = -128
fmt.Printf("a : %08b, uint8(a) : %08b, uint8(a) : %d\n" ,int8(a),uint8(a),uint8(a))
}
출력값
a : -10000000, uint8(a) : 10000000, uint8(a) : 128
비트로써 입력이 돼서 uint8 타입으로 변해 128이 된다.
비교 연산자
비교연산자는 불리언값을 반환한다. 조건에 만족하면 true 만족하지 못하면 false를 반환한다.
정수 오버플로
변수가 타입의 범위를 벗어나면 값이 비정상적으로 변화한다. 이런 현상을 오버플로우라고 한다.
예를 들어보자
package main
import (
"fmt"
)
func main() {
var x int8 = 127
fmt.Printf("x:%08b x+1:%08b x+1:%d\n" ,x,x+1,x+1)
}
출력값
x:01111111 x+1:-10000000 x+1:-128
범위가 -128부터 127까지인 int8의 범위를 벗어나자 오버플로 현상이 일어나서 -128이 되었다.
이와 반대로 -128에서 1을 빼면 범위를 벗어난 -129가 되어 언더플로우가 일어나 127이 반환된다.
논리 연산자
논리 연산자는 불리언 값을 반환한다. 실제로 실생활에서도 자주쓰는 표현들이다.
- && : and
- || : OR
- ! : not
대입연산자
var a int
var b int
a = 2
a=b=10 // 오류가 발생한다. 대입연산자는 논리연산자처럼 어떠한 값을 반환하지 않는다.
이 코드는 무엇을 의미할까?
a=2
에서 좌변은 공간 우변은 값에 해당한다. 대입연산에서 항상 유효하다.
a라는 메모리공간으로 가서 2라는 값을 넣어준다고 생각하면 된다.
복수 대입 연산자
여러 값을 한번에 대입할 수 있다.
a , b = b, a
a변수에는 b값을 대입하고 b에는 a값을 대입한다.
증감 연산자와 복합대입 연산자
대입 연산을 축약할 수 있다.
var a int = 10
//아래 세 식은 모두 같은 뜻이다.
a = a + 1
a += 1 //복합대입 연산자 *,/,-,+ 다 된다.
a ++ -,+ 만 된다.
연산자 우선순위
연산자 우선 순위가 있지만 외우기 쉽지 않아서
복잡하게 사용하려면 괄호를 이용하는 것이 좋다고 한다.
그냥 괄호를 사용하자.
그게 보는 사람이나 쓰는 사람 둘다에게 이롭다.
Uploaded by Notion2Tistory v1.1.0