03、Golang 教程 - 变量/常量与数据类型

一、变量

1. 定义

  • 变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。
  • 变量可以通过变量名访问。
  • Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
  • 声明变量的一般形式是使用 var 关键字。
var identifier type
//可以一次声明多个变量
var identifier1, identifier2 type

2. 变量声明方式

第一种:指定变量类型,如果没有初始化,则变量默认为零值

var num1 int = 123
var num2 float32	// num2 没有赋值,默认为 0

//---------------------------
package main

import "fmt"

var num1 int = 123
var num2 float32

func main() {
   
     
	fmt.Println(num1)
	fmt.Println(num2)
}
//---------------------------
结果:
123
0

第二种:根据值自行判定变量类型

var num = 456

//-----------------------
package main

import "fmt"

var num = 456

func main() {
   
     
	fmt.Println(num)
}
//----------------------------
456

第三种:如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误

v_name := value

//---------------------------
var intVal int 
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明

intVal := 1 
//等于
var intVal int
intVal =1 

f := "Runoob" // var f string = "Runoob"

3. 多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

4. 全局变量和局部变量

package main

import "fmt"

//全局变量
var num1 = 123
var num2 = 456

func main() {
   
     
	//局部变量
	num3 := 789
	fmt.Println(num1)
	fmt.Println(num2)
	fmt.Println(num3)
}

程序运行的时候,先加载全局变量,然后加载局部变量。

5. 变量的作用域

http://c.biancheng.net/view/4032.html

  • 在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。
  • 在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用。当然,不包含这个全局变量的源文件需要使用 “import” 关键字引入全局变量所在的源文件之后才能使用这个全局变量。全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。
  • 在定义函数时函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。形式参数会作为函数的局部变量来使用。

 

6. 值类型与引用类型

6.1 值类型

  • 所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量 直接指向存在内存中的值
     
  • 当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝。
     
  • 可以通过 &i 来获取变量 i 的内存地址,例如:0xc00000a0a0(每次的地址都可能不一样)。
package main

import "fmt"

func main() {
   
     
	i := 50
	fmt.Println(&i)
}

//结果
0xc00000a0a0

  • 值类型变量的值存储在堆中。
  • 内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。

值的拷贝

//定义了一个数组 a,它是值类型,复制给 b 是 copy,当 b 发生变化后 a 并不会发生任何变化
package main

import "fmt"

func main(){
   
     
	a := [5]int{
   
     2, 3, 4, 5, 6}
	b := a
	fmt.Println(a,b)
	b[2] = 77
	fmt.Println(a,b)
}

//结果
[2 3 4 5 6] [2 3 4 5 6]
[2 3 4 5 6] [2 3 77 5 6]

值引用

//定义了一个数组 a,它是引用类型(slice 切片),被 b 引用(指针)后,当 b 发生变化后 a 也发生任何变化
package main

import "fmt"

func main(){
   
     
	a := []int{
   
     2, 3, 4, 5, 6}
	b := a
	fmt.Println(a,b)
	b[2] = 77
	fmt.Println(a,b)
}

//结果
[2 3 4 5 6] [2 3 4 5 6]
[2 3 77 5 6] [2 3 77 5 6]

6.2 引用类型

  • 复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。
  • Golang 中引用类型:指针,slice,map,channel,接口,函数等。变量存放的是一个内存地址值,这个地址值指向的空间存的才是最终的值。内存通常在堆中分配,当没有任务变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,通过 GC 回收。
  • Go 没有引用传递,都是值传递。
var a = []int{
   
     1,2,3,4,5}
b := a      //此时a,b都指向了内存中的[1 2 3 4 5]的地址
b[1] = 10   //相当于修改同一个内存地址,所以a的值也会改变
c := make([]int,5,5)    //切片的初始化
copy(c,a)   //将切片acopy到c
c[1] = 20   //copy是值类型,所以a不会改变
fmt.Printf("a的值是%v,a的内存地址是%p\n",a,&a)   //a的值是[1 10 3 4 5],a的内存地址是0xc42000a180
fmt.Printf("b的值是%v,b的内存地址是%p\n",b,&b)   //b的值是[1 10 3 4 5],b的内存地址是0xc42000a1a0
fmt.Printf("c的值是%v,c的内存地址是%p\n",c,&c)   //c的值是[1 20 3 4 5],c的内存地址是0xc42000a1c0
d := &a     //将a的内存地址赋值给d,取值用*d
a[1] = 11
fmt.Printf("d的值是%v,d的内存地址是%p\n",*d,d)   //d的值是[1 11 3 4 5],d的内存地址是0xc420084060
fmt.Printf("a的值是%v,a的内存地址是%p\n",a,&a)   //a的值是[1 11 3 4 5],a的内存地址是0xc420084060

总结:
值赋值(值类型)和地址赋值(引用类型)是不一样的,值赋值它们的内存地址是不同的,只是值一样,修改一个变量的值不会改另一个;地址赋值两个变量指向了同一个地址,这个内存地址存放了一个值,表现出来的就是两个变量值一样,修改其中一个值另一个也会修改。

https://zhuanlan.zhihu.com/p/360306642
https://ld246.com/article/1566790851610
https://www.cnblogs.com/aresxin/p/GO-zhi-lei-xing-yu-yin-yong-lei-xing.html

7. 空白标识符

Golang 中不允许变量声明了但不使用。既然不想使用,何必声明变量呢,那就将变量用空白符代替,反正空白符就是用来抛弃的。

package main

import "fmt"

func main() {
   
     
	str1,str2,str3,str4 := test()
	fmt.Println("str1=",str1,"\nstr2=",str2,"\nstr3=",str3,"\nstr4=",str4)
}

func test() (int,float64,bool,string) {
   
     
	a,b,c,d := 20,30.5,true,"hello"
	return a,b,c,d
}

// 结果
str1= 20 
str2= 30.5 
str3= true 
str4= hello

上面的代码如果我们不想要输出第一个参数怎么办呢,代码如下:

package main

import "fmt"

func main() {
   
     
	_,str2,str3,str4 := test()
	fmt.Println("str2=",str2,"\nstr3=",str3,"\nstr4=",str4)
}

func test() (int,float64,bool,string) {
   
     
	a,b,c,d := 20,30.5,true,"hello"
	return a,b,c,d
}

// 结果
str2= 30.5 
str3= true 
str4= hello

想要替换哪个就在相应位置设为 -

二、常量

1. 定义

  • 常量是一个简单值的标识符,在程序运行时,不会被修改的量。
  • 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
  • 常量的定义格式:
const identifier [type] = value

可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义:const b string = "abc"
  • 隐式类型定义:const b = "abc"

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

声明一个常量

const MAX = 4096

声明一个指定类型的常量

const LIMIT int16 = 1024
const LIMIT2 = int16(1024)

声明一组常量

const (
    start  = 0x1 
    resume = 0x2 
    stop   = 0x4 
)

声明一组指定类型的常量

const (
    start  int8 = 0x1 
    resume int8 = 0x2 
    stop   int8 = 0x4 
)

综合示例

package main

import "fmt"

func main() {
   
     
   const LENGTH int = 10
   const WIDTH int = 5  
   var area int
   const a, b, c = 1, false, "str" //多重赋值

   area = LENGTH * WIDTH
   fmt.Printf("面积为 : %d", area)
   println()
   println(a, b, c)  
}

//结果
面积为 50
1 false str

2. iota

  • iota:特殊常量,可以认为是一个可以被编译器修改的常量。
  • iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
package main

import "fmt"

func main() {
   
     
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

// 结果
0 1 2 ha ha 100 100 7 8

package main

import "fmt"
const (
    i=1<<iota
    j=3<<iota
    k
    l
)

func main() {
   
     
    fmt.Println("i=",i)
    fmt.Println("j=",j)
    fmt.Println("k=",k)
    fmt.Println("l=",l)
}

// 结果
i= 1
j= 6
k= 12
l= 24

 

https://www.runoob.com/go/go-constants.html
https://studygolang.com/articles/3040

三、数据类型

  • 在 Go 编程语言中,数据类型用于声明函数和变量。
  • 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
  • Go 语言按类别有以下几种数据类型:
序号 类型和描述
1 布尔型: 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2 数字类型: 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4 派生类型: 包括:指针类型(Pointer),数组类型,结构化类型(struct),Channel 类型,函数类型,切片类型,接口类型(interface),Map 类型

1. 布尔值

package main

import "fmt"

func main() {
   
     
	var a bool = true
	var b bool
	fmt.Println(a)
	fmt.Println(b)
}

// 结果
true
false

2. 数字类型

序号 类型和描述
1 uint8 无符号 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
package main

import "fmt"

func main() {
   
     
	a := 10
	b := 10.0
	c := true
	d := "test"
	fmt.Printf("a 的类型为 %T",a)
	fmt.Println()
	fmt.Printf("b 的类型为 %T",b)
}

//结果
a 的类型为 int
b 的类型为 float64

//---------------------------------------------//
package main

import (
    "fmt"
    "math"
)

func main() {
   
     
    fmt.Printf("%f\n", math.Pi)
    fmt.Printf("%.2f\n", math.Pi)
}

// 结果
3.141593
3.14

//-----------------------------------------//
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

Go语言整型(整数类型)
[Go语言浮点类型(小数类型)][Go 1]
[Go语言复数][Go 2]

3. 字符串类型

package main

import "fmt"

func main() {
   
     
	a := "ddasas2252"
	fmt.Println(a)
	fmt.Printf("a 的类型为 %T",a)
}

// 结果
ddasas2252
a 的类型为 string

字符类型http://c.biancheng.net/view/18.html

4. 其他类型

4.1 指针类型

  • & 取内存地址
  • *** 根据地址取值**

基本数据类型和内存

package main
import(
        "fmt"
)
func main(){
   
     
        var age int = 18
        //&符号+变量 就可以获取这个变量内存的地址
        fmt.Println(&age) //0xc0000a2058
}

 

指针变量

package main
import(
        "fmt"
)
func main(){
   
     
        var age int = 18
        //&符号+变量 就可以获取这个变量内存的地址
        fmt.Println(&age) //0xc0000a2058
        //定义一个指针变量:
        //var代表要声明一个变量
        //ptr 指针变量的名字
        //ptr 对应的类型是:*int 是一个指针类型 (可以理解为指向int类型的指针)
        //&age 就是一个地址,是ptr变量的具体的值
        var ptr *int = &age
        fmt.Println(ptr)
        fmt.Println("ptr本身这个存储空间的地址为:",&ptr)
        //想获取ptr这个指针或者这个地址指向的那个数据:
        fmt.Printf("ptr指向的数值为:%v",*ptr) //ptr指向的数值为:18
}

 

可以通过指针改变指向值

func main(){
   
     
        var num int = 10
        fmt.Println(num)
        var ptr *int = &num
        *ptr = 20
        fmt.Println(num)
}

// 结果
10
20

  • 指针变量接收的一定是地址值。
  • 指针变量的地址不可以不匹配。
  • 基本数据类型(又叫值类型),都有对应的指针类型,形式为 *数据类型,比如 int 的对应的指针就是 *int,float32 对应的指针类型就是 *float32。依次类推。

https://www.runoob.com/go/go-pointers.html

4.2 数组类型

Golang 数据类型之数组

4.3 结构化类型(struct)

https://www.cnblogs.com/sparkdev/p/10761825.html

4.4 Channel 类型

https://www.cnblogs.com/lianggx6/p/12558663.html
https://www.jianshu.com/p/9cc621aaf899

4.5 函数类型

https://studygolang.com/articles/2841
https://www.cnblogs.com/igoodful/p/11519695.html

4.6 切片类型

https://www.cnblogs.com/Csir/p/9292146.html
https://www.imooc.com/code/7503
https://www.jb51.net/article/143729.htm

4.7 接口类型(interface)

https://www.cnblogs.com/ssgeek/p/15433859.html
https://blog.csdn.net/whatday/article/details/109775782

4.8 Map 类型

https://www.perfcode.com/p/1118.html
https://www.freesion.com/article/79891268155/
https://www.cnblogs.com/zpcoding/p/13603182.html

5. 数据类型转换

  • Go 在不同类型的变量之间赋值时需要显式转换,并且只有显式转换(强制转换)。
  • 类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型 将 int16 转换为 int32。当从一个取值范围较大的类型转换到取值范围较小的类型时 将 int32 转换为 int16 或 将 float32 转换为 int,会发生精度丢失 截断 的情况。
  • 只有相同底层类型的变量之间可以进行相互转换 如将 int16 类型转换成 int32 类型,不同底层类型的变量相互转换时会引发编译错误 如将 bool 类型转换为 int 类型。
  • 语法:valueOfTypeB = typeB(valueOfTypeA)(类型 B 的值 = 类型 B(类型 A 的值))
package main

import "fmt"

func main() {
   
     
	a := 5.2
	b := int(a)
	fmt.Println("a 的值为",a)	// 如果小数为 0 会省略
	fmt.Printf("a 的类型为 %T\n",a)
	fmt.Printf("b 的类型为 %T",b)
}

// 结果
a 的值为 5.2
a 的类型为 float64
b 的类型为 int

字符串格式化参考字符串格式化

5.1 基本数据类型转 string 类型

  • 方式 1:fmt.Sprintf("%参数",表达式)(推荐方式)
  • 方式 2:使用 strconv 包的函数

方法一

package main
import "fmt"
func main(){
   
     
	var n1 int = 19
	var n2 float32 = 4.78
	var n3 bool = false
	var n4 byte = 'a'
	var s1 string = fmt.Sprintf("%d",n1)
	fmt.Printf("s1对应的类型是:%T ,s1 = %q \n",s1, s1)
	var s2 string = fmt.Sprintf("%f",n2)
	fmt.Printf("s2对应的类型是:%T ,s2 = %q \n",s2, s2)
	var s3 string = fmt.Sprintf("%t",n3)
	fmt.Printf("s3对应的类型是:%T ,s3 = %q \n",s3, s3)
	var s4 string = fmt.Sprintf("%c",n4)
	fmt.Printf("s4对应的类型是:%T ,s4 = %q \n",s4, s4)
}

// 结果
s1对应的类型是:string ,s1 = "19" 
s2对应的类型是:string ,s2 = "4.780000" 
s3对应的类型是:string ,s3 = "false" 
s4对应的类型是:string ,s4 = "a"

方法一

package main
import(
        "fmt"
        "strconv"
)
func main(){
   
     
        var n1 int = 18
        var s1 string = strconv.FormatInt(int64(n1),10)  //参数:第一个参数必须转为int64类型 ,第二个参数指定字面值的进制形式为十进制
        fmt.Printf("s1对应的类型是:%T ,s1 = %q \n",s1, s1)
        var n2 float64 = 4.29
        var s2 string = strconv.FormatFloat(n2,'f',9,64)
        //第二个参数:'f'(-ddd.dddd)  第三个参数:9 保留小数点后面9位  第四个参数:表示这个小数是float64类型
        fmt.Printf("s2对应的类型是:%T ,s2 = %q \n",s2, s2)
        var n3 bool = true
        var s3 string = strconv.FormatBool(n3)
        fmt.Printf("s3对应的类型是:%T ,s3 = %q \n",s3, s3)
}

// 结果
s1对应的类型是:string ,s1 = "18" 
s2对应的类型是:string ,s2 = "4.290000000" 
s3对应的类型是:string ,s3 = "true"

5.2 string 类型转基本类型

package main
import(
        "fmt"
        "strconv"
)
func main(){
   
     
        //string-->bool
        var s1 string = "true"
        var b bool
        //ParseBool这个函数的返回值有两个:(value bool, err error)
        //value就是我们得到的布尔类型的数据,err出现的错误
        //我们只关注得到的布尔类型的数据,err可以用_直接忽略
        b , _ = strconv.ParseBool(s1)
        fmt.Printf("b的类型是:%T,b=%v \n",b,b)
        //string---》int64
        var s2 string = "19"
        var num1 int64
        num1,_ = strconv.ParseInt(s2,10,64)
        fmt.Printf("num1的类型是:%T,num1=%v \n",num1,num1)
        //string-->float32/float64
        var s3 string = "3.14"
        var f1 float64
        f1,_ = strconv.ParseFloat(s3,64)
        fmt.Printf("f1的类型是:%T,f1=%v \n",f1,f1)
        //注意:string向基本数据类型转换的时候,一定要确保string类型能够转成有效的数据类型,否则最后得到的结果就是按照对应类型的默认值输出
        var s4 string = "golang"
        var b1 bool
        b1 , _ = strconv.ParseBool(s4)
        fmt.Printf("b1的类型是:%T,b1=%v \n",b1,b1)
        var s5 string = "golang"
        var num2 int64
        num2,_ = strconv.ParseInt(s5,10,64)
        fmt.Printf("num2的类型是:%T,num2=%v \n",num2,num2)
}

// 结果
b的类型是:bool,b=true 
num1的类型是:int64,num1=19 
f1的类型是:float64,f1=3.14 
b1的类型是:bool,b1=false 
num2的类型是:int64,num2=0