- 22장 자료구조
- 23장 에러핸들링
22장 자료구조
- 리스트의 기본적인 사용법
- list.New()로 새로운 인스턴스를 만들어 PushBack, PushFront, InsertBefore, InsertAfter함수를 사용하여, 리스트의 여러위치에 요소를 삽입할수있다.
- Front(),Next()함수를 사용하여 각 요소별 순회가 가능하다. e는 List의 pointer변수이다.
//ch22/ex22.1/ex22.1.go
package main
import (
"container/list"
"fmt"
)
func main() {
v := list.New() // ❶ 새로운 리스트 생성
e4 := v.PushBack(4) // ❷ 리스트 뒤에 요소 추가
e1 := v.PushFront(1) // ❸ 리스트 앞에 요소 추가
v.InsertBefore(3, e4) // ❹ e4 요소 앞에 요소 삽입
v.InsertAfter(2, e1) // ➎ e1 요소 뒤에 요소 삽입
for e := v.Front(); e != nil; e = e.Next() { // ➏ 각 요소 순회
fmt.Print(e.Value, " ")
}
fmt.Println()
for e := v.Back(); e != nil; e = e.Prev() { // ➐ 각 요소 역순 순회
fmt.Print(e.Value, " ")
}
}
- 아래는 배열,슬라이스와 리스트의 Big-O 표기법이다.
- 리스트를 사용하여 Queue를 구현할수있다.
//ch22/ex22.2/ex22.2.go
package main
import (
"container/list"
"fmt"
)
type Queue struct { // ❶ Queue 구조체 정의
v *list.List
}
func (q *Queue) Push(val interface{}) { // ❷ 요소 추가
q.v.PushBack(val)
}
func (q *Queue) Pop() interface{} { // ❸ 요소을 반환하면서 삭제
front := q.v.Front()
if front != nil {
return q.v.Remove(front)
}
return nil
}
func NewQueue() *Queue {
return &Queue{list.New()}
}
func main() {
queue := NewQueue() // ❹ 새로운 큐 생성
for i := 1; i < 5; i++ { // ➎ 요소 입력
queue.Push(i)
}
v := queue.Pop()
for v != nil { // ➏ 요소 출력
fmt.Printf("%v -> ", v)
v = queue.Pop()
}
}
- 아래소스는 리스트를 사용하여 스택을 만드는 예제이다.
//ch22/ex22.3/ex22.3.go
package main
import (
"container/list"
"fmt"
)
type Stack struct {
v *list.List
}
func NewStack() *Stack {
return &Stack{list.New()}
}
func (s *Stack) Push(val interface{}) {
s.v.PushBack(val) // ❶ 맨 뒤에 요소 추가
}
func (s *Stack) Pop() interface{} {
back := s.v.Back() // ❷ 맨 뒤에서 요소를 반환
if back != nil {
return s.v.Remove(back)
}
return nil
}
func main() {
stack := NewStack()
for i := 1; i < 5; i++ {
stack.Push(i)
}
val := stack.Pop()
for val != nil {
fmt.Printf("%v -> ", val)
val = stack.Pop()
}
}
- 아래의 소스를 리스트를 사용하여 링 자료구조를 구현하였다.
//ch22/ex22.4/ex22.4.go
package main
import (
"container/ring"
"fmt"
)
func main() {
r := ring.New(5) // ❶ 요소가 5개인 링 생성
n := r.Len() // ❷ 링 개수 반환
for i := 0; i < n; i++ {
r.Value = 'A' + i // ❸ 순회하면 모든 요소에 값 대입
r = r.Next()
}
for j := 0; j < n; j++ {
fmt.Printf("%c ", r.Value) // ❹ 순회하며 값 출력
r = r.Next()
}
fmt.Println() // 한줄 띄우기
for j := 0; j < n; j++ {
fmt.Printf("%c ", r.Value) // ➎ 순회하며 값 출력
r = r.Prev()
}
}
- 아래 소스는 맵을 사용한 기본예제인데,
- 맵을 생성하고, 값을 추가하거나, 변경하고 출력하는 예제이다.
//ch22/ex22.5/ex22.5.go
package main
import "fmt"
func main() {
m := make(map[string]string) // ❶ 맵 생성
m["이화랑"] = "서울시 광진구"
m["송하나"] = "서울시 강남구"
m["백두산"] = "부산시 사하구"
m["최번개"] = "전주시 덕진구"
m["최번개"] = "청주시 상당구" // ❸ 값 변경
fmt.Printf("송하나의 주소는 %s입니다.\n", m["송하나"])
fmt.Printf("백두산의 주소는 %s입니다.\n", m["백두산"])
}
- 아래 예제는 다른 용도로 사용되는 맵의 소스이다.
- key값은 int로 , value는 Product구조체이다.
//ch22/ex22.6/ex22.6.go
package main
import "fmt"
type Product struct {
Name string
Price int
}
func main() {
m := make(map[int]Product) // ❶ 맵 생성
m[16] = Product{"볼펜", 500}
m[46] = Product{"지우개", 200}
m[78] = Product{"자", 1000}
m[345] = Product{"샤프", 3000}
m[897] = Product{"샤프심", 500}
for k, v := range m { // ❸ 맵 순회
fmt.Println(k, v)
}
}
- 맵에서 아래 소스와 같이 값을 삭제할수도있다.
//ch22/ex22.7/ex22.7.go
package main
import "fmt"
func main() {
m := make(map[int]int) // ❶ 맵 생성
m[1] = 0 // ❷ 요소 추가
m[2] = 2
m[3] = 3
delete(m, 3) // ❸ 요소 삭제
delete(m, 4) // ❹ 없는 요소 삭제 시도
fmt.Println(m[3]) // ➎ 삭제된 요소 값 출력
fmt.Println(m[1]) // ➏ 존재하는 요소 값 출력
}
- 아래는 간단한 해시함수를 사용하여 맵을 만드는 예제이다.
- 만약에 m[hash(33)] = 50 으로 값을 저장한다면 hash(23)과 충돌한다. 이런경우 가장 단순한 해결방법은 인덱스 위치마다 값이 아니라 리스트를 저장하게 하면 해결할수있다.
//ch22/ex22.8/ex22.8.go
package main
import "fmt"
const M = 10 // ❶ 나머지 연산의 분모
func hash(d int) int {
return d % M // ❷ 나머지 연산
}
func main() {
m := [M]int{} // ❸ 값을 저장할 배열 생성
m[hash(23)] = 10 // ❹ 키 23에 값 설정
m[hash(259)] = 50 // ➎ 키 259에 값 설정
fmt.Printf("%d = %d\n", 23, m[hash(23)])
fmt.Printf("%d = %d\n", 259, m[hash(259)])
}
23장 에러핸들링
- WriteFile()함수는 파일에 string을 저장하고, ReadFile()함수는 data.txt파일의 내용을 읽어서 반환하는 함수이다.
//ch23/ex23.1/ex23.1.go
package main
import (
"bufio"
"fmt"
"os"
)
func ReadFile(filename string) (string, error) {
file, err := os.Open(filename) // ❶ 파일 열기
if err != nil {
return "", err // ❷ 에러 나면 에러 반환
}
defer file.Close() // ❸ 함수 종료 직전 파일 닫기
rd := bufio.NewReader(file) // ❹ 파일 내용 읽기
line, _ := rd.ReadString('\n')
return line, nil
}
func WriteFile(filename string, line string) error {
file, err := os.Create(filename) // ➎ 파일 생성
if err != nil { // ➏ 에러 나면 에러 반환
return err
}
defer file.Close()
_, err = fmt.Fprintln(file, line) // ➐ 파일에 문자열 쓰기
return err
}
const filename string = "data.txt"
func main() {
line, err := ReadFile(filename) // ➑ 파일 읽기 시도
if err != nil {
err = WriteFile(filename, "This is WriteFile") // ➒ 파일 생성
if err != nil { // ❿ 에러를 처리
fmt.Println("파일 생성에 실패했습니다.", err)
return
}
line, err = ReadFile(filename) // ⓫ 다시 읽기 시도
if err != nil {
fmt.Println("파일 읽기에 실패했습니다.", err)
return
}
}
fmt.Println("파일내용:", line) // ⓬ 파일 내용 출력
}
- 위의 소스는 함수에서 발생한 에러를 처리하였고, 아래의 소스는 직접 에러를 만들어서 반환하는 함수를 만들어보겠다.
- Sqrt함수는 인자로 들어온값이 음수이면 에러를 반환하도록 하였다.
//ch23/ex23.2/ex23.2.go
package main
import (
"fmt"
"math"
)
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, fmt.Errorf(
"제곱근은 양수여야 합니다. f:%g", f) // ❶ f가 음수이면 에러 반환
}
return math.Sqrt(f), nil
}
func main() {
sqrt, err := Sqrt(-2)
if err != nil {
fmt.Printf("Error: %v\n", err) // ❷ 에러 출력
return
}
fmt.Printf("Sqrt(-2) = %v\n", sqrt)
}
- 아래소스를 보면 PasswordError구조체를 선언하고, Error()함수를 해당 구조체의 메서드로 선언하였다.
- RegisterAccount함수에서 에러가 발생하면 PasswordError객체를 반환하고, main함수에서 반환된객체가 null이 아니라면 에러가 반환된다. 해당 에러객체를 출력하게 된다.
//ch23/ex23.3/ex23.3.go
package main
import "fmt"
type PasswordError struct { // ❶ 에러 구조체 선언
Len int
RequireLen int
}
// PasswordError구조체에 대해서 Error() 메서드가 구현되어있다.
func (err PasswordError) Error() string { // ❷ Error() 메서드
return "암호 길이가 짧습니다."
}
// error인터페이스를 리턴해준다. PasswordError구조체는 Error()메서드를 가지고 있기때문에 문제없음.
func RegisterAccount(name, password string) error {
if len(password) < 8 {
return PasswordError{len(password), 8} // ❸ error 반환
}
return nil
}
func main() {
err := RegisterAccount("myID", "myPw") // ➍ ID, PW 입력
if err != nil { // ➎ 에러 확인
if errInfo, ok := err.(PasswordError); ok { // ➏ 인터페이스 변환
fmt.Printf("%v Len:%d RequireLen:%d\n",
errInfo, errInfo.Len, errInfo.RequireLen)
}
} else {
fmt.Println("회원 가입되었습니다.")
}
}
- 아래소스는 문자열에서 두단어를 읽어서 숫자로 변환후 곱한결과를 반환하는 소스이다.
//ch23/ex23.4/ex23.4.go
package main
import (
"bufio"
"errors"
"fmt"
"strconv"
"strings"
)
func MultipleFromString(str string) (int, error) {
scanner := bufio.NewScanner(strings.NewReader(str)) // ❶ 스캐너 생성
scanner.Split(bufio.ScanWords) // ❷ 한 단어씩 끊어읽기
pos := 0
a, n, err := readNextInt(scanner)
if err != nil {
return 0, fmt.Errorf("Failed to readNextInt(), pos:%d err:%w", pos, err) // ➏ 에러 감싸기
}
pos += n + 1
b, n, err := readNextInt(scanner)
if err != nil {
return 0, fmt.Errorf("Failed to readNextInt(), pos:%d err:%w", pos, err)
}
return a * b, nil
}
// 다음 단어를 읽어서 숫자로 변환하여 반환합니다.
// 변환된 숫자, 읽은 글자수, 에러를 반환합니다.
func readNextInt(scanner *bufio.Scanner) (int, int, error) {
if !scanner.Scan() { // ❸ 단어 읽기
return 0, 0, fmt.Errorf("Failed to scan")
}
word := scanner.Text()
number, err := strconv.Atoi(word) // ❹ 문자열을 숫자로 변환
if err != nil {
return 0, 0, fmt.Errorf("Failed to convert word to int, word:%s err:%w", word, err) // ➎ 에러 감싸기
}
return number, len(word), nil
}
func readEq(eq string) {
rst, err := MultipleFromString(eq)
if err == nil {
fmt.Println(rst)
} else {
fmt.Println(err)
var numError *strconv.NumError
if errors.As(err, &numError) { // ➐ 감싸진 에러가 NumError인지 확인
fmt.Println("NumberError:", numError)
}
}
}
func main() {
readEq("123 3")
readEq("123 abc")
}
- 패닉은 프로그램을 정상 진행시키기 어려운 상황을 만났을때 프로그램 흐름을 중지시키는 기능이다. Go에서는 panic()으로 내장함수를 제공한다.
//ch23/ex23.5/ex23.5.go
package main
import "fmt"
func divide(a, b int) {
if b == 0 {
panic("b는 0일 수 없습니다") // ❶ Panic 발생
}
fmt.Printf("%d / %d = %d\n", a, b, a/b)
}
func main() {
divide(9, 3)
divide(9, 0) // ❷ Panic 발생
}
- 프로그램이 문제가 발생할때 프로그램이 종료되는 대신 에러메세지를 표시하고 복구를 시도하는게 낫다.
- 아래 소스는 패닉이 발생하였을때 복구를 하는 소스이다.
- recover()함수에서 패닉이 전파중이면 panic객체를 출력하고, 계속 진행한다.
//ch23/ex23.6/ex23.6.go
package main
import "fmt"
func f() {
fmt.Println("f() 함수 시작")
defer func() { // ❹ 패닉 복구
if r := recover(); r != nil {
fmt.Println("panic 복구 -", r)
}
}()
g() // ❶ g() -> h() 순서로 호출
fmt.Println("f() 함수 끝")
}
func g() {
fmt.Printf("9 / 3 = %d\n", h(9, 3))
fmt.Printf("9 / 0 = %d\n", h(9, 0)) // ❷ h() 함수 호출 - 패닉
}
func h(a, b int) int {
if b == 0 {
panic("제수는 0일 수 없습니다.") // ❸ 패닉 발생!!
}
return a / b
}
func main() {
f()
fmt.Println("프로그램이 계속 실행됨") // ➎ 프로그램 실행 지속됨
}
'Go' 카테고리의 다른 글
[묘공단] Tucker의 Go 언어 프로그래밍 26장 : 단어검색프로그램 (0) | 2024.03.09 |
---|---|
[묘공단] Tucker의 Go 언어 프로그래밍 24장~25장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 20장~21장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 18장~19장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 15장~17장 (0) | 2024.01.29 |