Go Operators
Operators are the building blocks of expressions in Go, allowing you to perform calculations, comparisons, logical operations, and more. This comprehensive guide will teach you all of Go's operators, their precedence, associativity, and best practices for writing clear and efficient expressions.
Understanding Operators in Go
What Are Operators?
Operators are special symbols that tell Go how to manipulate values and variables. They enable you to perform various operations such as arithmetic calculations, logical comparisons, and data transformations.
Categories of Go Operators
Go provides several categories of operators:
Arithmetic Operators
- Addition, subtraction, multiplication, division
- Modulo (remainder) operation
- Increment and decrement operations
Comparison Operators
- Equality and inequality comparisons
- Relational comparisons (greater than, less than)
- Type-specific comparisons
Logical Operators
- Boolean logic operations (AND, OR, NOT)
- Short-circuit evaluation
- Conditional logic
Bitwise Operators
- Bit-level manipulation
- Bit shifting operations
- Bitwise logical operations
Assignment Operators
- Simple and compound assignment
- Operator shortcuts
- Multiple assignment
Special Operators
- Address and pointer operators
- Channel operations
- Type assertions
Arithmetic Operators
Basic Arithmetic Operations
package main
import "fmt"
func main() {
// Basic arithmetic with integers
var (
a int = 10
b int = 3
)
// Addition
sum := a + b
fmt.Printf("%d + %d = %d\n", a, b, sum)
// Subtraction
difference := a - b
fmt.Printf("%d - %d = %d\n", a, b, difference)
// Multiplication
product := a * b
fmt.Printf("%d * %d = %d\n", a, b, product)
// Division (integer division for integers)
quotient := a / b
fmt.Printf("%d / %d = %d\n", a, b, quotient)
// Modulo (remainder)
remainder := a % b
fmt.Printf("%d %% %d = %d\n", a, b, remainder)
}
Floating-Point Arithmetic
package main
import "fmt"
func main() {
// Arithmetic with floating-point numbers
var (
x float64 = 10.5
y float64 = 3.2
)
fmt.Printf("x = %.2f, y = %.2f\n", x, y)
// All arithmetic operations work with floats
fmt.Printf("Addition: %.2f + %.2f = %.2f\n", x, y, x + y)
fmt.Printf("Subtraction: %.2f - %.2f = %.2f\n", x, y, x - y)
fmt.Printf("Multiplication: %.2f * %.2f = %.2f\n", x, y, x * y)
fmt.Printf("Division: %.2f / %.2f = %.2f\n", x, y, x / y)
// Note: Modulo operator (%) doesn't work with floats
// fmt.Printf("%.2f %% %.2f = %.2f\n", x, y, x % y) // Compile error
}
Increment and Decrement Operators
package main
import "fmt"
func main() {
// Increment and decrement operators
var counter int = 5
fmt.Printf("Initial counter: %d\n", counter)
// Post-increment (returns value, then increments)
result := counter++
fmt.Printf("After counter++: result=%d, counter=%d\n", result, counter)
// Pre-increment (increments, then returns value)
result = ++counter
fmt.Printf("After ++counter: result=%d, counter=%d\n", result, counter)
// Post-decrement
result = counter--
fmt.Printf("After counter--: result=%d, counter=%d\n", result, counter)
// Pre-decrement
result = --counter
fmt.Printf("After --counter: result=%d, counter=%d\n", result, counter)
// Note: Go doesn't have ++ and -- operators as expressions
// These are statements, not expressions like in C/C++
}
Mixed Type Arithmetic
package main
import "fmt"
func main() {
// Mixed type arithmetic requires explicit conversion
var (
intVal int = 10
floatVal float64 = 3.5
int32Val int32 = 5
int64Val int64 = 15
)
// Can't mix different types directly
// result := intVal + floatVal // Compile error
// Explicit conversion required
result1 := float64(intVal) + floatVal
fmt.Printf("int + float: %.2f\n", result1)
result2 := intVal + int(int32Val)
fmt.Printf("int + int32: %d\n", result2)
result3 := int64(intVal) + int64Val
fmt.Printf("int64 + int64: %d\n", result3)
// Type inference with mixed types
mixed := 10 + 3.5 // Result is float64
fmt.Printf("Mixed literal: %.2f (type inferred)\n", mixed)
}
Comparison Operators
Equality and Inequality
package main
import "fmt"
func main() {
// Equality and inequality operators
var (
a int = 10
b int = 10
c int = 5
)
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
// Equality
fmt.Printf("a == b: %t\n", a == b) // true
fmt.Printf("a == c: %t\n", a == c) // false
// Inequality
fmt.Printf("a != b: %t\n", a != b) // false
fmt.Printf("a != c: %t\n", a != c) // true
// String comparison
str1 := "hello"
str2 := "hello"
str3 := "world"
fmt.Printf("'%s' == '%s': %t\n", str1, str2, str1 == str2) // true
fmt.Printf("'%s' == '%s': %t\n", str1, str3, str1 == str3) // false
// Boolean comparison
var (
flag1 bool = true
flag2 bool = true
flag3 bool = false
)
fmt.Printf("flag1 == flag2: %t\n", flag1 == flag2) // true
fmt.Printf("flag1 == flag3: %t\n", flag1 == flag3) // false
}
Relational Operators
package main
import "fmt"
func main() {
// Relational operators
var (
x int = 10
y int = 5
z int = 15
)
fmt.Printf("x = %d, y = %d, z = %d\n", x, y, z)
// Less than
fmt.Printf("x < y: %t\n", x < y) // false
fmt.Printf("y < z: %t\n", y < z) // true
// Less than or equal
fmt.Printf("x <= y: %t\n", x <= y) // false
fmt.Printf("x <= x: %t\n", x <= x) // true
// Greater than
fmt.Printf("x > y: %t\n", x > y) // true
fmt.Printf("y > z: %t\n", y > z) // false
// Greater than or equal
fmt.Printf("x >= y: %t\n", x >= y) // true
fmt.Printf("x >= x: %t\n", x >= x) // true
// String comparison (lexicographic order)
str1 := "apple"
str2 := "banana"
str3 := "apple"
fmt.Printf("'%s' < '%s': %t\n", str1, str2, str1 < str2) // true
fmt.Printf("'%s' > '%s': %t\n", str1, str2, str1 > str2) // false
fmt.Printf("'%s' <= '%s': %t\n", str1, str3, str1 <= str3) // true
}
Type-Specific Comparisons
package main
import (
"fmt"
"math"
)
func main() {
// Floating-point comparison (be careful with precision)
var (
a float64 = 0.1 + 0.2
b float64 = 0.3
)
fmt.Printf("a = %.20f\n", a)
fmt.Printf("b = %.20f\n", b)
fmt.Printf("a == b: %t\n", a == b) // false due to floating-point precision
// Proper floating-point comparison
const epsilon = 1e-9
isEqual := math.Abs(a - b) < epsilon
fmt.Printf("a ≈ b (within epsilon): %t\n", isEqual) // true
// Complex number comparison (only == and != are defined)
var (
c1 complex128 = 3 + 4i
c2 complex128 = 3 + 4i
c3 complex128 = 4 + 3i
)
fmt.Printf("c1 == c2: %t\n", c1 == c2) // true
fmt.Printf("c1 == c3: %t\n", c1 == c3) // false
// fmt.Printf("c1 < c3: %t\n", c1 < c3) // Compile error: no ordering for complex numbers
}
Logical Operators
Boolean Logic Operations
package main
import "fmt"
func main() {
// Logical operators with boolean values
var (
p bool = true
q bool = false
)
fmt.Printf("p = %t, q = %t\n", p, q)
// Logical AND (&&)
fmt.Printf("p && q: %t\n", p && q) // false
fmt.Printf("p && p: %t\n", p && p) // true
fmt.Printf("q && q: %t\n", q && q) // false
// Logical OR (||)
fmt.Printf("p || q: %t\n", p || q) // true
fmt.Printf("p || p: %t\n", p || p) // true
fmt.Printf("q || q: %t\n", q || q) // false
// Logical NOT (!)
fmt.Printf("!p: %t\n", !p) // false
fmt.Printf("!q: %t\n", !q) // true
fmt.Printf("!!p: %t\n", !!p) // true (double negation)
// Complex logical expressions
complex := (p && !q) || (!p && q)
fmt.Printf("(p && !q) || (!p && q): %t\n", complex) // XOR operation
}
Short-Circuit Evaluation
package main
import "fmt"
func main() {
// Short-circuit evaluation demonstration
fmt.Println("Testing short-circuit evaluation:")
// Function that prints and returns a value
printAndReturn := func(name string, value bool) bool {
fmt.Printf(" Evaluating %s\n", name)
return value
}
fmt.Println("\nTesting && (AND) operator:")
result1 := printAndReturn("A", true) && printAndReturn("B", false)
fmt.Printf("Result: %t\n", result1)
fmt.Println("\nTesting && with short-circuit:")
result2 := printAndReturn("C", false) && printAndReturn("D", true)
fmt.Printf("Result: %t\n", result2) // D is not evaluated
fmt.Println("\nTesting || (OR) operator:")
result3 := printAndReturn("E", false) || printAndReturn("F", true)
fmt.Printf("Result: %t\n", result3)
fmt.Println("\nTesting || with short-circuit:")
result4 := printAndReturn("G", true) || printAndReturn("H", false)
fmt.Printf("Result: %t\n", result4) // H is not evaluated
}
Practical Logical Operations
package main
import "fmt"
func main() {
// Practical examples of logical operations
var (
isLoggedIn bool = true
hasPermission bool = false
isAdmin bool = true
isDebugMode bool = false
)
// Access control logic
canAccess := isLoggedIn && (hasPermission || isAdmin)
fmt.Printf("Can access resource: %t\n", canAccess)
// Debug information logic
shouldLog := isDebugMode || (!isLoggedIn)
fmt.Printf("Should log debug info: %t\n", shouldLog)
// Complex business logic
canEdit := isLoggedIn && hasPermission && !isDebugMode
canDelete := isLoggedIn && isAdmin
canView := isLoggedIn
fmt.Printf("Can edit: %t\n", canEdit)
fmt.Printf("Can delete: %t\n", canDelete)
fmt.Printf("Can view: %t\n", canView)
// Validation logic
isValid := (isLoggedIn || isAdmin) && !(!hasPermission && isDebugMode)
fmt.Printf("Is valid operation: %t\n", isValid)
}
Bitwise Operators
Bitwise Logical Operations
package main
import "fmt"
func main() {
// Bitwise operators work on integer types
var (
a uint8 = 0b10101010 // 170 in decimal
b uint8 = 0b11001100 // 204 in decimal
)
fmt.Printf("a = %08b (%d)\n", a, a)
fmt.Printf("b = %08b (%d)\n", b, b)
fmt.Println()
// Bitwise AND (&)
result := a & b
fmt.Printf("a & b = %08b (%d)\n", result, result)
// Bitwise OR (|)
result = a | b
fmt.Printf("a | b = %08b (%d)\n", result, result)
// Bitwise XOR (^)
result = a ^ b
fmt.Printf("a ^ b = %08b (%d)\n", result, result)
// Bitwise NOT (^)
result = ^a
fmt.Printf("^a = %08b (%d)\n", result, result)
// Note: ^ is both XOR and NOT depending on context
// For NOT, it's ^x, for XOR it's x ^ y
}
Bit Shifting Operations
package main
import "fmt"
func main() {
// Bit shifting operations
var value uint8 = 0b00001010 // 10 in decimal
fmt.Printf("Original value: %08b (%d)\n", value, value)
fmt.Println()
// Left shift (`<<`)
leftShift1 := value << 1
leftShift2 := value << 2
leftShift3 := value << 3
fmt.Printf("Left shift 1: %08b (%d)\n", leftShift1, leftShift1)
fmt.Printf("Left shift 2: %08b (%d)\n", leftShift2, leftShift2)
fmt.Printf("Left shift 3: %08b (%d)\n", leftShift3, leftShift3)
fmt.Println()
// Right shift (`>>`)
rightShift1 := value >> 1
rightShift2 := value >> 2
rightShift3 := value >> 3
fmt.Printf("Right shift 1: %08b (%d)\n", rightShift1, rightShift1)
fmt.Printf("Right shift 2: %08b (%d)\n", rightShift2, rightShift2)
fmt.Printf("Right shift 3: %08b (%d)\n", rightShift3, rightShift3)
fmt.Println()
// Practical example: multiplying and dividing by powers of 2
original := 25
multiplied := original << 2 // Multiply by 4 (2^2)
divided := original >> 1 // Divide by 2 (2^1)
fmt.Printf("Original: %d\n", original)
fmt.Printf("Multiplied by 4: %d\n", multiplied)
fmt.Printf("Divided by 2: %d\n", divided)
}
Practical Bitwise Applications
package main
import "fmt"
func main() {
// Practical bitwise operations
// Setting and clearing bits
var flags uint8 = 0
// Set bit 0 (rightmost bit)
flags |= 1 << 0 // Set bit 0
flags |= 1 << 2 // Set bit 2
flags |= 1 << 4 // Set bit 4
fmt.Printf("Flags after setting bits 0, 2, 4: %08b (%d)\n", flags, flags)
// Check if bit 2 is set
if flags&(1<<2) != 0 {
fmt.Println("Bit 2 is set")
}
// Clear bit 2
flags &^= 1 << 2 // Clear bit 2
fmt.Printf("Flags after clearing bit 2: %08b (%d)\n", flags, flags)
// Toggle bit 3
flags ^= 1 << 3 // Toggle bit 3
fmt.Printf("Flags after toggling bit 3: %08b (%d)\n", flags, flags)
// Count set bits (population count)
count := 0
temp := flags
for temp != 0 {
count += int(temp & 1)
temp >>= 1
}
fmt.Printf("Number of set bits: %d\n", count)
}
Assignment Operators
Simple Assignment
package main
import "fmt"
func main() {
// Simple assignment
var x int = 10
fmt.Printf("Initial x: %d\n", x)
// Assignment operator
x = 20
fmt.Printf("After x = 20: %d\n", x)
// Multiple assignment
var a, b int
a, b = 5, 10
fmt.Printf("a = %d, b = %d\n", a, b)
// Swap values using multiple assignment
a, b = b, a
fmt.Printf("After swap: a = %d, b = %d\n", a, b)
// Assignment with type conversion
var y float64 = 3.14
var z int
z = int(y) // Explicit conversion required
fmt.Printf("y = %.2f, z = %d\n", y, z)
}
Compound Assignment Operators
package main
import "fmt"
func main() {
// Compound assignment operators
var x int = 10
fmt.Printf("Initial x: %d\n", x)
// Addition assignment
x += 5 // Equivalent to x = x + 5
fmt.Printf("After x += 5: %d\n", x)
// Subtraction assignment
x -= 3 // Equivalent to x = x - 3
fmt.Printf("After x -= 3: %d\n", x)
// Multiplication assignment
x *= 2 // Equivalent to x = x * 2
fmt.Printf("After x *= 2: %d\n", x)
// Division assignment
x /= 4 // Equivalent to x = x / 4
fmt.Printf("After x /= 4: %d\n", x)
// Modulo assignment
x %= 3 // Equivalent to x = x % 3
fmt.Printf("After x %%= 3: %d\n", x)
// Bitwise compound assignments
var flags uint8 = 0b00001010
fmt.Printf("Initial flags: %08b\n", flags)
flags |= 0b00000001 // Set bit 0
fmt.Printf("After |= 0b00000001: %08b\n", flags)
flags &^= 0b00001000 // Clear bit 3
fmt.Printf("After &^= 0b00001000: %08b\n", flags)
flags ^= 0b00000100 // Toggle bit 2
fmt.Printf("After ^= 0b00000100: %08b\n", flags)
// Bit shifting compound assignments
flags <<= 1 // Left shift by 1
fmt.Printf("After <<= 1: %08b\n", flags)
flags >>= 2 // Right shift by 2
fmt.Printf("After >>= 2: %08b\n", flags)
}
Special Operators
Address and Pointer Operators
package main
import "fmt"
func main() {
// Address operator (&)
var x int = 42
var y float64 = 3.14
// Get memory addresses
ptrX := &x // Address of x
ptrY := &y // Address of y
fmt.Printf("Value of x: %d\n", x)
fmt.Printf("Address of x: %p\n", ptrX)
fmt.Printf("Value of y: %.2f\n", y)
fmt.Printf("Address of y: %p\n", ptrY)
// Dereference operator (*)
fmt.Printf("Value at ptrX: %d\n", *ptrX)
fmt.Printf("Value at ptrY: %.2f\n", *ptrY)
// Modify value through pointer
*ptrX = 100
*ptrY = 2.71
fmt.Printf("Modified x through pointer: %d\n", x)
fmt.Printf("Modified y through pointer: %.2f\n", y)
}
Channel Operators
package main
import "fmt"
func main() {
// Channel operators (basic introduction)
ch := make(chan int, 1) // Create buffered channel
// Send operator (<-)
ch <- 42 // Send value to channel
fmt.Println("Sent value to channel")
// Receive operator (<-)
value := <-ch // Receive value from channel
fmt.Printf("Received value: %d\n", value)
// Close operator
close(ch)
fmt.Println("Channel closed")
}
Operator Precedence and Associativity
Understanding Precedence
package main
import "fmt"
func main() {
// Operator precedence demonstration
var (
a int = 2
b int = 3
c int = 4
)
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
// Multiplication has higher precedence than addition
result1 := a + b * c // Equivalent to a + (b * c)
result2 := (a + b) * c // Explicit parentheses
fmt.Printf("a + b * c = %d\n", result1) // 2 + (3 * 4) = 14
fmt.Printf("(a + b) * c = %d\n", result2) // (2 + 3) * 4 = 20
// Logical operators precedence
var (
p bool = true
q bool = false
r bool = true
)
// && has higher precedence than ||
logical1 := p || q && r // Equivalent to p || (q && r)
logical2 := (p || q) && r // Explicit parentheses
fmt.Printf("p || q && r = %t\n", logical1) // true || (false && true) = true
fmt.Printf("(p || q) && r = %t\n", logical2) // (true || false) && true = true
// Bitwise operators precedence
var (
x uint8 = 0b10101010
y uint8 = 0b11001100
z uint8 = 0b11110000
)
bitwise1 := x & y | z // Equivalent to (x & y) | z
bitwise2 := x & (y | z) // Explicit parentheses
fmt.Printf("x & y | z = %08b\n", bitwise1)
fmt.Printf("x & (y | z) = %08b\n", bitwise2)
}
Operator Precedence Table
package main
import "fmt"
func main() {
// Go operator precedence (highest to lowest)
// 1. Postfix operators: [] () . ->
// 2. Unary operators: ++ -- + - ! ^ * & <- (channel)
// 3. Binary operators:
// * / % << >> & &^
// + - | ^
// == != < <= > >=
// &&
// ||
// Practical example showing precedence
var (
a int = 10
b int = 3
c int = 2
)
// Complex expression demonstrating precedence
result := a + b * c - a / b % c
// Equivalent to: a + (b * c) - ((a / b) % c)
// Step by step:
// 1. b * c = 3 * 2 = 6
// 2. a / b = 10 / 3 = 3
// 3. (a / b) % c = 3 % 2 = 1
// 4. a + (b * c) = 10 + 6 = 16
// 5. 16 - 1 = 15
fmt.Printf("a + b * c - a / b %% c = %d\n", result)
// Use parentheses for clarity
clearResult := a + (b * c) - ((a / b) % c)
fmt.Printf("Clear version: %d\n", clearResult)
}
Best Practices for Using Operators
Writing Clear Expressions
package main
import "fmt"
func main() {
// Best practices for writing clear expressions
// 1. Use parentheses for clarity, even when not required
var (
a int = 10
b int = 3
c int = 2
)
// Unclear (relies on precedence)
unclear := a + b * c / b - a % c
// Clear (explicit precedence)
clear := a + (b * c / b) - (a % c)
fmt.Printf("Unclear: %d\n", unclear)
fmt.Printf("Clear: %d\n", clear)
// 2. Break complex expressions into multiple statements
// Complex (hard to read)
complex := (a + b) * (c - a) / (b + c) + (a % b)
// Simple (easy to read)
temp1 := a + b
temp2 := c - a
temp3 := b + c
temp4 := a % b
simple := (temp1 * temp2 / temp3) + temp4
fmt.Printf("Complex: %d\n", complex)
fmt.Printf("Simple: %d\n", simple)
// 3. Use meaningful variable names
var (
baseSalary int = 50000
bonus int = 5000
taxRate float64 = 0.25
overtimeHours int = 10
hourlyRate int = 25
)
// Clear calculation
grossPay := baseSalary + bonus + (overtimeHours * hourlyRate)
taxAmount := float64(grossPay) * taxRate
netPay := grossPay - int(taxAmount)
fmt.Printf("Gross Pay: $%d\n", grossPay)
fmt.Printf("Tax Amount: $%.2f\n", taxAmount)
fmt.Printf("Net Pay: $%d\n", netPay)
}
Avoiding Common Pitfalls
package main
import "fmt"
func main() {
// Common pitfalls and how to avoid them
// 1. Floating-point precision issues
var (
price1 float64 = 0.1
price2 float64 = 0.2
total float64 = price1 + price2
)
fmt.Printf("price1: %.20f\n", price1)
fmt.Printf("price2: %.20f\n", price2)
fmt.Printf("total: %.20f\n", total)
fmt.Printf("total == 0.3: %t\n", total == 0.3) // false!
// Solution: Use epsilon for comparison
const epsilon = 1e-9
isEqual := (total - 0.3) < epsilon
fmt.Printf("total ≈ 0.3: %t\n", isEqual)
// 2. Integer division truncation
var (
dividend int = 7
divisor int = 3
)
quotient := dividend / divisor
remainder := dividend % divisor
fmt.Printf("%d / %d = %d (truncated)\n", dividend, divisor, quotient)
fmt.Printf("%d %% %d = %d\n", dividend, divisor, remainder)
// Solution: Use float division when needed
floatQuotient := float64(dividend) / float64(divisor)
fmt.Printf("%d / %d = %.2f (precise)\n", dividend, divisor, floatQuotient)
// 3. Overflow in arithmetic operations
var (
maxInt8 int8 = 127
minInt8 int8 = -128
)
fmt.Printf("maxInt8: %d\n", maxInt8)
fmt.Printf("minInt8: %d\n", minInt8)
// Overflow example (uncomment to see overflow)
// overflow := maxInt8 + 1 // This would cause overflow
// fmt.Printf("maxInt8 + 1: %d\n", overflow)
// Solution: Check for overflow or use larger types
var safeResult int16 = int16(maxInt8) + 1
fmt.Printf("Safe calculation: %d\n", safeResult)
}
What You've Learned
Congratulations! You now have a comprehensive understanding of Go's operators:
Arithmetic Operators
- Basic arithmetic operations (+, -, *, /, %)
- Increment and decrement operations
- Mixed type arithmetic and conversions
- Floating-point arithmetic considerations
Comparison Operators
- Equality and inequality comparisons
- Relational operators for ordering
- Type-specific comparison behaviors
- Floating-point comparison best practices
Logical Operators
- Boolean logic operations (&&, ||, !)
- Short-circuit evaluation behavior
- Complex logical expressions
- Practical boolean logic applications
Bitwise Operators
- Bit-level manipulation (&, |, ^, ^)
- Bit shifting operations (
<<
,>>
) - Practical bitwise applications
- Flag manipulation techniques
Assignment Operators
- Simple and compound assignment
- Multiple assignment patterns
- Operator shortcuts and efficiency
- Type conversion in assignments
Special Operators
- Address and pointer operators
- Channel communication operators
- Operator precedence and associativity
- Best practices for clear expressions
Next Steps
You now have a solid foundation in Go's operator system. In the next section, we'll explore type conversion in Go, learning how to safely convert between different types and understanding when explicit conversion is required.
Understanding operators is crucial for writing effective expressions and implementing business logic. These operators form the foundation for all the more complex operations and algorithms we'll cover in the coming chapters.
Ready to learn about type conversion? Let's explore how to safely convert between different types in Go in the next section!