Golang 数据类型和运算符

一、变量

1. 声明

同一作用域内不支持重复声明,且 Go 语言的变量声明后必须使用。

(1)单个或批量声明

// 单变量声明
var 变量名 类型

// 批量声明
var (
    变量名 类型
    变量名 类型
)

(2)短变量声明

  • 仅能在函数内部使用,语法:变量名 := 表达式

(3)匿名变量

  • 用下划线(_)表示,用于忽略不需要的返回值等场景。

2. 初始化

Go 会自动对变量对应的内存区域进行初始化,每个变量会被初始化为其类型的默认值。

(1)初始化语法

// 类型可省略,编译器会自动推导,可一次初始化多个变量
var 变量名 类型 = 表达式

(2)常用类型的默认值

  • 整型、浮点型:0
  • 字符串:“”
  • bool:false
  • 切片、函数、指针:nil

3. 示例

package main

import "fmt"

func getNameAndAge() (string, int) {
    return "abcd", 30
}

func main() {
    // 1. 批量声明
    var (
        name1 string
        age1  int
        isOk1 bool
    )
    fmt.Printf("name1: %s, age1: %d, isOk1: %t\n", name1, age1, isOk1)

    // 2. 变量初始化
    var name2 = "yingzai"
    var site2 = "www.baidu.com"
    var age2 = 30
    fmt.Printf("name2: %s, site2: %s, age2: %d\n", name2, site2, age2)

    // 3. 初始化多个变量(逗号分隔)
    var name3, site3, age3 = "yingzai", "www.baidu.com", 30
    fmt.Printf("name3: %s, site3: %s, age3: %d\n", name3, site3, age3)

    // 4. 短变量声明(函数内部,用 := 声明和初始化)
    name4 := "yingzai"
    site4 := "www.baidu.com"
    age4 := 30
    fmt.Printf("name4: %s, site4: %s, age4: %d\n", name4, site4, age4)

    // 5. 匿名变量(用不到的变量用 _ 表示)
    name5, _ := getNameAndAge()
    fmt.Printf("name: %s\n", name5)
}

二、常量

1. 普通常量

(1)语法

const constantName [type] = value
  • 同时声明多个常量时,若省略值则表示与上一行值相同
  • 可使用下划线(_)跳过某些值

(2)示例

package main

import "fmt"

func main() {
    // 1. 常量声明
    const PI1 float64 = 3.14
    const PI2 = 3.1415 // 可省略类型

    const i, j = 1, 2 // 多重赋值
    const a, b, c = 1, 2, "foo"

    const (
        width  = 100
        height = 200
        a1     = 100
        a2     // 与上一行值相同(100)
        a3     // 与上一行值相同(100)
    )
    fmt.Printf("a1: %v, a2: %v, a3: %v\n", a1, a2, a3) // 100 100 100
}

2. iota

iota 是可被编译器修改的常量,代表 const 声明块的行索引(从 0 开始)。

(1)取值规则

  • 单个 const 声明块中,iota 从 0 开始,每增加一行声明,值 +1
  • 遇到 const 关键字时重置为 0
  • 单行声明中多个 iota 取值相同

用法和常量类似
- 如果为常量指定了一个表达式,但后续的常量没有表达式,则继承上面的表达式
- iota 还可以声明中间插队
- 可以使用下划线(_)跳过某些值

(2)示例

package main

import "fmt"

func main() {
    const (
        a5 = iota // 0
        a6 = iota // 1
        a7 = iota // 2
    )
    fmt.Printf("a5: %v, a6: %v, a7: %v\n", a5, a6, a7) // 0 1 2

    const (
        a8 = iota
        _  // 跳过值(1)
        a9 = iota // 2
    )
    fmt.Printf("a8: %v, a9: %v\n", a8, a9) // 0 2

    const (
        a10 = iota // 0
        a11 = 100  // 插队赋值
        a12 = iota // 2(继续按行索引计数)
    )
    fmt.Printf("a10: %v, a11: %v, a12: %v\n", a10, a11, a12) // 0 100 2

    // 实际应用示例
    type Priority int
    const (
        LOG_EMERG   Priority = iota // 0
        LOG_ALERT                   // 1
        LOG_CRIT                    // 2
        LOG_ERR                     // 3
        LOG_WARNING                 // 4
        LOG_NOTICE                  // 5
        LOG_INFO                    // 6
        LOG_DEBUG                   // 7
    )

    const (
        mutexLocked            = 1 << iota // 1(1<<0)
        mutexWoken                         // 2(1<<1)
        mutexStarving                      // 4(1<<2)
        mutexWaiterShift       = iota      // 3(行索引)
        startvationThresholdNs = 1e6       // 1000000
    )
  	const (
  		bit0, mask  = 1 << iota, 1<<iota - 1 // 1, 0
  		bit1, mask1                          // 2, 1
  		_, _                                 // 4, 3
  		bit3, mask3                          // 8, 7
  	)

三、数据类型

数据类型用于区分数据所需的内存大小,提高内存利用率。

1. 布尔类型

  • 取值:truefalse
  • 注意:不能用 0 或非 0 表示真假

2. 数值类型

(1)整型

  • 大小与操作系统相关:uintint(32 位系统占 4 字节,64 位系统占 8 字节)
  • 大小固定(与系统无关):
    • 无符号:uint8uint16uint32uint64
    • 有符号:int8int16int32int64
  • 特殊类型:uintptr(无符号整型,用于存放指针)

(2)浮点型

  • float32:IEEE-754 32 位浮点型,最大范围常量 math.MaxFloat32
  • float64:IEEE-754 64 位浮点型,最大范围常量 math.MaxFloat64

(3)复数

  • complex64:32 位实数和虚数
  • complex128:64 位实数和虚数

(4)其他

  • byte:类似 uint8(用于表示 ASCII 字符)
  • rune:类似 int32(用于表示 UTF-8 字符,如中文、日文等)

3. 字符串类型

使用 UTF-8 编码,是任意字节的常量序列。

(1)声明

func test01() {
    var s1 string
    s1 = "hello world"
    s2 := "yingzai"
    s3 := s1 + s2 // 拼接
    fmt.Printf("s1: %s, s2: %s, s3: %s\n", s1, s2, s3)
}

(2)字符串字面量

  • 双引号("):可解析字符串,支持转义,不可多行(如 "hello\nworld"
  • 反引号(`):原生字符串,不支持转义,可多行(如 `hello\nworld` 输出 hello\nworld

(3)字符串拼接

  • 加号(+):s3 := s1 + s2;追加(+=):s1 += s2(性能较差,会产生临时字符串)
  • fmt.Sprintf():内部用 []byte 实现,性能一般
  • strings.Join(a []string, sep string):先计算长度再申请内存,数组场景下效率高(sep 为分隔符)
  • buffer.WriteString():可变字符操作,可预估长度用 buffer.Grow() 优化性能

(4)字符串转义(\

常用转义符:\r(回车)、\n(换行)、\t(tab)、\'(单引号)、\"(双引号)、\\(反斜杠)

(5)字符串切片(截取)

  • str[n]:获取索引 n 的原始字节
  • str[n:m]:获取索引 n 到 m-1 的字符
  • str[n:m:max]:获取索引 n 到 m-1 的字符, 截取 max 个长度
  • str[n:]:获取索引 n 到末尾的字符
  • str[:m]:获取索引 0 到 m-1 的字符

(6)常用函数

  • 长度:len(str)
  • 切割:strings.Split
  • 包含判断:strings.Contains
  • 前后缀判断:strings.HasPrefix()strings.HasSuffix()
  • 子串位置:strings.Index()strings.LastIndex()

(7)注意事项

  • 空字符串长度为 0,但不是 nil

  • 字符串与 []byte 转换会发生内存拷贝(有性能开销):

    func test02() {
        b1 := []byte{'h', 'e', 'l', 'l', 'o'}
        s1 := string(b1) // []byte 转 string
        s2 := "hello world"
        b2 := []byte(s2) // string 转 []byte
        fmt.Printf("s1: %s, b1: %v, s2: %s, b2: %v\n", s1, b1, s2, b2)
    }
    
  • 在使用 for-range 遍历字符串时,每次迭代返回的是 UTF-8 编码的首个字节的下标和字节值,这意味着下标可能不连续:

    func test03() {
        s := "中国"
        for i, v := range s {
            fmt.Printf("index: %d, value: %c\n", i, v) 
            // index: 0, value: 中;index: 3, value: 国
        }
    }
    
  • 字符串不可通过下标修改(可重新赋值):

    func test04() {
        s := "hello"
        // &s[0] = 'a' // 编译错误
        s = "world" // 合法
        fmt.Printf("s: %s\n", s)
    }
    

4. 派生类型

  • 指针类型(Pointer)
  • 数组类型(Array)
  • 结构化类型(Struct)
  • 通道类型(Channel)
  • 函数类型(func)
  • 切片类型(Slice)
  • 接口类型(Interface)
  • 哈希表(Map)

5. 指针:uintptr 和 unsafe.Pointer

(1)转换规则

  • 任何类型指针 ↔ unsafe.Pointer
  • uintptrunsafe.Pointer

(2)区别

类型 本质 用途 限制
unsafe.Pointer 通用无类型指针(类似 C 的 void* 指针类型转换、与 uintptr 配合操作内存 不支持算术运算
uintptr 无符号整数(存储指针地址) 指针算术运算、系统调用交互 非指针类型,不能直接解引用

6. 示例

package main

import (
	"fmt"
	"math"
	"unsafe"
)

// bool 类型
func test01() {
	// 1.bool 类型
	var b1 bool = true
	var b2 bool = false

	var b3 = true
	var b4 = false

	b5 := true
	b6 := false

	fmt.Printf("b1: %v, b2: %v, b3: %v, b4: %v, b5: %v, b6: %v\n",
		b1, b2, b3, b4, b5, b6)

	// 2.条件判断中
	age := 18
	ok := age >= 18
	if ok {
		fmt.Printf("ok: true") // ok: true
	} else {
		fmt.Printf("ok: false")
	}
	fmt.Printf("\n")

	// 3.循环语句中
	count := 10
	for i := 0; i < count; i++ {
		fmt.Printf("%v ", i)
	}
	fmt.Printf("\n")

	// 4.逻辑表达式中
	age4 := 20
	gender := "man"
	if age4 >= 18 && gender == "man" {
		fmt.Printf("gender: %v, age4: %v\n", gender, age4)
	}

	// 5.注意:不能使用 0 和 非0 表示真假
	/*
		i := 1
		if i {
			// Error! 编译错误
		}
	*/
}

// 数值类型
func test02() {
	// 1.整型
	var i8 int8
	var i16 int16
	var i32 int32
	var i64 int64
	fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(i8), math.MinInt8, math.MaxInt8)     // int8 1B -128~127
	fmt.Printf("%T %dB %v~%v\n", i16, unsafe.Sizeof(i16), math.MinInt16, math.MaxInt16) // int16 2B -32768~32767
	fmt.Printf("%T %dB %v~%v\n", i32, unsafe.Sizeof(i32), math.MinInt32, math.MaxInt32) // int32 4B -2147483648~2147483647
	fmt.Printf("%T %dB %v~%v\n", i64, unsafe.Sizeof(i64), math.MinInt64, math.MaxInt64) // int64 8B -9223372036854775808~9223372036854775807

	var ui8 uint8
	var ui16 uint16
	var ui32 uint32
	var ui64 uint64
	fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(ui8), 0, math.MaxUint8)             // int8 1B 0~255
	fmt.Printf("%T %dB %v~%v\n", ui16, unsafe.Sizeof(ui16), 0, math.MaxUint16)         // uint16 2B 0~65535
	fmt.Printf("%T %dB %v~%v\n", ui32, unsafe.Sizeof(ui32), 0, math.MaxUint32)         // uint32 4B 0~4294967295
	fmt.Printf("%T %dB %v~%v\n", ui64, unsafe.Sizeof(ui64), 0, uint64(math.MaxUint64)) // uint64 8B 0~18446744073709551615

	// 2.浮点型
	var f32 float32
	var f64 float64
	fmt.Printf("%T %dB %v~%v\n", f32, unsafe.Sizeof(f32), -math.MaxFloat32, math.MaxFloat32) // float32 4B -3.4028234663852886e+38~3.4028234663852886e+38
	fmt.Printf("%T %dB %v~%v\n", f64, unsafe.Sizeof(f64), -math.MaxFloat64, math.MaxFloat64) // float64 8B -1.7976931348623157e+308~1.7976931348623157e+308

	// 越界
	var ui uint
	ui = uint(math.MaxUint64)                                  // +1 会导致 overfloat 错误
	fmt.Printf("%T %dB %v~%v\n", ui, unsafe.Sizeof(ui), 0, ui) // uint 8B 0~18446744073709551615

	var imax, imin int
	imax = int(math.MaxInt64)                                           // +1 会导致 overflows 错误
	imin = int(math.MinInt64)                                           // -1 会导致 overflows 错误
	fmt.Printf("%T %dB %v~%v\n", imax, unsafe.Sizeof(imax), imin, imax) // int 8B -9223372036854775808~9223372036854775807

	// 按进制输出
	var a int = 10
	fmt.Printf("%d \n", a) // 10
	fmt.Printf("%b \n", a) // 1010 二进制
	fmt.Printf("%o \n", a) // 12 八进制
	fmt.Printf("%x \n", a) // a 十六进制
	fmt.Printf("%X \n", a) // A 十六进制

	// 浮点型输出
	fmt.Printf("%f\n", math.Pi)   // 3.141593
	fmt.Printf("%.2f\n", math.Pi) // 3.14

	// 复数
	var c1 complex64
	var c2 complex128
	c1 = 1 + 2i
	c2 = 2 + 3i
	fmt.Println(c1) // (1+2i)
	fmt.Println(c2) // (2+3i)

}

func main() {
	//test01()
	test02()
}

四、类型定义和类型别名(type)

用于定义自定义类型和函数类型。

1. 语法

// 类型定义(全新类型)
type NewType Type

// 类型别名(原类型的别名)
type NewType = Type

2. 区别

  • 类型定义:创建全新类型,与原类型不兼容(需显式转换)
  • 类型别名:仅为原类型的别名,编译后不存在,与原类型兼容(可直接使用原类型方法)
  • 建议:优先使用类型别名,减少显式转换

五、运算符

1. 算术运算符

  • +-*/%(取余)
  • ++(自增)、--(自减):为单独语句,非运算符

2. 比较运算符

  • ><>=<===!=

3. 逻辑运算符

  • &&(与)、||(或)、!(非)

4. 位运算符

  • &(按位与)、|(按位或)、^(按位异或)
  • <<(左移)、>>(右移)

5. 赋值运算符

  • =+=-=*=/=%=&=|=^=<<=>>=

6. 示例

package main

import "fmt"

func main() {
	a := 100
	b := 10

	// 1.算术运算符
	fmt.Printf("a + b = %v\n", a+b)
	fmt.Printf("a - b = %v\n", a-b)
	fmt.Printf("a * b = %v\n", a*b)
	fmt.Printf("a / b = %v\n", a/b)

	a++
	fmt.Printf("a++ = %v\n", a) // 101
	a--
	fmt.Printf("a-- = %v\n", a) // 100
	fmt.Println("-----------------")

	// 2.比较运算符
	fmt.Printf("a b: %t\n", a b)
	fmt.Printf("a >= b: %t\n", a >= b)
	fmt.Printf("a < b: %t\n", a < b)
	fmt.Printf("a <= b: %t\n", a <= b)
	fmt.Printf("a == b: %t\n", a == b)
	fmt.Printf("a != b: %t\n", a != b)
	fmt.Println("-----------------")

	// 3.逻辑运算符
	c := true
	d := false
	fmt.Printf("c && d: %t\n", c && d)
	fmt.Printf("c || d: %t\n", c || d)
	fmt.Printf("!c: %t\n", !c)
	fmt.Println("-----------------")

	// 4.位运算符
	e := 7                              // 111
	f := 10                             // 1010
	fmt.Printf("e & f = %v\n", (e & f)) // 2
	fmt.Printf("e | f = %v\n", e|f)     // 15
	fmt.Printf("e ^ f = %v\n", e^f)     // 13
	fmt.Printf("f << 1 = %v\n", f<<1)   // 20
	fmt.Printf("f >1 = %v\n", f>>1)   // 5
	fmt.Println("-----------------")

	// 5.赋值运算符
	var g int
	g = 10
	fmt.Printf("g = %v\n", g)
	g += 1
	fmt.Printf("g += 1 = %v\n", g)
	g -= 1
	fmt.Printf("g -= 1 = %v\n", g)
	g *= 2
	fmt.Printf("g *= 2 = %v\n", g)
	g /= 2
	fmt.Printf("g /= 2 = %v\n", g)
	fmt.Println("-----------------")
}

六、格式化输出

1. 常用占位符

image-1766156968646

2. %v、%+v、%#v 的区别

  • %v:仅输出值
  • %+v:输出字段名 + 值(结构体)
  • %#v:输出结构体名 + 字段名 + 值(结构体)

3. 示例

package main

import "fmt"

// 结构体
type Website struct {
	Name string
}

// 定义结构体变量
var site = Website{Name: "www.baidu.com"}

type user struct {
	name string
}

func main() {
	u := user{"yingzai"}
	fmt.Printf("%+v\n", u) // {name:yingzai}
	fmt.Printf("%#v\n", u) // main.user{name:"yingzai"}
	fmt.Printf("%T\n", u)  // main.user
	fmt.Println("---------------------------")

	fmt.Printf("%t\n", true)               // true
	fmt.Printf("%b\n", 1024)               // 10000000000
	fmt.Printf("%c\n", 11111)              // ⭧
	fmt.Printf("%d, %o, %x\n", 10, 10, 10) // 10, 12, a
	fmt.Printf("%q\n", 10)                 // '\n'
	fmt.Printf("%U\n", 1233)               // U+04D1
	fmt.Printf("%b\n", 12.34)              // 6946802425218990p-49
	fmt.Printf("%e\n", 12.34)              // 1.234000e+01
	fmt.Printf("%f\n", 12.345)             // 12.345000
	fmt.Printf("%g\n", 12.345)             // 12.345
	fmt.Println("---------------------------")

	fmt.Printf("%s\n", "abcde") // abcde
	fmt.Printf("%q\n", "abcde") // "abcde"
	fmt.Printf("%x\n", "abc")   // 616263
	fmt.Printf("%p\n", 0x123)   // %!p(int=291)

}