C 개발자를 위한 Go 언어 강좌
1부: 시작하기
1장. Go 언어 소개
C와 Go의 철학적 차이
C의 철학:
- 프로그래머에게 최대한의 제어권을 준다
- 하드웨어에 가깝게, 최소한의 추상화
- "프로그래머가 무엇을 하는지 안다"고 가정
- 작고 단순한 언어
Go의 철학:
- 단순성과 가독성을 최우선으로
- 한 가지 일을 하는 한 가지 방법
- 명확함이 영리함보다 낫다
- 팀 협업과 대규모 소프트웨어에 최적화
- 내장된 동시성 지원
Go는 C의 단순성을 계승하면서도, 현대적인 소프트웨어 개발의 요구사항을 반영했습니다.
Go의 주요 특징과 장점
C 개발자 관점에서 본 Go의 특징:
-
빠른 컴파일
- C보다 훨씬 빠른 컴파일 속도
- 의존성 관리가 명확해서 불필요한 재컴파일 없음
- 대규모 프로젝트도 몇 초 안에 빌드
-
가비지 컬렉션
- malloc/free 불필요
- 메모리 누수와 이중 해제 걱정 없음
- 하지만 C만큼의 세밀한 제어는 포기
-
내장 동시성
- 고루틴: 경량 스레드 (스택 크기 ~2KB)
- 채널: 안전한 통신 메커니즘
- pthread보다 훨씬 사용하기 쉬움
-
정적 타입 + 타입 추론
- C처럼 정적 타입의 안정성
- 타입 추론으로 간결한 코드
-
표준 라이브러리
- HTTP 서버, JSON 파싱, 암호화 등 내장
- C는 외부 라이브러리 필요한 것들
-
단일 바이너리 배포
- 정적 링크가 기본
- 의존성 지옥 없음
- 크로스 컴파일 간편
포기해야 하는 것들:
- 매크로 (#define)
- 포인터 산술 연산
- 수동 메모리 관리의 완전한 제어
- 헤더 파일
- 전처리기
개발 환경 설정
1. Go 설치
Linux/macOS:
# 공식 사이트에서 다운로드
wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz
# PATH 설정 (~/.bashrc 또는 ~/.zshrc에 추가)
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
Windows
가능하기는 하지만 매우 불편하다.
https://go.dev/ 에서 다운로드
cgo 를 위한 gcc 설치
https://winlibs.com/ 에서 UCRT runtime 중 최신버전을 다운받아 적당한 폴더에 압축을 풀고 환경변수 PATH 에 bin 디렉토리를 추가해 줍니다.
확인:
go version
# go version go1.21.5 linux/amd64
go version
# go version go1.25.2 windows/amd64
2. 첫 프로젝트 만들기
# 프로젝트 디렉토리 생성
mkdir hello
cd hello
# 모듈 초기화 (C의 프로젝트 생성과 유사)
go mod init example.com/hello
# main.go 파일 생성
C 프로젝트와 다른 점:
- Makefile 대신
go build
사용 - 헤더 파일 없음
- 의존성은 go.mod 파일로 관리
3. 에디터 설정
추천 에디터와 플러그인:
- VS Code: Go (Go Team at Google)
필수 도구들:
# 코드 포맷터
go install golang.org/x/tools/cmd/goimports@latest
# 린터
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Go는 코드 포맷팅 스타일을 강제합니다. C처럼 스타일 논쟁이 없습니다.
2장. 첫 Go 프로그램
Hello World
main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
빌드와 실행
# 빌드 (컴파일 + 링크 한번에)
go build
# 실행
./hello
# 또는 빌드하지 않고 바로 실행
go run main.go
2부: 기본 문법
3장. 변수와 타입
- 변수 선언
var x int = 10
var name string = "John"
var pi float64 = 3.14
// 또는 타입 추론 사용
var x = 10 // int로 추론
var name = "John" // string으로 추론
var pi = 3.14 // float64로 추론
- 전역변수와 지역변수
// 전역변수 (패키지 외부에서도 접근가능)
var GlobalCounter int = 0
// 전역변수 (패키지 레벨)
var globalName string = "Global"
// 여러 변수 동시 선언
var (
MaxConnections int = 100
DefaultTimeout int = 30
ServerName string = "MyServer"
)
func main() {
// 지역변수 (함수 내부)
localVar := 10 // := 는 함수 내부에서만 사용 가능
// 전역변수는 := 로 선언 불가
// GlobalCounter := 100 // 컴파일 에러!
GlobalCounter = 100 // OK: 전역변수 값 변경
}
// 대문자로 시작하면 다른 패키지에서도 접근 가능 (exported)
var ExportedVar int = 10 // public과 유사
// 소문자로 시작하면 같은 패키지 내에서만 접근 가능
var privateVar int = 10 // private과 유사
- 기본 타입 비교
C 타입 | Go 타입 | 설명 |
---|---|---|
char | byte (또는 uint8) | 8비트 부호 없는 정수 |
int | int | 플랫폼 의존적 (32 또는 64비트) |
long | int64 | 64비트 정수 |
float | float32 | 32비트 부동소수점 |
double | float64 | 64비트 부동소수점 |
char* | string | 불변 문자열 (중요!) |
– | rune | UTF-8 문자 (int32의 별칭) |
– | bool | true/false |
- 타입 추론과 := 연산자
// 전통적인 방식
var x int = 10
var name string = "John"
// 타입 추론
var x = 10
var name = "John"
// 짧은 선언 (함수 내부에서만 사용 가능, 새로운 변수를 선언할 때만)
x := 10
name := "John"
pi := 3.14
- 포인터: C와의 유사점과 차이점
// 유사점
x := 10
p := &x
*p = 20 // x가 20으로 변경됨
// 차이점
arr := [5]int{1, 2, 3, 4, 5}
p := &arr[0]
// p++ // 컴파일 에러!
// p += 2 // 컴파일 에러!
// NULL 대신 nil
var p *int = nil
type Person struct {
Name string
Age int
}
p := &Person{}
p.Age = 30 // 자동으로 역참조됨 (*p).Age와 같음
p := new(int) // *int 타입, 0으로 초기화됨
*p = 10
// 자동으로 메모리 해제
- 타입 변환
var x int = 10
var y float64 = float64(x) // 명시적 변환 필요
4장. 함수
- 함수 선언과 호출
func add(a int, b int) int {
return a + b
}
// 같은 타입 파라미터는 한 번만 명시 가능
func add(a, b int) int {
return a + b
}
func printHello() {
fmt.Println("Hello")
}
func main() {
result := add(3, 5)
printHello()
// return 문 불필요 (main에서)
}
- 다중 반환값 (C에는 없는 기능)
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}
func main() {
q, r := divide(10, 3)
fmt.Printf("Quotient: %d, Remainder: %d\n", q, r)
// 출력: Quotient: 3, Remainder: 1
}
- Named return values
// 명명된 반환값 사용
func divide(a, b int) (quotient, remainder int) {
quotient = a / b
remainder = a % b
return // naked return (값 명시 불필요)
}
- 가변 인자 함수
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 사용
result := sum(10, 20, 30)
result2 := sum(1, 2, 3, 4, 5)
result3 := sum() // 인자 없이도 호출 가능
- 함수를 값으로 다루기
Go에서 함수는 일급 객체(first-class citizen)입니다.
// 함수 타입 정의
type operation func(int, int) int
func add(a, b int) int { return a + b }
func subtract(a, b int) int { return a - b }
func calculate(a, b int, op operation) int {
return op(a, b)
}
func main() {
result := calculate(10, 5, add)
result = calculate(10, 5, subtract)
}
5장. 제어 구조
- if문 (선언문 포함 가능)
Go의 if문은 C와 비슷하지만 조건식을 괄호로 감싸지 않습니다. 대신 중괄호는 필수입니다.
if x > 10 {
fmt.Println("크다")
}
조건식 안에서 변수 생성이 가능합니다.
package main
import (
"fmt"
)
func getValue() int {
return 0
}
func main() {
if val := getValue(); val > 0 {
fmt.Println("양수:", val)
} else {
fmt.Println("음수 또는 0:", val) // val은 여전히 유효
}
}
- for문 (Go의 유일한 반복문)
Go에는 for문만 존재합니다. while이나 do-while은 없습니다.
// C와 거의 동일하지만 괄호가 없음
for i := 0; i < 10; i++ {
fmt.Println(i)
}
for {
// 무한 반복
if shouldStop {
break
}
}
range 키워드
// 배열/슬라이스 순회
numbers := []int{10, 20, 30, 40, 50}
for index, value := range numbers {
fmt.Printf("numbers[%d] = %d\n", index, value)
}
// 인덱스만 필요한 경우
for i := range numbers {
fmt.Println(i)
}
// 값만 필요한 경우 (인덱스 무시)
for _, value := range numbers {
fmt.Println(value)
}
- switch문 (break 불필요, fallthrough)
- defer 문 (C에는 없는 기능)
함수가 종료되기 직전에 특정 코드를 실행하도록 예약하는 기능.
여러 개의 defer 문이 있으면 LIFO(Last In First Out) 순서로 실행.
func example() {
defer fmt.Println("마지막")
defer fmt.Println("두 번째")
fmt.Println("첫 번째")
}
// 출력:
// 첫 번째
// 두 번째
// 마지막
3부: 메모리와 데이터 구조
6장. 포인터 심화
- C 포인터와 Go 포인터의 차이
- 포인터 산술 연산이 없는 이유
- nil과 NULL
- 포인터를 사용하는 시기
7장. 배열과 슬라이스
-
배열과 슬라이스
배열은 크기가 고정이다. 속도가 빠르다. 함수 전달시 포인터를 전달해야 한다.
슬라이스는 크기를 변경할 수 있다. 참조 타입이기에 함수에 그대로 전달 가능하다.
// 배열 - 거의 사용하지 않음
var arr [5]int = [5]int{1, 2, 3, 4, 5}
// 슬라이스 - 일반적으로 사용
slice := []int{1, 2, 3, 4, 5}
slice = append(slice, 6) // 동적으로 확장 가능
// 슬라이스 생성 방법들
s1 := make([]int, 5) // 길이 5
s2 := make([]int, 0, 10) // 길이 0, 용량 10
s3 := []string{"a", "b", "c"}
- 슬라이스 내부 구조 이해
s1 := make([]int, 5) // 길이 5
s2 := make([]int, 0, 10) // 길이 0, 용량 10
용량을 지정하지 않는 경우, 용량이 부족하면 2배 크기의 슬라이스를 새로생성하고 값을 복사한다.
- make와 new의 차이
구분 | new | make |
---|---|---|
적용 대상 | 모든 타입 | slice, map, channel만 |
반환 타입 | 포인터 (*T ) |
참조 타입 (T ) |
초기화 | 제로값으로 초기화 | 사용 가능한 상태로 초기화 |
메모리 할당 | 메모리만 할당 | 내부 구조까지 초기화 |
p := new(Person) // &Person{} 와 동일
참조타입을 반환하며, 함수 파라미터로 전달될 때에도 복사가 아닌 참조가 전달된다.
s := make([]int, 0, 10)
m := make(map[string]int)
ch := make(chan int)
- append와 copy
8장. 맵과 구조체
- 구조체
type Person struct {
Name string
Age int
Height float64
}
// 리터럴로 초기화
p := Person{
Name: "Alice",
Age: 30,
Height: 165.5,
}
// 필드만 지정 (순서 무관)
p2 := Person{Age: 25, Name: "Bob"}
// 포인터로 접근 (-> 연산자 불필요!)
ptr := &p
ptr.Age = 31 // Go는 자동으로 역참조
// 타입 이름 없이 바로 사용
config := struct {
Host string
Port int
}{
Host: "localhost",
Port: 8080,
}
fmt.Println(config.Host) // localhost
- 메서드 추가하기
type Rectangle struct {
Width int
Height int
}
// 값 리시버 (복사복을 실행)
func (r Rectangle) Area() int {
return r.Width * r.Height
}
// 포인터 리시버 (실제 값을 변경)
func (r *Rectangle) Scale(factor int) {
r.Width *= factor
r.Height *= factor
}
// 사용
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area()) // 50
rect.Scale(2)
fmt.Println(rect.Area()) // 200
- 맵(Map): 내장 해시테이블
// 선언과 초기화
ages := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
// 추가/수정
ages["Dave"] = 40
// 조회
age := ages["Alice"]
fmt.Println(age) // 30
// 삭제
delete(ages, "Bob")
// 존재 확인
age, exists := ages["Eve"]
if exists {
fmt.Println("Eve's age:", age)
} else {
fmt.Println("Eve not found")
}
4부: 메모리 관리
9장. 가비지 컬렉션
- malloc/free에서 GC로
type Person struct {
Name string
Age int
}
func createPerson(name string, age int) *Person {
p := &Person{
Name: name,
Age: age,
}
return p
// free() 호출 불필요! GC가 자동으로 처리
}
func main() {
p := createPerson("Alice", 30)
// p를 사용
// 더 이상 참조되지 않으면 GC가 자동으로 메모리 회수
}
- 스택 vs 힙 할당
go 컴파일러가 스택(임시메모리) 에 저장할지 힙에 저장할지를 자동으로 선택합니다.
func stackAllocation() *int {
x := 42
return &x // C에서는 위험! Go에서는 안전
}
func heapAllocation() *int {
x := 42
return &x // 컴파일러가 x를 힙에 할당
}
func localOnly() {
x := 42
println(x) // x는 스택에 할당 (함수 밖으로 나가지 않음)
}
10장. 메모리 안전성
-
버퍼 오버플로우 방지
언어레벨에서 오버플로우를 차단합니다.
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr[4]) // OK
// fmt.Println(arr[5]) // panic: runtime error: index out of range [5] with length 5
// 슬라이스도 마찬가지
slice := []int{1, 2, 3}
// value := slice[10] // panic: runtime error: index out of range [10] with length 3
- 댕글링 포인터 문제 해결
func noMoreDangling() *int {
local := 42
return &local // Go 컴파일러가 자동으로 힙에 할당
}
func main() {
ptr := noMoreDangling()
fmt.Println(*ptr) // 42 - 완전히 안전함!
}
- 메모리 누수 방지
// 문제가 있는 코드
func processData(data []byte) []byte {
// 큰 슬라이스에서 작은 부분만 필요한 경우
result := data[0:10] // 전체 배열을 참조하고 있음!
return result
}
// 올바른 방법
func processDataCorrectly(data []byte) []byte {
result := make([]byte, 10)
copy(result, data[0:10]) // 필요한 부분만 복사
return result
}
-
unsafe 패키지 (주의해서 사용하기)
위와 같은 언어레벨 체크를 배제하기 위해 사용됩니다.
성능이 극도로 중요한 부분에 제한적으로 사용되어야 합니다.
5부: 동시성
11장. 고루틴
언어레벨에서 스레드를 지원합니다.
go
키워드를 사용하는 것으로 스레드가 생성됩니다.
메인 함수가 종료됨과 동시에 스레드도 종료되므로 적절한 동기화를 해주어야 합니다.
(채널 챕터 참조)
package main
import (
"fmt"
"time"
)
func worker(id int) {
fmt.Printf("Worker %d\n", id)
}
func main() {
for i := 0; i < 10; i++ {
go worker(i) // 고루틴 생성은 단 한 줄!
}
time.Sleep(3 * time.Second) // 고루틴이 끝나길 기다림 (임시 방편)
}
12장. 채널
"메모리를 공유해서 통신하지 말고, 통신해서 메모리를 공유하라"
(Don’t communicate by sharing memory; share memory by communicating)
- 채널의 개념과 사용법
채널을 이요해 값을 전달하고 수신합니다.
package main
import "fmt"
func main() {
// 채널 생성
ch := make(chan int)
// 고루틴에서 채널로 데이터 전송
go func() {
ch <- 42 // 채널에 값 보내기
}()
// 메인 고루틴에서 데이터 수신
value := <-ch // 채널에서 값 받기
fmt.Println(value)
}
- 버퍼드 채널 vs 언버퍼드 채널
언버퍼드 채널 – 동기 방식
package main
import "fmt"
func main() {
ch := make(chan int) // 버퍼 없음
go func() {
fmt.Println("고루틴: 데이터 전송 시작")
ch <- 42 // 누군가 받을 때까지 블록됨
fmt.Println("고루틴: 데이터 전송 완료")
}()
fmt.Println("메인: 데이터 수신 대기")
value := <-ch // 누군가 보낼 때까지 블록됨
fmt.Printf("메인: 수신한 값 = %d\n", value)
}
버퍼드 채널 – 비동기 방식
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 3) // 버퍼 크기 3
// 버퍼가 가득 찰 때까지 블록되지 않음
ch <- 1
ch <- 2
ch <- 3
fmt.Println("3개의 값을 전송함")
// 이 시점에서 ch <- 4를 하면 블록됨
go func() {
time.Sleep(time.Second)
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}()
time.Sleep(2 * time.Second)
}
- select 문
select
는 여러 채널 작업을 동시에 대기할 수 있게 해줍니다.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
// 두 채널 중 먼저 준비된 것을 처리
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("received", msg1)
case msg2 := <-ch2:
fmt.Println("received", msg2)
}
}
}
13장. 동시성 패턴
- Worker Pool 패턴
- Pipeline 패턴
- Fan-out, Fan-in
- Context를 이용한 취소 처리
- sync 패키지 (Mutex, WaitGroup 등)
6부: 실전 Go
14장. 에러 처리
- errno에서 error 인터페이스로
Go에서 에러는 내장된 error 인터페이스로 표현됩니다.
type error interface {
Error() string
}
- 다중 반환값을 이용한 에러 처리
오류가 발생하면 발생한 오류를 함수를 호출한 곳으로 전달합니다.
package main
import (
"fmt"
"os"
)
func main() {
// 파일 열기
file, err := os.Open("data.txt")
if err != nil {
fmt.Fprintf(os.Stderr, "파일 열기 실패: %v\n", err)
os.Exit(1)
}
defer file.Close()
// 파일 작업 수행
fmt.Println("파일을 성공적으로 열었습니다")
}
- 오류 생성
package main
import (
"errors"
"fmt"
)
func example1() error {
// 1. errors.New() - 간단한 에러 메시지
return errors.New("뭔가 잘못되었습니다")
}
func example2(code int) error {
// 2. fmt.Errorf() - 포맷팅된 에러 메시지
return fmt.Errorf("잘못된 코드: %d", code)
}
func example3(filename string) error {
// 3. 조건부 에러 반환
if filename == "" {
return errors.New("파일명이 비어있습니다")
}
return nil // 에러 없음
}
- panic과 recover
일반적으로 panic과 recover 는 거의 사용되지 않습니다.
panic 은 복구 불가능한 오류가 발생했을 때 사용되며, 오류 출력 후 프로그램을 종료시킵니다.
- 에러 래핑과 언래핑
- 커스텀 에러 타입
15장. 인터페이스
- C에는 없는 Go의 인터페이스
package main
import (
"fmt"
"math"
)
type Shape interface {
Draw()
Area() float64
}
type Circle struct {
Radius float64
}
// 메서드만 구현하면 자동으로 인터페이스 만족
func (c Circle) Draw() {
fmt.Printf("Drawing circle with radius %f\n", c.Radius)
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// 사용
func main() {
var s Shape = Circle{Radius: 5.0}
s.Draw()
fmt.Printf("Area: %f\n", s.Area())
}
- 빈 인터페이스 (interface{})
// 모든 타입을 받을 수 있음 (C의 void*와 유사)
func printAnything(v interface{}) {
fmt.Printf("Value: %v, Type: %T\n", v, v)
}
func main() {
printAnything(42) // int
printAnything("hello") // string
printAnything(3.14) // float64
printAnything([]int{1,2,3}) // []int
}
- 타입 추출
인터페이스의 실제값을 추출한다.
추출에 실패하면 런타임 오류가 발생한다.
package main
func main() {
var a interface{} = 1
i := a // a와 i는 dynamic type이고 값은 1
j := a.(int) // j는 int 타입이고, 값은 1
println(i) // 포인터 주소 출력
println(j) // 1 출력
}
16장. 패키지와 모듈
- 헤더 파일 없는 세상
go 언어는 헤더 파일을 사용하지 않습니다.
// math/math.go
package math
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
// main.go
package main
import "example.com/hello/math"
func main() {
println(math.Add(5, 3))
}
// go.mod
module example.com/hello
go 1.21.5
-
패키지 구조화
하나의 디렉토리 = 하나의 패키지
myproject/
├── main.go (package main)
├── utils/
│ ├── string.go (package utils)
│ └── number.go (package utils)
└── database/
└── db.go (package database)
- 공개/비공개 (대소문자 규칙)
첫 글자가 소문자인 경우, 패키지 내부에서만 접근가능합니다.
package calculator
// 공개 (Exported) - 첫 글자 대문자
func Add(a, b int) int {
return a + b
}
// 비공개 (Unexported) - 첫 글자 소문자
func validate(n int) bool {
return n >= 0
}
// 공개 변수
var MaxValue = 1000
// 비공개 변수
var threshold = 100
// 공개 타입
type Calculator struct {
Name string // 공개 필드
version int // 비공개 필드
}
// 비공개 타입
type internalState struct {
count int
}
-
go modules와 의존성 관리
C의 Makefile이나 CMakeLists.txt 대신, Go는 go.mod 파일로 의존성을 관리합니다.
go mod init github.com/username/myproject
go.mod
module github.com/username/myproject
go 1.21
require (
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.10.9
)
17장. 테스팅
-
내장된 testing 패키지
Go는 강력한 테스팅 프레임워크를 표준 라이브러리로 제공합니다.
테스트 파일은
_test.go
접미사를 가집니다
테스트 함수는 Test로 시작하고*testing.T
파라미터를 받습니다
별도의 main 함수나 테스트 러너가 필요 없습니다
go test
명령어로 자동으로 모든 테스트를 찾아 실행합니다 -
단위 테스트 작성하기
test\math.go
package test
func Add(a, b int) int {
return a + b
}
func Multiply(a, b int) int {
return a * b
}
test\math_test.go
package test
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
}
}
7부: C와의 상호운용
18장. Cgo
-
Cgo란 무엇인가
Cgo는 Go 프로그램에서 C 코드를 호출하거나, 반대로 C 프로그램에서 Go 코드를 호출할 수 있게 해주는 기능입니다.
-
C 코드를 Go에서 호출하기
C 코드 포함
import "C" 앞의 주석과 import 문 사이에 빈 줄이 있으면 안 됩니다.
To use cgo write normal Go code that imports a pseudo-package "C". The Go code can then refer to types such as C.size_t, variables such as C.stdout, or functions such as C.putchar.
If the import of "C" is immediately preceded by a comment, that comment, called the preamble, is used as a header when compiling the C parts of the package. For example:
package main
/*
#include <stdio.h>
#include <stdlib.h>
void sayHello(const char* name) {
printf("Hello, %s from C!\n", name);
fflush(stdout);
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
name := C.CString("Gopher")
defer C.free(unsafe.Pointer(name))
fmt.Println("Hello from GO!")
C.sayHello(name)
}
라이브러리 호출
calc.h
#ifndef CALC_H
#define CALC_H
double calc(double x, double y);
#endif
calc.c
#include <math.h>
double calc(double x, double y) {
return sqrt(x * x + y * y);
}
main.go
package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -lm
#include "calc.h"
*/
import "C"
import "fmt"
func main() {
result := C.calc(3.0, 4.0)
fmt.Printf("Distance: %.2f\n", float64(result))
}
19장. C 코드 마이그레이션
- 마이그레이션 전략
- 일반적인 변환 패턴
- 주의할 점들
- 실전 예제
8부: 고급 주제
20장. 성능 최적화
- 프로파일링 도구
- 벤치마킹 best practices
- 메모리 최적화
- 컴파일러 최적화 이해하기
21장. 표준 라이브러리 활용
- io 패키지
- fmt와 printf의 차이
- net/http (웹 서버 만들기)
- encoding/json
- flag 패키지 (명령행 인자)
22장. 실전 프로젝트
- CLI 도구 만들기
- 간단한 웹 서버 구현
- 시스템 프로그래밍 예제
- C 프로젝트를 Go로 재작성하기
23장. 제네릭(Generics)
func Min[T int | float64](a, b T) T {
if a < b {
return a
}
return b
}
// 사용
result1 := Min[int](5, 3) // 3
result2 := Min[float64](5.5, 3.2) // 3.2
result3 := Min(5, 3) // 타입 추론으로 [int] 생략 가능
커스텀 제약 정의
type Number interface {
int | int64 | float64 | float32
}
func Sum[T Number](numbers []T) T {
var sum T
for _, n := range numbers {
sum += n
}
return sum
}