안녕하세요, 중간고사 휴식기간을 가지고 2주만에 돌아온 고랭 스터디입니다!
이번 주는 "Tucker의 Go 언어 프로그래밍"의 6, 7, 8장을 각자 공부하고 세미나에서 같이 내용을 리뷰해보았습니다.
Ch 6. 연산자
6.1 산술 연산자
산술 연산자는 숫자 연산을 하는 연산자입니다. 사칙 연산, 비트연산, 시프트 연산이 속합니다.
- Go 언어에서 모든 연산자의 각 항의 타입은 항상 같습니다. (시프트 연산 제외)
- 다른 타입을 연산하려면 타입 변환 후 연산해야합니다.
- 연산의 결과타입도 인수 타입과 같습니다.
6.2 비교 연산자
비교 연산자는 양변을 비교해서 조건에 만족하면 불리언값 true를, 만족하지 못할 경우 false를 반환하는 연산자입니다. 분기문(if문, switch문)과 반복문(for문)에서 주로 사용합니다.
비교 연산자 사용 시 주의할 점
- 정수 오버플로: 범위 내 가장 큰 값에서 +1 을 했을 때 가장 작은 값이 되는 현상
- 정수 언더플로: 범위 내 가장 작은 값에서 -1 을 했을 때 가장 큰 값이 되는 현상
- float 표현의 오차
6.3 실수 오차
Go언어에서 실숫값을 사용할 때, 오차가 발생할 수 밖에 없습니다. (2의 음수 승수로 표현할 수 없는 소수점 이하 숫자들이 있기 때문이에요!)
실수 허용 오차를 줄이는 방법
1. 작은 오차 무시하기
- 매우 작은 상숫값을 선언합니다. 무시할 오차 한계를 정의한 값입니다.
- 두 값의 차이가 이 값보다 작을 경우 두 값이 같다고 간주합니다.
- 예) 오차 허용 범위 : 0.000001, 이하 숫자들은 전부 float64타입입니다.
- 0.1 + 0.2 == 0.3 는 기존에 false가 출력되었지만 0.1 + 0.2와 0.3 값의 차이가 0.000001보다 작기 때문에 두 값을 같은 값으로 간주하여 true가 출력됩니다.
- 단점 : 얼만큼의 오차가 무시할만큼 작은 오차인지? float64의 범위는 e-138 ~ e308로 매우 크기 때문에 지정한 값이 무시할 만큼 작지 않을 수 있어 그리 좋은 방법은 아닙니다..
2. Nextafter() 함수 사용하기
- 1비트 차이만큼 비교해서 가장 마지막 비트가 1비트이내의 차이가 난다면 같다고 간주합니다.
- func Nextafter(x, y float64) (r float64) : x가 y를 향해서 1비트만 조정한 값을 반환
3. math/big의 Float 패키지 사용하기
- 정밀도를 직접 조정할 수 있어 더 정 확한 수치 계산을 할 수 있습니다.
6.4 논리 연산자
논리 연산자는 불리언 피연산자를 대상으로 연산해 결과로 true나 false를 반환합니다.
6.5 대입 연산자
= 대입 연산자는 우변값을 좌변(메모리 공간)에 복사합니다. 좌변은 반드시 저장할 공간이 있는 변수가 와야 합니다.
a = b = 10 //오류 발생
대입 연산자는 아무런 값도 반환하지 않기 때문에 위 구문은 아래와 같이 수정해줍니다.
b = 10
a = b
복수 대입 연산자
- 여러 값을 한 번에 대입할 수 있습니다.
- 우변 개수에 맞춰서 좌변 변수 개수도 맞춰야 합니다.
- 예) a, b = b, a
복합 대입 연산자
- 대입 연산자 앞에 다른 산술 연산자를 붙여 변수의 값과 연산의 결과를 다시 변수에 대입합니다.
- 모든 산술 현산자는 다 복합 대입 연산자로 쓸 수 있습니다. (+=, -=, *=, /=, %=, &=, |=, ^= 등)
- 예) a += 3
증감 연산자
- 변숫값을 1 증가하거나 1 감소하는 구문은 자주 사용됩니다. (++, --)
- a++은 a+=1 과 같은 의미, . a—은 a-=1과 같은 의미입니다.
6.6 연산자 우선순위
우선순위가 높은 연산자, 같은 우선순위라면 좌측부터 연산됩니다.
Ch 7. 함수
7.1 함수 정의
함수 코드 블록의 시작을 알리는 중괄호 {가 함수를 정의하는 라인과 항상 같은 줄에 있어야 합니다.
7.2 함수를 호출하면 생기는 일
함수를 호출하며 매개변수로 입력한 인숫값은 실제 함수에 전달될 때 보낸 값을 그대로 사용하는 것이 아니라 값을 복사해 사용하게 됩니다.
c := Add(3, 6) //함수 호출
func Add(a int, b int) int { //매개변수 생성 및 초기화
//a와 b를 더한 결과를 반환
return a+b //값 반환(복사)
} //로컬 변수 삭제
1. Add() 함수 호출합니다.
2. 매개변수 선언 → 입력한 인숫값을 복사합니다. (a, b, 두 변수에 초깃값으로 3과 6을 대입)
3. return 키워드로 함수 결과 반환한 값을 전달합니다.
4. 반환된 값은 함수가 호출된 곳을 대체하는 것과 같습니다.
5. 호출한 함수 종료 시 함수에서 사용한 지역 변수에 접근할 수 없습니다.
c := Add(3, 6)
c := 9
6. c에 반환값이 대입(복사)됩니다.
7.3 함수는 왜 쓰나?
함수를 사용해서 반복 사용되는 코드를 묶을 수 있습니다. 중복 코드를 제거하여 코드를 간결하게 만들 수 있습니다.
func main() {
math := 80
eng := 74
history := 95
fmt.Println("김일등 님 평균 점수는", (math+eng+history)/3, "입니다.")
math = 88
eng = 92
history = 53
fmt.Println("송이등 님 평균 점수는", (math+eng+history)/3, "입니다.")
math = 78
eng = 73
history = 78
fmt.Println("박삼등 님 평균 점수는", (math+eng+history)/3, "입니다.")
}
- 만약 학생이 1000명이라면? 국어 점수가 추가된다면?
func printAvgScore(name string, math int, eng int, history int) {
total := math + eng + history
avg := total / 3
fmt.Println(name, "님 평균 점수는", avg, "입니다.")
}
func main() {
printAvgScore("김일등", 80, 74, 95)
printAvgScore("송이등", 88, 92, 53)
printAvgScore("박삼등", 78, 73, 78)
}
- 자주 사용되거나 변경 가능성이 있는 코드 블록을 묶어서 함수를 만들면 효율적으로 코딩할 수 있고 추후 프로그램 변경 요구에도 간단히 대처할 수 있습니다.
- 또 관련된 코드를 묶어서 이름을 부여하기 때문에 코드를 읽기에도 훨씬 편해집니다.
멀티 반환 함수
- 함수는 값을 여러개 반환할 수 있습니다.
- 반환 타입들을 소괄호로 묶어서 표현합니다.
func Divide(a, b int) (int, bool) {
if b == 0 {
return 0, false //제수가 0일 때 반환
}
return a / b, true //제수가 0이 아닐 때 반환
}
변수명을 지정해 반환하기
- 함수 선언부에 반환 타입을 적을 때 변수명까지 지정해주면, return문으로 해당 변수를 명시적으로 반환하지 않아도 값을 반환할 수 있습니다.
func Divide(a, b int) (result int, success bool) { //반환할 변수명 명시
if b == 0 {
result = 0
success = false
return //명시적으로 반환할 값을 지정하지 않은 return문
}
result = a / b
success = true
return
}
- 주의 : 반환할 변수의 이름을 지정할 경우 모든 반환 변수의 이름을 지정해야합니다. 모두 지정하거나 모두 지정하지 않거나!
7.4 재귀 호출
재귀 호출이란 함수 안에서 자기 자신 함수를 다시 호출하는 것을 말합니다.
func printNo(n int) {
if n == 0 { //재귀 호출 탈출 조건
return
}
fmt.Println(n)
printNo(n - 1) //재귀 호출
fmt.Println("After", n) //재귀 호출 이후 출력
}
func main() {
printNo(3)
}
3
2
1
After 1
After 2
After 3
- 호출 순서: printNo(3), printNo(2), printNo(1), printNo(0)
- return 순서: printNo(0), printNo(1), printNo(2), printNo(3)
- 주의 : 재귀 호출을 사용할 때는 항상 탈출 조건을 정해야 합니다. 재귀 호출이 종료되는 시점을 명확히 하지 않으면 재귀 호출이 무한히 반복되어 프로그램이 비정상 종료됩니다.
Ch 8. 상수
8.1 상수 선언
상수는 변하지 않는 값입니다. 값을 수시로 바꿀 수 있는 변수와 달리 상수는 초기화된 값이 변하지 않습니다. 기본 타입값들만 상수로 선언 가능합니다. (가능한 타입: 불리언, 룬(rune), 정수, 실수, 복소수, 문자열)
값으로만 동작하기 때문에 대입문의 좌변에 올 수 없습니다. 상수의 메모리 주솟값을 접근할 수 없기 때문에 에러가 발생합니다.
const C int = 10 //상수 선언
var b int = C * 20
C = 20 //에러 발생 - 상수는 대입문 좌변에 올 수 없다.
fmt.Println(&C) //상수 주소 출력
.\ex8.1.go:9:4: cannot assign to C (declared const)
8.2 상수는 언제 사용하나?
변하면 안 되는 값에 상수 사용하기
- 상수로 변하는 값에 이름을 부여하면 매번 값을 쓰지 않고 편리하게 이용할 수 있습니다.
- 예) 원주율 π = 3.141592...
- 고정 불변의 값을 여러 번 사용할 때 변수 대신 상수로 정의하는 것이 더 안전하고 확실합니다.
const PI1 float64 = 3.141592653589793238
var PI2 float64 = 3.141592653589793238
// PI1 = 4 //에러 발생
PI2 = 4
fmt.Printf("원주율: %f\n", PI1)
fmt.Printf("원주율: %f\n", PI2)
- 상수를 사용하면 상수를 변경하는 시도를 할 때 컴파일 단계에서 에러가 출력되므로 의도치 않은 결과를 미연에 방지할 수 있습니다.
코드값으로 사용하기
- 코드값이란 어떤 숫자에 의미를 부여하는 것입니다. 컴퓨터에서 코드는 매우 다양하게 사용됩니다.
- 예) ASCII 문자 코드에서 'A'는 65입니다.
- 예) 월드와이드웹의 통신 프로토콜 HTTP에서 응답코드 200번은 OK를 의미, 404번은 NOT FOUND입니다. 숫자 자체에 의미가 있는 게 아니라 통신을 편하게 하기 위해서 숫자값에 의미를 부여한 것입니다.
- 처리가 번거롭고 성능이나 메모리에 더 안 좋은 문자열 대신 숫자값에 의미를 부여하는 코드로 간편하게 처리할 수 있습니다.
iota로 간편하게 열거값 사용하기
- 코드값으로 사용하기 때문에 값이 그냥 1, 2, 3, ...처럼 1씩 증가하도록 정의할 때 iota 키워드를 사용하면 편리합니다.
- iota는 0부터 1씩 증가하며 소괄호를 벗어나면 다시 초기화됩니다.
const (
BitFlag1 uint = 1 << iota //1 = 1 << 0
BitFlag2 //2 = 1 << 1
BitFlag3 //4 = 1 << 2
BitFlag4 //8 = 1 << 3
)
const (
A int = iota //0
B //1
)
- 만약 첫번째 값과 똑같은 규칙이 계속 적용된다면 타입과 iota를 생략할 수 있습니다
8.3 타입 없는 상수
상수 선언 시 타입을 명시 않을 수 있습니다. 타입 없는 상숫값은 타입이 정해지지 않은 상태로 사용됩니다.
const PI = 3.14 //타입 없는 상수
const FloatPI float64 = 3.14 //타입 상수
func main() {
var a int = PI * 100 //오류 발생하지 않는다.
var b int = FloatPI * 100 //타입 오류 발생
fmt.Println(a)
fmt.Println(b)
}
- PI 값은 타입이 없기 때문에 3.14 숫자로만 동작합니다. → PI * 100 = 314는 정수 타입 변수에 대입 가능!
- FloatPI는 float64 타입이기 때문에 타입 오류가 발생합니다.
- 타입 없는 상수는 변수에 복사될 때 타입이 정해지기 때문에 여러 타입에 사용되는 상수값을 사용할 때 편리합니다.
8.4 상수와 리터럴
컴퓨터에서 리터럴이란 고정된 값, 값 자체로 쓰인 문구입니다.
var str string = "Hello World"
var i int = 0
i = 30
"Hello World, 0, 30과 같이 고정된 값 자체로 쓰인 문구가 리터럴입니다. Go 언어에서 상수는 리터럴과 같이 취급합니다. 즉, 컴파일될 때 상수는 리터럴로 변환되어 실행 파일에 쓰이게 됩니다.
상수 표현식 역시 컴파일 타임에 실제 결괏값 리터럴로 변환하기 때문에 상수 표현식 계산에 CPU 자원을 사용하지 않습니다.
const PI = 3.14
var a int = PI * 100
위 구문은 컴파일 타임에 아래와 같이 변환됩니다.
var a int = 314
상수의 메모리 주솟값에 접근할 수 없는 이유 역시 컴파일 타임에 리터럴로 전환되어서 실행 파일에 값 형태로 쓰이기 때문입니다. 그래서 동적 할당 메모리 영역을 사용하지 않습니다.
이렇게 2주차까지 Go언어의 기본 문법인 연산자, 함수, 상수까지 배워보았습니다! 다음 주는 if문, switch문, for문을 다루게 되겠습니다.
기초를 다지는 작업이라 그런지, 어서 직접 프로그램을 만들어 보고 싶네요! 화이팅합시다!
'3-1기 스터디 > Golang' 카테고리의 다른 글
[6주차] 숫자 맞추기 게임 만들기, Go 언어의 슬라이스 (0) | 2021.12.01 |
---|---|
[5주차] Chapter 15~16. 문자열, 패키지 (0) | 2021.11.30 |
[4주차] Go 언어의 배열, 구조체, 포인터 (0) | 2021.11.22 |
[3주차] Go언어의 if, switch, for (0) | 2021.11.11 |
[1주차] Go 언어 입문하기 (개념, 변수, 표준입출력, 설치 방법) (0) | 2021.10.13 |
댓글