본문으로 건너뛰기
Previous
Next
Go in 2 Weeks #02 ??Complete Guide

Go in 2 Weeks #02 ??Complete Guide

Go in 2 Weeks #02 ??Complete Guide

이 글의 핵심

Go pointers, slices, and maps for C++ developers: safe *T, len/cap/append, map lookup with ok, and how slices differ from std::vector. Part of the 2-week Go series.

Series overview

?�� Go in 2 Weeks #02 | Full series index

This post covers Days 3?? of the two-week Go curriculum for C++ developers.

Previous: [#01 Philosophy & syntax](/en/blog/go-series-01-philosophy-syntax/ ??| ??Next: [#03 OOP & composition](/en/blog/go-series-03-oop-composition/


Introduction: the world of safe pointers

In C++, pointer arithmetic (p++, p + offset) lets you walk memory freely?�but segmentation faults come with the territory. Go has pointers but no pointer arithmetic. Safety was chosen on purpose. This article compares Go pointers and core data structures?�slices and maps?�to C++. You will learn:

  • Go pointer restrictions and safety
  • Call by value vs pointers
  • Slice length and capacity
  • Map usage and pitfalls

Real-world notes

Lessons from adopting Go in real projects.

Moving from C++ to Go

I spent over a decade building servers in C++. Go felt almost too simple at first. In production, that simplicity became a strength. Takeaways:

  • Faster delivery: work that took three days in C++ often took one in Go
  • Stability: fewer memory-leak worries with the GC
  • Deployment: single static binaries simplify rollout This series reflects that experience.

Table of contents

  1. Pointers: no arithmetic, but dereference works
  2. Arrays: fixed-size value types
  3. Slices: Go?�s dynamic arrays
  4. Maps: hash tables
  5. Exercises

1. Pointers: no arithmetic, but dereference works

C++ vs Go: pointer basics

// C++: pointer arithmetic allowed
int x = 10;
int* p = &x;
*p = 20;        // dereference
p++;            // pointer arithmetic (next int)
*(p + 5) = 30;  // offset access
int arr[10];
int* ptr = arr;
ptr[5] = 100;   // array indexing = pointer arithmetic
// Go: no pointer arithmetic
x := 10
p := &x
*p = 20         // ??dereference OK
// p++          // ??compile error: no pointer arithmetic
// *(p + 5)     // ??compile error
// Use indexing for arrays
arr := [10]int{}
arr[5] = 100    // ??index access

Key differences:

  • Go does not allow pointer arithmetic (safety)
  • Only * (dereference) and & (address-of)
  • Array access uses index syntax only

C++ vs Go: function arguments

// C++: value, pointer, reference
void byValue(int x) {
    x = 100;  // original unchanged
}
void byPointer(int* p) {
    *p = 100;  // original changed
}
void byReference(int& r) {
    r = 100;  // original changed
}
int main() {
    int x = 10;
    byValue(x);        // x still 10
    byPointer(&x);     // x becomes 100
    byReference(x);    // x becomes 100
}
// Go: value or pointer (no references)
func byValue(x int) {
    x = 100  // original unchanged
}
func byPointer(p *int) {
    *p = 100  // original changed
}
func main() {
    x := 10
    byValue(x)      // x still 10
    byPointer(&x)   // x becomes 100
}

Pointer usage:

  • Small types (int, bool): pass by value
  • Large structs (~64+ bytes): pass pointer
  • Mutation needed: pass pointer
  • Read-only: value or pointer (stay consistent)

Nil pointers

// C++: nullptr
int* p = nullptr;
if (p == nullptr) {
    std::cout << "null pointer\n";
}
// Dereference ??segfault
// *p = 10;  // crash!
// Go: nil
var p *int
if p == nil {
    fmt.Println("nil pointer")
}
// Dereference ??panic
// *p = 10  // panic: runtime error: invalid memory address

2. Arrays: fixed-size value types

C++ vs Go: array declarations

// C++: array
int arr[5] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);  // 5
// Decays to pointer when passed to functions
void process(int* arr, int size) {
    // ...
}
// Go: array (size is part of the type)
var arr [5]int = [5]int{1, 2, 3, 4, 5}
// or
arr := [5]int{1, 2, 3, 4, 5}
// or (size inferred)
arr := [...]int{1, 2, 3, 4, 5}
length := len(arr)  // 5
// Passed by value?�full copy (no decay to pointer)
func process(arr [5]int) {
    // entire array copied
}

Key differences:

  • In Go, array size is part of the type: [5]int and [10]int differ
  • Passing an array copies the whole array (no C-style decay)
  • In practice, slices are used far more often than arrays

3. Slices: Go?�s dynamic arrays

Slices are the most common container in Go. They resemble std::vector but with different ergonomics.

C++ vs Go: dynamic arrays

// C++: std::vector
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec;
    
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    
    std::cout << "Size: " << vec.size() << "\n";
    std::cout << "Capacity: " << vec.capacity() << "\n";
    
    vec.reserve(100);
    
    for (const auto& v : vec) {
        std::cout << v << " ";
    }
}
// Go: slice
package main
import "fmt"
func main() {
    var slice []int  // nil slice
    
    slice = append(slice, 1)
    slice = append(slice, 2)
    slice = append(slice, 3)
    
    fmt.Println("Length:", len(slice))
    fmt.Println("Capacity:", cap(slice))
    
    slice2 := make([]int, 0, 100)  // len=0, cap=100
    
    for i, v := range slice {
        fmt.Println(i, v)
    }
}

Slice internals

// A slice is a small struct of three fields
type slice struct {
    ptr *[...]T  // pointer to backing array
    len int      // length
    cap int      // capacity
}
graph LR
    A[Slice] --> B["ptr: array pointer"]
    A --> C["len: length"]
    A --> D["cap: capacity"]
    B --> E[Backing array memory]

Creating slices

package main
import "fmt"
func main() {
    // 1. nil slice
    var s1 []int
    fmt.Println(s1 == nil)  // true
    
    // 2. literal
    s2 := []int{1, 2, 3}
    
    // 3. make (length and capacity)
    s3 := make([]int, 5)      // len=5, cap=5, zero-filled
    s4 := make([]int, 5, 10)  // len=5, cap=10
    
    // 4. slicing
    arr := [5]int{1, 2, 3, 4, 5}
    s5 := arr[1:4]  // [2, 3, 4], shares arr
    
    fmt.Println(s1, s2, s3, s4, s5)
}

Slice expressions

// C++: sub-range (copy)
std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> sub(vec.begin() + 1, vec.begin() + 4);  // [2, 3, 4]
// Go: slicing (shares backing array)
slice := []int{1, 2, 3, 4, 5}
sub := slice[1:4]   // [2, 3, 4], indices 1..3
sub[0] = 100        // slice[1] becomes 100 too!
// slice[low:high]   // low through high-1
// slice[low:]       // low to end
// slice[:high]      // start through high-1
// slice[:]          // whole slice (still a view, not a deep copy)
copied := make([]int, len(sub))
copy(copied, sub)

Caveat: slicing shares the backing array; writes through one slice affect the other.

append and reallocation

package main
import "fmt"
func main() {
    slice := make([]int, 0, 3)  // len=0, cap=3
    fmt.Printf("len=%d cap=%d\n", len(slice), cap(slice))
    
    slice = append(slice, 1)
    slice = append(slice, 2)
    slice = append(slice, 3)
    fmt.Printf("len=%d cap=%d\n", len(slice), cap(slice))
    
    slice = append(slice, 4)  // len=4, cap=6 (realloc!)
    fmt.Printf("len=%d cap=%d\n", len(slice), cap(slice))
}

Performance tip: when you know final size, use make([]T, 0, capacity).

// ??inefficient: many reallocations
slice := []int{}
for i := 0; i < 10000; i++ {
    slice = append(slice, i)
}
// ??preallocate capacity
slice := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
    slice = append(slice, i)
}

4. Maps: hash tables

C++ vs Go: maps

#include <unordered_map>
#include <iostream>
int main() {
    std::unordered_map<std::string, int> m;
    
    m[apple] = 100;
    m[banana] = 200;
    m.insert({"cherry", 300});
    
    if (m.find("apple") != m.end()) {
        std::cout << "Found: " << m[apple] << "\n";
    }
    
    int x = m[nonexistent];  // inserts "nonexistent": 0
    
    m.erase("apple");
    
    for (const auto& [key, value] : m) {
        std::cout << key << ": " << value << "\n";
    }
}
// ?�키지 ?�언
package main
import "fmt"
func main() {
    m := make(map[string]int)
    
    m[apple] = 100
    m[banana] = 200
    m[cherry] = 300
    
    if v, ok := m[apple]; ok {
        fmt.Println("Found:", v)
    }
    
    x := m[nonexistent]  // 0, map unchanged
    
    delete(m, "apple")
    
    for key, value := range m {  // order not defined
        fmt.Println(key, ":", value)
    }
}

Key differences:

  • Go maps do not guarantee iteration order
  • Missing keys yield the zero value without inserting (unlike operator[] on unordered_map in the ?�read missing key??case?�Go does not insert)
  • Use the comma-ok form (v, ok := m[k]) to detect presence

Map creation

package main
func main() {
    m1 := make(map[string]int)
    
    m2 := map[string]int{
        "apple":  100,
        "banana": 200,
    }
    
    var m3 map[string]int
    // m3[key] = 1  // ??panic: assignment to entry in nil map
    v := m3[key]    // ??0, read OK
    
    m4 := make(map[string]int, 100)  // hint ~100 entries
}

Map patterns

package main
import "fmt"
func wordCount(words []string) map[string]int {
    counts := make(map[string]int)
    for _, word := range words {
        counts[word]++
    }
    return counts
}
func uniqueElements(numbers []int) []int {
    seen := make(map[int]bool)
    result := []int{}
    
    for _, num := range numbers {
        if !seen[num] {
            seen[num] = true
            result = append(result, num)
        }
    }
    return result
}
func main() {
    words := []string{"go", "is", "go", "is", "simple"}
    fmt.Println(wordCount(words))
    
    nums := []int{1, 2, 2, 3, 3, 3, 4}
    fmt.Println(uniqueElements(nums))
}

5. Exercises

Exercise 1: reverse a slice in place

package main
import "fmt"
func reverse(slice []int) {
    for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
        slice[i], slice[j] = slice[j], slice[i]
    }
}
func main() {
    nums := []int{1, 2, 3, 4, 5}
    reverse(nums)
    fmt.Println(nums)  // [5 4 3 2 1]
}

C++ comparison: std::reverse(vec.begin(), vec.end()).

Exercise 2: deduplicate

package main
import "fmt"
func removeDuplicates(slice []int) []int {
    seen := make(map[int]bool)
    result := []int{}
    
    for _, v := range slice {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}
func main() {
    nums := []int{1, 2, 2, 3, 3, 3, 4, 5, 5}
    fmt.Println(removeDuplicates(nums))
}

Exercise 3: merge two slices

package main
import "fmt"
func merge(s1, s2 []int) []int {
    result := make([]int, 0, len(s1)+len(s2))
    result = append(result, s1...)
    result = append(result, s2...)
    return result
}
func main() {
    a := []int{1, 2, 3}
    b := []int{4, 5, 6}
    fmt.Println(merge(a, b))
}

Exercise 4: group with a map

package main
import "fmt"
type Student struct {
    Name  string
    Score int
}
func groupByScore(students []Student) map[int][]string {
    groups := make(map[int][]string)
    for _, s := range students {
        groups[s.Score] = append(groups[s.Score], s.Name)
    }
    return groups
}
func main() {
    students := []Student{
        {"Alice", 90},
        {"Bob", 85},
        {"Charlie", 90},
        {"David", 85},
    }
    fmt.Println(groupByScore(students))
}

Wrap-up: Days 3?? checklist

Goals

  • Go pointers: no arithmetic; dereference only
  • Arguments: by value vs by pointer
  • Arrays are fixed-size; slices are the default tool
  • Understand len, cap, and append growth
  • Slicing shares storage?�watch aliasing
  • Maps: construction, lookup, comma-ok
  • Complete the four exercises

C++ ??Go cheat sheet

C++GoNotes
int* p; p++p := &x (no ++)Safety first
std::vector<T>[]TSimpler surface syntax
vec.size()len(slice)builtin
vec.capacity()cap(slice)builtin
std::unordered_mapmap[K]Vbuiltin type
m.find(k) != m.end()v, ok := m[k]idiomatic

Next

Next: OOP without classes?�composition over inheritance, methods and receivers.

?�� Series navigation

PreviousIndexNext
[??#01 Syntax](/en/blog/go-series-01-philosophy-syntax/?�� Index[#03 OOP ??(/en/blog/go-series-03-oop-composition/
Go in 2 weeks:
Curriculum ??[#01](/en/blog/go-series-01-philosophy-syntax/ ??[#02](/en/blog/go-series-02-memory-data-structures/ ??[#03](/en/blog/go-series-03-oop-composition/ ??[#04](/en/blog/go-series-04-interface/ ??[#05](/en/blog/go-error-handling-guide/ ??[#06](/en/blog/go-series-06-goroutine-channel/ ??#07 ??#08 ??#09

TL;DR: Go pointers are safe, slices are powerful, maps are concise?�you can do the same jobs as in C++ with less machinery.


Keywords

Go slice, Go map, Go pointer, append len cap, Golang data structures, Go tutorial, C++ vector comparison.

Practical tips

Debugging

  • Start from compiler warnings; reproduce with a minimal case.

Performance

  • Profile before optimizing; define measurable goals.

Code review

  • Align with team conventions; check edge cases and errors.

Field checklist

Before coding

  • Is this the right tool for the problem?
  • Will teammates maintain it?
  • Does it meet performance needs?

While coding

  • Warnings cleared?
  • Edge cases covered?
  • Errors handled?

At review

  • Intent clear?
  • Tests adequate?
  • Docs where needed?

FAQ

Q. Where does this show up in real systems?

A. Understanding vector-vs-slice behavior, safe pointers, and map semantics?�exactly what you need for everyday Go services and CLIs.

Q. What should I read first?

A. Follow Previous / Related links at the bottom of each post, or the C++ series index.

Q. Go deeper?

A. Official Go docs and cppreference for C++ parallels.


같이 보면 좋�? 글 (?��? 링크)

??주제?� ?�결?�는 ?�른 글?�니??

  • [Go in 2 Weeks #01](/en/blog/go-series-01-philosophy-syntax/
  • [Go in 2 Weeks #05](/en/blog/go-error-handling-guide/
  • [Go in 2 Weeks #06](/en/blog/go-series-06-goroutine-channel/

??글?�서 ?�루???�워??(관??검?�어)

Go, Golang, C++, Pointers, Slice, Map, Memory ?�으�?검?�하?�면 ??글???��????�니??