Golang 数据类型 Golang
package main
import "fmt"
// 数据类型
func main(){
// 基本数据类型
// 布尔类型 bool (布尔类型用于表示真值,只有两个取值:true 或 false)
bool_true := true
bool_false := false
fmt.Println(bool_true,bool_false) // true,false
// 整数类型(int, int8, int16, int32, int64))
// Go 提供了多种整数类型,int 是根据操作系统架构来选择大小的,一般是 32 位或 64 位。其他整数类型的位数分别是 8 位、16 位、32 位和 64 位。
// int 和 uint:根据平台选择相关大小(32或64)
// int8、int16、int32、int64 带符号整数
// uint8、uint16、uint32、uint64 无符号整数
// 注:int、uint根据系统自动选择32或者64,如果当前操作系统位32位架构,想使用64位int,可手动指定类型,比如 var int_64 int64 = 1000000
var int_a int = 10
var int_b int64 = 10000000000
var int_c uint = 20
fmt.Println(int_a,int_b,int_c)
// 浮点数类型(float32、float64)
var f1 float32 = 3.14
var f2 float64 = 3.141592653589793
fmt.Println(f1,f2)
// 字符串类型(string)
// Go 中的字符串是由一系列字节组成的,使用 UTF-8 编码。字符串是不可变的,一旦创建就不能更改。
// 创建不能更改意思为不能修改字符串的内容,可以重新赋值字符串变量
var s1 string = "Hello Go"
// s1[0] = 'H' // 错误: 不能修改字符串中的字符
s1 = "Hello" // 合法的
fmt.Println(s1)
// 字节类型(byte)
// byte 是 uint8 的别名,用于表示字节类型,通常用于处理原始数据(如文件、网络协议等)。
// byte 的范围是 0 - 255
var b_a byte = 'A'
var b_b byte = 30
fmt.Println(b_a,b_b) // b_a 输出 65
// rune类型
// rune 是 int32 的别名,表示一个 Unicode 字符。它用于表示单个字符,特别是在处理 Unicode 字符集时。
// rune 的范围是 -2147483648 - 2147483647
var r_a rune = 'A'
fmt.Println(r_a) // 输出65
// 复合数据类型
// Go 提供了多种复合数据类型,包括数组、切片、映射、结构体和指针等。
// 数组(Array)
// 数组是固定大小的元素序列,元素的类型相同。Go 中的数组长度是数组类型的一部分,因此数组的大小是不可变的。
// 显式指定大小
var arr_a [5]int
arr_a[0] = 100
arr_a[1] = 200
// arr_a[5] = 100 // 错误,数组下标从0开始,当前变量最大下标为4,给5设置值则会报错
fmt.Println(arr_a)
// 使用字面量初始化
arr_b := [3]int{100,200,300} //arr_b := [4]int{100,200,300} 也是合法声明,最后一个下标默认则为0
fmt.Println(arr_b)
// 自动推导数组大小
arr_c := [...]int{1,2,3,4,5,6}
fmt.Println(arr_c)
// 访问和修改数组元素
fmt.Println("访问数组:",arr_a[0])
arr_a[0] = 300 //修改数组
// 遍历数组
for arr_a_i := 0;arr_a_i < len(arr_a);arr_a_i++ {
fmt.Println(arr_a[arr_a_i])
}
// 获取数组长度
fmt.Println("获取数组长度(len()):",len(arr_a))
// 多维数组
arr_dw := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(arr_dw[0][1]) // 输出 2
// 遍历多维数组
for arr_dw_i := 0;arr_dw_i < len(arr_dw);arr_dw_i++ {
fmt.Println(arr_dw[arr_dw_i])
for arr_dw_i_c := 0;arr_dw_i_c < len(arr_dw[arr_dw_i]);arr_dw_i_c++ {
fmt.Println(arr_dw[arr_dw_i][arr_dw_i_c])
}
}
// 切片(Slice)
// 切片是动态数组,可以动态增长,长度和容量都可以变化。切片是 Go 中常用的复合数据类型。
// 它是基于数组的动态视图。
// 切片在 Go 编程中非常常用,因为它提供了比数组更灵活和高效的操作方式。
// 与数组不同,切片是引用类型,因此切片的操作会影响原始数据(即底层数组)。
// 切片没有固定大小,因此它是动态的。切片有三个主要属性:
// 1.指针 指向底层数组的起始位置
// 2.长度 切片中元素的个数
// 3.容量 从切片的起始位置到数组的末尾的元素个数
// 可以通过字面量、make 函数或从数组中切片来创建切片
// 通过字面量创建切片
slice_a := []int{1,2,3,4,5} // 创建一个切片,包含5个元素
fmt.Println(slice_a)
// 使用make函数创建切片
// make函数有三个参数:make([]T, len, cap),其中 len 是切片的长度,cap 是切片的容量。cap 是可选的,如果省略,默认 cap 等于 len。
slice_b := make([]int,5)// 创建一个长度为5的切片,初始值为0
fmt.Println(slice_b)
// 从数组创建切片
slice_arr := [...]int{1,2,3,4,5}
slice_c := slice_arr[1:4] // 获取数组 arr 从下标 1 到 3 的切片
fmt.Println(slice_c) // 输出: [2 3 4]
// 切片的基本操作
//访问切片元素 通过索引访问切片元素,和数组的访问方式一样
slice_new_a := []int{1,2,3,4,5,6}
fmt.Println(slice_new_a[0],slice_new_a[2])
//修改切片元素 切片是引用类型,因此修改切片元素会直接影响底层数组
fmt.Println(slice_arr);
slice_c[0] = 100
fmt.Println(slice_arr,slice_c) // 由于slice_c 是从slice_arr 中创建,所以更改slice_c 会同时修改slice_arr中数组内容
//获取切片的长度和容量
//使用len()和cap()函数分别获取切片的长度和容量
slice_d := make([]int,5)
fmt.Println("长度:",len(slice_d),"容量:",cap(slice_d))
//切片的扩展和重新切片
//使用 append 扩展切片
//append 函数用于向切片末尾添加元素。如果切片的容量足够,它会在原地修改切片;如果容量不够,append 会创建一个新的底层数组。
slice_d = append(slice_d,4,5,6) //向切片添加元素
fmt.Println(slice_d)
//重新切片
//可以通过切片操作符来从一个切片创建新的切片(即重新切片),这不会复制数据,而是创建一个新的切片指向原数组的一部分
oldslice := []int{1,2,3,4,5}
newslice := oldslice[1:3] //// 创建一个新的切片,包含元素 [2,3]
fmt.Println(oldslice,newslice)
//切片容量增长规则
//Go 中的切片会根据需要自动增长。当切片的容量不够时,它会创建一个新的底层数组,通常将容量翻倍。
slice_nn := []int{1, 2, 3}
slice_nn = append(slice_nn, 4) // 新的底层数组会被分配
fmt.Println(cap(slice_nn)) // 输出大于 3 的值,容量通常是 8(根据实现不同可能会有所不同)
//数组是值类型
//切片是引用类型赋值或传递切片时不会复制底层数据,而是共享同一个底层数组
// 切片非常适合用作函数参数,可以传递多个值,而无需显式创建数组。例如下面:
// func sum(nums []int) int {
// total := 0
// for _, num := range nums {
// total += num
// }
// return total
// }
// numbers := []int{1, 2, 3, 4}
// fmt.Println(sum(numbers)) // 输出: 10
//由于切片共享底层数组,它们是内存高效的。在大多数情况下,切片的传递是按引用进行的,而不是复制整个数组。这避免了内存的重复分配,提高了性能。
//切片复制
//可以使用 copy 函数来复制切片的内容。copy 函数将源切片的数据复制到目标切片,返回复制的元素个数。
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // 将 src 的内容复制到 dst
fmt.Println(dst) // 输出: [1 2 3]
// 总结
// 切片是对数组的动态视图,支持灵活的操作。
// 切片的长度是动态变化的,而容量在大多数情况下会自动扩展。
// 切片是引用类型,多个切片可以共享同一个底层数组。
// 使用 append 可以动态增加切片的元素,而 copy 函数用于复制切片的内容。
// 切片非常适用于动态数据结构(如队列、栈等)的实现。
// 映射Map
// 映射是一种键值对的数据结构。Go 中的 map 是无序的。
// Map提供了基于键值对的存储方式。它允许根据键快速查找对应的值
// 定义和初始化
m_a := map[string]int{
"a":1,
"b":2,
"c":3,
}
fmt.Println(m_a)
// 通过make函数创建map
m_b := make(map[string]int) //创建一个空的 map,键是 string 类型,值是 int 类型
m_b["a"] = 1
m_b["b"] = 2
m_b["c"] = 3
fmt.Println(m_b)
// 访问和操作Map元素
// 访问元素
// 可以通过键来访问 map 中的值。如果键存在,则返回对应的值;如果键不存在,则返回值类型的零值。
fmt.Println(m_b["a"],m_b["ccc"])
// 判断某个键是否存在 可以通过多重赋值来判断某个键是否存在。如果键存在,第二个值会是 true,否则是 false。
m_b_a_value, exists := m_b["a"]
if exists {
fmt.Println("键 'a' 存在,值为:", m_b_a_value) // 输出: 键 'a' 存在,值为: 1
} else {
fmt.Println("键 'a' 不存在") // exists = false
}
//删除元素
//使用 delete 函数删除 map 中的某个键值对。如果键不存在,delete 不会引发错误。
delete(m_b,"a")
delete(m_b,"ccc") // ccc不存在,不会报错
fmt.Println(m_b)
// 遍历map 可以使用for 和 range 来遍历map中的键值对
for m_b_key,m_b_value := range m_b{
// 由于map是无序的,所以输出顺序不确定
fmt.Println(m_b_key,m_b_value)
}
//Map的零值
//map 是引用类型,零值是 nil。一个 nil 的 map 不可以用于存储键值对,尝试插入元素会引发运行时错误。你需要使用 make 来初始化 map
var m_nil map[string]int // 未初始化,m 是 nil
fmt.Println(m_nil == nil) // 输出: true
// 以下代码会导致运行时错误: panic: assignment to entry in nil map
// m["a"] = 1
// 使用make 初始化
m_nil = make(map[string]int)
m_nil["a"] = 1 // 可以正常运行
fmt.Println(m_nil)
// map的容量和扩展
// map 的容量会根据元素的数量动态增长。可以使用 len() 函数获取 map 中元素的数量,但不能直接修改 map 的容量。map 的容量增长是自动的,无需手动调整
fmt.Println(len(m_nil))
//应用场景
// 计数 map 是一个很好的工具,常用于统计某些元素的出现次数,例如字符频率统计:
// s := "hello world"
// freq := make(map[rune]int)
// for _, c := range s {
// freq[c]++
// }
// fmt.Println(freq)
// // 输出: map[ 1 d:1 e:1 h:1 l:3 o:2 r:1 w:1]
//去重 利用 map 的键唯一性,可以轻松去重数组或切片中的元素
// arr := []int{1, 2, 2, 3, 4, 4, 5}
// unique := make(map[int]bool)
// for _, num := range arr {
// unique[num] = true
// }
// for key := range unique {
// fmt.Println(key) // 输出: 1 2 3 4 5
// }
// Map注意事项
// 1.无序性:map 是无序的,遍历 map 时,元素的顺序是不确定的。
// 2.不能直接比较 map:map 不能直接进行比较操作(例如,map1 == map2)。可以通过遍历 map 来比较它们的内容。
// 3.并发访问问题:Go 的 map 不是线程安全的。如果多个 goroutine 同时读写同一个 map,需要使用同步机制(如 sync.Mutex 或 sync.RWMutex)来保护 map 的并发访问。
// // 示例: 使用互斥锁同步访问 map
// import "sync"
// var m = make(map[string]int)
// var mu sync.Mutex
// func safeWrite(key string, value int) {
// mu.Lock() // 锁住
// m[key] = value // 写入 map
// mu.Unlock() // 解锁
// }
// func safeRead(key string) int {
// mu.Lock() // 锁住
// value := m[key] // 读取 map
// mu.Unlock() // 解锁
// return value
// }
// 性能: map 采用哈希表(hash table)实现,支持常数时间复杂度的查找、插入和删除操作。具体的时间复杂度依赖于哈希函数和负载因子。
// 内存: map 会根据需要动态扩展其大小,当元素数量达到一定阈值时,Go 会重新调整 map 的底层存储结构,通常通过哈希扩容来提高性能。
// 总结
// 1.map 是基于哈希表实现的键值对集合,提供了高效的插入、查找和删除操作。
// 2.map 的键是唯一的,值可以是任意类型。
// 3.map 是引用类型,零值为 nil,需要使用 make 或字面量初始化。
// 4.遍历 map 是无序的,顺序不可预期。
// 5.在多 goroutine 并发访问 map 时,需要使用锁(如 sync.Mutex)来确保安全。
// 结构体(Struct)
// 是一种聚合类型,用于将多个不同类型的数据组合成一个单一的数据结构。结构体通常用来表示一些具有不同属性(字段)的对象或实体。
// 结构体的字段可以是任意类型(包括基本类型、数组、切片、映射、其他结构体等),它是 Go 中实现面向对象编程的一种方式。
// 结构体的定义和创建
// 定义结构体 结构体定义通过 type 关键字完成,使用 struct 关键字来声明结构体的类型。
type Person struct {
Name string
Age int
}
// Person是结构体的类型名,结构体包含两个字段,Name字段 string类型,Age字段 int类型
// 创建结构体实例
// 通过字面量初始化结构体
person_1 := Person{
Name: "张三",
Age: 30,
}
fmt.Println(person_1) // 输出: {张三 30}
// 使用结构体指针(&)
//通过 & 可以创建结构体的指针,这样可以避免复制结构体,特别是在结构体很大时,传递指针更加高效。
person_2 := &Person{
Name: "李四",
Age: 30,
}
fmt.Println(*person_2)
// 结构体的指针也可以像结构体实例一样访问字段,Go 会自动解引用。
fmt.Println(person_2.Name,person_1.Name)
// 结构体的字段
// 结构体的字段可以是任何类型,包括基本类型、数组、切片、映射、其他结构体等。字段的名字通常使用首字母大写(如 Name、Age),表示字段是 可导出的,即可以在其他包中访问。
// 结构体字段的访问,可以通过 (.) 操作符访问结构体字段
fmt.Println(person_1.Age)
// 结构体指针
// 结构体是值类型。如果你直接传递结构体给函数,函数会接收到结构体的副本。为了避免复制结构体,可以传递结构体的指针,这样函数可以修改原始结构体。
//结构体指针和结构体实例的区别
//结构体实例:直接存储数据。
//结构体指针:存储结构体的内存地址,通过指针可以间接访问数据。
// 结构体方法
// Go 语言支持通过结构体定义 方法。方法就是与某个类型(如结构体)相关联的函数。
dog1 := Dog{
Name:"dog1",
Age:1,
}
dog1.Greet() //值传递,不可修改dog1 数据
dog1.Hello() //指针传递,可以修改dog1 数据
fmt.Println(dog1)
// 匿名字段(嵌套结构体)
//Go 支持匿名字段,即没有字段名的结构体字段。如果结构体类型是匿名的,那么该类型的字段可以直接通过类型名来访问。匿名字段通常用于嵌套结构体。
type Address struct{
Street, City string
}
type Nperson struct{
Name string
Age int
Address // 匿名字段
}
np1 := Nperson{
Name:"张三",
Age:20,
Address: Address{Street:"sss",City:"cityvalue"},
}
fmt.Println(np1,np1.Name,np1.Address.City)
// 结构体比较
// Go 中的结构体可以通过 == 和 != 运算符进行比较(前提是结构体的字段类型支持比较)。比较时,两个结构体的所有字段必须相等。
ad1 := Address{
Street:"aa",
City:"bb",
}
ad2 := Address{
Street:"aa",
City:"bb",
}
ad3 := Address{
Street:"aaa",
City:"bb",
}
fmt.Println(ad1 == ad2) // true
fmt.Println(ad1 == ad3) // false
// 结构体与数组/切片的区别
// 数组:固定大小,存储一组相同类型的数据。
// 切片:动态大小,支持更多灵活的操作。
// 结构体:可以包含不同类型的字段,适用于表示具有不同属性的实体。
// 总结
// 结构体(struct) 是将不同类型的数据聚合在一起的一种类型,通常用于表示某种具有多个属性的实体。
// 结构体字段可以是任意类型,可以包含其他结构体、切片、映射等。
// 可以通过值类型或指针类型的接收者为结构体定义方法,支持面向对象的编程风格。
// Go 中的结构体非常灵活,适合用来表示对象、数据模型等。
// 指针 Pointer
// 指针类型用于存储另一个变量的地址。Go 中的指针不支持指针算术(即不允许直接修改指针值)。
// *:指针类型声明时使用,表示指针指向某种类型的值。
// &:取地址操作符,返回变量的内存地址。
var p *int // p 是一个指向 int 类型的指针
x := 20
x_ad := &x // x_ad 是 x 的指针,指向 x 的内存地址
p = &x
fmt.Println(x_ad) // 输出: 指向 x 的内存地址
//通过 * 操作符访问指针所指向的值(解引用):
fmt.Println(*p) // 输出20
// 零值和nil指针
// Go 中,指针的零值是 nil,表示没有指向任何变量。使用一个未初始化的指针(nil 指针)会导致运行时错误。
// var p *int // p 是一个 nil 指针
// fmt.Println(p) // 输出: <nil>,指针没有指向任何变量
// var p *int
// if p == nil {
// fmt.Println("p is nil") // 输出: p is nil
// }
// 总结
// 指针 是存储内存地址的变量,它允许通过地址访问和修改数据。
// Go 使用 * 来声明指针类型,使用 & 获取变量的地址。
// 通过指针可以避免值传递带来的性能开销,特别是在结构体、数组等大型数据结构中。
// 指针用于修改函数外部的数据,或是返回对数据的引用。
// Go 指针与 C/C++ 中的指针相似,但它避免了直接内存管理和复杂的指针运算,提供了一定程度的安全性。
// 接口(Interface)
// 接口是 Go 实现 多态 的核心机制,允许不同的类型(只要它们实现了接口的所有方法)都可以作为接口类型的值。
// 接口定义了一组方法签名,但并不提供具体实现。一个类型如果实现了接口中的所有方法,那么它就隐式地实现了这个接口。
// 声明接口
type Animal interface {
Speak() string
}
// 在上面的例子中,Animal 是一个接口类型,包含了一个方法 Speak,它返回一个字符串。任何类型只要实现了 Speak 方法,就隐式地实现了 Animal 接口
// 实现接口
dogs_1 := Dogs{
Name:"小狗1",
}
fmt.Println(dogs_1.Speak())
// 空接口
// Go 中的 空接口 interface{} 是一个特殊的接口类型,它没有方法签名,意味着所有类型都实现了空接口。空接口可以用来存储任何类型的值,通常用在需要处理不同类型的场景中
var ki interface {}
ki = 2
fmt.Println(ki)
ki = "hello"
fmt.Println(ki)
ki = [...]int{1,2,3}
fmt.Println(ki)
ki = "hello123"
// 接口断言
// Go 提供了 类型断言 来判断和提取接口类型中实际存储的具体类型。类型断言用于从接口类型提取实际的类型值。
// 基本语法
// value, ok := interfaceValue.(ConcreteType)
// value 是接口值实际存储的数据。 ok 是一个布尔值,表示断言是否成功。
str, ok := ki.(string) // 判断是否是string类型
if ok {
fmt.Println(str) // 输出: Hello, World!
} else {
fmt.Println("ki is not a string")
}
// 强制类型断言
// 如果你确信一个接口值是某个具体类型,可以进行强制类型断言。如果断言失败,Go 会引发运行时错误(panic)。
// str := ki.(int) // 运行时错误: panic: interface conversion: interface {} is int, not string
// 接口类型的类型判断
//Go 语言也提供了一个 类型开关(type switch) 来根据不同的接口类型执行不同的操作。类型开关是一个基于 switch 语句的扩展,专门用于处理接口类型的多态
// func PrintType(v interface{}) {
// switch v := v.(type) {
// case int:
// fmt.Println("int:", v)
// case string:
// fmt.Println("string:", v)
// case bool:
// fmt.Println("bool:", v)
// default:
// fmt.Println("unknown type")
// }
// }
// func main() {
// PrintType(42) // 输出: int: 42
// PrintType("hello") // 输出: string: hello
// PrintType(true) // 输出: bool: true
// }
// 在上面的例子中,PrintType 函数根据传入的接口值的实际类型执行不同的操作
// 接口嵌套
// 接口可以嵌套其他接口,这允许你构建更加灵活和组合的接口设计。
// type ReadWriter interface {
// Reader
// Writer
// }
// type Reader interface {
// Read(p []byte) (n int, err error)
// }
// type Writer interface {
// Write(p []byte) (n int, err error)
// }
// 接口的实现和组合
// Go 的接口可以通过组合其他接口来构建更复杂的接口。这种组合接口的设计可以使得代码更加灵活和可复用
// type Speaker interface {
// Speak() string
// }
// type Walker interface {
// Walk() string
// }
// type Person interface {
// Speaker
// Walker
// }
// type Human struct {
// Name string
// }
// func (h Human) Speak() string {
// return "Hello, my name is " + h.Name
// }
// func (h Human) Walk() string {
// return h.Name + " is walking"
// }
// func main() {
// // 注:该位置p类型为Person接口类型
// var p Person = Human{Name: "Alice"}
// fmt.Println(p.Speak()) // 输出: Hello, my name is Alice
// fmt.Println(p.Walk()) // 输出: Alice is walking
// }
// // 在这个例子中,Person 接口通过组合 Speaker 和 Walker 接口来实现更复杂的行为。
// 接口和多态
// 接口的主要用途之一是实现多态。多态允许不同类型的对象以相同的方式进行操作,只要这些对象实现了相同的接口
// type Speaker interface {
// Speak() string
// }
// type Dog struct {
// Name string
// }
// func (d Dog) Speak() string {
// return "Woof!"
// }
// type Cat struct {
// Name string
// }
// func (c Cat) Speak() string {
// return "Meow!"
// }
// func MakeSound(speaker Speaker) {
// fmt.Println(speaker.Speak())
// }
// func main() {
// dog := Dog{Name: "Buddy"}
// cat := Cat{Name: "Whiskers"}
// MakeSound(dog) // 输出: Woof!
// MakeSound(cat) // 输出: Meow!
// }
// 以上两个不同的结构体实现了相同的方法
// 总结
// 接口(Interface) 是 Go 中的核心概念,用于定义类型的行为集合。接口的实现是隐式的,只要类型实现了接口的所有方法,就自动实现该接口。
// 空接口 interface{} 可以存储任何类型的值,是实现多态的基础。
// 类型断言 和 类型开关 提供了对接口实际类型的处理方法。
// 接口可以通过组合多个接口来创建更加复杂的接口。
// 接口支持多态,允许不同类型通过相同的接口进行操作。
// 数据类型转换
// Go 中的类型转换不像其他语言那样隐式发生,必须显式地进行类型转换
var i float64 = 66.66
var f int = int(i)
fmt.Println(f) // 输出: 66
// 常量(const)
// 常量是不可改变的值,Go 中常量的类型是由值的上下文决定的,常量可以是数字、字符、字符串或布尔值。
// Go 会自动推导常量的类型,如果没有显式指定类型,则由常量的值推导出类型
const Pi = 3.14
const Hello = "Hello, Go!"
const IsTrue = true
// 批量声明常量
const (
PA = 1
PB = 2
Pc = 3
)
// 常量表达式
// Go 支持在常量声明中使用 常量表达式,这些表达式在编译时就被计算出结果。例如,你可以在常量声明中使用算术运算、位运算等
const (
xA = 10
yA = 20
sumA = xA + yA // sumA 计算为 30
)
// 常量与枚举
// Go 语言本身没有 enum(枚举)类型,但可以使用常量组合来实现类似枚举的效果,通常通过 iota 来实现一组有序常量。
// iota 是 Go 中的一个常量生成器,用于生成一组递增的常量。每次使用 iota 时,它的值会自动递增。
const (
Sunday = iota // Sunday = 0
Monday // Monday = 1
Tuesday // Tuesday = 2
Wednesday // Wednesday = 3
Thursday // Thursday = 4
Friday // Friday = 5
Saturday // Saturday = 6
)
// 在上面的例子中,iota 从 0 开始递增,因此每个常量依次取得不同的整数值。如果你想手动调整某个常量的值,也可以在它之前设置一个常量的值
const (
_ = iota // 忽略第一个值
SundayA = iota // SundayA = 1
MondayA // MondayA = 2
)
// 在这里,iota 会从 1 开始递增。
// 使用 iota 实现位标志
const (
Read = 1 << iota // Read = 1 << 0 = 1
Write // Write = 1 << 1 = 2
Execute // Execute = 1 << 2 = 4
)
// 在这个例子中,iota 被用来生成二进制位标志,表示不同的权限。
// 也可以这样写
const (
Num = iota*2 // 0
Num1 // 2
Num2 // 4
Num3 // 6
Num4 // 8
)
// 常量的用途
// 表示固定的值:例如数学常量(Pi、e)、常用的系统配置(如最大值、最小值)。
// 避免魔法数字(Magic Numbers):通过常量代替数字常量,使代码更加易读和易维护。
// 类型安全:通过常量定义,使代码在编译时能捕捉到潜在的类型错误。
// 位标志和掩码:利用 iota 和常量,可以生成一组位标志,并用于位操作。
// 常量的作用域
// 常量的作用域与其他变量一样,在常量声明时确定。如果常量是全局声明的,它的作用域会是整个包;如果常量在函数内部声明,则它的作用域仅限于函数内部
// package main
// import "fmt"
// const GlobalConst = 100 // 全局常量
// func main() {
// const LocalConst = 200 // 局部常量
// fmt.Println(GlobalConst) // 输出: 100
// fmt.Println(LocalConst) // 输出: 200
// }
// 总结
// const 声明常量,常量在 Go 中是不可修改的,编译时就确定了其值。
// 常量支持多种基本数据类型,包括整数、浮点数、布尔值和字符串。
// iota 用于生成有序的常量,可以轻松实现类似枚举的效果。
// 常量可以用来避免魔法数字,提供类型安全,帮助实现位标志等。
// 常量的作用域由声明位置决定,可以是全局常量也可以是局部常量。
// 类型别名与自定义类型
// Go 支持类型别名和自定义类型。类型别名为已有类型提供新的名称,而自定义类型是基于已有类型定义的新类型。
// 类型别名
type MyInt = int
var mx MyInt = 88888
fmt.Println(mx) // 输出: 88888
// 自定义类型
type Age int
var myAge Age = 20
fmt.Println(myAge) // 输出: 20
// 匿名变量 _
// 由于在函数内部存在未使用的变量会无法通过编译,但有些变量又确实用不到,这个时候就可以使用匿名变量_,使用_来表示该变量可以忽略,例如
// a, b, _ := 1, 2, 3
//各种数据类型的零值
// 整数类型 0
// 浮点数类型 0.0
// 复数类型 0 + 0i
// 布尔类型 false
// 字符串类型 "" (空字符串)
// 指针类型 nil
// 数组类型 每个元素的零值
// 切片类型 nil
// 映射类型 nil
// 通道类型 nil
// 接口类型 nil
// 函数类型 nil
// 结构体类型 字段的零值
// 通道Channel
// Channel 是 Go 中用于 goroutines 之间传递数据的管道。你可以把它想象成两个 goroutine 之间的通信线路。
// 一个 goroutine 向 channel 发送数据,另一个 goroutine 从 channel 接收数据。
// Channel 的声明和创建
// 在 Go 中,channel 通过 make 函数来创建
// ch1 := make(chan Type)
// Type 是 channel 中存储的数据类型。
// make(chan Type) 会创建一个用于传递 Type 类型数据的 channel。
ch1 := make(chan int) // 创建一个传递int类型的channel
// 带缓冲区的 Channel
// Go 的 channel 可以有缓冲区,也就是说,当发送数据时,数据会先存储在缓冲区里,直到接收方接收它们。缓冲区的大小是在创建时指定的:
ch2 := make(chan int, 3) // 创建一个缓冲区大小为 3 的 channel
//如果向一个已满的缓冲区发送数据,发送操作会阻塞,直到有空间可以存放新的数据。如果从一个空的缓冲区接收数据,接收操作也会阻塞,直到有数据可以接收。
// Channel 的发送和接收
//发送数据:使用 <- 操作符将数据发送到 channel。
//接收数据:使用 <- 操作符从 channel 接收数据。
// ch := make(chan int) // 创建一个 channel
// // 发送数据
// go func() {
// ch <- 42 // 将数字 42 发送到 channel
// }()
// // 接收数据
// value := <-ch // 从 channel 接收数据
// Channel 的关闭
//关闭 channel 表示不再向该 channel 发送数据。
//你可以使用 close 函数来关闭 channel。关闭 channel 后,接收方可以继续接收数据,但不能再向 channel 发送数据。
// ch := make(chan int)
// go func() {
// for i := 1; i <= 5; i++ {
// ch <- i // 发送数据到 channel
// }
// close(ch) // 关闭 channel
// }()
// // 接收数据直到 channel 关闭
// for val := range ch {
// fmt.Println(val) // 输出:1 2 3 4 5
// }
// 这里,for val := range ch 会在 channel 关闭时自动结束,它会一直接收数据,直到 channel 被关闭。
// Channel 的方向性
// Channel 可以通过声明为不同的方向来限制它的使用方式。
//双向 channel:既可以发送数据,也可以接收数据。
var ch3 chan int // 双向 channel
//只发送数据的 channel:只能发送数据,不能接收数据。通过 chan<- 来声明:
// func sendData(ch chan<- int) {
// ch <- 42 // 只能发送数据
// }
//只接收数据的 channel:只能接收数据,不能发送数据。通过 <-chan 来声明:
// func receiveData(ch <-chan int) {
// val := <-ch // 只能接收数据
// fmt.Println(val)
// }
// 方向性声明可以帮助你在函数中限制 channel 的操作,确保数据流动的方向性。
// Channel 的传递
// Channel 是 引用类型,因此在函数传递时,实际上是传递了一个指向该 channel 的指针。这意味着你不需要担心内存开销或复制问题。当你把 channel 作为参数传递时,多个 goroutine 都可以共享同一个 channel
}
// 结构体测试值/指针接口方法
type Dog struct {
Name string
Age int
}
// 值接收者
func (d Dog) Greet(){
fmt.Println("值接收者",d.Name)
}
// 指针接收者
func (d *Dog) Hello(){
d.Age++
}
// 测试实现接口方法
type Dogs struct{
Name string
}
func (d Dogs) Speak() string {
return "wangwang"
}
Golang 基本语法 Golang
package main
import "fmt"
// 基本语法
func main(){
// 一、变量声明 Go有多种方式声明变量
// 1.显式声明
var x int
x = 10
// 2.简短声明 (简短声明只能是新变量,现在给上面的x使用简短声明,则会报错)
k := 10
// 二、数据类型
// 1.整型 int int8 int16 int32 int64
var int_a int = 5
// 2.浮点型 float32 float64
var float_a float32 = 3.14
// 3.字符串 string
var string_a string = "你好,Golang"
// 4.布尔型 bool
var bool_a bool = true
var bool_b bool = false
// 5.字符型 rune
// 三、常量声明 (常量一旦声明就不能更改其值。常量声明使用cost关键字)
const PI = 3.14
const Greeting = "Hello"
// 四、控制结构
// 条件语句 Go使用 if 语句进行条件判断
ifa := 10
if ifa > 10 {
fmt.Println("ifa 大于 10")
} else {
fmt.Println("ifa 小于等于 10")
}
// 循环语句 Go只有一个循环语句 for, 它可以用于多种情况
for i :=0; i < 5; i++ {
fmt.Println(i)
}
// 使用for 模拟 while 循环
wi := 0
for wi < 5 {
fmt.Println(wi)
wi++
}
// 无限循环
for {
fmt.Println("这是一个无限循环(死循环)")
}
// Switch 语句
day := 2
switch day {
case 1:
fmt.Println("这是1")
case 2:
fmt.Println("这是2")
default:
fmt.Println("不是1也不是2")
}
// Select语句 (用于多路选择 常用于 goroutines ) 此处略过
// 五、函数
// 函数声明 (函数使用func 关键字声明,函数可以有多个返回值)
func add(a int, b int) int {
return a + b
}
// 多返回值函数
func divide(a int, b int) (int, int) {
return a / b, a % b
}
// 匿名函数
addfunc := func(a, b int) int {
return a + b
}
fmt.Println(addfunc(3, 4))
// 函数作为参数(go支持将函数作为参数传递)
func apply(a int, f func(int) int) int {
return f(a)
}
func square(x int) int {
return x * x
}
fmt.Println(apply(5, square)) // 25
// 六、数组与切片
// 数组(Go中的数组大小是固定的,一旦声明不能更改大小)
var arr [3]int = [3]int{1, 2, 3}
fmt.Println(arr)
// 切片(切片是GO中更加灵活的数组,可以动态增长)
var slice []int = []int{1, 2, 3}
slice = append(slice, 4)
fmt.Println(slice)
// 七、结构体(结构体是 Go 中的复合数据类型,通常用来表示对象或记录)
type Person struct {
Name string
Age int
}
p := Person{Name: "John", Age: 30}
fmt.Println(p.Name)
// 八、接口(接口是 Go 中的核心概念之一,它定义了行为而不关心具体类型)
type Speaker interface {
Speak() string
}
type Person struct {
Name string
}
func (p Person) Speak() string {
return "Hello, my name is " + p.Name
}
func introduce(speaker Speaker) {
fmt.Println(speaker.Speak())
}
p := Person{Name: "John"}
introduce(p)
// 九、错误处理(Go 中没有异常处理机制,而是通过返回值来传递错误信息)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
Go Hello World Golang
首先安装golang环境,安装后使用 go version 验证是否安装成功
Hello World
在 Go 中,程序的入口是 main 函数。下面是一个简单的 Go 程序:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
- package main:声明包名,main 是 Go 程序的入口包。
- import "fmt":导入标准库 fmt,用于格式化输入输出。
- func main():main 函数是 Go 程序的入口函数,程序从这里开始执行。
- fmt.Println:输出内容。
运行代码
go run main.go
开始学习Golang Golang
从 PHP 转向 Go:探索 Go 语言的优势与提升的技能
随着技术的不断发展,越来越多的开发者选择在不同的编程语言之间转换,以应对日益增长的需求和挑战。作为一个曾经的 PHP 开发者,我决定开始学习 Go 语言,并探索它带来的新体验。Go(也叫 Golang)是一门由 Google 开发的编程语言,因其高效、简洁、并发友好的特性,成为了很多开发者和企业的首选语言。
在这篇文章中,我将分享 Go 语言的几个关键优势,以及从 PHP 转向 Go 后,哪些技能和体验会得到显著提升。
Go 的优势
1. 高效的性能
Go 是一门编译型语言,它编译后的代码能够直接运行在机器上,性能接近 C 语言,而不需要像 Java 那样通过虚拟机执行。Go 的高效性能使其在处理高并发任务和大规模分布式系统时,能够提供优异的表现。这对于开发 Web 服务、微服务以及高流量应用非常重要。
相比 PHP,Go 的运行效率显著提高,尤其是在处理大量请求或高并发任务时,Go 可以更好地利用硬件资源,减少延迟。
2. 简洁的语法
Go 语言的设计哲学之一是简洁性,它去除了许多传统编程语言中复杂的特性,例如类、继承、泛型等,使得语言本身更加易学易用。Go 的语法非常简洁,只有少量的关键字和规则,这使得代码更容易阅读和维护。
从 PHP 转向 Go 后,我发现 Go 的代码结构更加直观,函数和方法的定义、变量的声明都非常清晰。你可以在很短的时间内理解其他开发者的代码,提高了团队协作的效率。
3. 内建并发支持
Go 的并发编程是其最突出的特点之一。Go 使用 goroutines 和 channels 来实现轻量级并发,这比传统的线程更加高效和易用。在 Go 中,启动一个新的 goroutine 只需要一个 go
关键字,它能够并发地执行任务,而不会造成性能瓶颈。
在 PHP 中,通常需要使用额外的工具或扩展来处理并发,而 Go 内建的并发支持使得并发编程变得更加简单和高效。这对于构建高并发的 Web 服务或实时系统来说,Go 是一个理想的选择。
4. 垃圾回收与内存管理
Go 提供了自动垃圾回收功能,它能够在后台处理内存的分配和释放,减少了手动管理内存的复杂性。Go 的垃圾回收机制是增量式的,能够有效地减少停顿时间,保持应用的高性能。
对于 PHP 开发者来说,Go 的内存管理相比 PHP 的 garbage collector
更加高效,可以帮助你在开发中更加专注于业务逻辑,而无需过多担心内存泄漏等问题。
5. 强大的标准库和生态系统
Go 提供了一个丰富的标准库,涵盖了 Web 开发、文件处理、网络编程、加密等各个方面。这意味着你可以不依赖第三方库就能实现许多常见功能。Go 的标准库文档也非常详细,学习起来非常方便。
另外,Go 语言的生态系统正在快速发展,越来越多的第三方库和框架被社区提供。例如,常用的 Web 框架(如 Gin、Echo)以及数据库操作库(如 GORM)都能帮助开发者快速开发应用。
结语
虽然目前在 PHP 中使用 Webman 提供 Web 接口,能够带来非常棒的编程体验,且开发速度较快,但 Go 语言在性能和可扩展性上具备明显的优势。Go 的高性能使得它在处理大规模并发和高负载时更加得心应手,同时它的扩展性也为构建复杂的系统提供了更多的可能性。在未来的职业发展中,掌握 Go 语言将使你在竞争中更具优势。尽管现在使用 Webman 感觉非常舒适,但成长往往来自于跳出舒适圈。加油!