Golang语言基础篇001_常量变量与数据类型
在任何编程语言中,常量和变量都是最基本的概念之一。它们用于存储程序运行过程中需要的数据。在Go语言中,常量和变量有着不同的特性和使用方式。
本文将介绍Golang中常量、变量的概念、声明方式、命名规范等内容。另外,还会概述Golang中的数据类型。
1. 常量(Constant)
1.1. 什么是常量
常量是在程序编译时就确定的值,在程序运行过程中不能被修改。常量可以是数值类型(包括整数型、浮点型和复数型)、布尔型、字符串类型等。
1.2. 常量的声明
在Go语言中,使用const
关键字来声明常量。常量的声明方式有以下几种:
1.2.1. 显式类型声明
// 声明并初始化单个常量
const <变量名> <类型> = <表达式 / 值>// 声明并初始化同一类型的多个常量
const <变量名1>,<变量名2>,<变量名n> <类型> = <表达式1/值1>,<表达式2/值2>,<表达式n/值n>// 声明并初始化不同类型的多个常量
const (<变量名1> <类型1> = <表达式1/值1><变量名2> <类型2> = <表达式2/值2><变量名n> <类型n> = <表达式n/值n>
)
示例:
// 声明并初始化单个常量
const a int = 1 + 1
// 声明并初始化同一类型的多个常量
const b, c, d int = 1, 2, 3
// 声明并初始化不同类型的多个常量
const (e string = ""f int = 1
)
需要特别注意的是:在第三种声明并初始化常量的方式中,如果省略了值则表示和上面一行的值相同。
const (a int = 1 // a=1b // b=1c = 2 // c=2d // d=2
)
1.2.2. 隐式类型声明(类型推断)
常量声明与初始化时,如果类型推导的类型与目标类型一致,也可以忽略常量类型的编写。
const identifier = value
示例:
const pi = 3.14159
const appName = "MyApp"
const version = "1.0.0"
const maxBufferSize = 1024
当使用隐式类型声明时,Go编译器会根据赋值自动推断常量的类型。
1.3. iota枚举
iota
是go
语言的常量计数器,只能在常量的表达式中使用。 iota
在常量定义中出现时将被重置为0
。批量定义常量时,const
中每新增一行常量声明将使iota
计数一次(iota
可理解为const
语句块中的行索引)。 使用iota
能简化定义,在定义枚举时很有用。
-
定义单个常量时,iota=0。
const a = iota // a=0 const b = iota // b=0 const c // 报错
-
批量定义同一行的多个常量时,iota可以出现多次,但每次都是0.
const a,b,c = iota,1,iota // a=0,b=1,c=0
-
批量定义同一行的多个常量时,iota可以出现多次,但每次都是0.
const (a = iota // a=0b // b=iota=1c // c=iota=2 )const (a = iota*2 // a=0b // b=iota*2=1*1=2c // c=iota*2=2*2=4 )const (a = 4 // a=4b = iota // b=iota=1c // c=iota=2 )const (a = iota // a=iota=0b = 100 // b = 100c // c=100d = iota // d=iota=3e // e=iota=4 )const (a, b = iota+1, iota+2 // a=1,b=2c, d // c=2,d=3e, f // e=3,f=4 )
1.4. 常量的命名规范
常量的命名遵循Go语言的命名规范:
- 不导出的常量使用小驼峰命名法:
maxBufferSize
- 导出的常量使用大驼峰命名法:
MaxBufferSize
- 也可以使用全大写加下划线的传统风格:
MAX_BUFFER_SIZE
2. 变量(Variable)
2.1. 什么是变量
变量是用来存储程序运行时可能发生变化的数据的标识符。
在Go语言中,每个变量都有特定的类型,决定了变量可以存储什么类型的值。
2.2. 变量的声明
在Go语言中,使用var
关键字来声明变量。变量的声明方式有以下几种:
2.2.1. 显式类型声明
变量显式声明有如下三种格式:
// 声明单个变量
var <变量名> <变量类型>// 声明同一类型的多个变量
var <变量名1>,<变量名2>,<变量名n> <变量类型>// 声明不同类型的多个变量
var (<变量名1> <变量类型1><变量名2> <变量类型2><变量名n> <变量类型n>
)
示例:
// 声明单个变量
var name string
var age int
var isStudent bool// 声明同一个类型的多个变量
var maxAge,minAge int// 声明多个变量(不限类型)
var (name stringage intisStudent bool
)
声明后,变量会被赋予对应类型的零值,比如:
- 数值类型:0
- 布尔类型:false
- 字符串类型:“”
2.2.2. 声明时初始化
Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。
当然我们也可在声明变量的时候为其指定初始值。变量初始化的格式如下:
// 声明并初始化单个变量
var <变量名> <变量类型> = <表达式 / 值>// 声明并初始化同一类型的多个变量
var <变量名1>,<变量名2>,<变量名n> <变量类型> = <表达式1/值1>,<表达式2/值2>,<表达式n/值n>// 声明并初始化不同类型的多个变量
var (<变量名1> <变量类型1> = <表达式1/值1><变量名2> <变量类型2> = <表达式2/值2><变量名n> <变量类型n> = <表达式n/值n>
)
示例:
// 声明并初始化单个变量
var a int = 1 + 1
// 声明并初始化同一类型的多个变量
var b, c, d int = 1, 2, 3
// 声明并初始化不同类型的多个变量
var (e string = ""f int = 1
)
2.2.3. 隐式类型声明(类型推断)
在变量声明并初始化时,如果我们不设置变量类型,编译器会根据等号右边的值或表达式结果来推导变量的类型,完成初始化。
所以如果编译器推导的类型与我们的目标类型一致,就可以不编写变量类型:
var a int = 1 // 指定变量类型为int
var a = 1 // 编译器推导变量类型为int
var a int32 = 1 // 指定变量类型为int32
在上述示例中,第一个语句和第二个语句效果完全一致。只有在第三种情况,编译器推导的变量类型与我们的目标变量类型不一致时,才有必要编写变量类型。
2.2.4. 短变量声明(:=)
在类型推导章节中,我们知道当编译器推导的类型与我们的目标类型一致时,var a int =1
和 var a = 1
效果完全一样。
在函数内部,可以将这两个表达式替换成更简略的:=
方式声明并初始化变量:
<变量> := <值>
示例:
a := 1
注意:短变量声明只能在函数内部使用,并且要求左侧至少有一个新变量。
2.2.5. 匿名变量
当使用下划线占位符来代替变量名称时,即称该占位符代表的变量为匿名变量,表示忽略值。匿名变量最常用的方式是用来接收调用方法的返回值,表示忽略该返回值。
data,_:=json.Marshal(p )
2.3. 变量的赋值和重新赋值
变量声明后(不论是否初始化),都可以通过赋值语句进行重新赋值。
// 声明变量
var count int// 赋值
count = 10// 重新赋值
count = 20// 多重赋值
a, b := 1, 2
a, b = b, a // 交换a和b的值
2.4. 变量的命名规范
变量的命名遵循Go语言的命名规范:
- 全局变量:首字母大写表示导出,首字母小写表示私有
- 局部变量:推荐使用小驼峰命名法
- 避免使用下划线分隔符(特殊情况除外)
// 全局变量
var GlobalCounter int // 导出变量
var localCounter int // 私有变量// 局部变量
func processData() {userName := "张三"itemCount := 10
}
3. 常量与变量的区别
特性 | 常量 | 变量 |
---|---|---|
关键字 | const | var 或 := |
修改性 | 编译时确定,不可修改 | 运行时可修改 |
存储位置 | 符号表 | 内存 |
性能 | 更高(编译时优化) | 相对较低 |
使用场景 | 固定不变的值 | 可能变化的值 |
4. Go语言数据类型
Go语言是一种静态类型语言,这意味着每个变量都必须有一个明确的类型。Go语言提供了丰富的内置数据类型,主要包括基本数据类型和复合数据类型。
4.1. 基本数据类型
Go语言的基本数据类型包括数值类型、布尔类型和字符串类型。
4.1.1. 数值类型
Go语言提供了丰富的数值类型,包括整数类型和浮点数类型。
整数类型
整数类型分为有符号整数和无符号整数:
- 有符号整数:int8、int16、int32、int64、int
- 无符号整数:uint8、uint16、uint32、uint64、uint
- 特殊整数类型:uintptr(用于存储指针值)
var a int8 = 127 // 8位有符号整数,范围:-128 到 127
var b int16 = 32767 // 16位有符号整数,范围:-32768 到 32767
var c int32 = 2147483647 // 32位有符号整数,范围:-2147483648 到 2147483647
var d int64 = 9223372036854775807 // 64位有符号整数var e uint8 = 255 // 8位无符号整数,范围:0 到 255
var f uint16 = 65535 // 16位无符号整数,范围:0 到 65535
var g uint32 = 4294967295 // 32位无符号整数,范围:0 到 4294967295
var h uint64 = 18446744073709551615 // 64位无符号整数var i int = 100 // 平台相关大小,32位系统上为int32,64位系统上为int64
var j uint = 100 // 平台相关大小,32位系统上为uint32,64位系统上为uint64
var k uintptr = 0x1234 // 用于存储指针值的整数类型
浮点数类型
Go语言提供了两种浮点数类型:
- float32:32位浮点数(单精度)
- float64:64位浮点数(双精度,默认类型)
var f1 float32 = 3.14
var f2 float64 = 3.141592653589793
var f3 = 3.14 // 默认为float64类型// 特殊浮点数值
var inf = math.Inf(1) // 正无穷
var negInf = math.Inf(-1) // 负无穷
var nan = math.NaN() // 非数字(Not a Number)
复数类型
Go语言原生支持复数类型:
- complex64:实部和虚部都是float32类型
- complex128:实部和虚部都是float64类型(默认类型)
var c1 complex64 = 3 + 4i
var c2 complex128 = 3 + 4i
var c3 = 3 + 4i // 默认为complex128类型// 使用内置函数创建复数
c4 := complex(3.0, 4.0) // 创建复数3+4i// 获取复数的实部和虚部
realPart := real(c4) // 获取实部
imagPart := imag(c4) // 获取虚部
4.1.2. 布尔类型
布尔类型只有两个值:true和false。
var isTrue bool = true
var isFalse bool = false
var b1 = true // 默认布尔类型// 布尔运算
result1 := true && false // 与运算
result2 := true || false // 或运算
result3 := !true // 非运算
4.1.3. 字符串类型
Go语言中的字符串是UTF-8编码的不可变字节序列。
var str1 string = "Hello, 世界"
var str2 = "Hello, Go" // 隐式类型声明
str3 := "Hello, World" // 短变量声明// 多行字符串
str4 := `这是一个多行字符串
可以包含换行符
还可以包含"引号"`// 字符串连接
str5 := "Hello" + " " + "World"// 字符串长度
length := len(str1)// 字符串遍历
for i, char := range str1 {fmt.Printf("位置%d: %c\n", i, char)
}
4.1.4. 字符类型
Go语言没有专门的字符类型,字符是通过整数类型或rune类型表示的:
- byte:uint8的别名,用于表示ASCII字符
- rune:int32的别名,用于表示Unicode字符
var ch1 byte = 'A' // ASCII字符
var ch2 rune = '世' // Unicode字符// 字符与字符串的区别
char := 'A' // 类型为rune或int32
str := "A" // 类型为string// 遍历字符串中的字符
str := "Hello, 世界"
for i, r := range str {fmt.Printf("位置%d: %c\n", i, r)
}
4.2. 复合数据类型
Go语言提供了多种复合数据类型,用于组织和存储复杂的数据结构。
4.2.1. 数组(Array)
数组是固定长度的序列,所有元素必须是相同类型。
// 声明数组
var arr1 [5]int // 声明长度为5的整数数组
var arr2 [3]string = [3]string{"a", "b", "c"} // 声明并初始化
arr3 := [5]int{1, 2, 3, 4, 5} // 短变量声明并初始化
arr4 := [...]int{1, 2, 3} // 编译器自动计算长度// 访问数组元素
first := arr3[0] // 获取第一个元素
arr3[0] = 10 // 修改元素值// 数组长度
length := len(arr3) // 获取数组长度// 多维数组
var matrix [3][3]int // 3x3二维数组
matrix[0][0] = 1
4.2.2. 切片(Slice)
切片是对数组的封装,提供了动态数组的功能。
// 创建切片
var slice1 []int // 声明空切片
slice2 := make([]int, 5) // 创建长度为5的切片
slice3 := make([]int, 3, 5) // 创建长度为3,容量为5的切片
slice4 := []int{1, 2, 3, 4, 5} // 直接初始化// 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice5 := arr[1:4] // 从索引1到3创建切片
slice6 := arr[:3] // 从开始到索引2
slice7 := arr[2:] // 从索引2到结束// 切片操作
length := len(slice4) // 切片长度
capacity := cap(slice4) // 切片容量// 添加元素
slice8 := append(slice4, 6) // 添加单个元素
slice9 := append(slice4, 7, 8, 9) // 添加多个元素
slice10 := append(slice4, slice8...) // 合并切片// 复制切片
dest := make([]int, len(slice4))
copy(dest, slice4)
4.2.3. 映射(Map)
映射是一种无序的键值对集合。
// 创建映射
var map1 map[string]int // 声明空映射
map2 := make(map[string]int) // 使用make创建
map3 := map[string]int{ // 直接初始化"apple": 5,"banana": 3,"orange": 8,
}// 操作映射
map2["apple"] = 5 // 添加或更新键值对
value, exists := map2["apple"] // 获取值,exists表示键是否存在
delete(map2, "apple") // 删除键值对// 遍历映射
for key, value := range map3 {fmt.Printf("%s: %d\n", key, value)
}// 映射长度
length := len(map3)
4.2.4. 结构体(Struct)
结构体是一种用户自定义的数据类型,可以包含多个不同类型的字段。
// 定义结构体
type Person struct {Name stringAge intEmail string
}type Address struct {City, State stringZIP int
}// 创建结构体实例
var person1 Person // 零值初始化
person2 := Person{Name: "张三", Age: 30, Email: "zhangsan@example.com"}
person3 := Person{"李四", 25, "lisi@example.com"} // 按顺序初始化// 访问结构体字段
name := person2.Name
person2.Age = 31// 匿名结构体
person4 := struct {Name stringAge int
}{Name: "王五",Age: 28,
}// 嵌套结构体
type Employee struct {PersonAddressSalary int
}emp := Employee{Person: Person{Name: "赵六", Age: 35, Email: "zhaoliu@example.com"},Address: Address{City: "北京", State: "北京", ZIP: 100000},Salary: 10000,
}
4.2.5. 指针(Pointer)
指针存储了另一个变量的内存地址。
// 声明指针
var ptr *int // 声明指向int的指针
var value int = 42// 获取变量地址
ptr = &value // 获取value的地址// 解引用
fmt.Println(*ptr) // 输出指针指向的值
*ptr = 100 // 修改指针指向的值// 指针与结构体
type Student struct {Name stringAge int
}stu := Student{Name: "小明", Age: 18}
var stuPtr *Student = &stu
fmt.Println((*stuPtr).Name) // 通过指针访问字段
fmt.Println(stuPtr.Name) // 简化写法// 指针数组和数组指针
arr := [3]int{1, 2, 3}
var ptrArr [3]*int // 指针数组
for i := range arr {ptrArr[i] = &arr[i]
}var arrPtr *[3]int = &arr // 数组指针
fmt.Println((*arrPtr)[0])
4.2.6. 通道(Channel)
通道用于goroutine之间的通信。
// 创建通道
var ch1 chan int // 声明通道
ch2 := make(chan int) // 创建双向通道
ch3 := make(chan int, 5) // 创建带缓冲的通道
sendCh := make(chan<- string) // 只发送通道
receiveCh := make(<-chan string) // 只接收通道// 通道操作
ch2 <- 42 // 发送数据
data := <-ch2 // 接收数据// 关闭通道
close(ch2)// 使用range遍历通道
ch4 := make(chan int, 3)
go func() {ch4 <- 1ch4 <- 2ch4 <- 3close(ch4)
}()for value := range ch4 {fmt.Println(value)
}// 选择语句
ch5 := make(chan string)
ch6 := make(chan string)go func() { ch5 <- "hello" }()
go func() { ch6 <- "world" }()select {
case msg1 := <-ch5:fmt.Println(msg1)
case msg2 := <-ch6:fmt.Println(msg2)
default:fmt.Println("没有可用的数据")
}
4.2.7. 接口(Interface)
接口是一种类型,它定义了一组方法的签名,但不提供具体实现。任何类型只要实现了接口中定义的所有方法,就被认为是实现了该接口。
4.2.7.1. 接口的定义
// 定义一个简单的接口
type Writer interface {Write([]byte) (int, error)
}// 定义一个包含多个方法的接口
type ReadWriter interface {Read([]byte) (int, error)Write([]byte) (int, error)
}// 定义一个空接口(可以表示任何类型)
type Any interface{}
// 或者使用Go 1.18+的any关键字
// var value any
4.2.7.2 接口的实现
// 定义结构体
type File struct {name string
}// 实现Writer接口
func (f *File) Write(data []byte) (int, error) {fmt.Printf("向文件 %s 写入数据: %s\n", f.name, string(data))return len(data), nil
}// 实现Reader接口
func (f *File) Read(data []byte) (int, error) {// 简化实现copy(data, []byte("从文件读取的数据"))return len(data), nil
}// 使用接口
func processData(w Writer) {w.Write([]byte("Hello, Interface!"))
}// 示例使用
func main() {file := &File{name: "test.txt"}processData(file) // File实现了Writer接口// 接口变量var writer Writer = filewriter.Write([]byte("通过接口调用"))
}
4.2.7.3. 接口的组合
// 接口嵌入
type Reader interface {Read([]byte) (int, error)
}type Writer interface {Write([]byte) (int, error)
}type ReadWriter interface {Reader // 嵌入Reader接口Writer // 嵌入Writer接口
}// 实现ReadWriter接口的结构体
type File struct {name string
}func (f *File) Read(data []byte) (int, error) {copy(data, []byte("文件内容"))return len(data), nil
}func (f *File) Write(data []byte) (int, error) {fmt.Printf("写入文件 %s: %s\n", f.name, string(data))return len(data), nil
}
4.2.7.4. 空接口和类型断言
// 空接口可以存储任何类型的值
var x interface{}x = 42
fmt.Println(x) // 42x = "Hello"
fmt.Println(x) // Hellox = true
fmt.Println(x) // true// 类型断言
str, ok := x.(string)
if ok {fmt.Printf("x 是字符串: %s\n", str)
} else {fmt.Println("x 不是字符串")
}// 类型选择
switch v := x.(type) {
case int:fmt.Printf("x 是整数: %d\n", v)
case string:fmt.Printf("x 是字符串: %s\n", v)
case bool:fmt.Printf("x 是布尔值: %t\n", v)
default:fmt.Printf("x 是其他类型: %T\n", v)
}
4.2.7.5. 接口的最佳实践
// 1. 倾向于定义小而专注的接口
type Stringer interface {String() string
}type Reader interface {Read([]byte) (int, error)
}// 2. 接受接口,返回结构体
func ProcessData(r Reader) []byte {data := make([]byte, 1024)r.Read(data)return data
}// 3. 使用接口作为函数参数实现解耦
type Animal interface {Speak() string
}type Dog struct{}
func (d Dog) Speak() string {return "汪汪"
}type Cat struct{}
func (c Cat) Speak() string {return "喵喵"
}func MakeAnimalSpeak(a Animal) {fmt.Println(a.Speak())
}// 使用示例
func main() {dog := Dog{}cat := Cat{}MakeAnimalSpeak(dog) // 汪汪MakeAnimalSpeak(cat) // 喵喵
}
4.3. 类型别名和自定义类型
Go语言允许创建类型别名和自定义类型。
// 类型别名(Go 1.9+)
type Celsius float64
type Fahrenheit float64
type ID = int32 // 类型别名// 自定义类型
type UserID int32 // 新类型,与int32不兼容
type MyInt int // 新类型,与int不兼容// 使用示例
var temp Celsius = 25.0
var id ID = 12345
var userId UserID = 67890// 类型转换
func CToF(c Celsius) Fahrenheit {return Fahrenheit(c*9/5 + 32)
}func FToC(f Fahrenheit) Celsius {return Celsius((f - 32) * 5 / 9)
}
4.4. 零值概念
在Go语言中,所有变量在声明时都会被赋予一个零值:
- 数值类型:0
- 布尔类型:false
- 字符串类型:“”
- 指针类型:nil
- 切片、映射、通道:nil
- 接口类型:nil
- 结构体:各字段的零值
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // ""
var ptr *int // nil
var slice []int // nil
var m map[string]int // nil
var ch chan int // nil// 结构体零值
type User struct {Name stringAge intActive bool
}var user User // User{"", 0, false}
4.5. 类型转换
Go语言不支持隐式类型转换,必须显式进行类型转换。
// 基本类型转换
var i int = 42
var f float64 = float64(i) // int转float64
var u uint = uint(f) // float64转uint// 字符串与其他类型转换
import "strconv"// 数字转字符串
str := strconv.Itoa(42) // int转字符串
str = strconv.FormatFloat(3.14, 'f', -1, 64) // float64转字符串// 字符串转数字
i, err := strconv.Atoi("42") // 字符串转int
if err != nil {// 处理错误
}f, err = strconv.ParseFloat("3.14", 64) // 字符串转float64// 字节切片与字符串转换
str = "hello"
bytes := []byte(str) // 字符串转字节切片
str = string(bytes) // 字节切片转字符串
4.6. 类型断言
类型断言用于接口类型的转换。
var i interface{} = "hello"// 类型断言
s, ok := i.(string) // 安全类型断言
if ok {fmt.Println(s)
}// 直接类型断言(如果类型不匹配会panic)
s = i.(string)// 类型选择
switch v := i.(type) {
case string:fmt.Println("字符串:", v)
case int:fmt.Println("整数:", v)
default:fmt.Println("未知类型")
}