我是靠谱客的博主 土豪面包,最近开发中收集的这篇文章主要介绍go语言学习(第二章,类型)(go 语言学习笔记),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

第二章 类型

2.1 变量

定义

用关键字var定义变量,变量类型放在后面,运行时自动初始化为二进制零值,避免出现不可预测行为,如果显示提供初始化值,可以省略变量类型,由编译器推断

var x int		// 自动初始化为 0
var y = false	// 自动推断为bool类型

可以一次定义多个变量,初始化不同类型

var x,y int
var a,s = 100, "aaa"

惯例使用组方式整理多行变量

var (
	x,y int
	a,s = 100,"ass"
)

简短模式

除关键字var外,可以使用简短的变量定义方法

func main(){
	x := 100
	a,s := 1,"as"
}

注意,简短模式限制

  • 定义变量,同时显式初始化
  • 不能提供数据类型
  • 只能用在函数内部
    对于新手,可能造成意外错误,比如,原打算修改全局变量,结果变成重新定义同名局部变量
package main

var x = 100

func main(){
	println(&x,x)
	x := 10
	println(&x,x)
}
结果
0x4b2138 100
0xc00002df80 10

简短模式并不总是重新定义变量,也可能是部分退化的赋值操作

package main

var y = 33

func main(){
	var x = 100
	println(&x,x)
	println(&y,y)
	x, y ,z:= 10,22,33
	println(&x,x)
	println(&y,y)
	println(z)
}
结果
0xc00002df80 100
0x4b2138 33
0xc00002df80 10
0xc00002df78 22
33

可以看到x的地址相同,y的地址不同
退化赋值的前提条件是:最少有一个新变量被定义,且必须是同一作用域
在处理函数错误返回值时,退化赋值允许我们重复使用err变量,这是相当有益的。

package main

import(
	"log"
	"os"
)

func main(){
	f,err := os.Open("/dev/random")
	...
	buf := make([]byte,1024)
	n,err := f.Read(buf)
	...
}

多变量赋值

在进行多变量赋值操作时,先计算出所有右值,再依次完成赋值操作

package main

func main() {
	x, y := 1, 2
	x, y = y+3, x+2
	println(x, y)
}
结果
5 3

未使用错误

编译器会将未使用的变量和引用包报错

2.2 命名

对于变量、常量、函数、自定义类型,通常选用有实际含义,易于阅读和理解的字母或单词组合。
命名建议:

  • 以字母或下划线开始,由多个字母、数字和下划线组合而成。
  • 区分大小写
  • 使用驼峰拼写格式
  • 局部变量优先使用短名
  • 不要使用保留关键字
  • 不建议使用与预定义常量、类型、内置函数相同的名字
  • 专有名词通常会全部大写
package main

func main() {
	var c int 			// c代替成count
	for i := 0; i <10 ; i++{		// i 代替 index
		c++
	}
	println(c)
}

符号名字首字母大小决定了作用域。首字母大写的为导出成员,可被包外引用,而小写则仅能在包内使用

空标识符

和Python类似,Go也有个名为“_”的特殊成员,通常作为忽略占位符使用,可作表达式左值,无法读取内容。

package main

import "strconv"

func main() {
	x,_ := strconv.Atoi("12") 			// _ 用来忽略 返回的err
	println(x)
}

2.3 常量

常量表示运行时恒定不可改变的值,通常是一些字面量。使用常量就可用一个易于阅读理解的标识符来代替“魔法数字”,也使得在调整常量值时,无需修改所有代码。
常量值必须是编译期可确定的字符、字符串、数字或布尔类型。可指定常量类型,或由编译器通过初始值推断,不支持C/C++数字类型后缀
未使用的常量不会引发编译错误

package main

const x, y int = 123,0x22
const s string = "asd"
const c = "我"

const (
	i, f = 1,0.123
	b = false
)

func main() {
	println(x,y)
}

如果显示指定类型,必须保证常量左右类型一致,需要时可做显示转换。右值不能超出常量类型取值范围,否则会引发溢出错误

package main

func main() {
	const(
		x,y int = 99,-999
		b byte = byte(x)
		n = uint8(y)		// 错误:constant -999 overflows uint8
	)
}

常量值也可以是某些编译器能计算出结果的表达式,如unsafe.Sizeof 、len、cap 等

package main

import (
	"unsafe"
)

func main() {
	const(
		ptrSize = unsafe.Sizeof(uintptr(0))
		strSize = len("hello")
	)
	println(ptrSize,strSize)
}

在常量组中,如果不指定类型和初始化值,则与上一行非空常量右值相同

package main

import "fmt"

func main() {
	const (
		x uint16 = 120
		y
		s = "asd"
		z
	)
	fmt.Printf("%T,%vn", y, y)
	fmt.Printf("%T,%vn", z, z)
}
结果
uint16,120
string,asd

枚举

Go 并没有明确意义上的enum定义,不过可借助itoa标识符实现一组自增常量值来实现枚举类型。

package main
func main() {
	const (
		x = iota
		y
		z
	)
	println(x, y, z)
}
结果
0 1 2 
package main

func main() {
	const (
		_ = iota		// 0
		x = 1 << (10 * iota) // 1 << (10 * 1) 
		y					// 1 << (10 * 2)
		z
	)
	println(x, y, z)
}
结果
1024 1048576 1073741824

自增作用范围为常量数组。可在多常量定义中使用多个iota,它们各自单独计数,只需确保组中每行常量的列数量相同即可

package main

func main() {
	const (
		_,_ = iota,iota *10 
		x,y 
		z,k
	)
	println(x, y, z,k) //1,10,2,20
}

如中断itoa自增,必须显示恢复。且后续自增值按行序递增,而非C enum 那般按上一取值递增

package main

func main() {
	const (
		_ = iota
		x				// 1
		y = 100			// 100
		z 				// 100
		k = iota		// 4
	)
	println(x, y, z,k)
}

在实际编码中,建议使用自定义类型实现用途明确的枚举类型。但这并不能将取值范围限定在预定义的枚举内。

package main

type color byte
const(
	black color = iota
	red
	blue
)

func test(c color){
	println(c)
}
func main() {
	test(red)
	test(100)		//100 并未超出color/byte 类型
	// x := 1 
	//test(x)  //cannot use x (type int) as type color in argument to test
}

展开

常量除了“只读”外,和变量究竟有什么不同?
不同于于变量在运行期间分配储存内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用

2.4 基本类型

数据类型:

类型长度默认值说明
bool1false
byte10uint8
int,uint4,80默认整数类型,依据目标平台,32或64位
int8,unit810-128-127,0-255
int16,unit1620-32768-32767 , 0-65535
int32,unit3240-21亿-21亿,0-42亿
int64,unit6480
float3240.0
float6480.0默认浮点数类型
complex648
complex12816
rune40Unicode Code Point, int32
uintptr4,80足以储存指针的uint
string“”字符串,默认空字符串,而非NULL
arry数组
struct结构体
functionnil函数
interfacenil接口
mapnil字典,引用类型
slicenil切片,引用类型
channelnil通道,引用类型

支持八进制,十六进制及科学计数法。标准库math定义了各个数字类型呢的取值范围

package main

import (
	"fmt"
	"math"
)

func main() {
	a, b, c := 100, 0144, 0x64
	println(a, b, c)
	fmt.Printf("0b%b,%#o,%#xn", a, a, a)
	fmt.Println(math.MaxInt64, math.MinInt64)
}
result:
100 100 100
0b1100100,0144,0x64
9223372036854775807 -9223372036854775808

标准库strconv可以在不同进制(字符串)间转换

package main

import (
	"strconv"

)

func main() {
	a,_ := strconv.ParseInt("1100100",2,32)
	b,_ := strconv.ParseInt("0144",8,32)
	c,_ := strconv.ParseInt("64",16,32)
	println(a,b,c)
	println("0b"+strconv.FormatInt(a,2))
	println("0"+strconv.FormatInt(a,8))
	println("0b"+strconv.FormatInt(a,16))
}
结果
100 100 100
0b1100100
0144
0b64

使用浮点数时,需注意小数的有效精度,相关细节参考IEEE-754标准

package main

import "fmt"

func main() {
	var a float32 = 1.1234567899
	var b float32 = 1.12345678
	var c float32 = 1.123456781
	println(a, b, c)
	println(a == b, b == c)
	fmt.Printf("%v, %v, %v", a, b, c)

}
结果:
+1.123457e+000 +1.123457e+000 +1.123457e+000
true true
1.1234568, 1.1234568, 1.1234568

别名

在官方语言规范中,专门提到两个别名

byte				alias for unit8
rune				alias for int32

别名类型无需转换,可以直接赋值

package main

func test(x byte) {
	println(x)
}
func main() {
	var a byte = 0x11
	var b uint8 = a
	var c uint8 = a + b
	test(c)
}
结果
34

但这并不表示,拥有相同底层结构的就属于别名,就算在64位平台上,int 和 int64 结构完全一致,也分属于不同类型,需显示转换

package main

func add(x, y int) int {
	return x + y
}
func main() {
	var x int = 100
	var y int64 = x		 //cannot use x (type int) as type int64 in assignmen
	add(x, y) 		//cannot use y (type int64) as type int in argument to add
}

2.5 引用类型

所谓引用类型,特指slice、map、channel这三种预定义类型
相比数字、数组等类型,引用类型拥有更复杂的存储结构。除分配内存外,它们还需要初始化一系列属性,如指针,长度,甚至包括哈希分布、数据队列等。
内置函数new按指定类型长度分配零值内存,返回指针,并不关心类型内部构造和初始化方式。而引用类型必须使用make函数创建,编译器会将make转换成为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化

package main

func mkslice()[]int{
	s := make([]int,0,10)
	s = append(s,100)
	return s
}

func mkmap()map[string]int{
	m := make(map[string]int)
	m["a"] = 1
	return m
}
func main() {
	m := mkmap()
	println(m,m["a"])
	s := mkslice()
	println(s,s[0])
}
结果
0xc00002de88 1
[1/10]0xc00002de38 100

当然,new函数也可为引用类型分配内存,但这不是完整创建。以字典为例,它仅分配了字典类型本身(实际上就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作

package main

import "fmt"

func main() {
	p := new(map[string]int)
	m := *p
	m["a"] = 1
	fmt.Println(m)		//panic: assignment to entry in nil map
}

2.6 类型转换

隐式转换造成的问题远大于它带来的好处
除常量、别名类型以及未命名类型外,Go强制要求显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义

a := 10
b := byte(a)
c := a + int(b)		// 混合类型表达式必须确保类型一致

同样,不能将非bool类型当做 true/false 使用

package main

func main() {
	x := 100
	var b bool = x		//cannot use x (type int) as type bool in assignment
	if x {			//non-bool x (type int) used as if condition

	}
}

语法歧义

如果转换的目标是指针、单向通道或没有返回值的函数类型,那么必须使用括号,以避免造成语法歧义

package main

func main() {
	x := 100
	p := (*int)(&x)   // 不能写成 *int(&x)
	println(p,&x)
}
结果
0xc00002df80 0xc00002df80

2.7 自定义类型

使用关键字type 定义用户自定义类型,包括基于现有基础类型创建,或者是结构体、函数类型等。

package main

import "fmt"

type flags byte

const (
	read flags = 1 << iota
	write
	exec
)

func main() {
	f := read | exec
	fmt.Printf("%bn", f)
}
结果
101

和var、const类似,多个type定义可合并成组,可在函数或代码块内定义局部类型

package main

import "fmt"


func main() {
	type(
		user struct{
			name string
			age uint8
		}
		event func(string)bool 
	)
	u := user{"tom",20}
	fmt.Println(u)
	var f event = func(s string)bool{
		println(s)
		return s != ""
	}
	f("abc")
}
结果
{tom 20}
abc

即便指定了基础类型,也只能表明他们具有相同的底层数据结构,两者间不存在任何关系,属于完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视作别名,不能用于直接比较

package main

func main() {
	type data int
	var d data = 10
	var x int = d			//cannot use d (type data) as type int in assignment
	println(x)
	println(x == d)			//invalid operation: x == d (mismatched types int and data
}

未命名类型

与有明确标识符的bool、int、string等类型相比,数组、切片、字典、通道等类型与具体元素类型或长度等属性有关,故称未命名类型。当然,可用type为其提供具体名称,将其改变为命名类型
具有相同声明的未命名类型被视为同一类型:

  • 具有相同基类型的指针
  • 具有相同元素类型和长度的数组(array)
  • 具有相同元素类型的切片(slice)
  • 具有相同类型的字典(map)
  • 具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体
  • 具有相同签名(参数和返回列表,不包括参数名)的函数(func)
  • 具有相同方法集(方法名,方法签名,不包括顺序)的接口(interface)

容易被忽视的是struct tag,它也属于类型组成部分,而不仅仅是元数据描述

package main

import "fmt"

func main() {
	var a struct {
		x string `x`
		y int    `y`
	}
	var b struct {
		x string
		y int
	}
	_ = a
	//b = a		//cannot use a (type struct { x string "x"; y int "y" }) as type struct { x string; y int } in assignment
	fmt.Println(b)
}

同样,函数的参数顺序也属于签名组成部分

package main

func main() {
	var a func(int, string)
	var b func(string, int)
	b = a // cannot use a (type func(int, string)) as type func(string, int) in assignment
	b("s", 1)
}

未命名类型转换规则

  • 所属类型相同
  • 基础类型相同,且其中一个是未命名类型
  • 数据类型相同,将双向通道赋值给单向通道,且其中一个为未命名类型
  • 将默认值nil赋值给切片、字典、通道、指针、函数或接口
  • 对象实现了目标接口
package main

import "fmt"

func main() {
	type data [2]int
	var d data = [2]int{1, 2}	//基础类型相同,右值为未命名类型
	fmt.Println(d)
	a := make(chan int, 2)
	var b chan<- int = a		//将双向通道转换为单向通道,其中b为未命名类型
	b <- 2
}

最后

以上就是土豪面包为你收集整理的go语言学习(第二章,类型)(go 语言学习笔记)的全部内容,希望文章能够帮你解决go语言学习(第二章,类型)(go 语言学习笔记)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部