Golang教程第2篇:包、数据类型

目录

1.关键字

break default func interface select
case defer go map struct
chan else goto package switch
continue for import return var
const fallthough if range type

2.包引用

  • 任何代码文件隶属一个包
  • 同一个包中的函数直接调用
  • 不同包中的函数通过包名加点加函数名调用

关于包的使用下面有几个例子
下面是代码结构
04go9

练习1:输出和等于5的所有等式,类似
0+5=5
1+4=5
2+3=5
3+2=5
4+1=5
5+0=5

cat go_dev/day2/example1/main/main.go

package main

import (
    "fmt"
)

func List(a int) {
    for i := 0;i < a;i++ {
        fmt.Printf("%d+%d=%d",i,a - i,a)
    }
}

func main() {
    List(5)
}

练习2:一个程序包含两个包add和main,其中add包中有两个变量:Name和Age,那么main包中如何访问Name和Age

cat go_dev/day2/example2/add/add.go

package add

var Name string = "hello world"
var Age int = 33

在解释性语言比如php、python,上面可以写成这样

package add

var Name string
var Age int

Name = "hello world"
Age = 33   

上面这种写法编译执行会报错,这是因为解释性语言是一边解释一边执行,但是Go是编译语言,需要入口函数main

可以把赋值语句放导函数中

package add

var Name string
var Age int

func Test() {
    Name = "hello world"
    Age = 33
}

注意如果使用这种写法,在入口函数main中需要调用该函数初始化 add.Test()

那么可以这样写吗

package add

Name := "hello world"
Age := 33  

这种写法编译的时一样会报错,:=其实是两句代码
var Name string
Name = "hello world"
而var Name string = "hello world"是在声明变量的时候就赋值了,这个动作在编译之前就已经完成

cat go_dev/day2/example2/main/main.go

package add

import (
    "fmt"
    "go_dev/day2/example2/add"
)

func main() {
    fmt.Println("Name=",add.Name)
    fmt.Println("Age=",add.Age)
}

编译
go build -o bin/example2.exe go_dev/day2/example2/main
执行
cd bin
example2.exe

有时候在引入第三方包的时候不是很规范,可以使用别名
cat go_dev/day2/example2/main/main.go

package add

import (
    "fmt"
    a "go_dev/day2/example2/add"
)

func main() {
    fmt.Println("Name=",a.Name)
    fmt.Println("Age=",a.Age)
}

例3:每一个代码源文件可以包含一个init函数
比如前面例子2中对Name与Age变量进行初始化的时候
cat go_dev/day2/example2/add/add.go

package add

var Name string
var Age int

func init() {
    Name = "hello world"
    Age = 33
}

cat go_dev/day2/examgple2/main/main.go

package main

import (
    "fmt"
    "go_dev/day2/example2/add"
)

func main() {
    fmt.Println("Name=",add.Name)
    fmt.Println("Age=",add.Age)
}

init函数在main函数之前执行,如果是自定义的函数比如前面的Test,就需要在main函数里面进行调用Test()

为了进一步说明init函数的执行过程,再添加一个包
cat go_dev/day2/example2/test/test.go

package test

import (
    "fmt"
)

var Name string = "this package is in test"
var Age int = 10

func init() {
    fmt.Println("this i a test")
    fmt.Println("test.package.Name=",Name)
    fmt.Println("test.package.Age=",Age)
    
    Age = 1000
    fmt.Println("test.package.Age=",Age)
}

add包调用test包,但不使用test包
cat go_dev/day2/example2/add/add.go

package add

import (
    _ "go_dev/day2/example2/test"
    "fmt"
)

var Name string
var Age int

func init() {
    Name = "hello world"
    Age = 33
}

入口函数文件执行调用add包
cat cat go_dev/day2/example2/main/main.go

package main

import (
    "fmt"
    "go_dev/day2/example2/add"
)

func main() {
    fmt.Println("Name=",add.Name)
    fmt.Println("Age=",add.Age)
}

编译执行入口文件代码
go build -o bin/example2_test.exe go_dev/day2/example2/main
04go10

3.常量

常量再运行的时候是无法更改的,永远是只读的,使用const修饰
const只能修饰布尔值、数值型和字符串型

常量的表示方式主要有以下三种

  • 初级的表示方式
    const b string = "hello world"
    const Pi = 3.1414
    const a int = 9

  • 优雅的表示方式
    const (
    a = 0
    b = 1
    c = 2
    )

  • 更专业的写法
    const (
    a = iota //iota会自动把a赋值为0,然后后面的变量值依次加1
    b
    c
    )

练习:定义两个常量Male与Female,并获取当前时间的秒数,如果能被Female整除,则在终端打印female,否则打印male

cat go_dev/day2/example3/main.go

package main

import (
    "fmt"
    "time"
)

const (
    Female = 2
    Male = 1
)

func Test() {
    second := time.Now().Unix()
    if (second % Female == 2) {
        fmt.Println("female")
    } else {
        fmt.Println("Male")
    }
    time.Sleep(1000 * time.Millisecond)
}

func main() {
    Test()
}

4.变量

变量声明
var a int
var b string
var c bool
var a int = 1 //声明的时候直接初始化

多个变量定义
var (
a int //不进行初始化默认为0
b string //默认为空
c string = "hello world"
e bool //默认为false
)

5.值类型与引用类型

值类型:int string bool 数组 struct 存的是值

引用类型: 指针 slice map chan 存的是地址

通过函数调用来传入这两种变量会有什么区别,下面看一个例子

例子1:打印一个值类型和引用类型变量都终端

cat go_dev/day2/example4/main/main.go

package main

import (
    "fmt"
)

func main() {
    a := 5
    b := make(chan int,1)
    
    fmt.Println("a=",a)
    fmt.Println("b=",b)
}   

编译执行输出
a= 5
b= 0xc0000140e0

下面自定义一个函数将a改变

package main

import (
    "fmt"
)

func modify1(a int) int {
    a = 10
}

func main() {
    a := 5
    b := make(chan int,1)
    
    fmt.Println("a=",a)
    fmt.Println("b=",b)
    modify1(a)
    fmt.Println("a=",a)
}

输出
a= 5
b= 0xc0000d2000
a= 5
这说明传值进去,作了一次拷贝,赋值给了一个副本a=10,但不影响调用函数时传进去的a

下面再看一段代码

package main

import (
    "fmt"
)

func modify2(a *int) {
    *a = 10
}

func main() {
    a := 5
    b := make(chan int,1)
    
    fmt.Println("a=",a)
    fmt.Println("b=",b)
    
    modify2(&a)
    fmt.Println("a=",a)
}

输出
a= 5
b= 0xc0000d2000
a= 10
在调用modify2函数会将变量a的地址传进去,并将该地址指向10,最终改变了变量a的值

例子:交换两个整数的值
先看下面的代码
cat go_dev/day2/example6/main.go

package main

import (
    "fmt"
)

func swap(a int,b int) {
    tmp := a
    b := a
    a := tmp
    return
}

func main() {
    first := 100
    second := 200
    swap(first,second)
    fmt.Println("first=",first)
    fmt.Println("second=",second)
}

执行输出
first= 100
second= 200
结果表明没有交换,这是为什么呢?
调用函数的时候会将first := 100 和 second := 200拷贝一份变成a :=100和b := 200,函数里的代码改的是副本的值,并不会改变函数外first和second的内存地址及其地址指向的值,这里就需要传first与second的地址进去

package main

import (
	"fmt"
)

func swap(a *int, b *int) {
	tmp := *a
	*a = *b
	*b = tmp
	return
}

func main() {
	first := 100
	second := 200

	swap(&first, &second)
	fmt.Println("first=", first)
	fmt.Println("second=", second)
}

执行输出
first= 200
second= 100

这次调用函数传递的是引用类型,一样也是拷贝一套副本,但是副本中存的是地址,将指针指向的内存的值进行了交换
05go12

其实还有更简单的两种实现方式
方式1:

package main

import (
    "fmt"
)

func swap1(a int,b int) (int,int) {
    return b,a
}

func main() {
    first := 100
    second := 200
    
    first,second = swap1(first,second)
    fmt.Println("first=",first)
    fmt.Println("second=",second)
}

方式2:

package main

import (
    "fmt"
)

func main() {
    first := 100
    second := 200
    first,second = second,first
    fmt.Println("first=",first)
    fmt.Println("second=",second)
}

调用函数传递一个值类型是不会改变当前变量的值;传递一个引用类型是会改变当前变量的值的

6.堆和栈

函数调用分配内存是通过栈来分配的,而堆内存是通过系统来分配的,栈的性能更高,值类型在栈中分配,引用类型在堆中分配

7.变量作用域

函数内部声明的变量叫局部变量,其生命周期局限于函数内部,函数外无法访问,函数内部的语句块里面的变量也只能在语句块内访问,语句块外面无法访问,看下面这段代码

package main

import (
    "fmt"
)

func global1() {
    var a int
    for i := 0;i < 10;i++ {
        var b int = 10
        fmt.Println(b)
    }
    fmt.Println(b)
}

func main() {
    global1()
    fmt.Println(a)
}

代码会报错提示第一个和第三个fmt输出报错

函数内部有一个局部便令a,同时存在着一个全局变量a,但是在函数内部访问变量a的时候肯定是优先访问该函数内部的局部变量

来看一个例子:

package main

import (
	"fmt"
)

var a string

func main() {
	a = "G"
	fmt.Println(a)
	f1()
}

func f1() {
	a = "O"
	fmt.Println(a)
	f2()
}

func f2() {
	fmt.Println(a)
}

输出
G
O
O

8.数值类型与操作符

数值类型:int int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64

int8范围是-128~127
uint8范围是0~255

类型转换:不同类型的变量是不能进行赋值的

package main

import (
    "fmt"
)

func test() {
    var a int8 = 100
    var b int16 = int16(a)
    
    fmt.Printf("a=%d b=%d\n",a,b)
}

func main() {
    test()
}

例子:随机生成10个整肃、10个浮点数、10个小于100的整数,利用math/rand包

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func init() {
    // 随机生成种子,否则每次输出的结果相同
    rand.Seed(time.Now().UnixNano())
}

func main() {
    for i := 0;i < 10;i++ {
        a := rand.Int()
        fmt.Println(a)
    }
    
    for i := 0;i < 10;i++ {
        a := rand.Float32()
        fmt.Println(a)
    }
    
    for i := 0;i < 10;i++ {
        a := rand.Intn(100)
        fmt.Println(a)
    }
}

9.字符串与字符

两种表示方式

  • 双引号
  • 反引号
package main

import (
    "fmt"
)

func main() {
    var str = "hello world"
    var str2 = `
    窗前明月光,
    疑是地上霜,
    举头望明月,
    低头思故乡。
    `
    
    var b byte = 'c'
    
    fmt.Println(str)
    fmt.Println(str2)
    fmt.Println(b)
    fmt.Printf("%c\n",b)
}

输出
05go13

在标准化输出的时候针对不同的变量类型有不同的表示,看下面这个例子
例:格式化输出二进制、十进制、十六进制,浮点型、整型数据

package main

import (
    "fmt"
)

func main() {
    var a int
    var b bool
    c := 'a'
    fmt.Printf("%#v\n",a) // 相应值的Go语法输出
    fmt.Printf("%v\n",b) // 默认输出格式
    fmt.Println("%t\n",b) //布尔值输出
    fmt.Printf("%T\n",c) //变量类型输出
    fmt.Printf("%b\n",100) // 二进制输出
    fmt.Printf("%f\n",3.1415) //浮点型输出
    fmt.Printf("%q\n","this is a test") //带引号的字符串输出
    fmt.Printf("%x\n",39999943) //十六进制输出
    fmt.Printf("%p\n",&a) //指针输出   
}   

输出
0
false
false
int32
1100100
3.141500
"this is a test"
2591ac9
0xc0000120d8

10.字符串操作

字符串操作主要包括字符串拼接、切片、反转等
看下面这段代码

package main 

import (
    "fmt"
)

func Reverse1(str string) string {
    var result string
    strlen := len(str)
    for i :=0;i < strlen;i++ {
        result = result + fmt.Sprintf("%c",str[strlen-i-1])
    }
    return result
}

func Reverse2(str string) string {
    var result []byte
    tmp := []byte(str)
    strlen := len(str)
    for i := 0;i < strlen;i++ {
       result = append(result,tmp[strlen-i-1]) 
    }
    return string(result)    
}

func main() {
    var str1 = "hello"
    var str2 = "world"
    
    // 字符串拼接
    str3 := str1 + " " + str2
    fmt.Println(str3)
    
    //字符串切片1
    substr1 := str3[0:5]
    fmt.Println(substr1)   
    
    //字符串切片2
    substr2 := str3[6:]
    fmt.Println(substr2)
    
    //字符串反转1
    substr3 := Reverse1(str3)
    fmt.Println(substr3)
    
    //字符串反转2   
    substr4 := Reverse(substr3)
    fmt.Println(substr4)    
}

11.练习

练习1:输出101~200的素数,并输出个数


package main

import (
	"fmt"
)

// SuShu is a function for 判断是否是素数
func SuShu(a int) int {
	var b int
	var c int
	// 对传入的数循环整除,计算不可以被整除的次数
	for i := 2; i < a; i++ {
		if a%i != 0 {
			b = b + 1
		}
	}
	// 判断可以被整除的次数与预期是否一致
	if b == a-2 {
		c = a
	}
	return c
}

func main() {
	var k int
	for j := 101; j < 201; j++ {
		num := SuShu(j)
		if num != 0 {
			fmt.Println(num)
			k = k + 1
		}
	}
	fmt.Println(k)
}

练习2:打印出100~999中所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字的立方和等于该数本身,例如153是一个“水仙花数”

package main

import (
	"fmt"
	"strconv"
)

// Exchange is a function
func Exchange(a int) (int, int, int) {
	var str string
	str = strconv.Itoa(a)
	substr1 := str[0:1]
	substr2 := str[1:2]
	substr3 := str[2:]

	num1, _ := strconv.Atoi(substr1)
	num2, _ := strconv.Atoi(substr2)
	num3, _ := strconv.Atoi(substr3)

	return num1, num2, num3
}

func main() {
	for i := 100; i < 1000; i++ {
		num4, num5, num6 := Exchange(i)
		if i == num4*num4*num4+num5*num5*num5+num6*num6*num6 {
			fmt.Println(i)
		}
	}
}

还有一种写法:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var str string
	for i := 100; i < 1000; i++ {
		str = strconv.Itoa(i)
		substr1 := str[0:1]
		substr2 := str[1:2]
		substr3 := str[2:]
		num1, _ := strconv.Atoi(substr1)
		num2, _ := strconv.Atoi(substr2)
		num3, _ := strconv.Atoi(substr3)

		if i == num1*num1*num1+num2*num2*num2+num3*num3*num3 {
			fmt.Println(i)
		}
	}
}

练习3:对于数值n,计算1!+ 2! + 3! + ... + n!

package main

import (
	"fmt"
)

// JieChen is a function for 计算单个阶乘
func JieChen(a int) int {
	var s int = 1
	for i := 1; i < a+1; i++ {
		s = s * i
	}
	return s
}

func main() {
	var sum int
	// 此处可以修改n的值
	n := 3
	for j := 1; j < n+1; j++ {
		p := JieChen(j)
		sum = sum + p
	}
	fmt.Println(sum)
}

练习4:打印九九乘法表

package main

import (
	"fmt"
)

// ChenFa is a function for 九九乘法表
func ChenFa(a int) {
	var p int
	for i := 1; i < a+1; i++ {
		p = i * a
		fmt.Printf("%d*%d=%d ", i, a, p)
	}
	fmt.Printf("\n")
}

func main() {
	for j := 1; j < 10; j++ {
		ChenFa(j)
	}
}

12.fmt package参考

fmt package