【go从入门到精通】go基本类型和运算符用法

             大家好,这是我给大家准备的新的一期专栏,专门讲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的常用的运算符用法和其他语言没有区别