Skip to content

定义变量

go
package main

import "fmt"

func main() {
	// 1.先定义 后赋值
	var num1 int
	num1 = 11
	fmt.Println("num1 value is: ", num1)

	// 2.定义并赋值
	var num2 int = 12
	fmt.Println("num2 value is: ", num2)

	// 3.自动推导类型并赋值(推荐)
	num3 := 13
	fmt.Println("num3 value is: ", num3)

	// 4.同时定义多个变量和值
	name, age, isMale := "张三", 11, false
	fmt.Println("name:", name)
	fmt.Println("age:", age)
	fmt.Println("isMale:", isMale)

	// 也可以这样一次定义多个变量和值
	var (
		name2   = "李四"
		age2    = 12
		isMale2 = true
	)
	fmt.Println("name2:", name2)
	fmt.Println("age2:", age2)
	fmt.Println("isMale2:", isMale2)
}

数据类型

强烈推荐先阅读 这个笔记,这样才知道为什么需要有这么多不同的数据类型,为什么这些数据类型取值范围是这些数字

go-data-type

基本数据类型

[提示]

在 C 语言中, 无符号整数类型是这样定义的 unsigned int n = 10

所以,在 Go 语言中无符号整数被简写成了 uint

整型

长度有符号类型无符号类型
8 位int8 (-128 ~ 127)uint8 (0 ~ 255)
16 位int16 (-32768 ~ 32767)uint16 (0 ~ 65535)
32 位int32 (-2147483648 ~ 2147483647)uint32 (0 ~ 4294967295)
64 位int64 (-9223372036854775808 ~ 9223372036854775807)uint64 (0 ~ 18446744073709551615)

特殊的整型类型

类型是否有符号占用存储空间取值范围
int有符号32位系统: 4字节, 64位系统: 8字节32位: -2147483648~2147483647, 64位: -9223372036854775808~9223372036854775807
uint无符号32位系统: 4字节, 64位系统: 8字节32位: 0~4294967295, 64位: 0~18446744073709551615
uintptr无符号与指针大小相同与指针大小相同
rune有符号4字节与int32相同 (-2147483648 ~ 2147483647)
byte无符号1字节与uint8相同 (0 ~ 255)

由上表可知, int 类型是根据不同位数的操作系统的值而定的, 在32位操作系统上可以看作 int32 类型, 在64为操作系统上可以看作 int64

[如果使用类型自动推导,整型默认会是哪个具体类型呢?]

答案是 int 类型, 因为 int 类型明显更适合将代码编译到不同位数的操作系统然后运行

go
package main

// 导入多个包可以这写
import (
	"fmt"
	"unsafe"
)

func main() {
	// 定义一个变量, 类型让编译器自动推导
	n := 1

	// Printf %d 占位符可以获取变量的类型字符串
	// 会输出: n type is int
	fmt.Printf("n type is: %T", n)

	// 换行
	fmt.Println()

	// 使用 unsafe.sizeOf 可以获取一个变量的占用直接数
	fmt.Printf("n size is: %d", unsafe.Sizeof(n))

	// 强行赋值一个超出取值范围的数字就会报错
	// 64位操作系统最大值: 9223372036854775807
    // 硬是要赋一个超出取值范围的值就会报错
	n = 9223372036854775808
	// 编译就会报错:
	// # command-line-arguments
	// ./main.go:26:6: cannot use 9223372036854775808 (untyped int constant) as int value in assignment (overflows)
}

浮点型

长度类型取值范围/精度
32 位float32±3.4e38,约7位小数精度
64 位float64±1.8e308,约15位小数精度

[提示]

在Go中,如果没有指定类型,浮点字面量默认为float64类型

字符类型

在 Go 语言中没有 char 这个类型,而是使用:

  • byte:uint8 的别名,适合存储 ASCII 字符
  • rune:int32 的别名,适合存储 Unicode 字符
go
var b byte = 'A'  // ASCII字符
var r rune = '' // Unicode字符

字符串类型

字符类型只能存储单个字符, 要存储多个字符, 就必须使用 string 类型

  • 字符串的本质就是一个只读的 rune 类型的切片
go
package main

import "fmt"

func main() {
	// 定义字符串类型的变量
	str := "hello"
	fmt.Printf("str type: %T, value:%v \n", str, str)
	// 输出: str type: string, value:hello

	var str2 string = "world"
	fmt.Printf("str2 type: %T, value:%v \n", str2, str2)
	// 输出: str2 type: string, value:world
}

布尔类型

布尔类型(bool)只有两个取值:truefalse, 用于条件判断和流程控制

go
isGreater := 10 > 5  // isGreater 为 true

复杂数据类型

Go 语言的复杂数据类型包括:

  • 数组(Array): 固定长度的有序元素序列
  • 切片(Slice): 动态长度的数组视图
  • 映射(Map): 键值对集合, 类似字典(python dict)或哈希表(js object)
  • 结构体(Struct): 自定义复合类型, 组合多个字段, 和 C 语言类似
  • 指针(Pointer): 存储变量内存地址的类型
  • 函数(Function): 可作为值传递的代码块
  • 接口(Interface): 定义行为契约的抽象类型
  • 通道(Channel): 用于 Goroutine 间通信的管道

这些复杂数据类型将在后续章节中详细介绍

基本数据类型的默认值

在定义变量的时候, 可以使用 var aaa type 然后再 aaa = 111 这样两个步骤

那么假设我现在不给他赋值, 而直接去使用, 那它的值是多少呢?

go
package main

import "fmt"

func main() {
	var i8 int8
	var u8 uint8
	var i16 int16
	var u16 uint16
	var i32 int32
	var u32 uint32
	var f32 float32
	var f64 float64

	// 默认值全都是 0
	fmt.Printf("%T default value is %v\n", i8, i8)
	fmt.Printf("%T default value is %v\n", u8, u8)
	fmt.Printf("%T default value is %v\n", i16, i16)
	fmt.Printf("%T default value is %v\n", u16, u16)
	fmt.Printf("%T default value is %v\n", i32, i32)
	fmt.Printf("%T default value is %v\n", u32, u32)
	fmt.Printf("%T default value is %v\n", f32, f32)
	fmt.Printf("%T default value is %v\n", f64, f64)

	// 默认值是false
	var flg bool
	fmt.Printf("%T default value is %v\n", flg, flg) // false
}

强制类型转换

数字相关类型转换

go
package main

import (
	"fmt"
)

func main() {
	// 小 -> 大: 是没有问题的
	var x1 int8 = 111
	var x2 int16 = int16(x1)
	fmt.Printf("x1=%d, x2=%d \n", x1, x2)

	// 大 -> 小: 编译不会报错, 但是会出现数值溢出现象
	var n1 int16 = 333
	var n2 int8 = int8(n1)
	fmt.Printf("n1=%d, n2=%d \n", n1, n2)
	// 输出: n1=%d, n2=%d 333 77
}

其他基本类型转字符串

go
package main

import (
	"fmt"
)

func main() {
	n := 10
	f := 3.14
	b := false
	c := 'a'

	str1 := fmt.Sprintf("%d", n)
	fmt.Printf("str1 is %T type, str1 value is %s", str1, str1)
	fmt.Println()
	// 输出: str1 is string type, str1 value is 10

	str2 := fmt.Sprintf("%f", f)
	fmt.Printf("str2 is %T type, str2 value is %s", str2, str2)
	fmt.Println()
	// 输出: str2 is string type, str2 value is 3.140000

	str3 := fmt.Sprintf("%t", b)
	fmt.Printf("str3 is %T type, str3 value is %s", str3, str3)
	fmt.Println()
	// 输出: str3 is string type, str3 value is false

	str4 := fmt.Sprintf("%c", c)
	fmt.Printf("str4 is %T type, str4 value is %s", str4, str4)
	fmt.Println()
	// 输出: str4 is string type, str4 value is a
}

字符串转其他基本类型

go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// str -> int
	// strconv.ParseInt 这个函数返回2个值, 先不用管第二个值是什么
	s1 := "11"
	v1, _ := strconv.ParseInt(s1, 10, 32)
	fmt.Printf("v1: type=%T, value=%d \n", v1, v1)
	// 输出: v1: type=int64, value=11

	// str -> float
	s2 := "11.22"
	v2, _ := strconv.ParseFloat(s2, 32)
	fmt.Printf("v2: type=%T, value=%f \n", v2, v2)
	// 输出: v2: type=float64, value=11.220000

	// str -> byte
	s3 := "false"
	v3, _ := strconv.ParseBool(s3)
	fmt.Printf("v3: type=%T, value=%t \n", v3, v3)
}

指针类型

  • 指针变量只能存储内存地址
  • 指针变量的类型必须匹配内存地址对应空间存储的数据的值
  • 指针变量可以解引用(直接通过保存的内存地址去修改对应内存空间的值)
  • 基本数据类型(值类型), 都有对应的指针类型(如: int 和 *int, float32 和 *float32)
go
package main

import (
	"fmt"
)

func main() {
	n := 10
	// 与C语言中一样: & 是取地址符号
	fmt.Println("获取 n 的内存地址:", &n)

	// 取地址符号后面加上变量名就可以获取到变量的地址
	// 这个地址就是一种特殊的变量类型, 指针类型
	// 可以自动推断简写为 nptr := &n
	// nptr: 变量名
	// *int: 指针类型
	// &n  : 取普通变量 n 的内存地址并赋值给 nptr 变量
	var nptr *int = &n
	fmt.Println("获取 nptr 的内存地址:", nptr)

	// 根据 nptr 指针变量的存储的内存地址获取内存中的值
	// 使用 *nptr 就可以获取指针变量内存地址中的值
	// 这个操作就叫做: 解引用
	fmt.Println("nptr 指针变量对应内存地址中存储的值:", *nptr)

	// 可以通过指针变量保存的地址来修改对应内存中的值
	*nptr = 20
  fmt.Println("再次通过 n 来访问对应内存空间的值:", n)
}

类型别名与自定义类型

  • 使用 type 关键字, 可以定义类型别名
  • 注意: 虽然是类型别名, 但是编译器会认为是不同的类型, 需要手动转换
  • 类型别名: 仅仅是给复杂的类型取个别名,让代码看起来更简洁一些
  • 自定义类型: 类似其他变成语言中的对象,可以给这个类型绑定自有的方法
go
package main

import "fmt"

// 定义一些类型别名(绣化了🤣)
type (
	isize = int
	usize = uint
	i8    = int8
	u8    = uint8
	i16   = int16
	u16   = uint16
	i32   = int32
	u32   = uint32
	i64   = int64
	u64   = uint64
	f32   = float32
	f64   = float64
)

func main() {
	var n1 u8 = 20
	var n2 uint

	// n2 = n1 // 直接赋值会报错, 编译器报错信息如下: 说类型不一样
	// # command-line-arguments
	// ./main.go:25:7: cannot use n1 (variable of uint8 type u8) as uint value in assignment
	n2 = uint(n1) // 这里的 uint 可以接受 u8 类型的值, 因为它只是个别名, 使用的内存空间是一样的
	fmt.Println("n1 = ", n1)
	fmt.Println("n2 = ", n2)
}
go
package main

import "fmt"

type uid uint32

func (id uid) findOne() {
	// 查询数据库, uid 对应的资源
	fmt.Println("findOne by id")
}

func main() {
  // 将一个数字转换成 uid 类型
	uid := uid(1001)

  // 通过类型调用类型上的方法
	uid.findOne()
}

任意类型 any

如果学过 TypeScript, 这个就很好理解了

  • any 表示任意类型(或者说:不限制数据类型)
go
package main

import "fmt"

// 输出一个变量的值和类型(推荐 any 而不是 interface{})
func dd(x any) {
	fmt.Printf("值:%v \t 类型:%T \n", x, x)
}

// 注意: any 类型关键字是 Go1.18 版本引入的关键字
// 在 Go1.18 之前的版本需要使用 interface{}
func dd2(x interface{}) {
	fmt.Printf("值:%v \t 类型:%T \n", x, x)
}

func main() {
	dd(1)
	dd2(1)

	dd("1")
	dd2("1")

	dd(1.1)
	dd2(1.1)

	dd(true)
	dd2(true)

	dd([]int{1, 2, 3})
	dd2([]int{1, 2, 3})
}

定义常量

注意点:

  1. 定义常量字符全大写_分割多个单词
  2. 初始化常量, 必须赋值(const DB_DSN string 这样不赋值是不行的)
  3. 常量也可以自动推导类型
go
package main

import "fmt"

// 使用 const 定义常量
const DB_URL string = "sqlite3:///example.db"

// 一次定义多个
const (
	DB_TYPE = "mysql"
	DB_HOST = "localhost"
	DB_PORT = "3306"
	DB_NAME = "test_db"
	DB_USER = "root"
	DB_PASS = "root123456"
)

func main() {
	// 常量定义后, 不能修改, 只能读取
	fmt.Println("DB_URL: ", DB_URL)
	fmt.Println("DB_TYPE: ", DB_TYPE)
	fmt.Println("DB_HOST: ", DB_HOST)
	fmt.Println("DB_PORT: ", DB_PORT)
	fmt.Println("DB_NAME: ", DB_NAME)
	fmt.Println("DB_USER: ", DB_USER)
	fmt.Println("DB_PASS: ", DB_PASS)

	// 硬是修改, 就无法通过编译
	// DB_URL = "hello";
	// # command-line-arguments
	// ./main.go:12:2: cannot assign to DB_URL (neither addressable nor a map index expression)
}

printf 占位符参考

占位符描述接收类型
%%输出百分号%任意类型
%s输出string/[] byte值string,[] byte
%q格式化字符串,输出的字符串两端有双引号""string,[] byte
%d输出十进制整型值整型类型
%f输出浮点数浮点类型
%e输出科学计数法形式 ,也可以用于复数浮点类型
%E%e相同浮点类型
%g根据实际情况判断输出%f或者%e,会去掉多余的0浮点类型
%b输出整型的二进制表现形式数字类型
%#b输出二进制完整的表现形式数字类型
%o输出整型的八进制表示整型
%#o输出整型的完整八进制表示整型
%x输出整型的小写十六进制表示数字类型
%#x输出整型的完整小写十六进制表示数字类型
%X输出整型的大写十六进制表示数字类型
%#X输出整型的完整大写十六进制表示数字类型
%v输出值原本的形式,多用于数据结构的输出任意类型
%+v输出结构体时将加上字段名任意类型
%#v输出完整Go语法格式的值任意类型
%t输出布尔值布尔类型
%T输出值对应的Go语言类型值任意类型
%c输出Unicode码对应的字符int32
%U输出字符对应的Unicode码rune,byte
%p输出指针所指向的地址指针类型

Released under the MIT License.