Constants
In Go, constants are defined using the const keyword and store values that remain unchanged during program execution. These constants are evaluated at compile time, even when declared within functions, and can only be of boolean, numeric (integer, floating-point, complex), or string types.
Due to compile-time constraints, constant expressions must be evaluable by the compiler.
Declaration syntax:
const name [type] = value
const a1 int8 = 9
The type can be omitted, similar to variable declarations, allowing multiple constants to be declared together.
All constant operations are performed at compile time. Arithmetic, logical, comparison operations between constants, type conversions, and calls to functions like len, cap, real, imag, complex, and unsafe.Sizeof all return constant values.
When declaring multiple constants, initialization expressions can be omitted for all but the first one. If omitted, it defaults to the previous constant's expression and type.
const a1 int8 = 9
const (
a = 1
b
c = 13.0
d
)
func main() {
fmt.Println(a)
fmt.Printf("%d %d %.2f %.2f\n", a, b, c, d)
}
iota Constant Generator
Constants can utilize the iota generator for sequential initialization. It starts at zero in the first line of a const block and increments by one for each subsequent line.
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
}
Pointers
Go has two types of pointers:
- Pointer types allow modification of data they reference, enabling efficient access without copying.
- Slices consist of a pointer to an underlying array, element count, and capacity.
Slices offer enhanced safety compared to raw pointers, providing bounds checking that prevents crashes from out-of-bounds access.
Understanding Pointers
Memory space is allocated where a variable's value resides. Each memory location has a unique address. A pointer variable holds this address.
A pointer can reference any memory address, occupying 4 bytes on 32-bit systems and 8 bytes on 64-bit systems, independent of the referenced value size.
Uninitialized pointers default to nil. Every variable has a memory address accessible via the & operator.
func main() {
a := 1
str := "Beta"
var aptr *int = &a
var sptr *string = &str
fmt.Printf("%p %p \n", aptr, sptr)
}
The relationship among variables, addresses, and pointers is:
- Variables have memory addresses
- Pointers hold these addresses
- Dereferencing (
*) retrieves the value at the address
Modifying Values Through Pointers
Values pointed to can be modified using pointers:
func main() {
var a = 10
fmt.Printf("main method value=%d\n", a)
update1(a)
fmt.Printf("after update1, main method value=%d\n", a)
update2(&a)
fmt.Printf("after update2, main method value=%d\n", a)
}
func update1(a int) {
a = 66
fmt.Printf("update1 internal=%d\n", a)
}
func update2(a *int) {
*a = 99
fmt.Printf("update2 internal value=%d\n", *a)
}
Creating Pointers
Use the new keyword to create a pointer:
func main() {
ptr := new(string)
*ptr = "BetaBeta"
fmt.Println(*ptr)
}
Command Line Input
To capture command-line arguments:
var mode = flag.String("name", "", "Enter your name")
func main() {
flag.Parse()
fmt.Printf("Your name is: %s", *mode)
}
Variable Lifetimes
Variable lifetime refers to the duration a variable exists during program execution.
Types of lifetimes:
- Global variables: Live throughout the entire program execution.
- Local variables: Exist from declaration until no longer referenced.
- Parameters and return values: Local variables created upon function call and destroyed afterward.
Go uses heap and stack for storing variables:
- Heap: For dynamically allocated memory segments. Size can expand or shrink.
- Stack: Stores temporary local variables with in function blocks.
Stack follows LIFO principle. Memory allocation and deallocation are fast.
Compiler decides whether to allocate on stack or heap. Both var and new do not influence this decision.
var global *int
func f() {
var x int
x = 1
global = &x // Escapes to heap
}
func g() {
y := new(int)
*y = 1
// y does not escape
}
Type Aliases
type IntNew int
type IntAlias = int
func main() {
var a IntNew
var b IntAlias
fmt.Printf("aType=%T\n", a)
fmt.Printf("bType=%T\n", b)
}
Comments
Comments come in single-line (//) and multi-line (/* */) forms. Multi-line comments cannot be nested. Package documentation should precede the package statement.
Keywords and Identifiers
There are 25 keywords in Go:
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
Identifiers are character sequences used for naming variables, functions, etc., consisting of letters, underscores, and digits, starting with a letter.
Predefined identifiers (36 total) such as int, string, make, len cannot be used as identifiers.
Operator Precedence
| Precedence | Category | Operators | Associativity |
|---|---|---|---|
| 1 | Comma | , | Left-to-right |
| 2 | Assignment | =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, | = |
| 3 | Logical OR | ||
| 4 | Logical AND | && | Left-to-right |
| 5 | Bitwise OR | ||
| 6 | Bitwise XOR | ^ | Left-to-right |
| 7 | Bitwise AND | & | Left-to-right |
| 8 | Equality/Inequality | ==, != | Left-to-right |
| 9 | Relational | <, <=, >, >= | Left-to-right |
| 10 | Shift | <<, >> | Left-to-right |
| 11 | Addition/Subtraction | +, - | Left-to-right |
| 12 | Multiplication/Division | *, /, % | Left-to-right |
| 13 | Unary | !, *, &, ++, --, +, - | Right-to-left |
| 14 | Postfix | ( ), [ ], -> | Left-to-right |
String Conversion
Integer to String
func main() {
str := "666"
iVal, _ := strconv.Atoi(str)
fmt.Printf("%d %T \n", iVal, iVal)
sVal := strconv.Itoa(iVal)
fmt.Printf("%s %T \n", sVal, sVal)
}
Float to String
func main() {
str := "3.1415"
fValue, _ := strconv.ParseFloat(str, 64)
fmt.Printf("%f %T \n", fValue, fValue)
sValue := strconv.FormatFloat(fValue, 'f', -1, 64)
fmt.Printf("%s %T \n", sValue, sValue)
}
Arrays
Arrays are fixed-length sequences of elements of the same type.
var arrayName [elementCount]Type
var arr [3]int
func main() {
fmt.Printf("%d %d %d \n", arr[0], arr[1], arr[2])
for idx, val := range arr {
fmt.Printf("idx = %d value = %d |", idx, val)
}
fmt.Println()
for _, val := range arr {
fmt.Printf("value=%d |", val)
}
fmt.Println()
for idx := range arr {
fmt.Printf("index=%d |", idx)
}
}
Initialization
var arr [3]int = [3]int{1, 2, 3}
var arr1 [3]int = [3]int{1, 2}
var arr2 [3]int = [...]int{1, 2, 3}
func main() {
arr := [3]int{1, 2, 3}
fmt.Printf("%d", arr)
arr[0] = 4
arr[1] = 5
arr[2] = 6
}
Type Definition
type arr3 [3]int
var arr arr3
func main() {
arr[0] = 1
arr[1] = 2
fmt.Printf("%d %T", arr, arr)
}
Index Assignment
var arr [3]int
func main() {
arr = [3]int{1: 5}
fmt.Printf("%d", arr)
}
Comparison
Arrays of identical type and length can be compared using == and !=.
func main() {
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Printf("%t %t %t \n", a == b, b != c, a != c)
d := [3]int{1, 2}
// Compilation error
fmt.Printf("%t", a == d)
}
Multi-dimensional Arrays
Go supports multi-dimensional arrays where each dimension initializes to zero values.
var arr [4][2]int
var arr1 = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
var arr2 = [4][2]int{1: {20, 21}, 3: {40, 41}}
var arr3 = [4][2]int{1: {0: 20}, 3: {1: 41}}
func main() {
fmt.Printf("%d\n", arr2[1])
for idx, val := range arr3 {
fmt.Printf("Index:%d Value:%d \n", idx, val)
}
}
Slices
Slices are references to contiguous sections of arrays.
slice [start:end]
func main() {
var val = [3]int{1, 2, 3}
slice := val[1:2]
fmt.Printf("%d %d %T", val, slice, slice)
}
Slice Operations
func main() {
var val [30]int
for i := 0; i < 30; i++ {
val[i] = i + 1
}
fmt.Printf("%d\n", val[10:15])
fmt.Printf("%d\n", val[20:])
fmt.Printf("%d\n", val[:2])
}
Declaration
var name []Type
func main() {
var strList []string
var val1 []int
var val2 []int = []int{}
fmt.Println(strList, val1, val2)
fmt.Println(len(strList), len(val1), len(val2))
fmt.Println(strList == nil, val1 == nil, val2 == nil)
strList = append(strList, "BetaBeta")
fmt.Println(strList)
}
Using make
make([]Type, size, cap)
func main() {
var a = make([]int, 2)
b := make([]int, 2, 3)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
fmt.Println(cap(a), cap(b))
}
Copying
copy(destSlice, srcSlice []T) int
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{7, 6, 5}
copy(slice1, slice2)
fmt.Println(slice1)
slice2 = []int{1, 1, 1}
copy(slice2, slice1)
fmt.Println(slice2)
}
Maps
Maps are unordered collections of key-value pairs.
var mapname map[keytype]valuetype
func main() {
var map1 = map[string]int{"k1": 1, "k2": 2}
var map2 map[string]int
map2 = map1
map2["k1"] = 99
fmt.Println(map1["k1"], map2["k1"], map1["k99"])
}
Creating with Capacity
make(map[keytype]valuetype, cap)
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
Iteration
func main() {
var map1 map[string]string
map1 = make(map[string]string, 3)
map1["k1"] = "key1"
map1["k2"] = "key2"
map1["k3"] = "key3"
for key, value := range map1 {
fmt.Printf("key:%s value:%s \n", key, value)
}
}
Deletion
func main() {
map1 := make(map[string]int, 3)
map1["a"] = 1
map1["b"] = 2
map1["c"] = 3
delete(map1, "b")
fmt.Println(map1)
map1 = make(map[string]int)
fmt.Println(map1)
}
Concurrent Access
Map access is not thread-safe. Use sync.Map for concurrent scenarios:
func main() {
var syncMap sync.Map
syncMap.Store("a", 1)
syncMap.Store("b", 2)
syncMap.Store("c", 3)
fmt.Println(syncMap.Load("a"))
syncMap.Delete("b")
syncMap.Range(func(k, v interface{}) bool {
fmt.Printf("%s %d \n", k, v)
return true
})
}
Nil Values
Nil represents zero values for pointers, slices, maps, channels, functions, and interfaces.
func main() {
var a []int
var b *int
fmt.Printf("%p %p \n", a, b)
var m map[string]string
var sl []int
var ptr *int
var c chan int
var f func()
var i interface{}
fmt.Printf("%#v\n%#v\n%#v\n%#v\n%#v\n%#v\n", m, sl, ptr, c, f, i)
}
new vs make
makeallocates and initializes slices, maps, and channels.newallocates memory and returns a pointer to it, initializing to zero values.
Control Flow
if-else
if condition1 {
// Execute if condition1 is true
} else if condition2 {
// Execute if condition1 is false and condition2 is true
} else {
// Execute if both conditions are false
}
if a := 5; a > 0 {
fmt.Println(a)
}
for loops
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
var a int
for {
a++
if a == 2 {
break
}
}
i := 0
for ; i < 3; i++ {
fmt.Println(i)
}
b := 0
for b < 2 {
fmt.Println(b)
b++
}
}
Loop Control
Break, return, panic, and goto are available for loop control:
for i := 0; i < 5; i++ {
fmt.Println(i)
if i == 2 {
break
}
}
fmt.Println("end")
for i := 0; i < 5; i++ {
fmt.Println(i)
if i == 2 {
return
}
}
fmt.Println("end")
for i := 0; i < 5; i++ {
fmt.Println(i)
if i == 2 {
panic("exception")
}
}
fmt.Println("end")
func main() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
goto breakHere
}
}
}
return
breakHere:
fmt.Println("end")
}
for-range
map1 := make(map[string]string, 3)
map1["a"] = "1"
map1["b"] = "2"
map1["c"] = "3"
for k, v := range map1 {
fmt.Printf("key:%s value:%s \n", k, v)
if k == "a" {
v = "AAA"
fmt.Printf("updated: %s\n", map1[k])
}
}
str := "你好Beta"
for idx, s := range str {
fmt.Printf("%d %c \n", idx, s)
}
switch
func main() {
var exp = 100
var level int
switch exp {
case 50, 60, 70:
level = 7
case 80:
level = 8
case 100:
level = 10
default:
level = 5
}
switch {
case level == 7:
fmt.Println("Average Level")
case level > 7:
fmt.Println("High Level")
case level < 7:
fmt.Println("Beginner Level")
}
}
fallthrough
str := "GOGO"
switch str {
case "GOGO":
fmt.Print("GOGO")
fallthrough
case "Lets GO":
fmt.Print(" Lets GO")
}
goto
func main() {
for x := 0; x < 10; x++ {
for y := 0; y < 10; y++ {
if y == 2 {
goto breakPoint
}
}
}
fmt.Println("done")
breakPoint:
fmt.Println("goto break")
}
break
func main() {
breakPoint:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
break breakPoint
}
fmt.Println("j:", j)
}
}
}
continue
func main() {
continuePoint:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue continuePoint
}
fmt.Println("j:", j)
}
}
}