一、语言结构**************************************************************************************
1.包声明
2.引入包
3.函数
4.变量
5.语句 & 表达式
6.注释
package main
import "fmt"
func main() {
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!")
}
详情步骤:
1.第一行代码:定义了一个 main包。表示一个可独立执行的程序,每个go的应用程序都包含了一个main包
2.第二行代码:引用了fmt 这个包,并使用了其中的函数
3.fun main() 程序开始执行的函数 这个函数是每个程序必须包含的 他是这个程序的入口
4.注释
5.使用了fmt 这个包的函数打印了 Hello World
二、基础语法**************************************************************************************
1.字符串链接 + 号
三、数据结构**************************************************************************************
数据类型用于声明变量和函数。把数据分成所需内存大小不同的数据,充分利用内存。
1.布尔类型:true 和 false
2.数字类型:整型、浮点型、复数
3.字符串类型:一串固定长度的字符链接起来的字符序列。
4.派生类型:指针类型、数组类型、结构化类型、channel类型、切片类型、接口类型、map类型
四、语言变量**************************************************************************************
变量的声明
1.指定变量类型 如果没有初始值,则默认为零值
公式:
var v_name v_type var data int
v_name = value data = 12345
2.根据值自动判断变量类型
公式:
var v_name = value var data = 12345
3.省略var
公式:
v_name := value data := 12345 (v_name 当前就是声明变量了,其他地方不可再次声明,否则报错)
多变量声明
1.第一种:
var v_name1,v_name2,v_name3 type
v_name1,v_name2,v_name3 = value1,value2,value3
2.第二种:
var v_name1,v_name2,v_name3 = value1,value2,value3
3.第三种:
v_name1,v_name2,v_name3 := value1,value2,value3
值类型和引用类型
所有像 int、float、bool、string、数组、结构体 都是值类型。这些类型的变量的值都是直接指向存在内存中的值。
eg: var demo1,demo2 int
demo1 = 1
demo2 = demo1
值传递 就是将 demo1的值 从内存中复制了一份 给了 demo2 可以&demo1 来获取demo1变量的内存地址
引用类型 包括指针、slice切片、map、chan、interface
变量直接存放的就是一个内存地址值,这个地址指向的空间的值才是值。所以修改其中一个,另一个也会改变,因为他们是同一个内存地址
引用类型必须申请内存才能使用。make()是给引用类型申请内存空间
var slice1 = []int{1,2,3,4,5}
slice2 := slice1 //此时s1 s2 都指向了同一个内存地址
slcie2[1] = 10 //相当于修改了同一个内存地址,所以s1的值也会改变
slcie3 := make([]int,2,3)
copy(slice3,slice1) //将 slice1 的值 复制给 slice3
slcie3[1] = 10 //slice1的值不会改变 copy相当于值传递
slice4 := &slice1 将slice1的内存地址给slice4 取值用*slice4
五、语言常量**************************************************************************************
常量是一个简单值的标识符,在程序运行时不会被修改的值
常量的数据类型只有布尔型、数字型、浮点型、复数、字符串型
语法:
const identifier [type] = value
省略type:(编译器会根据常量的值来判断类型)
1.显示定义:const co int = 3
2.隐示定义:const co = 3
多个常量定义
const c1,c2 = cValue1,cValue2
常量还可以用作枚举
eg:
const (
a = 1
b = 2
c = 3
)
iota 特殊常量 可以认为是一个可以被编译器修改的常量
iota 在const 出现时 会被重置为0 const 中每新增一个 iota的值累计一个 iota 可以被理解为 const的索引
在定义常量的时候不写值默认是上一个值 但是第一个值必须定义
六、语言运算符**************************************************************************************
1.算数运算符 + - * /(取都是整数) %(取余数)
2.关系运算符 == != > <
3.逻辑运算符 && || !
4.位运算符
5.赋值运算符 = +=(C += A 等于 C = C + A) -= (c -= a 等于 c = c - a ) *= /= %=
6.其他运算符 & 返回变量存储地址 &a 就是 将给出变量的实际地址 * 指针变量 *a 是一个指针变量
指针变量 * 和地址值 & 的区别:指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字。
七、语言条件句**************************************************************************************
1.if
2.if else
3.if 嵌套
4.switch
5.select
switch
公式1:
var v int
switch v {
case 1:
...
case 2:
...
default:
...
}
公式2:
switch {
case v ==121,v=222:
...
case v = 444:
...
fallthrough
default:
...
}
公式3:switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
eg:
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
fallthrough 强行执行 下一个case
select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行.
公式:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
eg:
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
八、循环语句**************************************************************************************
for循环
1.和 C 语言的 for 一样:for init; condition; post { }
2.和 C 的 while 一样:for condition { }
3.和 C 的 for(;;) 一样:for { }
4.for range 这种格式的循环可以对字符串、数组、切片等进行迭代输出元素。
eg:
str := make([]string,"google","runoob")
for i,s range str{
fmt.println(i,s)
}
arr := [3]string{"a","b","c"}
for i,s range arr{
fmt.pintln(i,s)
}
跳出循环
1.break 中断 for switch 循环
2.continue 结束当前循环的剩余语句 执行下一个循环
3.goto 将控制转移到标记语句
goto语法:
goto label:
。。。
。。。
label:statement
eg:
/* 定义局部变量 */
var a int = 10
/* 循环 */
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
eg:
//for循环配合goto打印九九乘法表
func gotoTag() {
for m := 1; m < 10; m++ {
n := 1
LOOP: if n <= m {
fmt.Printf("%dx%d=%d ",n,m,m*n)
n++
goto LOOP
} else {
fmt.Println("")
}
n++
}
}
九、语言函数**************************************************************************************
公式:
func func_name ([parameter list]) [return_type]{
函数体
}
eg:
func demo(d1,d2 int) int {
re := d1 + d2
return re
}
函数返回多个值
eg:
func demo1 (a,b string)(string,string){
return a,b
}
十、变量作用域**************************************************************************************
1.函数内部定义的变量叫做局部变量
2.函数外定义的变量叫全部变量
3.函数定义中的变量叫形式参数
Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
十一、数组 **************************************************************************************
声明数组 需要指定元素的个数和类型
公式:
var var_name [size] var_type
eg:
var demo [10] int
初始化数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
十二、切片 **************************************************************************************
切片是数组的一种高级运用,相对于数组,切片是一种更加方便,灵活,高效的数据结构。切片并不存储任何元素而只是对现有数组的引用(不是值拷贝,是指针)
1.通过数组创建一个切片
arr := [3]int{1,2,3}
slice := arr[1:2]
2.直接声明一个切片
slice := []int{1,2,3}
3.通过make创建
slice make([]int,3,6)
4.通过切片生成一个切片
slice4 := []int {1,2,3,4,5,6}
//通过slice4创建一个切片,元素是slice4下标从0到1(不包含1)的元素
slice5 := slice4[0:1]
//通过slice4创建一个切片,元素是slice4下标从0到末尾的元素
slice6 := slice4[1:]
//通过slice4创建一个切片,元素是slice4下标从0到3的元素
slice7 := slice4[:3]
十三、指针 **************************************************************************************
go语言的取地址符是 & ,放到一个变量前面就是获取相应的内存地址
指针:一个指针变量指向了一个值的内存地址 再使用指针前也需要声明
公式:
var var_name *var_type
var ip *int
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
十四、结构体 **************************************************************************************
结构体是由一系列具有相同或和不同类型的数据构成的集合
公式:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
eg:
type Books struct {
title string
author string
subject string
book_id int
}
// 创建一个新的结构体
fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
// 也可以使用 key => value 格式
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
// 忽略的字段为 0 或 空
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
访问结构体成员 如果要访问结构体成员,需要使用点号 . 操作符,格式为:结构体.成员名
十五、map **************************************************************************************
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
定义 Map 可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
func main() {
var countryCapitalMap map[string]string /*创建集合 */
countryCapitalMap = make(map[string]string)
/* map插入key - value对,各个国家对应的首都 */
countryCapitalMap [ "France" ] = "巴黎"
countryCapitalMap [ "Italy" ] = "罗马"
countryCapitalMap [ "Japan" ] = "东京"
countryCapitalMap [ "India " ] = "新德里"
/*使用键输出地图值 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [country])
}
/*查看元素在集合中是否存在 */
capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
/*fmt.Println(capital) */
/*fmt.Println(ok) */
if (ok) {
fmt.Println("American 的首都是", capital)
} else {
fmt.Println("American 的首都不存在")
}
}
delete() 函数 delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。实例如下:
func main() {
/* 创建map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
fmt.Println("原始地图")
/* 打印地图 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
/*删除元素*/ delete(countryCapitalMap, "France")
fmt.Println("法国条目被删除")
fmt.Println("删除元素后地图")
/*打印地图*/
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
}
十六、递归函数 **************************************************************************************
递归,就是在运行的过程中调用自己。
func recursion() {
recursion() /* 函数调用自身 */
}
func main() {
recursion()
}
阶乘
func Factorial(n uint64)(result uint64) {
if (n > 0) {
result = n * Factorial(n-1)
return result
}
return 1
}
func main() {
var i int = 15
fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}
斐波那契数列
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-2) + fibonacci(n-1)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d\t", fibonacci(i))
}
}
十八、类型转换 **************************************************************************************
类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:
type_name(expression) type_name 为类型,expression 为表达式。
func main() {
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count)
fmt.Printf("mean 的值为: %f\n",mean)
}
十九、语言接口 **************************************************************************************
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
二十、错误处理 **************************************************************************************
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
二十一、并发 **************************************************************************************
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
go 函数名( 参数列表 ) go f(x, y, z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
通道(channel)
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:ch := make(chan int)
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Go 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
网友评论