[Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법 - C++ 개발자의 첫인상

[Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법 - C++ 개발자의 첫인상

이 글의 핵심

C++ 경험자를 위한 Go 첫걸음입니다. 설치부터 :=·var·for·range·가비지 컬렉터·go fmt까지 C++ 코드와 나란히 비교하며 익힙니다. Go 2주 완성 시리즈 Day 1~2입니다.

시리즈 안내

📚 Go 2주 완성 시리즈 #01 | 전체 목차 보기

이 글은 C++ 개발자를 위한 2주 완성 Go 언어 커리큘럼Day 1~2 내용입니다.

이전: 커리큘럼 소개 ← | → 다음: #02 메모리와 자료구조


들어가며: Go 언어와의 첫 만남

C++ 개발자로서 Go를 처음 접하면 “이게 정말 프로그래밍 언어인가?” 싶을 정도로 심플합니다. 헤더 파일도 없고, 템플릿 메타프로그래밍도 없고, new/delete도 신경 쓸 필요가 없습니다. 이 글에서는 Go 언어의 철학과 기본 문법을 C++ 개발자의 시선으로 살펴봅니다.

이 글에서 배울 내용:

  • Go 설치 및 기본 툴체인 (go build, go run, go fmt)
  • Go의 철학(단순성·명시성·동시성)과 C++와의 차이(포인터·상속·제네릭)
  • 패키지·모듈(go mod, import, internal) 개요
  • 변수 선언: auto vs :=의 차이
  • 반복문: while이 없는 세계
  • 가비지 컬렉터(GC)로 메모리 관리 자동화

실무에서의 체감

C++ 위주로 서버를 다루던 환경에서 Go를 도입할 때 흔히 드는 인상은 문법과 툴체인이 단순해 보인다는 점입니다. 프로덕션에서는 그 단순함이 빌드·배포·동시성 코드 가독성으로 이어지는 경우가 많습니다.

자주 언급되는 장점:

  • 개발 속도: 팀·도메인에 따라 다르지만, 네트워크·CLI 코드를 빠르게 완성하기 쉬운 편입니다.
  • 안정성: GC가 있어 수동 할당 해제 부담이 줄어듭니다.
  • 배포: 단일 바이너리로 옮기기 쉬운 구조입니다.

Go의 철학: 단순성, 명시성, 동시성

Go는 읽기 쉬운 코드예측 가능한 동작을 최우선으로 둡니다. C++에서 흔한 “같은 문제를 여러 방식으로 풀 수 있다”는 유연함보다, 한 가지 명확한 관용구를 택하는 쪽에 가깝습니다.

의미실무에서의 체감
단순성문법·키워드 수를 줄이고, 도구(go fmt, go test)로 일관성 확보리뷰 시 스타일·빌드 스크립트 논쟁이 줄어듦
명시성암시적 변환·숨은 제어 흐름을 줄임 (if err != nil, 명시적 go로 동시 실행)“이 함수가 실패할 수 있는가?”가 시그니처에 드러남
동시성동시 실행은 고루틴 + 채널이라는 고정된 도구로 표현 (이 시리즈 후반에서 다룸)스레드·뮤텍스만 던지지 않고, 통신으로 동기화하는 습관을 강제하기 쉬움

C++에서 온 개발자에게는 “표현력이 부족해 보인다”는 인상이 들 수 있지만, 팀 단위로 유지보수할 때 비용이 낮아지는 방향으로 설계되어 있다고 보면 됩니다.


C++와의 차이: 포인터, 상속, 제네릭

이미 본문 여기저기에서 다루지만, 첫날 관점에서만 핵심만 정리합니다.

포인터

  • C++: T*, 참조 T&, 스마트 포인터, 포인터 연산까지 한 언어 안에 공존.
  • Go: 포인터는 *T이지만 포인터 연산은 없음. nil 역참조는 런타임(프로그램이 실행 중일 때) 패닉. 대부분의 “소유권”은 GC가 담당합니다.
  • 실무: “주소만 넘겨서 복사 비용 줄이기”는 비슷하지만, 댕글링 포인터로 잘못된 메모리 접근하는 패턴은 설계상 막히는 편입니다.

상속

  • C++: public 상속, 다중 상속, 가상 함수로 다형성.
  • Go: 클래스 상속 없음. 구조체 임베딩과 인터페이스(메서드 집합)로 대체합니다(#03, #04).
  • 실무: “기본 클래스에 몰아넣기” 대신 작은 인터페이스 + 합성이 기본 패턴입니다.

제네릭

  • C++: 템플릿으로 컴파일 타임 다형성 (강력하지만 에러 메시지·빌드 비용 이슈).
  • Go: Go 1.18+에서 타입 매개변수가 들어왔습니다. 모든 코드가 제네릭일 필요는 없고, map, 슬라이스, 채널 등이 이미 제네릭 느낌으로 쓰입니다.
  • 실무: 입문 단계에서는 인터페이스와 구체 타입을 먼저 익히고, 반복되는 알고리즘·컨테이너에만 제네릭을 도입하면 됩니다.

패키지와 모듈 시스템

패키지 한 줄 요약

  • 모든 .go 파일은 package 선언으로 묶입니다. 실행 파일은 package main + func main().
  • 같은 디렉터리의 파일들은 같은 패키지 이름을 가져야 합니다.
  • 대문자로 시작하는 이름만 패키지 밖에서 보입니다(Exported). 소문자는 패키지 내부 전용입니다. C++의 namespace + private에 가깝게 이해하면 됩니다.

모듈(go mod)과 의존성

  • 모듈 루트go.mod가 있으면, 모듈 경로(예: github.com/org/project)가 import 경로의 접두사가 됩니다.
  • go mod init, go mod tidy로 의존성을 정리하고, 버전은 go.mod/go.sum에 고정됩니다.
  • 표준 라이브러리는 import 문자열만으로 사용 ("fmt", "net/http"). 서드파티는 원격 모듈 경로 또는 replace/vendor로 관리합니다.

실무에서 자주 쓰는 흐름

mkdir myapp && cd myapp
go mod init github.com/you/myapp
# 코드 작성 후
go build ./...
go test ./...

internal 디렉터리: myproject/internal/foo 패키지는 부모 트리 밖에서는 import할 수 없게 막아, 공개 API를 좁히는 데 쓰입니다. 팀 프로젝트에서 경계를 나눌 때 유용합니다.


실전: 함수·제어 문법 빠른 참조

아래는 본문에서 세부적으로 다루는 내용을 함수 관점에서만 압축한 것입니다.

함수

package example

import "fmt"

// 여러 반환값 (에러는 마지막에 두는 것이 관례)
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// 이름 있는 반환값 (defer와 함께 쓸 때 가끔 사용)
func stats() (sum int, n int) {
    // ...
    return // naked return — 남용하지 말 것
}

제어

  • 조건: if, for, switch만 사용 (while 없음).
  • 이름 충돌 줄이기: if err := f(); err != nil { ... }처럼 짧은 스코프를 쓰는 패턴이 표준입니다.

이미 변수·반복문·조건문 절에서 같은 내용을 예제와 함께 다루었으므로, 막혔을 때 이 절로 돌아와 머릿속을 정리하면 됩니다.


목차

  1. Go 설치와 Hello World
  2. Go의 철학: 단순성, 명시성, 동시성
  3. C++와의 차이: 포인터, 상속, 제네릭
  4. 패키지와 모듈 시스템
  5. 실전: 함수·제어 문법 빠른 참조
  6. 변수 선언: auto vs :=
  7. 반복문: for 하나로 모든 것을
  8. 조건문과 기본 제어 흐름
  9. 가비지 컬렉터: new/delete로부터의 해방
  10. go fmt: 코딩 스타일 논쟁 종결자
  11. 실습 과제

1. Go 설치와 Hello World

Go 설치

Go 설치 과정 상세 가이드:

# Windows (PowerShell)
# 1. https://go.dev/dl/ 접속
# 2. "Microsoft Windows" 섹션에서 .msi 파일 다운로드
# 3. 설치 파일 실행 (기본 경로: C:\Go)
# 4. 설치 완료 후 PowerShell 재시작

# 설치 확인
go version
# 출력: go version go1.21.0 windows/amd64

# GOPATH 확인 (Go 작업 공간)
go env GOPATH
# 출력: C:\Users\YourName\go

# macOS (Homebrew)
brew install go

# 설치 확인
go version
# 출력: go version go1.21.0 darwin/arm64

# Linux (Ubuntu/Debian)
# 방법 1: 공식 바이너리 (권장)
wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
sudo rm -rf /usr/local/go  # 기존 설치 제거
sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz

# PATH 추가 (~/.bashrc 또는 ~/.zshrc)
export PATH=$PATH:/usr/local/go/bin

# 설정 적용
source ~/.bashrc

# 방법 2: apt (버전이 오래될 수 있음)
sudo apt update
sudo apt install golang-go

# 설치 확인
go version
# 출력: go version go1.21.0 linux/amd64

명령 정리: go version으로 툴체인이 맞는지 확인하고, go env·GOPATH는 모듈(go mod) 시대에도 여전히 바이너리 캐치 경로 등에 쓰입니다. 팀과 CI에서는 동일한 Go 마이너 버전을 맞춰 두면 재현성 있는 빌드가 쉬워집니다.

Go 환경 변수:

# Go 환경 변수 전체 확인
go env

# 주요 환경 변수:
# GOROOT: Go 설치 경로
# GOPATH: Go 작업 공간 (패키지, 바이너리 저장)
# GOBIN: 실행 파일 설치 경로
# GOOS: 운영체제 (windows, darwin, linux)
# GOARCH: CPU 아키텍처 (amd64, arm64)

# 특정 변수 확인
go env GOROOT
go env GOPATH

환경 변수 읽기: GOOS/GOARCH는 크로스 컴파일할 때 특히 중요합니다. 예를 들어 macOS에서 linux/amd64 바이너리를 만들 때 GOOS=linux를 앞에 붙이는 식으로 씁니다. 컨테이너 이미지를 여러 아키텍처로 배포할 때도 같은 개념이 반복됩니다.

C++ vs Go: Hello World 상세 비교

C++ Hello World:

// C++: Hello World
#include <iostream>  // 헤더 파일 포함 (전처리기)

int main() {  // 반환 타입 명시 필수
    std::cout << "Hello, World!" << std::endl;
    return 0;  // 명시적 반환 (생략 가능하지만 권장)
}

// 컴파일 및 실행 (2단계)
// 1. 컴파일: g++ hello.cpp -o hello
//    - 전처리 → 컴파일 → 어셈블 → 링크
//    - 실행 파일 생성: hello (또는 hello.exe)
// 2. 실행: ./hello (또는 hello.exe)
//    - 출력: Hello, World!

Go Hello World:

// Go: Hello World
package main  // 패키지 선언 (필수, 실행 파일은 main)

import "fmt"  // 표준 라이브러리 import (헤더 파일 없음)

func main() {  // 반환 타입 없음 (항상 void)
    fmt.Println("Hello, World!")
    // return 불필요 (자동 종료)
}

// 실행 방법 1: 즉시 실행 (개발 중)
// go run hello.go
// 동작: 임시 컴파일 → 실행 → 정리
// 출력: Hello, World!

// 실행 방법 2: 빌드 후 실행 (배포용)
// go build hello.go
// 실행 파일 생성: hello (또는 hello.exe)
// ./hello
// 출력: Hello, World!

// 실행 방법 3: 설치 (GOPATH/bin에 복사)
// go install hello.go
// hello (어디서든 실행 가능)

핵심 차이점 상세 설명:

  1. 패키지 선언:

    • C++: 없음 (네임스페이스는 선택)
    • Go: package main 필수 (실행 파일은 main, 라이브러리는 다른 이름)
  2. import vs include:

    • C++: #include <iostream> (헤더 파일 텍스트 복사)
    • Go: import "fmt" (패키지 참조, 순환 import 불가)
  3. main 함수:

    • C++: int main() (반환 타입 필수, return 0)
    • Go: func main() (반환 타입 없음, return 불필요)
  4. 빌드 과정:

    • C++: 컴파일러 명령 + 링커 플래그 + Makefile/CMake
    • Go: go build 하나로 끝 (의존성 자동 해결)
  5. 실행 파일:

    • C++: 동적 링크 (libstdc++.so 등 필요)
    • Go: 정적 링크 (단일 바이너리, 의존성 없음)

실전 예제 - 명령줄 인자 받기:

// C++: 명령줄 인자
#include <iostream>

int main(int argc, char* argv[]) {
    std::cout << "인자 개수: " << argc << '\n';
    
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << '\n';
    }
    
    return 0;
}

// 실행: ./hello arg1 arg2
// 출력:
// 인자 개수: 3
// argv[0]: ./hello
// argv[1]: arg1
// argv[2]: arg2
// Go: 명령줄 인자
package main

import (
    "fmt"
    "os"
)

func main() {
    args := os.Args  // []string 슬라이스
    
    fmt.Println("인자 개수:", len(args))
    
    for i, arg := range args {
        fmt.Printf("args[%d]: %s\n", i, arg)
    }
}

// 실행: go run hello.go arg1 arg2
// 출력:
// 인자 개수: 3
// args[0]: /tmp/go-build.../exe/hello
// args[1]: arg1
// args[2]: arg2

Go 툴체인 소개

Go는 통합 툴체인을 제공하여 별도의 빌드 도구가 필요 없습니다.

주요 Go 명령어 상세 설명:

# 1. go run - 즉시 실행 (개발 중 사용)
go run main.go
# 동작: 임시 디렉토리에 컴파일 → 실행 → 삭제
# 장점: 빠른 테스트, 스크립트처럼 사용
# 단점: 매번 컴파일 (배포용 아님)

# 2. go build - 실행 파일 생성
go build
# 현재 디렉토리의 모든 .go 파일을 컴파일
# 출력: 현재 디렉토리 이름으로 실행 파일 생성

go build -o myapp
# 출력 파일명 지정

go build -o myapp main.go
# 특정 파일만 컴파일

# 크로스 컴파일 (다른 OS용 빌드)
GOOS=linux GOARCH=amd64 go build -o myapp_linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=arm64 go build -o myapp_mac_m1

# 3. go fmt - 코드 포맷팅 (자동)
go fmt main.go          # 단일 파일
go fmt ./...            # 모든 하위 디렉토리
# 코드 스타일을 자동으로 통일 (들여쓰기, 공백 등)

# 4. go vet - 정적 분석 (버그 탐지)
go vet main.go
# 잠재적 버그 검사:
# - 포맷 문자열 오류 (Printf)
# - 사용하지 않는 변수
# - 잘못된 메서드 시그니처

# 5. go test - 테스트 실행
go test                 # 현재 패키지 테스트
go test ./...           # 모든 하위 패키지 테스트
go test -v              # 상세 출력
go test -cover          # 커버리지 확인

# 6. go mod - 모듈 관리 (의존성)
go mod init myproject   # go.mod 파일 생성
go mod tidy             # 사용하지 않는 의존성 제거
go mod download         # 의존성 다운로드
go mod vendor           # vendor 폴더에 의존성 복사

# 7. go get - 패키지 다운로드
go get github.com/gin-gonic/gin
# 패키지 다운로드 및 go.mod에 추가

# 8. go install - 바이너리 설치
go install github.com/user/tool@latest
# $GOPATH/bin에 실행 파일 설치

C++과의 비교:

작업C++Go
컴파일g++ main.cpp -o maingo build
실행./maingo run main.go
포맷팅clang-format (별도 설치)go fmt (내장)
테스트Google Test (별도 설치)go test (내장)
의존성vcpkg, Conan (별도 도구)go mod (내장)
빌드 시스템CMake, Make, Ninja불필요 (go 명령어)

실전 워크플로우:

# 1. 새 프로젝트 시작
mkdir myproject
cd myproject
go mod init github.com/username/myproject

# 2. 코드 작성 (main.go)

# 3. 개발 중 테스트
go run main.go

# 4. 코드 포맷팅 (저장 시 자동화 권장)
go fmt ./...

# 5. 정적 분석
go vet ./...

# 6. 테스트 실행
go test ./...

# 7. 빌드 (배포용)
go build -o myapp

# 8. 실행
./myapp

2. 변수 선언: auto vs :=

C++ vs Go: 변수 선언 철학

C++: 다양한 선택지 제공 (유연성 중시)
Go: 단순하고 명확한 방법 (일관성 중시)

C++ 변수 선언 (다양한 방식)

// C++: 여러 가지 변수 선언 방식
int x = 10;                    // 명시적 타입 (전통적)
auto y = 20;                   // 타입 추론 (C++11)
int z(30);                     // 생성자 스타일
int w{40};                     // 균일 초기화 (C++11)

const int MAX = 100;           // 런타임 상수
constexpr int SIZE = 50;       // 컴파일 타임 상수

// 초기화하지 않으면 쓰레기 값 (위험!)
int uninitialized;             // 예측 불가능한 값
std::cout << uninitialized << std::endl;  // 쓰레기 값 출력

// 여러 변수 동시 선언
int a = 1, b = 2, c = 3;

// auto 사용 시 주의
auto d = 1, e = 2;             // ✅ 같은 타입
// auto f = 1, g = 2.0;        // ❌ 다른 타입 (에러)

// 타입 추론 예시
auto str = "Hello";            // const char* (C 스타일 문자열)
auto str2 = std::string("Hello");  // std::string

Go 변수 선언 (단순하고 명확)

// Go: 3가지 변수 선언 방식
var x int = 10                 // 명시적 타입
var y = 20                     // 타입 추론
z := 30                        // 짧은 선언 (함수 내부만)

const MAX = 100                // 상수 (타입 추론)
const SIZE int = 50            // 상수 (명시적 타입)

// 초기화하지 않으면 제로 값 (안전!)
var uninitialized int          // 0으로 자동 초기화
fmt.Println(uninitialized)     // 0 출력 (예측 가능)

// 여러 변수 동시 선언
a, b, c := 1, 2, 3

// 블록 선언 (패키지 레벨)
var (
    name    string = "Go"
    version int    = 1
    stable  bool   = true
)

// 타입 추론 예시
pi := 3.14                     // float64
count := 100                   // int
message := "Hello"             // string

제로 값 (Zero Value) - Go의 안전 장치

Go는 초기화하지 않은 변수를 제로 값으로 자동 초기화합니다. C++의 쓰레기 값 문제가 완전히 사라집니다.

// Go: 제로 값 (타입별)
var i int                      // 0
var f float64                  // 0.0
var b bool                     // false
var s string                   // "" (빈 문자열)
var p *int                     // nil (null 포인터)
var slice []int                // nil (빈 슬라이스)
var m map[string]int           // nil (빈 맵)

// 제로 값 활용 예시
var counter int
counter++  // 0에서 시작하여 1이 됨
fmt.Println(counter)  // 1

// C++과 비교
// C++: int counter; counter++; // 쓰레기 값 + 1 (위험!)
// Go: var counter int; counter++ // 0 + 1 = 1 (안전!)

제로 값의 장점:

// 1. 안전한 초기화
func processData() {
    var sum int  // 0으로 초기화 (명시적 초기화 불필요)
    for _, v := range []int{1, 2, 3} {
        sum += v
    }
    fmt.Println(sum)  // 6
}

// 2. 간결한 코드
type User struct {
    Name  string
    Age   int
    Email string
}

var user User  // 모든 필드가 제로 값으로 초기화
fmt.Println(user)  // {  0 } (Name="", Age=0, Email="")

// 3. nil 체크 간소화
var data []int  // nil
if data == nil {
    data = []int{1, 2, 3}
}

짧은 선언 (:=)의 규칙과 활용

기본 규칙:

func example() {
    // ✅ 함수 내부에서만 사용 가능
    x := 10
    
    // ✅ 여러 변수 동시 선언
    name, age := "Alice", 30
    
    // ✅ 재선언: 최소 하나는 새 변수여야 함
    name, email := "Bob", "[email protected]"  // email은 새 변수
    // name은 재할당, email은 새로 선언
    
    // ❌ 모두 기존 변수면 에러
    // name, age := "Charlie", 40  // 에러: no new variables
    
    // ✅ 타입 자동 추론
    pi := 3.14        // float64
    count := 100      // int
    message := "Hi"   // string
    items := []int{1, 2, 3}  // []int (슬라이스)
}

// ❌ 패키지 레벨에서는 := 사용 불가
// x := 10  // 에러: syntax error: non-declaration statement outside function body

// ✅ 패키지 레벨은 var 사용
var globalVar = 10

실전 활용:

// 1. 에러 처리와 함께
file, err := os.Open("data.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// 2. 타입 변환
str := "123"
num, err := strconv.Atoi(str)
if err != nil {
    fmt.Println("변환 실패")
}

// 3. 함수 여러 반환값 받기
result, ok := myMap["key"]
if !ok {
    fmt.Println("키가 없습니다")
}

// 4. 짧은 if 문과 결합
if value, exists := cache["key"]; exists {
    fmt.Println("캐시 히트:", value)
} else {
    fmt.Println("캐시 미스")
}

var vs := 선택 가이드

상황사용이유
함수 내부, 타입 명확:=간결
패키지 레벨var:= 불가
제로 값 초기화var명시적 의도
명시적 타입 필요var타입 지정
// 예시
func process() {
    // ✅ := 사용 (타입 명확)
    count := 0
    name := "Alice"
    
    // ✅ var 사용 (제로 값 의도)
    var buffer []byte  // nil로 초기화 의도
    
    // ✅ var 사용 (명시적 타입)
    var timeout time.Duration = 5 * time.Second
    
    // ❌ := 사용 (타입 불명확)
    // x := 0  // int? int32? int64?
    
    // ✅ var 사용 (타입 명확)
    var x int64 = 0
}

3. 반복문: for 하나로 모든 것을

Go의 반복문 철학

Go는 for 하나로 모든 반복을 처리합니다. while, do-while, foreach 같은 키워드가 없습니다.

왜 하나만?

  • 단순함: 배울 것이 적음
  • 일관성: 모든 반복이 같은 패턴
  • 충분함: for만으로 모든 경우 커버

패턴 1: 전통적 for 루프

// C++: 전통적인 for 루프
for (int i = 0; i < 10; i++) {
    std::cout << i << "\n";
}
// Go: 동일한 for 루프 (괄호 없음)
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// 세미콜론 생략 가능 (단일 조건)
for i := 0; i < 10; {
    fmt.Println(i)
    i++
}

차이점:

  • Go는 괄호 () 없음 (더 깔끔)
  • Go는 중괄호 {} 필수 (스타일 통일)

패턴 2: while 루프 (조건만)

// C++: while 루프
int i = 0;
while (i < 10) {
    std::cout << i << "\n";
    i++;
}

// do-while
int j = 0;
do {
    std::cout << j << "\n";
    j++;
} while (j < 10);
// Go: for로 while 대체 (초기화, 증감 생략)
i := 0
for i < 10 {  // while (i < 10)과 동일
    fmt.Println(i)
    i++
}

// do-while 패턴 (조건을 끝에서 체크)
j := 0
for {
    fmt.Println(j)
    j++
    if j >= 10 {
        break
    }
}

// 또는 첫 실행 보장
j := 0
for ok := true; ok; ok = j < 10 {
    fmt.Println(j)
    j++
}

패턴 3: 무한 루프

// C++: 무한 루프
while (true) {
    // ...
    if (condition) break;
}

// 또는
for (;;) {
    // ...
    if (condition) break;
}
// Go: 무한 루프 (조건 생략)
for {
    // ...
    if condition {
        break
    }
}

// 실전 예시: 서버 루프
for {
    conn, err := listener.Accept()
    if err != nil {
        log.Println(err)
        continue
    }
    go handleConnection(conn)  // 고루틴으로 처리
}

패턴 4: range 루프 (컬렉션 순회)

// C++: 범위 기반 for (C++11)
std::vector<int> numbers = {1, 2, 3, 4, 5};

// 값만
for (int num : numbers) {
    std::cout << num << "\n";
}

// 인덱스 + 값 (수동)
for (size_t i = 0; i < numbers.size(); i++) {
    std::cout << i << ": " << numbers[i] << "\n";
}

// 맵 순회
std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}};
for (const auto& [name, age] : ages) {  // C++17
    std::cout << name << ": " << age << "\n";
}
// Go: range로 간단하게
numbers := []int{1, 2, 3, 4, 5}

// 인덱스 + 값
for i, num := range numbers {
    fmt.Printf("%d: %d\n", i, num)
}

// 값만 (인덱스 무시)
for _, num := range numbers {
    fmt.Println(num)
}

// 인덱스만 (값 무시)
for i := range numbers {
    fmt.Println(i)
}

// 맵 순회
ages := map[string]int{"Alice": 30, "Bob": 25}
for name, age := range ages {
    fmt.Printf("%s: %d\n", name, age)
}

// 문자열 순회 (rune 단위)
for i, char := range "Hello" {
    fmt.Printf("%d: %c\n", i, char)
}
// 0: H
// 1: e
// 2: l
// 3: l
// 4: o

range의 특징:

  • 슬라이스/배열: 인덱스, 값 반환
  • : 키, 값 반환
  • 문자열: 인덱스, rune(유니코드 문자) 반환
  • 채널: 값만 반환 (인덱스 없음)

C++ vs Go: 범위 기반 for (Range-based for)

// C++: 범위 기반 for (C++11)
#include <vector>

std::vector<int> vec = {1, 2, 3, 4, 5};

// 값 복사
for (auto v : vec) {
    std::cout << v << " ";
}

// 참조 (수정 가능)
for (auto& v : vec) {
    v *= 2;
}

// const 참조 (읽기 전용, 복사 방지)
for (const auto& v : vec) {
    std::cout << v << " ";
}
// Go: range로 컨테이너 순회
slice := []int{1, 2, 3, 4, 5}

// 인덱스와 값 모두 받기
for i, v := range slice {
    fmt.Println(i, v)
}

// 값만 받기 (인덱스 무시)
for _, v := range slice {
    fmt.Println(v)
}

// 인덱스만 받기
for i := range slice {
    fmt.Println(i)
}

// 값 수정 (인덱스로 접근)
for i := range slice {
    slice[i] *= 2
}

for 루프 제어: break, continue, 레이블

// C++: break와 continue
for (int i = 0; i < 10; i++) {
    if (i == 5) continue;  // 5 건너뛰기
    if (i == 8) break;     // 8에서 종료
    std::cout << i << " ";
}
// 출력: 0 1 2 3 4 6 7

// 중첩 루프 탈출 (goto 사용)
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 10) {
            goto end;  // 외부 루프까지 탈출
        }
    }
}
end:
std::cout << "Done\n";
// Go: break와 continue
for i := 0; i < 10; i++ {
    if i == 5 {
        continue  // 5 건너뛰기
    }
    if i == 8 {
        break  // 8에서 종료
    }
    fmt.Print(i, " ")
}
// 출력: 0 1 2 3 4 6 7

// 레이블과 break (중첩 루프 탈출)
outer:
for i := 0; i < 5; i++ {
    for j := 0; j < 5; j++ {
        if i*j > 10 {
            break outer  // 외부 루프까지 탈출
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
fmt.Println("\nDone")

// 레이블과 continue (외부 루프 계속)
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue outer  // 외부 루프의 다음 반복으로
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// 출력: (0,0) (1,0) (2,0)

핵심 차이:

  • C++: goto 사용 (가독성 낮음)
  • Go: 레이블 + break/continue (명확)

4. 조건문과 기본 제어 흐름

if 문: 괄호 없는 조건문

// C++: if 문 (괄호 필수)
int x = 10;
if (x > 5) {
    std::cout << "x is greater than 5\n";
} else if (x > 0) {
    std::cout << "x is positive\n";
} else {
    std::cout << "x is non-positive\n";
}

// 한 줄 if (중괄호 선택)
if (x > 5)
    std::cout << "Greater\n";  // 중괄호 없어도 됨 (위험!)

// C++17: 초기화 구문
if (auto it = map.find(key); it != map.end()) {
    std::cout << "Found: " << it->second << "\n";
}
// Go: if 문 (괄호 없음, 중괄호 필수)
x := 10
if x > 5 {
    fmt.Println("x is greater than 5")
} else if x > 0 {
    fmt.Println("x is positive")
} else {
    fmt.Println("x is non-positive")
}

// 한 줄이어도 중괄호 필수 (안전)
if x > 5 {
    fmt.Println("Greater")  // 중괄호 필수!
}

// 초기화 구문 (변수 스코프 제한)
if v, ok := m[key]; ok {
    fmt.Println("Found:", v)
    // v는 if 블록 내에서만 사용 가능
}
// fmt.Println(v)  // 에러: v는 여기서 사용 불가

// Go의 일반적인 에러 체크 패턴
if err := doSomething(); err != nil {
    fmt.Println("Error:", err)
    return
}
// err는 if 블록 내에서만 존재

// 여러 조건 체크
if file, err := os.Open("data.txt"); err != nil {
    log.Fatal(err)
} else {
    defer file.Close()
    // file 사용...
}

핵심 차이점:

항목C++Go
조건식 괄호필수 if (x > 5)없음 if x > 5
중괄호선택 (한 줄이면)필수 (항상)
초기화 구문C++17부터처음부터 지원
변수 스코프블록 전체if 블록만

if 초기화 구문의 장점

// ❌ 나쁜 예: 변수 스코프가 넓음
v, ok := m[key]
if ok {
    fmt.Println(v)
}
// v와 ok가 여기서도 사용 가능 (불필요)

// ✅ 좋은 예: 변수 스코프 제한
if v, ok := m[key]; ok {
    fmt.Println(v)
}
// v와 ok는 if 블록 내에서만 존재 (깔끔)

// 실전 예시: 파일 처리
if data, err := os.ReadFile("config.json"); err != nil {
    log.Fatal(err)
} else {
    // data 사용
    fmt.Println(string(data))
}

// 에러 체크 패턴 (가장 일반적)
if err := saveToDatabase(user); err != nil {
    return fmt.Errorf("데이터베이스 저장 실패: %w", err)
}

switch 문: 자동 break

// C++: switch 문 (break 필수)
int day = 3;
switch (day) {
    case 1:
        std::cout << "Monday\n";
        break;  // break 빠뜨리면 다음 case로 떨어짐 (fallthrough)
    case 2:
        std::cout << "Tuesday\n";
        break;
    case 3:
    case 4:  // 여러 case 묶기
        std::cout << "Mid-week\n";
        break;
    default:
        std::cout << "Other day\n";
}

// break 빠뜨린 경우 (버그!)
switch (day) {
    case 1:
        std::cout << "Monday\n";
        // break 없음 → case 2로 떨어짐!
    case 2:
        std::cout << "Tuesday\n";
        break;
}
// Go: switch 문 (break 자동)
day := 3
switch day {
case 1:
    fmt.Println("Monday")
    // break 자동 (fallthrough 없음)
case 2:
    fmt.Println("Tuesday")
case 3, 4:  // 여러 값 동시 처리 (쉼표로 구분)
    fmt.Println("Mid-week")
default:
    fmt.Println("Other day")
}

// fallthrough 명시적 사용 (드물게)
switch day {
case 1:
    fmt.Println("Monday")
    fallthrough  // 다음 case로 강제 이동
case 2:
    fmt.Println("Weekday")
}
// day=1일 때 출력:
// Monday
// Weekday

조건식 없는 switch (if-else 대체)

// 복잡한 if-else 체인
x := 15
if x < 0 {
    fmt.Println("Negative")
} else if x < 10 {
    fmt.Println("Small")
} else if x < 100 {
    fmt.Println("Medium")
} else {
    fmt.Println("Large")
}

// switch로 더 깔끔하게 (조건식 생략)
switch {
case x < 0:
    fmt.Println("Negative")
case x < 10:
    fmt.Println("Small")
case x < 100:
    fmt.Println("Medium")
default:
    fmt.Println("Large")
}

// 실전 예시: 타입 체크
var value interface{} = 42

switch v := value.(type) {
case int:
    fmt.Printf("정수: %d\n", v)
case string:
    fmt.Printf("문자열: %s\n", v)
case bool:
    fmt.Printf("불린: %t\n", v)
default:
    fmt.Printf("알 수 없는 타입: %T\n", v)
}

switch 초기화 구문

// switch에도 초기화 구문 사용 가능
switch value := getValue(); value {
case 1:
    fmt.Println("One")
case 2:
    fmt.Println("Two")
default:
    fmt.Println("Other")
}
// value는 switch 블록 내에서만 존재

// 실전 예시: 에러 타입 체크
switch err := doSomething(); err.(type) {
case *os.PathError:
    fmt.Println("경로 에러")
case *os.SyscallError:
    fmt.Println("시스템 호출 에러")
default:
    fmt.Println("기타 에러:", err)
}

핵심 차이점:

항목C++Go
break필수 (빠뜨리면 버그)자동 (안전)
fallthrough기본 동작명시적 키워드
조건식 없는 switch불가가능 (if-else 대체)
타입 switch불가가능
여러 값여러 줄 필요쉼표로 한 줄

5. 가비지 컬렉터: new/delete로부터의 해방

메모리 관리 철학

C++: 개발자가 직접 관리 (성능 최적화 가능, 버그 위험)
Go: 가비지 컬렉터 자동 관리 (안전, 생산성 향상)

C++ 메모리 관리 (수동)

// C++: 수동 메모리 관리
int* p = new int(42);
delete p;  // 수동 해제 필수

// 배열
int* arr = new int[100];
delete[] arr;  // [] 필수 (delete만 쓰면 버그!)

// 흔한 실수들
int* p1 = new int(10);
// delete p1;  // 해제 안 하면 메모리 누수!

int* p2 = new int(20);
delete p2;
// *p2 = 30;  // 댕글링 포인터 (이미 해제된 메모리 접근)

int* arr2 = new int[100];
delete arr2;  // ❌ delete[] 아님 (정의되지 않은 동작)

// 스마트 포인터 (C++11, RAII)
#include <memory>

auto ptr = std::make_unique<int>(42);
// 스코프 벗어나면 자동 해제

// 공유 소유권
auto shared1 = std::make_shared<int>(42);
auto shared2 = shared1;  // 참조 카운트 증가
// 모든 shared_ptr이 소멸되면 메모리 해제

Go 메모리 관리 (자동)

// Go: 가비지 컬렉터 자동 관리
p := new(int)
*p = 42
// delete 불필요 - GC가 자동 수거

// 구조체 포인터
type Person struct {
    Name string
    Age  int
}

person := &Person{Name: "Alice", Age: 30}
// GC가 자동 수거 (참조가 없어지면)

// 슬라이스 (동적 배열)
arr := make([]int, 100)
// GC가 자동 수거

// 맵
m := make(map[string]int)
m["key"] = 100
// GC가 자동 수거

GC의 장단점:

장점:

  • 메모리 누수 방지: delete 잊어버릴 일 없음
  • 댕글링 포인터 불가능: 이미 해제된 메모리 접근 불가
  • 개발 생산성: 메모리 관리 신경 안 써도 됨
  • 안전성: 메모리 관련 버그 대부분 제거

단점:

  • ⚠️ GC 일시 정지: 수 ms (Go는 최적화되어 있음)
  • ⚠️ 메모리 사용량: C++보다 높을 수 있음
  • ⚠️ 예측 불가능: GC 실행 시점 제어 불가
  • ⚠️ 실시간 시스템: 부적합할 수 있음 (항공, 의료기기 등)

스택 vs 힙: Escape Analysis

Go 컴파일러는 Escape Analysis로 변수를 스택/힙에 자동 배치합니다.

// 예제 1: 스택 할당
func createLocal() int {
    x := 10  // 스택 할당 (함수 내부에서만 사용)
    return x  // 값 복사로 반환
}

// 예제 2: 힙 할당
func createHeap() *int {
    x := 10  // 힙 할당 (포인터가 반환되어 함수 밖에서 사용)
    return &x  // 포인터 반환
}

// C++에서는 두 번째 함수가 댕글링 포인터 오류!
// Go는 컴파일러가 자동으로 힙에 할당하여 안전

// 예제 3: 복잡한 경우
type Data struct {
    Value int
}

func process() *Data {
    d := Data{Value: 100}  // 힙 할당 (포인터 반환)
    return &d
}

func main() {
    data := process()
    fmt.Println(data.Value)  // 100 (안전하게 접근 가능)
}

Escape Analysis 확인:

# 컴파일러가 어디에 할당하는지 확인
go build -gcflags="-m" main.go

# 출력 예시:
# ./main.go:10:2: moved to heap: x
# ./main.go:15:2: d escapes to heap

C++ vs Go 메모리 관리 비교

항목C++Go
할당newnew() 또는 &
해제delete (수동)GC (자동)
배열 해제delete[]불필요
메모리 누수가능거의 불가능
댕글링 포인터가능불가능
성능최고약간 낮음
안전성낮음높음
개발 속도느림빠름

6. go fmt: 코딩 스타일 논쟁 종결자

C++의 스타일 전쟁

C++에는 통일된 코드 스타일이 없습니다. 팀마다, 회사마다 다른 스타일 가이드를 사용합니다.

// C++: 팀마다 다른 스타일

// 1. K&R 스타일 (Linux 커널)
if (condition) {
    doSomething();
}

// 2. Allman 스타일 (BSD)
if (condition)
{
    doSomething();
}

// 3. GNU 스타일
if (condition)
  {
    doSomething();
  }

// 4. Stroustrup 스타일
if (condition) {
    doSomething();
}
else {
    doSomethingElse();
}

// 논쟁거리들:
// - 탭 vs 스페이스?
// - 들여쓰기 2칸 vs 4칸?
// - 중괄호 위치는?
// - 포인터 선언: int* p vs int *p?
// - 함수 이름: camelCase vs snake_case?

C++ 포맷터 도구 (별도 설치 필요):

  • clang-format: LLVM 프로젝트
  • astyle: Artistic Style
  • 각 도구마다 설정 파일 필요 (.clang-format, .astylerc)

Go의 해결책: go fmt

Go는 하나의 공식 스타일만 존재합니다. go fmt가 자동으로 포맷팅합니다.

// Go: go fmt가 자동으로 포맷팅

// 저장 전 (엉망인 코드)
func example(){
if x>0{
fmt.Println("positive")
}else{
fmt.Println("non-positive")
}
}

// go fmt 실행 후 (자동 정리)
func example() {
    if x > 0 {
        fmt.Println("positive")
    } else {
        fmt.Println("non-positive")
    }
}

// 연산자 주변 공백
x:=1+2*3  // 저장 전
x := 1 + 2*3  // go fmt 후

// 불필요한 괄호 제거
if (x > 0) {  // 저장 전
if x > 0 {    // go fmt 후

// 들여쓰기는 항상 탭 (스페이스 아님)

go fmt의 철학:

  • 하나의 스타일: Go 커뮤니티 전체가 동일한 스타일
  • 자동화: IDE에 통합하여 저장 시 자동 실행
  • 논쟁 종결: 코드 리뷰에서 스타일 논쟁 제거
  • 설정 불필요: .gofmt 같은 설정 파일 없음

go fmt 사용법

# 단일 파일 포맷팅
go fmt main.go

# 현재 패키지 포맷팅
go fmt

# 모든 하위 디렉토리 포맷팅
go fmt ./...

# gofmt 직접 사용 (더 많은 옵션)
gofmt -w main.go         # 파일에 직접 쓰기
gofmt -d main.go         # 변경 사항만 출력 (diff)
gofmt -s -w main.go      # 코드 단순화 + 쓰기

VS Code에서 자동 포맷팅 설정

// settings.json
{
  "go.formatTool": "gofmt",
  "editor.formatOnSave": true,
  "[go]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  }
}

저장 시 자동 실행:

  1. go fmt - 코드 포맷팅
  2. goimports - import 정리 (사용하지 않는 import 제거, 필요한 import 추가)

go fmt의 실제 효과

// 포맷팅 전
package main
import "fmt"
func main(){
var x=10
if x>5{fmt.Println("big")}else{
fmt.Println("small")}}

// go fmt 실행 후
package main

import "fmt"

func main() {
    var x = 10
    if x > 5 {
        fmt.Println("big")
    } else {
        fmt.Println("small")
    }
}

결과: 모든 Go 코드가 동일한 스타일로 작성됩니다. GitHub의 Go 프로젝트를 보면 모두 같은 스타일입니다!


7. 실습 과제

과제 1: FizzBuzz

C++과 Go로 각각 구현하고 차이를 비교해보세요.

// C++: FizzBuzz
#include <iostream>

int main() {
    for (int i = 1; i <= 100; i++) {
        if (i % 15 == 0) {
            std::cout << "FizzBuzz\n";
        } else if (i % 3 == 0) {
            std::cout << "Fizz\n";
        } else if (i % 5 == 0) {
            std::cout << "Buzz\n";
        } else {
            std::cout << i << "\n";
        }
    }
    return 0;
}
// Go: FizzBuzz
package main

import "fmt"

func main() {
    for i := 1; i <= 100; i++ {
        switch {
        case i%15 == 0:
            fmt.Println("FizzBuzz")
        case i%3 == 0:
            fmt.Println("Fizz")
        case i%5 == 0:
            fmt.Println("Buzz")
        default:
            fmt.Println(i)
        }
    }
}

과제 2: 구구단 출력

// Go: 구구단 (중첩 루프)
package main

import "fmt"

func main() {
    for i := 2; i <= 9; i++ {
        for j := 1; j <= 9; j++ {
            fmt.Printf("%d x %d = %d\n", i, j, i*j)
        }
        fmt.Println()
    }
}

과제 3: 슬라이스 합계 계산 (range 활용)

C++ 구현:

// C++: 벡터 합계
#include <iostream>
#include <vector>

int sum(const std::vector<int>& numbers) {
    int total = 0;
    for (int num : numbers) {  // 범위 기반 for
        total += num;
    }
    return total;
}

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};
    std::cout << "Sum: " << sum(nums) << std::endl;  // 15
    return 0;
}

Go 구현:

// Go: 슬라이스 합계
package main

import "fmt"

func sum(numbers []int) int {
    total := 0
    for _, num := range numbers {  // range 루프
        total += num
    }
    return total
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    result := sum(nums)
    fmt.Printf("Sum: %d\n", result)  // Sum: 15
}

비교:

  • C++: const std::vector<int>& (참조 전달)
  • Go: []int (슬라이스는 참조 타입)
  • Go가 더 간결

과제 4: 짝수만 필터링 (슬라이스 조작)

C++ 구현:

// C++: 짝수 필터링
#include <iostream>
#include <vector>

std::vector<int> filterEven(const std::vector<int>& numbers) {
    std::vector<int> result;
    result.reserve(numbers.size());  // 메모리 예약
    
    for (int num : numbers) {
        if (num % 2 == 0) {
            result.push_back(num);
        }
    }
    return result;
}

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> evens = filterEven(nums);
    
    for (int num : evens) {
        std::cout << num << " ";
    }
    std::cout << std::endl;  // 2 4 6 8 10
    
    return 0;
}

Go 구현:

// Go: 짝수 필터링
package main

import "fmt"

func filterEven(numbers []int) []int {
    result := make([]int, 0, len(numbers))  // 용량 예약
    
    for _, num := range numbers {
        if num%2 == 0 {
            result = append(result, num)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    evens := filterEven(nums)
    fmt.Println(evens)  // [2 4 6 8 10]
}

비교:

  • C++: reserve(), push_back()
  • Go: make(), append()
  • 개념은 유사하지만 Go가 더 간결

과제 5: 맵 사용 (단어 빈도 계산)

C++ 구현:

// C++: 단어 빈도
#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> words = {
        "apple", "banana", "apple", "cherry", "banana", "apple"
    };
    std::map<std::string, int> freq;
    
    for (const auto& word : words) {
        freq[word]++;  // 없으면 0으로 초기화 후 증가
    }
    
    for (const auto& [word, count] : freq) {  // C++17 구조화 바인딩
        std::cout << word << ": " << count << "\n";
    }
    
    return 0;
}

Go 구현:

// Go: 단어 빈도
package main

import "fmt"

func main() {
    words := []string{"apple", "banana", "apple", "cherry", "banana", "apple"}
    freq := make(map[string]int)
    
    for _, word := range words {
        freq[word]++  // 제로 값(0)에서 시작
    }
    
    for word, count := range freq {
        fmt.Printf("%s: %d\n", word, count)
    }
}

// 출력 (순서는 무작위):
// apple: 3
// banana: 2
// cherry: 1

비교:

  • 문법이 거의 동일
  • Go의 제로 값 덕분에 초기화 걱정 없음
  • C++17 구조화 바인딩 vs Go의 기본 문법

8. 정리: Day 1~2 학습 체크리스트

완료해야 할 항목

  • Go 설치 및 go version 확인
  • Hello World 프로그램 작성 및 실행 (go run, go build)
  • :=var의 차이 이해
  • 제로 값 개념 숙지 (int → 0, string → "", bool → false)
  • for 하나로 모든 반복 처리하기 (while, 무한 루프, range)
  • if 초기화 구문 활용 (변수 스코프 제한)
  • switch 자동 break 이해
  • go fmt로 코드 포맷팅 자동화
  • go mod·패키지 경로·Exported 이름 규칙 이해
  • 실습 과제 5개 완료

C++에서 버려야 할 것

  • while, do-while 문법 → for 하나로 통일
  • ❌ 변수 초기화 걱정 → 제로 값 자동
  • new/delete 수동 관리 → GC 자동
  • switch에서 break 필수 → 자동 break
  • ❌ 코드 스타일 논쟁 → go fmt 자동
  • new/delete 수동 관리
  • ❌ 코딩 스타일 논쟁

Go에서 새로 익혀야 할 것

  • := 짧은 선언 (함수 내부 전용)
  • for 하나로 모든 반복 (while, 무한 루프, range)
  • range로 컨테이너 순회 (인덱스, 값 동시 접근)
  • if, switch 초기화 구문 (변수 스코프 제한)
  • go fmt 자동 포맷팅 (스타일 통일)
  • ✅ GC 신뢰하기 (메모리 관리 자동)

C++ vs Go 핵심 비교표

개념C++GoGo의 장점
변수 선언auto, int, const, constexprvar, :=, const단순함
초기화쓰레기 값 가능제로 값 자동안전
반복문for, while, do-whilefor 하나일관성
조건문괄호 필수괄호 없음간결
switchbreak 필수자동 break안전
메모리 관리수동 (new/delete)GC 자동생산성
코드 스타일팀마다 다름go fmt 통일협업

9. 다음 단계

Day 1~2에서는 Go의 기본 문법과 철학을 배웠습니다.

다음 글에서 배울 내용:

  • 포인터와 메모리 (C++의 *, &와 비교)
  • 배열 vs 슬라이스 (동적 배열)
  • 맵 (Map) - 해시 테이블
  • 구조체 (Struct) - 사용자 정의 타입

📚 시리즈 네비게이션

이전 글목차다음 글
← 커리큘럼 소개📑 전체 목차#02 자료구조 →

Go 2주 완성 시리즈: 커리큘럼#01 기본 문법#02 자료구조#03 객체지향#04 인터페이스#05 에러 처리#06 고루틴·채널#07 테스팅#08 REST API#09 context·우아한 종료


한 줄 요약: Go는 C++의 복잡함을 덜어내고 심플함에 집중한 언어입니다. :=, for, go fmt만 익히면 첫 단계는 끝입니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • Go 2주 완성 시리즈 전체 목차
  • C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼
  • C++ 개발자의 뇌 구조로 이해하는 Go 언어 [#47-2]
  • C++ vs Go | 성능·동시성·선택 가이드 완전 비교 [#47-1]

이 글에서 다루는 키워드 (관련 검색어)

Go 입문, Golang 설치, := 짧은 선언, Go for문, range, 가비지 컬렉터, go fmt, C++ Go 비교, Go 2주 완성, Golang 기본 문법 등으로 검색하시면 이 글이 도움이 됩니다.

실전 팁

실무에서 바로 적용할 수 있는 팁입니다.

디버깅 팁

  • 문제가 발생하면 먼저 컴파일러 경고를 확인하세요
  • 간단한 테스트 케이스로 문제를 재현하세요

성능 팁

  • 프로파일링 없이 최적화하지 마세요
  • 측정 가능한 지표를 먼저 설정하세요

코드 리뷰 팁

  • 코드 리뷰에서 자주 지적받는 부분을 미리 체크하세요
  • 팀의 코딩 컨벤션을 따르세요

실전 체크리스트

실무에서 이 개념을 적용할 때 확인해야 할 사항입니다.

코드 작성 전

  • 이 기법이 현재 문제를 해결하는 최선의 방법인가?
  • 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
  • 성능 요구사항을 만족하는가?

코드 작성 중

  • 컴파일러 경고를 모두 해결했는가?
  • 엣지 케이스를 고려했는가?
  • 에러 처리가 적절한가?

코드 리뷰 시

  • 코드의 의도가 명확한가?
  • 테스트 케이스가 충분한가?
  • 문서화가 되어 있는가?

이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++의 auto와 while을 Go의 := 와 for로 전환하기. 가비지 컬렉터 도입으로 new/delete에서 해방되는 첫 단계. 변수 선언, 반복문, 조건문을 C++ 코드와 직접 비교하며 배웁니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


관련 글

  • C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼
  • C++ vs Go | 성능·동시성·선택 가이드 완전 비교 [#47-1]
  • C++ 개발자의 뇌 구조로 이해하는 Go 언어 [#47-2]
  • [Go 2주 완성 #02] Day 3~4: 메모리와 자료구조 - 포인터 연산은 없지만 포인터는 있다
  • [Go 2주 완성 #03] Day 5~6: 클래스 없는 객체지향 - 상속을 버리고 합성을 취하다