大家好,这是我给大家准备的新的一期专栏,专门讲golang,从入门到精通各种框架和中间件,工具类库,希望对go有兴趣的同学可以订阅此专栏。
---------------------------------------------------------------------------------------------------------------------------------
上一篇文章中 【go从入门到精通】go包,内置类型和初始化顺序 偏重概念和知识,想必大家都对如何开始写代码已经开始蠢蠢欲动了,但是不要着急,基础知识扎实,能让你少走很多弯道。
基本数据类型
正如前一篇的内容,go的基本数据类型和其他语言都差不多,我们可以先看看go是如何声明变量的。
标准声明
Go语言的变量声明格式为:
var 变量名 变量类型
变量声明以关键字var
开头,变量类型放在变量的后面,行尾无需分号。 举个例子:
var str string
var i int32
var b bool
批量声明
每声明一个变量就需要写var
关键字会比较繁琐,go语言中还支持批量变量声明:
var (
a string
b int
c bool
d float32
)
变量的初始化
Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false
。 切片、函数、指针变量的默认为nil
。
当然我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:
var 变量名 类型 = 表达式
举个例子:
var a int32 = 1
类型推导
有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。
var str = "pprof.cn"
var i = 1
短变量声明
在函数内部,可以使用更简略的 := 方式声明并初始化变量。
package main
import (
"fmt"
)
func main() {
var i = 10
j := 100
fmt.Println(i,j)
}
匿名变量
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量。 匿名变量用一个下划线_表示,例如我希望通过strconv。Atoi来将数字字符串转整型数字,由于这个函数有2个返回值,我只想用i接收最终的整型变量,用_来作为匿名变量,可以用下面的代码来实现:
package main
import (
"fmt"
"strconv"
)
func main() {
i,_:=strconv.Atoi("100")
fmt.Println(i)
}
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。 (在Lua等编程语言里,匿名变量也被叫做哑元变量。)
注意事项:
函数外的每个语句都必须以关键字开始(var、const、func等)
:=不能使用在函数外。
_多用于占位,表示忽略值。
const常量的初始化
相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。
const pi = 3.1415
const同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:
const (
n1 = 100
n2
n3
)
上面示例中,常量n1、n2、n3的值都是100。
其他注意的
但是这里要注意的一些区别是:
(1) 空指针值 nil :
package main
import (
"fmt"
)
func main(){
i := 0
p := &i
if p == nil {
fmt.Println("p is nil")
}
}
(2)多行字符串的输出方式:
Go语言中要定义一个多行字符串时,就必须使用反引号
字符:
package main
import (
"fmt"
)
func main(){
s := `this
is
golang
program`
fmt.Println(s)
}
反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
(3)byte和rune类型
组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:
Go 语言的字符有以下两种:
uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
rune类型,代表一个 UTF-8字符。
var a := '啊'
var b := 'b'
当需要处理中文、日文或者其他复合字符时,则需要用到rune
类型。rune
类型实际是一个int32
。
Go 使用了特殊的 rune
类型来处理 Unicode
,让基于 Unicode
的文本处理更为方便,也可以使用 byte
型进行默认字符串处理,性能和扩展性都有照顾
package main
import (
"fmt"
)
func disp() {
s := "hello 你好"
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i])
}
fmt.Println("")
for _, r := range s { //rune
fmt.Printf("%v(%c) ", r, r)
}
}
func main() {
disp()
}
输出:
104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 228(ä) 189(½) 160( ) 229(å) 165(¥) 189(½)
104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 20320(你) 22909(好)
因为UTF8编码下一个中文汉字由3-4
个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。
字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
我们有时候会有这样的需求,判断某个游戏玩家起的昵称不能超过10个汉字或者字符,那么我想你这个时候就知道用哪种方式了。
(4)修改字符串
要修改字符串,需要先将其转换成[]rune或[]byte
,完成后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组。
package main
import (
"fmt"
)
func modstring1(s string) {
// 强制类型转换
bytes := []byte(s)
bytes[0] = 'H'
fmt.Println(string(bytes))
}
func modstring2(s string) {
// 强制类型转换
bytes := []rune(s)
bytes[0] = '你'
fmt.Println(string(bytes))
}
func main() {
s := "hello"
modstring1(s)
modstring2(s)
}
(5)array数组
go的数组array是同一种数据类型的固定长度的序列。
(1)如何定义一个数组呢?我们可以用: var 数组名 [长度],不过你要注意的是长度也是数组类型的一部分。
比如:
var a [10]int 和var a [100]int是不同的类型
var arr0 [5]int = [5]int{1, 2, 3} // 未初始化元素值为 0。
c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
var arr2 = [...]int{1, 2, 3, 4, 5, 6} // 通过初始化值确定数组长度。
(2)数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
遍历数组有两种方式:
for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
(3)访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
package main
import "fmt"
func main() {
a := [3]int{1, 2, 3}
b := a[0]
c := a[6]
fmt.Println(b, c)
}
运行时报错信息如下:
PS E:\project\go\hello> ./main.exe
panic: runtime error: index out of range [6] with length 3
goroutine 1 [running]:
main.main()
E:/project/go/hello/main.go:8 +0x1d
(4) 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
此时此刻, 一定有人想做下验证,于是写了如下代码:
package main
import "fmt"
func fun1(a []int) {
a[0] = 100
}
func main() {
a := []int{1, 2, 3}
fun1(a)
fmt.Println(a[0])
}
这段代码似乎是想修改数组a的第一个元素的值,但是很不幸,你可能认为调用fun1之后,a的第一个元素的值仍然是1。
为什么呢? 因为你传的不是数组,而且切片。 数组是指定了长度,且是值传递 而切片是没有指定长度,且是引用传递。所以你的代码改成这样就没有问题了:
package main
import "fmt"
func fun1(a [3]int) {
a[0] = 100
}
func main() {
a := [3]int{1, 2, 3}
fun1(a)
fmt.Println(a[0])
}
(6)slice和map
后边逐步针对这slIce,map,以及和数组的三者之间的比较差异来细细道来。
类型转换
Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。
强制类型转换的基本语法如下:
目标类型(表达式)
其中,目标类型表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.
package main
import (
"fmt"
)
func main() {
i := int32(0)
var j int
j = i
fmt.Println(j)
}
比如这段代码,简简单单,但是编译的时候报错:
cannot use i (variable of type int32) as int value in assignment
所以我们在把i赋值给j的时候,需要做个类型强制转换,j = int(i)
运算符
go的常用的运算符用法和其他语言没有区别