Go中文文档: https://www.topgoer.com/
官方Go练习指南: https://tour.go-zh.org/welcome/1
Golang标准库文档: https://studygolang.com/pkgdoc
一般语言是没有规定的项目结构,Go语言在这方面做了规定,为了保持一致性,做到统一、规则化比较明确
--demo
--bin
--pkg
--src
--project01
--project02
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}
go build hello.go
.\hello.go
go run hello.go
每个 Go 程序都是由包构成的。
程序从 main 包开始运行。
按照约定,包名与导入路径的最后一个元素一致。例如,“math/rand” 包中的源码均以 package rand 语句开始。
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(100))
}
此代码用圆括号组合了导入,这是“分组”形式的导入语句。
当然你也可以编写多个导入语句
package main
import (
"fmt"
"math"
)
/* 等同于上面的组合导入
import "fmt"
import "math"
*/
func main(){
fmt.Printf("7的平方根为: %g\n",math.Sqrt(7))
}
在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。例如,Pizza 就是个已导出名,Pi 也同样,它导出自 math 包。
pizza 和 pi 并未以大写字母开头,所以它们是未导出的。
在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi)
}
函数可以没有参数或接受多个参数。
在本例中,add 接受两个 int 类型的参数。
注意类型在变量名 之后。
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
//当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略
func add2(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(2, 16))
fmt.Println(add(18, 16))
}
函数可以返回任意数量的返回值。
swap 函数返回了两个字符串。
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
package main
import "fmt"
//没有参数的 return 语句返回已命名的返回值。也就是 直接 返回
func split(sum int) (x, y int){
x = sum * 4 / 9
y = sum - x
return
}
func main(){
fmt.Println(split(17))
}
var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
就像在这个例子中看到的一样,var 语句可以出现在包或函数级别。
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
变量声明可以包含初始值,每个变量对应一个。
如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
Go 的基本类型有
int, uint 和 uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 当你需要一个整数值时应使用 int 类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\n", z, z)
}
通用:
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 值的类型的Go语法表示 %% 百分号
布尔值:
%t 单词true或false
整数:
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"
浮点数与复数的两个组分:
%b 无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
字符串和[]byte:
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f)
%X 每个字节用两字符十六进制数表示(使用A-F)
指针:
%p 表示为十六进制,并加上前导的0x
没有%u。整数如果是无符号类型自然输出也是无符号的。类似的,也没有必要指定操作数的尺寸(int8,int64)。
宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下:
%f: 默认宽度,默认精度
%9f 宽度9,默认精度
%.2f 默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0
使用 fmt.Sprintf 和 strconv 进行转换
package main
import (
"fmt"
"strconv"
)
func main() {
sprintfTest()
strconvTest()
stringToDef()
}
// 使用fmt.Sprintf进行转换
func sprintfTest() {
var a int = 1
var b float64 = 1.2
var c bool = false
var d byte = 'c'
var str string
str = fmt.Sprintf("%d", a)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%f", b)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%t", c)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%c", d)
fmt.Printf("str type %T str=%q\n", str, str)
}
// 使用strconv包的函数
func strconvTest() {
var a int = 1
var b float64 = 1.2
var c bool = false
var str string
str = strconv.FormatInt(int64(a), 10)
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatFloat(b, 'f', 10, 64)
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatBool(c)
fmt.Printf("str type %T str=%q\n", str, str)
}
// String类型转基本数据类型
func stringToDef() {
var a string = "1"
var aa int64
aa, _ = strconv.ParseInt(a, 10, 64)
fmt.Printf("str type %T str=%v\n", aa, aa)
var b string = "1.2"
var bb float64
bb, _ = strconv.ParseFloat(b, 64)
fmt.Printf("str type %T str=%v\n", bb, bb)
var c string = "false"
var cc bool
cc, _ = strconv.ParseBool(c)
fmt.Printf("str type %T str=%v\n", cc, cc)
}
没有明确初始值的变量声明会被赋予它们的 零值。
零值是:
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
package main
import (
"fmt"
"math"
)
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x * x + y * y))
var z uint = uint(f)
fmt.Println(x, y, f, z)
typeChange()
typeChange2()
}
//Go 在不同类型的项之间赋值时需要显式转换
func typeChange() {
var i int = 42
var f float64 = float64(i)
//隐式转换会失败
// var f float64 = i
var u uint = uint(f)
fmt.Println(i, f, u)
}
func typeChange2() {
i := 42
f := float64(i)
u := uint(f)
fmt.Println(i, f, u)
}
在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。
当右值声明了类型时,新变量的类型与其相同:
var i int
j := i // j 也是一个 int
不过当右边包含未指明类型的数值常量时,新变量的类型就可能是 int, float64 或 complex128 了,这取决于常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
package main
import "fmt"
//在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出
func main() {
v := 42
fmt.Printf("v is of type %T\n", v)
typesIs()
typesIs2()
}
func typesIs(){
var i int
j := i
fmt.Printf("j的类型为%T\n", j)
}
func typesIs2() {
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
fmt.Printf("i的类型为%T\n", i)
fmt.Printf("f的类型为%T\n", f)
fmt.Printf("g的类型为%T\n", g)
}
常量的声明与变量类似,只不过是使用 const 关键字。
常量可以是字符、字符串、布尔值或数值。
常量不能用 := 语法声明。
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
再尝试一下输出 needInt(Big) 吧。
(int 类型最大可以存储一个 64 位的整数,有时会更小。)
(int 可以存放最大64位的整数,根据平台不同有时会更少。)
package main
import "fmt"
const (
// 将 1 左移 100 位来创建一个非常大的数字
// 即这个数的二进制是 1 后面跟着 100 个 0
Big = 1 << 100
// 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 2
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
值类型
1、基本数据类型:int32系列,float系列,string,bool,数组,结构体struct
2、通常在栈区分配
引用类型
1、引用类型:指针,slic切片,map,管道chan,interface
2、通常在堆区,分配空间
Go 拥有指针。指针保存了值的内存地址。
类型 *T 是指向 T 类型值的指针。其零值为 nil。
var p *int
& 操作符会生成一个指向其操作数的指针。
i := 42
p = &i
*操作符表示指针指向的底层值。
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
调用fmt的fmt.Scanln() 和 fmt.Scanf()
package main
import "fmt"
func main() {
// scanlnTest()
scanfTest()
}
func scanlnTest() {
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("输入姓名:")
fmt.Scanln(&name)
fmt.Println("输入年龄:")
fmt.Scanln(&age)
fmt.Println("输入薪资:")
fmt.Scanln(&sal)
fmt.Println("是否通过考试:")
fmt.Scanln(&isPass)
fmt.Println("姓名:", name, "年龄:", age, "薪资:", sal, "是否通过考试:", isPass)
}
func scanfTest() {
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("请输入你的姓名, 年龄, 薪资, 是否通过考试, 使用空格隔开")
fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
fmt.Println("姓名:", name, "年龄:", age, "薪资:", sal, "是否通过考试:", isPass)
}
对于整数有4种表示方式
规则:从最低位开始(右边的),将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和
1011 = 1 * 2 ^ 0 + 1 * 2^1 + 0 * 2 ^ 2 + 1* 2 ^ 3 = 11
规则:从最低位开始(右边的),将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和
0123 = 3 * 8 ^ 0 + 2 * 8^1 + 1 * 8 ^ 2 = 83
规则:从最低位开始(右边的),将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和
0X34A= 10 * 16 ^ 0 + 4 * 16^1 + 3 * 16 ^ 2 = 842
规则:将改数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
规则:将改数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
规则:将改数不断除以16,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
规则: 将二进制每三位一组(从低位开始组合),转成对应的八进制数即可。(因为三位数字的二进制最多可以表示为7)
请将二进制11010101转成八进制:
11010101 = 11 010 101 = 3 2 5 = 0325
规则: 将二进制每四位一组(从低位开始组合),转成对应的十六进制数即可。(因为四位数字的二进制最多可以表示为15)
请将二进制11010101转成十六进制:
11010101 = 1101 0101 = 13 5 = 0XD5
规则:将八进制每1位,转成对应的1个三位的二进制数即可
请将0237转成二进制
0237 = 10 011 111 = 10011111
规则:将十六进制每1位,转成对应的1个四位的二进制数即可
请将0X237转成二进制
0X237 = 10 0011 0111 = 1000110111
package main
import "fmt"
func main() {
var a int = 1 >> 2 // 0000 0001 => 0000 0000 => 0
fmt.Println(a)
b := -1 >> 2 //1111 1111 => 1111 1111 => -1
fmt.Println(b)
c := 1 << 2 // 0000 0001 => 0000 0100 => 4
fmt.Println(c)
d := -1 << 2 //1000 0001 => 1000 0100 => -4
fmt.Println(d)
}
Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。
该语句声明的变量作用域仅在 if 之内。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
在 if 的简短语句中声明的变量同样可以在任何对应的 else 块中使用。
(在 main 的 fmt.Println 调用开始前,两次对 pow 的调用均已执行并返回其各自的结果。)
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
switch 是编写一连串 if - else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
}
没有条件的 switch 同 switch true 一样。
这种形式能将一长串 if-then-else 写得更加清晰。
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
fallthrough : switch穿透,如果在case语句块后加fallthrough ,则会执行下一个case,也叫switch穿透
//输出结果为: 当前值为10 当前值为20
func fallthroughTest() {
var i int = 10
switch i {
case 10:
fmt.Println("当前值为10")
fallthrough
case 20:
fmt.Println("当前值为20")
case 30:
fmt.Println("当前值为30")
default:
fmt.Println("无匹配")
}
}
Go 只有一种循环结构:for 循环。
基本的 for 循环由三部分组成,它们用分号隔开:
初始化语句:在第一次迭代前执行
条件表达式:在每次迭代前求值
后置语句:在每次迭代的结尾执行
初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。
一旦条件表达式的布尔值为 false,循环迭代就会终止。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
初始化语句和后置语句是可选的。
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
如果省略循环条件,该循环就不会结束,因此无限循环可以写得很紧凑。
通常搭配break使用,来跳出循环
package main
func main() {
for {
}
}
func test2() {
var textString string = "Hello world 你好"
for index, val := range textString {
fmt.Printf("index=%d val=%c \n", index, val)
}
}
func 函数名 (形参列表) (返回值类型列表) {
执行语句..
return + 返回值列表
}
package main
import "fmt"
func cal (num1 int, num2 int) (int, int) {
var sum int = num1 + num2
var sub int = num1 - num2
return sum, sub
}
func main(){
sum, sub := cal(66, 33)
//也可以用下划线忽略
sum1, _ := cal(11, 22)
fmt.Println(sum, sub) //99, 33
fmt.Println(sum1) //33
}
package进行包的声明,建议:包的声明这个包和所在的文件夹同名
main包是程序的入口包,一般main函数会放在这个包下。main函数一定要放在main包下,否则不能编译执行
引入包的语法:import “包的路径”。包名是从$GOPATH/src/后开始计算的,使用/进行路径分隔。(配置完变量要重启IDE)
如果有多个包,建议一次性导入,格式如下:
import(
"fmt"
"gocode/testproject01/unit5/demo09/crm/dbutils"
//utils "gocode/testproject01/unit5/demo09/crm/dbutils" //这里表示对包起别名使用
)
包到底是什么:
package main
import "fmt"
var num int = test();
func test() int {
fmt.Println("test函数被调用执行") //先输出
return 10
}
func init() {
fmt.Println("init函数被调用执行") //其次输出
}
func main() {
fmt.Println("main函数被调用执行") //最后输出
}
多个源文件都有init函数的时候,如何执行?
Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数
匿名函数使用方式:
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)
package main
import "fmt"
func main() {
//定义匿名函数,定义的同时调用
result := func (num1 int, num2 int) int {
return num1 + num2
} (10, 20) //直接输入参数
fmt.Println(result) //30
}
将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数(用的少)
package main
import "fmt"
func main() {
//定义匿名函数,定义的同时调用
result := func (num1 int, num2 int) int {
return num1 + num2
}
result1 := result(10, 20) //调用result匿名函数
fmt.Println(result1) //30
}
如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了
package main
import "fmt"
var add = func (num1 int, num2 int) int {
return num1 + num2
}
func main() {
result := add(10, 20) //调用add
fmt.Println(result) //30
}
什么是闭包?
闭包就是一个函数和与其相关的引用环境组合的一个整体
代码展示
package main
import "fmt"
//函数功能:求和
//函数的名字:getSum 参数为空
//getSum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getSum() func (int) int {
var sum int = 0
return func (num int) int{
sum = sum + num
return sum
}
}
//闭包:返回的匿名函数+匿名函数以外的变量num
func main(){
f := getSum()
fmt.Println(f(1))//1
fmt.Println(f(2))//3
fmt.Println(f(3))//6
fmt.Println(f(4))//10
}
匿名函数中引用的那个变量会一直保存在内存中,可以一直使用
闭包的本质
特点
作用
代码展示
package main
import "fmt"
var add = func (num1 int, num2 int) int {
//在Golang中,程序遇到defer关键字,
//不会立即执行defer后的语句,而是先将语句压入一个栈中,然后继续执行后面的语句
defer fmt.Println("num1=", num1) //33
defer fmt.Println("num2=", num2) //66
sum := num1 + num2
fmt.Println("sum=", sum) //99
return sum
}
func main() {
fmt.Println(add(33, 66)) //99
}
var b string = "hello 世界"
r := []rune(b)
for i := 0; i < len(r); i++ {
fmt.Printf("字符 = %c \n", r[i])
}
time.Now()
now := time.Now()
fmt.Println(now.Year())
fmt.Println(now.Month())
fmt.Println(int(now.Month()))
fmt.Println(now.Day())
fmt.Println(now.Hour())
fmt.Println(now.Minute())
fmt.Println(now.Second())
fmt.Println(fmt.Sprintf("%d-%d-%d %d:%d:%d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()))
fmt.Println(now.Format("2006-01-02 15:04:05")) //2006-01-02 15:04:05 是固定的,不能更改
fmt.Println(now.Unix())
fmt.Println(now.UnixNano())
内置函数recover: 可以捕获到异常
func test2() {
defer func() {
err := recover()
if err != nil {
fmt.Println("报错信息为:", err)
}
}()
a := 0
b := 10
fmt.Println(a / b)
fmt.Println(b / a)
//后面的逻辑不会再执行
fmt.Println("我是报错后的操作")
}
package main
import (
"errors"
"fmt"
)
func main() {
test1()
fmt.Println("我是最后的输出逻辑")
}
func test1() {
err := test2()
if err != nil {
fmt.Println("自定义错误:", err)
panic(err) //panic会结束程序的执行
}
}
func test2() (err error) {
num1 := 10
num2 := 0
if num2 == 0 {
//抛出自定义错误:
return errors.New("除数不能为零!!!")
} else {
result := num1 / num2
fmt.Println(result)
//如果没有错误,返回零值
return nil
}
}
package main
import "fmt"
func main(){
//第一种:
var arr1 [3]int = [3]int{3,6,9}
fmt.Println(arr1)
//第二种:
var arr2 = [3]int{1,4,7}
fmt.Println(arr2)
//第三种:
var arr3 = [...]int{4,5,6,7}
fmt.Println(arr3)
//第四种:定义对应下标
var arr4 = [...]int{2:66,0:33,1:99,3:88}
fmt.Println(arr4)
}
for key, val := range coll {
...
}
package main
import "fmt"
func main(){
//实现的功能:给出五个学生的成绩,求出成绩的总和,平均数:
//给出五个学生的成绩:--->数组存储:
//定义一个数组:
var scores [5]int
//将成绩存入数组:(循环 + 终端输入)
for i := 0; i < len(scores);i++ {//i:数组的下标
fmt.Printf("请录入第个%d学生的成绩",i + 1)
fmt.Scanln(&scores[i])
}
//方式2:for-range循环
for key,value := range scores {
fmt.Printf("第%d个学生的成绩为:%d\n",key + 1,value)
}
}
func test3() {
arrayTest1 := [5]int{1, 2, 3, 4, 5}
fmt.Println("原始数组:", arrayTest1)
changeArray(&arrayTest1)
fmt.Println("改变后的数组:", arrayTest1)
}
func changeArray(arrayTest *[5]int) {
(*arrayTest)[0] = 12
}
package main
import (
"fmt"
)
func main() {
// test()
test2()
}
func test() {
var arr [4][6]int
arr[1][2] = 1
arr[2][2] = 2
arr[2][3] = 3
fmt.Println(arr)
}
func test2() {
arr := [3][4]int{
{1, 2, 3, 4},
{1, 2, 3, 4},
{1, 2, 3, 4},
}
//使用双重循环进行遍历
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Printf("第 %d 行第 %d 列的数为 %d \n", i+1, j+1, arr[i][j])
}
}
//使用for range进行遍历
for key, value := range arr {
for nextKey, nextValue := range value {
fmt.Printf("第 %d 行第 %d 列的数为 %d \n", key+1, nextKey+1, nextValue)
}
}
}
var 切片名 []类型 = 数组的一个片段引用
package main
import (
"fmt"
)
func main() {
var test1 [5]int = [...]int{1, 2, 3, 4, 5}
slice := test1[1:3]
fmt.Println(slice)
fmt.Println("slice的元素:", slice) //[2, 3]
fmt.Println("切片的长度=", len(slice)) //2
fmt.Println("切片的容量", cap(slice)) //4 容量可以动态变化
}
//定义数组:
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}
//切片构建在数组之上
//方式1:定义一个切片,然后让切片去引用一个已经创建好的数组
slice1 := intarr[1 : 3]
//方式2:通过make内置函数来创建切片。基本语法: var 切片名 []type = make([]type , len,[cap])
//PS : make底层创建一个数组,对外不可见,所以不可以直接操作这个数组,
//要通过slice去间接的访问各个元素,不可以直接对数组进行维护/操作
slice2 := make([]int , 4, 20)
//方式3:定一个切片,直接就指定具体数组,使用原理类似make的方式
slice3 := []int{1, 4, 7}
package main
import "fmt"
func main(){
//定义切片:
slice := make([]int,4,20)
slice[0] = 66
slice[1] = 88
slice[2] = 99
slice[3] = 100
//方式1:普通for循环
for i := 0;i < len(slice);i++ {
fmt.Printf("slice[%v] = %v \t" ,i,slice[i])
}
fmt.Println("\n------------------------------")
//方式2:for-range循环:
for i,v := range slice {
fmt.Printf("下标:%v ,元素:%v\n" ,i,v)
}
}
var slice = arr[0:end] ----> var slice = arr[:end]
var slice = arr[start:len(arr)] ----> var slice = arr[start:]
var slice = arr[0:len(arr)] ----> var slice = arr[:]
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}
slice := intarr[1 : 5]
//再切片
slice2 := slice[1 : 3]
fmt.Println(slice2) //[9 1]
package main
import "fmt"
func main(){
//定义数组:
var intarr [6]int = [6]int{1,4,7,3,6,9}
//定义切片:
var slice []int = intarr[1:4] //4,7,3
fmt.Println(len(slice))
slice2 := append(slice,88,50)
fmt.Println(slice2) //[4 7 3 88 50]
fmt.Println(slice)
//底层原理:
//1.底层追加元素的时候对数组进行扩容,老数组扩容为新数组:
//2.创建一个新数组,将老数组中的4,7,3复制到新数组中,在新数组中追加88,50
//3.slice2 底层数组的指向 指向的是新数组
//4.往往我们在使用追加的时候其实想要做的效果给slice追加:
slice = append(slice,88,50)
fmt.Println(slice)
//5.底层的新数组 不能直接维护,需要通过切片间接维护操作。
}
//定义切片:
var a []int = []int{1,4,7,3,6,9}
//再定义一个切片:
var b []int = make([]int,10)
//拷贝:
copy(b,a) //将a中对应数组中元素内容复制到b中对应的数组中
fmt.Println(b)
如果要修改字符串,可以先将string => []byte 或者 []rune => 修改 =>重新转换成字符串
func test4() {
var string1 string = "Hello world"
slice1 := string1[2:]
fmt.Println(slice1)
fmt.Printf("slice1的类型为:%T \n", slice1)
slice2 := []byte(string1)
fmt.Printf("%c", slice2)
slice2[6] = 'W'
fmt.Printf("%c", slice2)
arr1 := []rune(string1)
fmt.Printf("%c", arr1)
arr1[0] = '好'
str := string(arr1)
fmt.Println(str)
}
原理:
冒泡排序就是从序列中的第一个元素开始,依次对相邻的两个元素进行比较,如果前一个元素大于后一个元素则交换它们的位置。如果前一个元素小于或等于后一个元素,则不交换它们;这一比较和交换的操作一直持续到最后一个还未排好序的元素为止。
当这样的一趟操作完成时,序列中最大的未排序元素就被放置到了所有未排序的元素中最后的位置上,它就像水中的石块一样沉到了水底。而其它较小的元素则被移动到了序列的前面,就像水中的气泡冒到了水面一样。这就是为什么该算法被叫做冒泡排序的原因。
package main
import (
"fmt"
)
func main() {
arr := [5]int{10, 9, 8, 7, 6}
fmt.Println("原始数据:", arr)
sort(&arr)
fmt.Println("排序完成后数据:", arr)
}
func sort(arr *[5]int) {
ls := 0
for i := 0; i < len(*arr)-1; i++ {
for j := 0; j < len(*arr)-1-i; j++ {
if (*arr)[j] > (*arr)[j+1] {
ls = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = ls
}
}
fmt.Println(i+1, "轮排序后的数组为:", *arr)
}
}
二分查找(Binary
search)也称折半查找,是一种效率较高的查找方法。但是,二分查找要求线性表中的记录必须按关键码有序,并且必须采用顺序存储。
package main
import (
"fmt"
)
func main() {
arr := [9]int{3, 17, 24, 36, 41, 56, 71, 80, 100}
key, findRes := findNumber(arr, 0, len(arr)-1, 101)
if findRes {
fmt.Println("找到的数据下标为:", key)
} else {
fmt.Println("查找结束,根本没有!!!")
}
}
func findNumber(arr [9]int, leftIndex, rightIndex int, findN int) (int, bool) {
fmt.Println("本轮查找的数组为:", arr)
if leftIndex > rightIndex {
return 0, false
}
middleKey := (rightIndex + leftIndex) / 2
fmt.Println("中间数下标:", middleKey)
if arr[middleKey] == findN {
fmt.Println("找到了!!!!")
return middleKey, true
}
if arr[middleKey] > findN {
rightIndex = middleKey - 1
} else {
leftIndex = middleKey + 1
}
return findNumber(arr, leftIndex, rightIndex, findN)
}
var map变量名 map[keytype]valuetype 如:
var a map[int]string
PS:key、value的类型:bool、数字、string、指针、channel 、还可以是只包含前面几个类型的接口、结构体、数组
PS:key通常为int 、string类型,value通常为数字(整数、浮点数)、string、map、结构体
PS:key:slice、map、function不可以
代码
package main
import "fmt"
func main(){
//定义map变量:
var a map[int]string
//只声明map内存是没有分配空间
//必须通过make函数进行初始化,才会分配空间:
a = make(map[int]string,10) //map可以存放10个键值对
//将键值对存入map中:
a[20095452] = "张三"
a[20095387] = "李四"
a[20097291] = "王五"
a[20095387] = "朱六"
a[20096699] = "张三"
//输出集合
fmt.Println(a)
}
package main
import "fmt"
func main(){
//方式1:
//定义map变量:
var a map[int]string
//只声明map内存是没有分配空间
//必须通过make函数进行初始化,才会分配空间:
a = make(map[int]string,10) //map可以存放10个键值对
//将键值对存入map中:
a[20095452] = "张三"
a[20095387] = "李四"
//输出集合
fmt.Println(a)
//方式2:
b := make(map[int]string)
b[20095452] = "张三"
b[20095387] = "李四"
fmt.Println(b)
//方式3:
c := map[int]string{
20095452 : "张三",
20098765 : "李四",
}
c[20095387] = "王五"
fmt.Println(c)
}
func curdAction() {
students := make(map[string]map[string]string)
//添加数据
students["小明"] = map[string]string{
"姓名": "小明",
"性别": "男",
}
students["李华"] = map[string]string{
"姓名": "李华",
"性别": "女",
}
fmt.Println(students)
//删除数据
delete(students, "李华")
fmt.Println(students)
//一次性删除方式
// students = make(map[string]map[string]string)
// fmt.Println(students)
//更新数据
students["小明"]["性别"] = "女"
fmt.Println(students)
//查找数据是否存在
val, ok := students["小明"]
if ok {
fmt.Println("小明的信息为:", val)
} else {
fmt.Println("小明不存在")
}
}
可以使用 for range
for key, val := range students {
println("key为:", key)
for key1, val1 := range val {
println("key1为:", key1)
println("val1为:", val1)
}
}
func sliceMap() {
var students []map[int]string
students = make([]map[int]string, 2)
if students[0] == nil {
students[0] = make(map[int]string)
students[0][0] = "小明"
}
if students[1] == nil {
students[1] = make(map[int]string)
students[1][1] = "小黄"
}
fmt.Println(students)
newStudent := map[int]string{
2: "Hello",
}
students = append(students, newStudent)
fmt.Println(students)
}
一个结构体(struct)就是一组字段(field)。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
vertex := Vertex{1, 2}
vertex.Y = 3
fmt.Println(vertex)
fmt.Println(vertex.Y)
}
func test(){
//1.直接声明
var vertext Vertex
//第2种
var vertex Vertex = Vertex{1, 2}
//3.结构体指针定义
var vertex Vertex = new(Vertex)
//4 结构体指针
var vertex Vertex = &Vertex{1, 2}
}
结构体字段可以通过结构体指针来访问。
如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
Go 没有类。不过你可以为结构体类型定义方法。
方法就是一类带特殊的 接收者 参数的函数。
方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
你可以为指针接收者声明方法。
这意味着对于某类型 T,接收者的类型可以用 *T 的文法。(此外,T 不能是像 *int 这样的指针。)
例如,这里为 *Vertex 定义了 Scale 方法。
指针接收者的方法可以修改接收者指向的值(就像 Scale 在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用
若使用值接收者,那么 Scale 方法会对原始 Vertex 值的副本进行操作。(对于函数的其它参数也是如此。)Scale 方法必须用指针接受者来更改 main 函数中声明的 Vertex 的值。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
package main
import (
"encoding/json"
"fmt"
)
type Persion struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
persion := Persion{Name: "小明", Age: 12}
fmt.Println(persion)
//将persion变量序列化为json格式字符串
jsonstr, err := json.Marshal(persion)
if err != nil {
fmt.Println("json处理错误:", err)
}
fmt.Println(jsonstr)
//需要把byte类型转为string进行输出
fmt.Println(string(jsonstr))
}
package main
import (
"encoding/json"
"fmt"
)
type Persion struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
persion := Persion{Name: "小明", Age: 12}
fmt.Println(persion)
//如果实现的&Persion类型的String方法,就会自动调用
fmt.Println(&persion)
}
// 给*persion实现方法String
func (persion *Persion) String() string {
str := fmt.Sprintf("Name=【%v】,Age=【%v】", persion.Name, persion.Age)
return str
}