본문 바로가기

Go

[묘공단] Tucker의 Go 언어 프로그래밍 15장~17장

15장 문자열 

 

  • Go에서 문자열의 타입명은 string이다. 문자열은 큰따옴표나 백쿼트로 묶어서 사용하는데, 백쿼트로 묶어서 사용하면 문자열안의 특수문자가 일반문자처럼 처리된다. (문자열 그대로 출력된다.)
//ch15/ex15.1/ex15.1.go
package main

import "fmt"

func main() {
	// ❶ 큰따옴표로 묶으면 특수 문자가 동작합니다.
	str1 := "Hello\t'World'\n"

	// ❷ 백쿼트로 묶으면 특수 문자가 동작하지 않습니다.
	str2 := `Go is "awesome"!\nGo is simple and\t'powerful'`
	fmt.Println(str1)
	fmt.Println(str2)
}

 

 

  • 큰따옴표로 문자열을 묶을경우 여러줄을 표현하려면 \n을 사용해야 가능하다.
  • 백쿼트에서는 여러줄표현에 특수문자가 필요없고, 여러줄로 작성하면 그대로 출력된다.
//ch15/ex15.2/ex15.2.go
package main

import "fmt"

func main() {

	// 큰따옴표에서 여러 줄을 표현하려면 \n을 사용해야 합니다.
	poet1 := "죽는 날까지 하늘을 우러러\n한 점 부끄럼이 없기를,\n잎새에 이는 바람에도\n나는 괴로워했다.\n"

	// 백쿼트에서는 여러 줄 표현에 특수 문자가 필요 없습니다.
	poet2 := `죽는 날까지 하늘을 우러러
한 점 부끄럼이 없기를,
잎새에 이는 바람에도
나는 괴로워했다.`

	fmt.Println(poet1)
	fmt.Println(poet2)
}

 

 

  • 문자하나를 표현하는데 rune타입을 사용한다. rune타입은 int32와 같다.
  • 아래의 소스를 보면, char이라는 이름의 rune타입이 있는데 , 해당 타입의 한문자를 출력하려면 %c를 사용해서 출력한다. 그냥 fmt.Println(char) 명령으로 출력하면 int32형이기때문에, 숫자값이 출력된다.
//ch15/ex15.3/ex15.3.go
package main

import "fmt"

func main() {
	var char rune = '한'

	fmt.Printf("%T\n", char) // ❶ char 타입 출력
	fmt.Println(char)        // ❷ char값 출력
	fmt.Printf("%c\n", char) // ❸ 문자 출력
}

 

 

  • 아래의 소스에서 한글 5글자의 길이는 15이다. UTF-8에서는 한글은 한글자당 3바이트를 차지하기때문이다.
//ch15/ex15.4/ex15.4.go
package main

import "fmt"

func main() {
	str1 := "가나다라마" // ❶ 한글 문자열
	str2 := "abcde" // ❷ 영문 문자열

	fmt.Printf("len(str1) = %d\n", len(str1)) // 한글 문자열 크기
	fmt.Printf("len(str2) = %d\n", len(str2)) // 영문 문자열 크기
}

 

  • []rune 타입을 string형으로 변환하는 예제이다. 서로 상호 변환이 가능하다.
//ch15/ex15.5/ex15.5.go
package main

import "fmt"

func main() {
	str := "Hello World"

	// ❶ ‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘W’, ‘o’, ‘r’, ‘l’, ‘d’ 문자코드 배열
	runes := []rune{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100}

	fmt.Println(str)
	fmt.Println(string(runes))
}

 

 

ASCII Code

 

  • string형을 []rune타입으로 타입변환을 하여, 한글을 포함한 글자수를 정확히 알수있다.  아래의 소스에서 보면 str의 길이는 12이다. 왜냐하면 한글은 한글자당 3바이트를 차지하기 때문이다.
//ch15/ex15.6/ex15.6.go
package main

import "fmt"

func main() {
	str := "hello 월드"    // ❶ 한글과 영문자가 섞인 문자열
	runes := []rune(str) // ❷ []rune 타입으로 타입 변환

	fmt.Printf("len(str) = %d\n", len(str))     // ❸ string 타입 길이
	fmt.Printf("len(runes) = %d\n", len(runes)) // ➍ []rune 타입 길이
}

 

 

문자열의 순회

  • 아래소스는 인덱스를 사용한 문자열순회 방법이다.  바이트단위로 순회하기때문에 한글은 깨져서 출력되는 문제가 있다.
//ch15/ex15.7/ex15.7.go
package main

import "fmt"

func main() {
	str := "Hello 월드!" // ❶ 한영이 섞인 문자열

	for i := 0; i < len(str); i++ { // ❶ 문자열 크기를 얻어 순회
		// ❸ 바이트 단위로 출력
		fmt.Printf("타입:%T 값:%d 문자값:%c\n", str[i], str[i], str[i])
	}
}

 

  • 아래 소스는 문자열을 []rune형으로 변환하여, 해당 배열을 순회하여 출력하는 소스이다. rune형은 int32타입이기때문에 영어를 포함하여, 한글을 한글자 한글자씩 []rune에 담을수있다.
//ch15/ex15.8/ex15.8.go
package main

import "fmt"

func main() {
	str := "Hello 월드!" // ❶ 한영 문자가 섞인 문자열
	arr := []rune(str) // ❷ 문자열을 []rune으로 형변환

	for i := 0; i < len(arr); i++ { // ❸ 문자열 크기를 얻어 순회
		fmt.Printf("타입:%T 값:%d 문자값:%c\n", arr[i], arr[i], arr[i])
	}
}

 

  • 아래소스는 range를 사용하여 문자열을 순회하는 방법이다. range를 사용하여 v에 한글자씩 순회한다.  v는 int32형이므로 한글이 깨지지 않는다.
//ch15/ex15.9/ex15.9.go
package main

import "fmt"

func main() {
	str := "Hello 월드!"      // ❶ 한영 문자가 섞인 문자열
	for _, v := range str { // ❷ range를 이용한 순회
		fmt.Printf("타입:%T 값:%d 문자:%c\n", v, v, v) // ❸ 출력
	}
}

 

 

  • 아래소스는 문자열을 합치는 예제이다. + 연산자나 += 연산자를 사용하여 두 문자열을 합칠수있다. 
//ch15/ex15.10/ex15.10.go
package main

import "fmt"

func main() {
	str1 := "Hello"
	str2 := "World"

	str3 := str1 + " " + str2 //❶ str1, " ", str2를 잇습니다.
	fmt.Println(str3)

	str1 += " " + str2 // ❷ str1에 " " + str2 문자열을 붙입니다.
	fmt.Println(str1)
}

 

 

  • 아래 소스는 , 문자열의 비교할때 사용하는 연산자인 == , != 연산자에 대한 예제이다.
//ch15/ex15.11/ex15.11.go
package main

import "fmt"

func main() {
	str1 := "Hello"
	str2 := "Hell"
	str3 := "Hello"

	fmt.Printf("%s == %s  : %v\n", str1, str2, str1 == str2)
	fmt.Printf("%s != %s : %v\n", str1, str2, str1 != str2)
	fmt.Printf("%s == %s : %v\n", str1, str3, str1 == str3)
	fmt.Printf("%s != %s : %v\n", str1, str3, str1 != str3)
}

 

 

  • 아래소스는 문자열을 비교하는 연산자인 < , > , <= , >= 를 사용한 예제이다.문자열의 대소비교는 문자열의 길이와 상관없이 앞글자부터 비교한다.
//ch15/ex15.12/ex15.12.go
package main

import "fmt"

func main() {
	str1 := "BBB"
	str2 := "aaaaAAA"
	str3 := "BBAD"
	str4 := "ZZZ"

	fmt.Printf("%s > %s : %v\n", str1, str2, str1 > str2)   // ❶
	fmt.Printf("%s < %s : %v\n", str1, str3, str1 < str3)   // ❷
	fmt.Printf("%s <= %s : %v\n", str1, str4, str1 <= str4) // ❸
}

 

 

 

 

 

  • string타입은 Go에서 제공하는 내장타입으로 내부구현은 감춰저 있지만, reflect패키지의 StringHeader구조체를 통해서 내부를 어느정도 엿볼수있다.
  • 아래 소스처럼 str1을 str2에 대입하면 내부적으로 구조체가 복사되기때문에 같은 메모리를 가리키게 된다.
//ch15/ex15.13/ex15.13.go
package main

import "fmt"

func main() {
	str1 := "안녕하세요. 한글 문자열입니다."
	str2 := str1

	fmt.Printf(str1) // ❷
	fmt.Printf("\n")
	fmt.Printf(str2) // ❷
}

 

 

  • 아래의 예제를 통해서 str1과 str2가 같은 메모리주소를 가리키는걸 알수있다.
//ch15/ex15.14/ex15.14.go
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	str1 := "Hello World!"
	str2 := str1 // ❶ str1 변수값을 str2에 복사

	stringHeader1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) // ❷ Data값 추출
	stringHeader2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) // ❸  Data값 추출

	fmt.Println(stringHeader1) // ❹ 각 필드 값을 출력합니다.
	fmt.Println(stringHeader2)
}

 

  • 문자열은 불변이다. string타입이 가리키는 문자열의 일부만 변경이 불가능하다. (전체바꾸기는 가능하다.)
  • 아래 소스에서 str이라는 string형의 변수가 있는데, 해당 변수를 슬라이스타입으로 바꾸어 slice에 저장하였다. 이럴경우slice는 값의 변경이 가능하다.
  • 하지만, slice의 값을 바꾼다고 해서 str이 바뀌지는 않는다. 왜냐하면 서로 다른 메모리 주소를 가리키고 있기때문이다.
//ch15/ex15.15/ex15.15.go
package main

import "fmt"

func main() {
	var str string = "Hello World"
	var slice []byte = []byte(str) // ❶ 슬라이스로 타입 변환

	slice[2] = 'a' // ❷ 3번째 문자 변경

	fmt.Println(str)
	fmt.Printf("%s\n", slice)
}

 

 

  • 아래의 예제를 통해서 str과 slice가 서로 다른 메모리 주소를 가리키는것을 확인할수있다.
//ch15/ex15.16/ex15.16.go
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var str string = "Hello World"
	var slice []byte = []byte(str)

	stringheader := (*reflect.StringHeader)(unsafe.Pointer(&str)) // ❶
	sliceheader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))

	fmt.Printf("str:\t%x\n", stringheader.Data) // ❷
	fmt.Printf("slice:\t%x\n", sliceheader.Data)
}

 

 

  • 아래는 문자열을 합산했을때 내부적으로 어떻게 연산이 진행되는지를 보여주는 예제이다.
  • 문자열의 합연산을 하면 두문자열이 하나로 합쳐지게 된다. 이때 기존의 메모리 공간은 건드리지 않고, 새로운 메모리 공간을 만들어 두 문자열을 합치기 때문에 합연산을 하면 주소값이 변경된다. 
  • 아래의 addr1, addr2,addr3를 출력하면 모두 다른것을 확인할수있다. 
//ch15/ex15.17/ex15.17.go
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var str string = "Hello"
	stringheader := (*reflect.StringHeader)(unsafe.Pointer(&str)) // ❶
	addr1 := stringheader.Data                                    // ❷

	str += " World"            // ❸
	addr2 := stringheader.Data // ➍

	str += " Welcome!"         // ➎
	addr3 := stringheader.Data // ➏

	fmt.Println(str)
	fmt.Printf("addr1:\t%x\n", addr1)
	fmt.Printf("addr2:\t%x\n", addr2)
	fmt.Printf("addr3:\t%x\n", addr3)
}

 

 

  • 아래 소스에서 ToUpper1()함수에서는 str크기만큼의 합연산을 하게 된다. 이럴경우 합연산을 할때마다 새로운 메모리가 할당되고, 기존 메모리 공간은 버려진다. 따라서 메모리공간낭비와 성능이 문제가 된다.
  • ToUpper2()함수에서는 strings.Builder를 사용하여, WriteRune()함수를 호출할때마다 새로운 메모리를 생성하지 않고 기존메모리공간에 빈자리가 있으면 그냥 더하게 된다. 그래서 메모리 절약과 성능향상을 할수있다.
  • 다시 말해서,strings.Builder는 내부적으로 가변크기의 버퍼를 사용하여 문자를 버퍼에 직접추가한다. 내부버퍼는 필요할때마다 적절한 크기로 확장되어 문자열 조합시 메모리를 효율적으로 사용할 수 있다. 
//ch15/ex15.18/ex15.18.go
package main

import (
	"fmt"
	"strings"
)

func ToUpper1(str string) string {
	var rst string
	for _, c := range str {
		if c >= 'a' && c <= 'z' {
			rst += string('A' + (c - 'a')) // ❶ 합연산 사용
		} else {
			rst += string(c)
		}
	}
	return rst
}

func ToUpper2(str string) string {
	var builder strings.Builder
	for _, c := range str {
		if c >= 'a' && c <= 'z' {
			builder.WriteRune('A' + (c - 'a')) // ❷ strings.Builder 사용
		} else {
			builder.WriteRune(c)
		}
	}
	return builder.String()
}

func main() {
	var str string = "Hello World"

	fmt.Println(ToUpper1(str))
	fmt.Println(ToUpper2(str))
}

 

 

 

 

 

16장 패키지

  • Package는 GO에서 코드를 묶는 가장큰단위이다.
  • main패키지는 특별한 패키지로 프로그램의 시작점을 포함하는 패키지이다. 그 외에 다른 여러가지 편리한 패키지가 있다. 해당 패키지를 사용하여 프로그램만들때  많은 시간을 절약할수있다.
  • 아래의 Url에서 원하는 패키지를 찾을수있다.
 

Standard library - Go Packages

Discover Packages Standard library Version: go1.21.6 Opens a new window with list of versions in this module. Published: Jan 9, 2024 License: BSD-3-Clause Opens a new window with license information. Jump to ... Directories Directories ¶ Show internal Exp

pkg.go.dev

 

GitHub - avelino/awesome-go: A curated list of awesome Go frameworks, libraries and software

A curated list of awesome Go frameworks, libraries and software - GitHub - avelino/awesome-go: A curated list of awesome Go frameworks, libraries and software

github.com

 

 

  • 아래 소스는 math/rand패키지를 사용하여 랜덤한 숫자값을 출력하는 소스이다.
//ch16/ex16.1/ex16.1.go
package main

import ( // ❶ 둘 이상의 패키지는 소괄호로 묶어줍니다.
	"fmt"
	"math/rand" // ❷ 패키지명은 rand입니다.
)

func main() {
	fmt.Println(rand.Int()) // ❸ 랜덤한 숫자값을 출력합니다.
}

 

  • 패키지 이름이 겹칠때는 별칭을 붙여서 해결할수있다.
  • 패키지는 가져오면 반드시 사용해야한다. 사용하지 않으면 오류가 발생한다. 이럴경우 _ 을 사용해여 해결할수있다.
  • Go에서 기본으로 제공하는 package는 Go를 설치할때 기본으로 같이 설치된다.

 

  • go build를 하려면 반드시 Go 모듈 root 폴더에 go.mod파일이 있어야한다. 
  • go 모듈은 go mod init 명령을 통해서 만들수있다. (go mod init [패키지명])

 

  • 아래의 소스는 go module을 만들고 외부 패키지를 활용하는 예제이다.
  • go mod init goproject/usepkg 를 입력하여 go.mod파일을 생성한다. (go module파일이 만들어진다.)
  • custompkg.go, usepkg.go코드를 작성한다.
  • go mod tidy 를 입력하면 go 모듈에 필요한 패키지를 찾아서 다운로드 해주고, 필요한 패키지 정보를 go.mod, go.sum파일에 저장하게 된다.
  • go build를 하여 exe파일을 생성하고 usepkg.exe를 실행한다.
  • 아래는 해당 명령을 사용한 최종 결과파일이다.

 

 

package custompkg

import "fmt"

func PrintCustom() {
	fmt.Println("this is custom package!")
}

 

 

package main

import (
	"fmt"
	"goproject/usepkg/custompkg"

	"github.com/guptarohit/asciigraph"
	"github.com/tuckersGo/musthaveGo/ch16/expkg"
)

func main() {
	custompkg.PrintCustom()
	expkg.PrintSample()

	data := []float64{3, 4, 5, 6, 9, 7, 5, 8, 5, 10, 2, 7, 2, 5, 6}
	graph := asciigraph.Plot(data)

	fmt.Println(graph)
}

 

 

 

 

 

 

 

 


 

배열을 asciigraph 로 만드는 프로그램을 패키지를 사용하여 만들어보자.

  • 아래와 같이 폴더와 파일을 준비해둔다. 

 

  • custompkg.go 
package custompkg

import "fmt"

func PrintCustom() {
	fmt.Println("this is custom package!")
}

 

  • usepkg.go
package main

import (
	"fmt"
	"goproject/usepkg/custompkg"

	"github.com/guptarohit/asciigraph"
	"github.com/tuckersGo/musthaveGo/ch16/expkg"
)

func main() {
	custompkg.PrintCustom()
	expkg.PrintSample()

	data := []float64{3, 4, 5, 6, 9, 7, 5, 8, 5, 10, 2, 7, 2, 5, 6}
	graph := asciigraph.Plot(data)

	fmt.Println(graph)
}

 

  • 아래의 명령을 입력하여 go.mod파일을 생성한다. 
D:\go\exam\goproject\usepkg> go mod init usepkg

 

  • 생성된 go.mod파일에는 아래와 같이 구성되어있다.  
  • 하지만, asciigraph 같은 외부 패키지가 없어서 컴파일은 안된다.
module usepkg

go 1.21.4

 

 

  • 아래의 명령(go mod tidy)으로 외부 패키지를 다운받는다.
D:\go\exam\goproject\usepkg> go mod tidy
go: finding module for package github.com/guptarohit/asciigraph
go: finding module for package github.com/tuckersGo/musthaveGo/ch16/expkg
go: found github.com/guptarohit/asciigraph in github.com/guptarohit/asciigraph v0.5.6
go: found github.com/tuckersGo/musthaveGo/ch16/expkg in github.com/tuckersGo/musthaveGo/ch16/expkg v0.0.0-20231030141649-380a04ad7102
D:\go\exam\goproject\usepkg>

 

 

 

  • go.mod파일이 아래와 같이 외부 패키지를 다운로드하였다.
module usepkg

go 1.21.4

require (
    github.com/guptarohit/asciigraph v0.5.6
    github.com/tuckersGo/musthaveGo/ch16/expkg v0.0.0-20231030141649-380a04ad7102
)

 

  • 다운받은 파일들은 go env를 사용하여 gopath위치에 가면 /pkg/mod/ 에서 확인할수있다.
  • go build를 실행하여 exe파일을 생성한다.

 

 

 

 

 


 

 

  • 아래 소스는 패키지파일에서 공개 여부를 결정하는 소스이다. 대문자로 시작하는 변수나 상수,함스는 공개되고, 소문자로 시작하는 변수나 함수는 비공개된다. 
//ch16/ex16.2/ex16.2.go
package main

import (
	"fmt"

	"ch16/ex16.2/publicpkg"
)

func main() {
	fmt.Println("PI:", publicpkg.PI)
	publicpkg.PublicFunc()

	var myint publicpkg.MyInt = 10
	fmt.Println("myint:", myint)

	var mystruct = publicpkg.MyStruct{Age: 18}
	fmt.Println("mystruct:", mystruct)
}



// =====================================================
//ch16/ex16.6/publicpkg/publicpkg.go
package publicpkg

import "fmt"

const (
	PI = 3.1415   // 외부로 공개되는 상수
	pi = 3.141516 // 외부로 공개되지 않는 상수
)

var ScreenSize int = 1080 // 외부로 공개되는 변수
var screenHeight int      // 외부로 공개되지 않는 변수

func PublicFunc() { // 외부로 공개되는 함수
	const MyConst = 100 // 외부로 공개되지 않습니다.
	fmt.Println("This is a public function", MyConst)
}

func privateFunc() { // 외부로 공개되지 않는 함수
	fmt.Println("This is a private function")
}

type MyInt int       // 외부로 공개되는 별칭 타입
type myString string // 외부로 공개되지 않는 별칭 타입

type MyStruct struct { // 외부로 공개되는 구조체
	Age  int    // 외부로 공개되는 구조체 필드
	name string // 외부로 공개되지 않는 구조체 필드
}

func (m MyStruct) PublicMethod() { // 외부로 공개되는 메서드
	fmt.Println("This is a public method")
}

func (m MyStruct) privateMethod() { // 외부로 공개되지 않는 메서드
	fmt.Println("This is a private method")
}

type myPrivateStruct struct { // 외부로 공개되지 않는 구조체
	Age  int    // 외부로 공개되지 않는 구조체 필드
	name string // 외부로 공개되지 않는 구조체 필드
}

func (m myPrivateStruct) PrivateMethod() { // 외부로 공개되지 않는 메서드
	fmt.Println("This is a private method")
}

 

 

  • 실행결과에서 공개된 상수와 함수,및 별칭타입을 사용할수있다.
  • MyStruct는 Age,name이 있지만 name이 비공개라서 age만 출력이 된다. 

 

 

 

  • 패키지를 임포트하면, 컴파일러는 하는 일은 다음과 같다.
    • 패키지내의 전역변수를 초기화한다.
    • 패키지에 init()함수가 있다면 해당 함수를 호출한다. (해당 함수는 입력값과 반환값이 없어야한다.)
      • 만약 해당 패키지의 init()함수만 사용하기를 원하면 _를 사용하여 임포트하면 된다.
//ch16/ex16.3/ex16.3.go
package main

import (
	"ch16/ex16.3/exinit" // ⓬ exinit 패키지 임포트
	"fmt"
)

func main() { // ⓭ main() 함수
	fmt.Println("main function")
	exinit.PrintD()
}



// ===============================================================

//ch16/exinit/exinit.go
package exinit

import "fmt"

var (
	a = c + b // ❶ a값은 c와 b가 초기화된 다음 초기화됩니다.
	b = f()   // ❷ b값은 4가 됩니다.
	c = f()   // ❸ c값은 5 입니다.
	d = 3     // ❹ d값은 초기화가 끝난뒤 6이 됩니다.
)

func init() { // ➎
	d++                             // ➏
	fmt.Println("init function", d) // ➐
}

func f() int { // ➑
	d++                      // ➒
	fmt.Println("f() d:", d) // ❿
	return d                 // ⓫
}

func PrintD() {
	fmt.Println("d:", d)
}

 

 

 

 

 

  • fmt
    • fmt.Println
    • fmt.Scanln
  • math
    • math.Sqrt, math.Sin, math.Cos
    • math.rand
  • time
    • time.Now, time.Date
  • bufio
  • os
  • Http패키지
  • DB 패키지

 

  • time package Example
package main

import (
	"fmt"
	"time"
)

func main() {
	currentTime := time.Now()
	fmt.Printf("Current time: %s\n", currentTime.Format(time.RFC3339))

	// 특정 시간을 생성
	specificTime := time.Date(2024, time.February, 6, 12, 0, 0, 0, time.UTC)

	// 두 시간 사이의 차이 계산
	duration := specificTime.Sub(currentTime)

	fmt.Printf("Time difference: %s\n", duration)
}

 

 

  • Http Server Example
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, HTTP server!")
}

func main() {
	http.HandleFunc("/", handler)
	fmt.Println("Starting server on :8080...")
	http.ListenAndServe(":8080", nil)
}

 

 

 

 

  • Http Client Example
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	resp, err := http.Get("https://www.naver.com")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	fmt.Println(string(body))
}

 

 

  • Mysql 패키지 예제
package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// MySQL 데이터베이스 연결
	db, err := sql.Open("mysql", "gouser:dA9!Ij@T3]@tcp(192.168.219.107:3307)/go")
	if err != nil {
		fmt.Println("Error opening database:", err)
		return
	}
	defer db.Close()

	// 데이터 조회
	rows, err := db.Query("SELECT id, name,age,phone FROM student")
	if err != nil {
		fmt.Println("Error querying data:", err)
		return
	}
	defer rows.Close()

	fmt.Println("Users:")
	for rows.Next() {
		var id, age int
		var name, phone string
		err := rows.Scan(&id, &name, &age, &phone)
		if err != nil {
			fmt.Println("Error scanning row:", err)
			return
		}
		fmt.Printf("ID: %d, name: %s, age: %d , phone: %s\n", id, name, age, phone)
	}
}

 

 

 

 

 

 

 

17장 숫자맞추기 게임 만들기

 

  • 이번장에서는 숫자맞추기 게임을 만들어보자. 규칙은 아래와 같다.
    1. 먼저 0~99사이의 랜덤한 숫자를 하나 정한다.
    2. 사용자 입력을 받는다.
    3. 입력값과 랜덤값을 비교한다. 사용자입력이 크면 "숫자가 큽니다."를 출력하고, 작으면 "숫자가 작습니다."를 출력하고 2번으로 가서 다시 사용자 입력을 받는다.
    4. 숫자값이 맞으면 "축하합니다. 시도횟수 N번"을 출력한다.
    5. 프로그램을 종료한다.

 

  • 아래 소스는 해당 게임을 만들기 위한 외부모듈을 import하여 사용한 예제이다.
  • math/rand의 Intn()함수를 사용하여 랜덤값을 얻는다.
  • 생성되는 랜덤값은 완전랜덤값이 아닌 유사랜덤값이다. 그래서 Seed()를 사용하여 시드값을 매번 실행할때마다 다른값으로 설정한다.
  • time package의 time.Now().UnixNano() 함수를 사용하여 시간값을 가져와 랜덤시드로 설정한다. 이렇게 되면 매번 다른 랜덤값이 나오게 된다.
//ch17/ex17.1/ex17.1.go
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano()) // ❶ 시간값을 랜덤 시드로 설정

	n := rand.Intn(100)
	fmt.Println(n)
}

 

 

rand.Seed()가 deprecated되어서 아래의 소스로 변경하였다.

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	n := rng.Intn(100)
	fmt.Println(n)
}

 

 

 

  • bufio.NewReader()로 표준입력설정
  • fmt.Scanln() 함수를 사용하여 n값을 입력받는다.
  • for()문에 조건이 없으므로 무한루프를 돌게 된다.
  • 따라서 아래 소스는 무한히 숫자를 입력받게 되고 숫자를 입력하면 해당 숫자를 출력하는 소스이다. 
//ch17/ex17.2/ex17.2.go
package main

import (
	"bufio"
	"fmt"
	"os"
)

var stdin = bufio.NewReader(os.Stdin)

func InputIntValue() (int, error) {
	var n int
	_, err := fmt.Scanln(&n) // ❶ int 타입값을 입력받음
	if err != nil {
		stdin.ReadString('\n') // ❷ 에러 발생 시 입력스트림을 비움
	}
	return n, err
}

func main() {
	for {
		fmt.Printf("숫자값을 입력하세요:")
		n, err := InputIntValue()
		if err != nil {
			fmt.Println("숫자만 입력하세요.")
		} else {
			fmt.Println("입력하신 숫자는 ", n, " 입니다.")
		}
	}
}

 

 

  • 위의 두소스를 서로 결합하여, 아래 소스로 만들었다.
  • 처음에 랜덤값을 r에 저장하고
  • for문으로 무한루프를 돌면서 숫자(n)를 입력받는다.
  • 입력받는 숫자가 크거나, 작으면 메세지를 출력하고 다시 입력을 받는다.
  • 숫자를 맞추면 성공메세지를 출력하고 프로그램을 종료한다.
// ch17/ex17.3/ex17.3.go
package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"time"
)

var stdin = bufio.NewReader(os.Stdin)

func InputIntValue() (int, error) {
	var n int
	_, err := fmt.Scanln(&n)
	if err != nil {
		stdin.ReadString('\n')
	}
	return n, err
}

func main() {
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	r := rng.Intn(100)
	// rand.Seed(time.Now().UnixNano())
	// r := rand.Intn(100) // ❶ 랜덤값 생성

	cnt := 1
	for {
		fmt.Printf("숫자값을 입력하세요:")
		n, err := InputIntValue() // ❷ 숫자값 입력
		if err != nil {
			fmt.Println("숫자만 입력하세요.")
		} else {
			if n > r { // ❸ 숫자값 비교
				fmt.Println("입력하신 숫자가 더 큽니다.")
			} else if n < r {
				fmt.Println("입력하신 숫자가 더 작습니다.")
			} else {
				fmt.Println("숫자를 맞췄습니다. 축하합니다. 시도한 횟수:", cnt)
				break // ❹ 같을 경우 메시지를 출력하고 break로 종료
			}
			cnt++
		}
	}
}

 

 

 

 

 

 

 

 

//ch17/ex17.2/ex17.2.go
package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"time"
)

const (
	Balance       = 1000
	EarnPoint     = 500
	LosePoint     = 100
	VictoryPoint  = 5000
	GameoverPoint = 0
)

var stdin = bufio.NewReader(os.Stdin)

func InputIntValue() (int, error) {
	var n int
	_, err := fmt.Scanln(&n) // ❶ int 타입값을 입력받습니다.
	if err != nil {
		stdin.ReadString('\n') // ❷ 에러 발생시 입력스트림을 비웁니다.
	}
	return n, err
}

func main() {
	rand.Seed(time.Now().UnixNano())

	balance := Balance

	for {
		fmt.Print("1~5사이의 값을 입력하세요:")
		n, err := InputIntValue()
		if err != nil {
			fmt.Println("숫자만 입력하세요.")
		} else if n < 1 || n > 5 {
			fmt.Println("1~5사이의 값만 입력하세요.")
		} else {
			r := rand.Intn(5) + 1
			if n == r {
				balance += EarnPoint
				fmt.Println("축하합니다. 맞추셨습니다. 남은 돈:", balance)
				if balance >= VictoryPoint {
					fmt.Println("게임 승리")
					break
				}
			} else {
				balance -= LosePoint
				fmt.Println("꽝 아쉽지만 다음 기회를.. 남은 돈:", balance)
				if balance <= GameoverPoint {
					fmt.Println("게임 오버")
					break
				}
			}
		}
	}
}