struct
구조체(Struct)는 여러 필드를 하나로 묶어서 새로운 타입을 정의하는 방법이다.
기본 구조체 정의
package main
import "fmt"
type person struct {
name string
age int
favFood []string
}
func main() {
favFood := []string{"kimchi, ramen"}
jihyun := person{name: "jihyun", age: 25, favFood: favFood}
fmt.Println(jihyun)
}
출력 결과
{jihyun 25 [kimchi, ramen]}
Methods
메서드는 특정 타입에 속한 함수다. Receiver를 통해 구조체와 연결된다.
구조체와 생성자 함수
accounts.go
package accounts
// Account struct
type Account struct {
owner string
balance int
}
// NewAccount creates Account
func NewAccount(owner string) *Account {
account := Account{owner: owner, balance: 0}
return &account
}
main.go
package main
import (
"/study-go-language/accounts"
"fmt"
)
func main() {
account := accounts.NewAccount("jihyun")
fmt.Println(account)
}
출력 결과
&{jihyun 0}
&가 출력되는 이유는 NewAccount가 포인터를 반환하기 때문이다.
Value Receiver의 문제점
accounts.go
package accounts
type Account struct {
owner string
balance int
}
func NewAccount(owner string) *Account {
account := Account{owner: owner, balance: 0}
return &account
}
// Deposit x amount to the account
func (a Account) Deposit(amount int) { // receiver method: (a Account)
a.balance += amount
}
// Balance returns the account balance
func (a Account) Balance() int {
return a.balance
}
main.go
package main
import (
"/study-go-language/accounts"
"fmt"
)
func main() {
account := accounts.NewAccount("jihyun")
account.Deposit(10)
fmt.Println(account.Balance())
}
출력 결과
0
예상과 달리 결과는 10이 아닌 0이 나온다. 이유가 뭘까?
Go는 (a Account)에서 구조체의 복사본을 만든다. 따라서 Deposit 메서드에서 수정한 balance는 복사본의 값이며, 원본 account는 변경되지 않는다.
Pointer Receiver로 해결
누군가가 account.Deposit()을 호출한면, account를 복사하지 말고 Deposit method를 호출한 account를 사용하도록 해야 한다.
package accounts
type Account struct {
owner string
balance int
}
func NewAccount(owner string) *Account {
account := Account{owner: owner, balance: 0}
return &account
}
// Deposit x amount to the account
func (a *Account) Deposit(amount int) { // receiver method: (a Account)
a.balance += amount
}
// Withdraw x amount from the account
func (a *Account) Withdraw(amount int) {
a.balance -= amount
}
// Balance returns the account balance
func (a Account) Balance() int {
return a.balance
}
package main
import (
"/study-go-language/accounts"
"fmt"
)
func main() {
account := accounts.NewAccount("jihyun")
account.Deposit(10)
fmt.Println(account.Balance())
account.Withdraw(20)
fmt.Println(account.Balance())
}
출력 결과
10
-10
이처럼 복사본이 아닌 실제 객체를 다루고 싶다면, 포인터 receiver(Account)를 사용해야 한다.
Error Handling
Go에는 try-catch나 exception이 없다. 에러 처리를 직접 구현해야 한다.
에러 처리 방법 1 - log.Fatalln
package main
import (
"fmt"
"log"
"/study-go-language/accounts"
)
func main() {
account := accounts.NewAccount("jihyun")
account.Deposit(10)
fmt.Println(account.Balance())
err := account.Withdraw(20)
if err != nil {
log.Fatalln(err) // 프로그램 종료
}
fmt.Println(account.Balance())
}
출력 결과
10
2025/11/08 14:42:01 Can't withdraw more than the current balance
exit status 1
에러 처리 방법 2 - 에러 출력 후 계속 실행
package main
import (
"fmt"
"log"
"/study-go-language/accounts"
)
func main() {
account := accounts.NewAccount("jihyun")
account.Deposit(10)
fmt.Println(account.Balance())
err := account.Withdraw(20)
if err != nil {
fmt.Println(err)
}
fmt.Println(account.Balance())
}
출력 결과
10
Can't withdraw more than the current balance
10
에러 변수 선언 컨벤션
에러 변수는 err로 시작하는 것이 Go의 관례다.
var errNoMoney = errors.New("Can't withdraw more than the current balance")
추가 메서드 구현
Owner 관련 메서드
// ChangeOwner changes the account owner
func (a *Account) ChangeOwner(newOwner string) {
a.owner = newOwner
}
// Owner returns the account owner
func (a Account) Owner() string {
return a.owner
}
package main
import (
"fmt"
"../study-go-language/accounts"
)
func main() {
account := accounts.NewAccount("jihyun")
account.Deposit(10)
err := account.Withdraw(20)
if err != nil {
fmt.Println(err)
}
fmt.Println(account.Balance(), account.Owner())
}
출력 결과
Can't withdraw more than the current balance
10 jihyun
String() 메서드로 출력 커스터마이징
Go에서 String() 메서드를 구현하면 fmt.Println으로 출력할 때 자동으로 호출된다. (Java의 toString()과 유사)
func (a Account) String() string {
return fmt.Sprint(a.Owner(), "'s account.\nHas: ", a.Balance())
}
package main
import (
"fmt"
"../study-go-language/accounts"
)
func main() {
account := accounts.NewAccount("jihyun")
account.Deposit(10)
fmt.Println(account)
}
출력 결과
jihyun's account.
Has: 10
Dictionary 구현
Map을 기반으로 한 Dictionary 타입을 만들어보자.
Dictionary 타입 정의
package mydict
import "errors"
// Dictionary type
type Dictionary map[string]string
var (
errNotFound = errors.New("Not Found")
errCantUpdate = errors.New("Can't update non-existing word")
errWordExists = errors.New("That word already exists")
)
Search 메서드
func (d Dictionary) Search(word string) (string, error) {
value, exists := d[word]
if exists {
return value, nil
}
return "", errNotFound
}
Map에서 값을 조회할 때 두 번째 반환값(exists)은 해당 키가 존재하는지 여부를 나타낸다.
Add 메서드
func (d Dictionary) Add(word, def string) error {
_, err := d.Search(word)
switch err {
case errNotFound:
d[word] = def
case nil:
return errWordExists
}
return nil
}
package main
import (
"fmt"
"/study-go-language/mydict"
)
func main() {
dictionary := mydict.Dictionary{}
word := "hello"
definition := "Greeting"
err := dictionary.Add(word, definition)
if err != nil {
fmt.Println(err)
}
hello, _ := dictionary.Search(word)
fmt.Println(hello)
err2 := dictionary.Add(word, definition)
if err2 != nil {
fmt.Println(err2)
}
}
출력 결과
Greeting
That word already exists
Update 메서드
func (d Dictionary) Update(word, def string) error {
_, err := d.Search(word)
switch err {
case nil:
d[word] = def
case errNotFound:
return errCantUpdate
}
return nil
}
package main
import (
"fmt"
"/study-go-language/mydict"
)
func main() {
dictionary := mydict.Dictionary{}
baseWord := "hello"
dictionary.Add(baseWord, "First")
err := dictionary.Update(baseWord, "Second")
if err != nil {
fmt.Println(err)
}
word, _ := dictionary.Search(baseWord)
fmt.Println(word)
}
출력 결과
Second
Delete 메서드
func (d Dictionary) Delete(word, def string) {
delete(d, word)
}
package main
import (
"fmt"
"/study-go-language/mydict"
)
func main() {
dictionary := mydict.Dictionary{}
baseWord := "hello"
dictionary.Add(baseWord, "First")
dictionary.Search(baseWord)
dictionary.Delete(baseWord)
word, err := dictionary.Search(baseWord)
if err != nil {
fmt.Println(err)
}
fmt.Println(word)
}
출력 결과
Not Found
핵심 정리
- Value Receiver: 구조체 복사본을 사용 (읽기 전용에 적합)
- Pointer Receiver: 원본 구조체를 직접 수정 (상태 변경에 필수)
- 에러 처리: 함수가 error 타입을 반환하도록 설계
- String() 메서드: 출력 형식을 커스터마이징
- Map 기반 타입: 기존 타입에 메서드를 추가해 새로운 타입 생성 가능
아래 강의를 통해 학습한 내용을 바탕으로 작성했습니다.
https://nomadcoders.co/go-for-beginners
Go 프로그래밍 언어 무료 강의
Channel, Goroutines 등 Go의 핵심 개념을 익히고, 간단한 스크래핑 서비스를 만들며 실습 위주로 기초부터 배웁니다. 17,000명이 선택한 인기 무료 입문 강의로, 지금 Go 입문을 시작하세요!
nomadcoders.co
https://go.dev/doc/#references
Documentation - The Go Programming Language
Documentation The Go programming language is an open source project to make programmers more productive. Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and netwo
go.dev
'Language > Go' 카테고리의 다른 글
| [Go] 4. URL Checker & Go routines & Channel (0) | 2025.11.09 |
|---|---|
| [Go] 2. pointer, map, arrays (0) | 2025.11.08 |
| [Go] 1. package, function, for, if, switch (0) | 2025.11.07 |