1. 变量声明
- var name type 指定变量类型,若不赋值为默认值
- var name = value(推断体:根据值类型隐式表明变量类型)
- name := value(包内变量不可用,用var声明会编译报错)
- var (name1 = value1 name2 = value2)(聚合定义变量,多个变量之间需换行,多用于全局变量)
package main
import (
"fmt"
)
func typeDefaultValue(){
// 变量定义1: var name type
var a int //0
var s string //""
var b bool //false
// 0 false
fmt.Println(a, s, b)
}
func variableDeclaration2(){
// 变量定义2:类型推断(根据值推断变量类型)--> var name = value
var b = 3
var c = true
var d, e, f = 4, "aa", false;
// 结果:3 true 4 aa false
fmt.Println(b, c, d, e, f)
}
func variableDeclaration3(){
// 变量定义3:不需要var声明,仅用于函数内变量,不能用于聚合变量: name := value
s1 := "hello go"
// 结果:hello go
fmt.Println(s1)
}
// 变量定义4:变量聚合定义: var(name1 = value1 name2=value2)
var (
str = "hello go world"
bb = true
//下面语句报错:syntax error: unexpected :=, expecting type
//cc := 33
)
func main(){
typeDefaultValue()
variableDeclaration2()
variableDeclaration3()
//hello go world true
fmt.Println(str,bb)
//下面语句报错:聚合定义变量多个变量之间要换行
//var (dd="dd" ee=44)
}
2. 值类型和引用类型
值类型
- 值类型的变量直接指向内存中的值。
- 当使用=将一个值变量赋值给另一个变量,其实是将内存中的值进行了拷贝,并不是相同的内存区域。(i和j的内存地址不同)
- 值类型变量的值存在栈中。
- 通过&b来获取b的内存地址。
package main
import (
"fmt"
)
func main(){
//i的内存地址:0xc0420080a8
//j的内存地址:0xc0420080f0
//i重新赋值后的内存地址不变:0xc0420080a8
var i = 7
fmt.Println(i)
fmt.Println(&i)
var j = i
fmt.Println(j)
fmt.Println(&j)
i = 8
fmt.Println(i)
fmt.Println(&i)
}
引用类型
一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。
当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
3. 常量
- 类型只有:字符串,数字(整数,浮点,复数),布尔
- const identifier [type] = value(type可选,编译器可以通过值推断出来)
- 常量还可以是内置函数的值。
- iota:特殊常量,可以被编译器修改,表示常量每加一行,它的值加1,相当于常量的行索引。
package main
import (
"fmt"
"unsafe"
)
const (
d = "test"
b = unsafe.Sizeof(d)
c = len(d)//内置函数
)
func main(){
//test 16 4
fmt.Println(d, b, c)
const (
gg = iota //常量中每加一行加1,初始值为0
hh
kk
ll = "aaaa"
mm //不初始化时,默认是上一个变量的值
nn = 100
oo
pp = iota
qq
)
//0 1 2 aaaa aaaa 100 100 7 8
fmt.Println(gg, hh, kk, ll, mm, nn, oo, pp, qq)
}
4. 运算符
4.1 算术运算符
+、- 、* 、/ 、%、++ 、–(加减乘除求余自增自减)
4.2 关系运算符
== 、!=、 >、 <、 >=、 <=
4.3 逻辑运算符
&&、 ||、 !
4.4 位运算符
&(全1是1)、 |(有1是1)、 ^(异或,相同为1)、 << 、>>
4.5 赋值运算符
=、 +=、 -=、 *=、 /=、 %=、 <<= 、>>=、 &= 、|= 、^=
4.6 其他运算符
&a:获取变量a的实际地址(内存地址)
*a:指针变量
指针变量 * 和地址值 & 的区别:
指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字。
5. 条件判断语句
package main
import (
"fmt"
)
func main(){
var a = 25
// if条件语句
if a < 20 {
fmt.Println("a < 20")
} else {
fmt.Println("a 不小于 20")
}
fmt.Printf("a的值为: %d\n", a)
//switch语句
var score = 90
var level = "F"
switch {
case score >= 90 : level = "A"
case score >=80 && score <90 : level = "B"
case score >=70 && score < 80: level = "C"
}
switch level {
case "A" : fmt.Println("优秀")
case "B" : fmt.Println("良好")
case "C": fmt.Println("一般")
default : fmt.Println("不及格")
}
}
6. 循环
package main
import "fmt"
func main() {
var b int = 15
var a int
numbers := [6]int{1, 2, 3, 5}
/* 循环 */
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
/* for 循环 */
for a := 0; a < 10; a++ {
if a == 5 {
continue;
}
fmt.Printf("a 的值为: %d\n", a)
if a > 8 {
break;
}
}
for a < b {
a++
fmt.Printf("=a 的值为: %d\n", a)
}
// range 格式可以对 slice、map、数组、字符串等进行迭代循环
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}
7. 函数
func func_name([params])[returns] {
}
值传递:
调用函数的时候实际参数复制一份传递到函数中,这样在函数中对参数进行修改,并不会影响实际参数的值。
引用传递:
调用函数时将实际参数的地址传递到函数中,函数对参数的修改将影响到实际参数的值。
package main
import "fmt"
func exchange1(x,y int) int {
var temp int;
temp = x;
x = y;
y = temp;
return temp;
}
func exchange2(x *int,y *int) int {
var temp int;
temp = *x;
*x = *y;
*y = temp;
return temp;
}
func main() {
var a = 100;
var b = 200;
fmt.Printf("交换前a的值: %d\n", a)
fmt.Printf("交换前b的值: %d\n", b)
exchange1(a, b)
fmt.Printf("值传递交换前a的值: %d\n", a)
fmt.Printf("值传递交换前b的值: %d\n", b)
exchange2(&a, &b)
fmt.Printf("引用传递交换前a的值: %d\n", a)
fmt.Printf("引用传递交换前b的值: %d\n", b)
}
函数用法:作为值,作为闭包,作为方法
Go中的方法类似于java中类的成员方法
package main
import (
"fmt"
"math"
)
/* 函数作为返回值
匿名函数作为闭包,在闭包中对i递增1*/
func getSqueue() func() int{
i := 0
return func() int{
i += 1
return i
}
}
func main() {
/* 声明函数变量
函数作为值
*/
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
/* 使用函数 :结果3*/
fmt.Println(getSquareRoot(9))
getNextNumber := getSqueue()
fmt.Println(getNextNumber())
fmt.Println(getNextNumber())
fmt.Println(getNextNumber())
}
8. 变量作用域
- 局部变量:函数体中的变量,作用域在函数内
- 全局变量: 函数体外的变量,全包或包外(导出后)
- 形式参数:函数定义中参数,作为函数的局部变量来使用。
全局变量和局部变量可以同名,函数内优先使用局部变量
9. 数组
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列:类型相同,长度固定。
声明方法:
package main
import "fmt"
func main() {
//长度为3类型为String的数组arr
var arr [3] string
fmt.Println(len(arr)) // 3
//定义数组同时初始化
var arr1 = [3] string {"hello", "","go"}
for i:=0;i<len(arr1);i++ {
fmt.Println(arr1[i])
}
//根据数组元素个数确定数组长度
var arr2 = [...] string {"hello", "go"}
fmt.Println(len(arr2)) //2
}
9.2 多维数组
package main
import "fmt"
func main() {
//声明二维数组x行y列
//var arr [x][y][...] int
//初始化二维数组
var arr =[3][4]int {
{0,1,2,3},
{1,2,3,4},
{2,3,4,5},
}
fmt.Println(len(arr))
}
10. 指针
定义:var name *type
package main
import "fmt"
func main() {
var a = 10 //声明int变量
var ip *int //声明指针变量
ip = &a //给指针变量赋值,如果没有这一步,ip的值为nil(空指针)
var pptr **int //指向指针的指针
pptr = &ip
fmt.Printf("变量a的值:%d\n", a) //变量a的值:10
fmt.Printf("变量a的地址:%d\n", &a) //变量a的地址:825741320320
fmt.Printf("指针变量的值:%d\n", ip) //指针变量的值:825741320320
fmt.Printf("指针变量的地址:%d\n", &ip) //指针变量的地址:825741443096
fmt.Printf("通过指针获取变量的值:%d\n", *ip) //通过指针获取变量的值:10
}
11. 结构体
- 类似于java中的类,是一个数据集合
- type class_name struct{}
- 访问结构体成员:结构体.成员名
- 结构体间赋值操作,指向的内存空间并不是同一块,和java中不一样
package main
import "fmt"
//结构体,类似于java中的类
type Book struct {
name string
author string
subject string
bid int
}
func main() {
//初始化类方法一
b1 := Book{"盘龙", "我吃西红柿", "小说", 10001}
fmt.Println(b1)
//初始化类方法二
b2 := Book{name:"星辰变", author:"我吃西红柿", subject:"小说", bid:10002}
fmt.Println(b2)
//初始化类方法二
b3 := Book{name:"吞噬星空", author:"我吃西红柿", subject:"小说"}
fmt.Println(b3)
//编译错误:cannot use 10001 (type int) as type string in field value
// b4 := Book{"盘龙", "我吃西红柿", 10001}
// fmt.Println(b4)
var bo1 Book
var bo2 Book
bo1.name = "武动乾坤"
bo1.author = "天蚕土豆"
bo1.subject = "小说"
bo1.bid = 20001
bo2.name = "大主宰"
bo2.author = "天蚕土豆"
bo2.subject = "小说"
bo2.bid = 20002
fmt.Println(bo1)
fmt.Println(bo2)
fmt.Println(bo1.name)
fmt.Println(bo2.bid)
var ptr *Book
ptr = &bo1
fmt.Printf("bo1的结构体指针:%d\n", ptr)
fmt.Printf("bo2的结构体指针:%d\n", &bo2)
printBook(ptr)
//这样和java是不一样的,java中这样的赋值操作指向对象是指向同一内存空间,打印出来的name是相同的,go中并不是
var bo3 = bo1
bo3.name = "wuuu"
//指针访问成员:武动乾坤
//title内存地址:825741304192
//指针访问成员:wuuu
//title内存地址:825741304576
printBook(&bo1)
printBook(&bo3)
}
func printBook(book *Book) {
//var title = book.name
fmt.Printf("指针访问成员:%s\n", book.name)
fmt.Printf("title内存地址:%d\n", &book.name)
}
12.map
定义map方式:
- 声明map:var m1 map[key-type]value-type
- 初始化map: m1 = make(map[key-type]value-type)
package main
import "fmt"
func main() {
//定义map
var m1 map[string]int
//初始化
m1 = make(map[string]int)
fmt.Println(m1) //map[]
//放入键值对
m1["语文"] = 98
m1["数学"] = 100
m1["英语"] = 99
m1["物理"] = 100
//遍历
for k := range m1 {
fmt.Printf("我的%s成绩=%d\n", k, m1[k])
}
//判断是否存在,如果存在返回两个值:1.key对应value;2.bool类型的flag
v, isExist := m1["化学"]
if isExist {
fmt.Printf("我的化学成绩=%d\n", v)
} else {
//value=%!(EXTRA int=0)
fmt.Printf("value=", v)
fmt.Printf("我的化学成绩还没出来\n")
}
//删除元素,需要两个参数,map和要删除的key
delete(m1, "数学")
fmt.Printf("删除====\n")
//遍历
for k,v := range m1 {
fmt.Printf("==我的%s成绩=%d\n", k, v)
}
}
13. range
range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。
14. slice 切片(动态数组)
package main
import "fmt"
func main() {
//定义slice
var s1 []string
fmt.Println(s1)
s2 := make([]string, 3)
fmt.Println(s2)
var arr = [] string {"语文", "数学", "英语", "物理", "化学", "生物", "音乐"}
s := arr[1:3]
for index := range s{
fmt.Println(s[index])
}
}
15. 接口
- 定义接口的方式:
type name interface{
method1() [return-type]
method2() [return-type]
} - 结构体实现接口中的方法就是实现该接口
package main
import "fmt"
//定义Animal接口和eat()方法
type Animal interface{
eat()
}
//定义动物:狗
type Dog struct {
}
//重写接口Animal方法
func (dog Dog) eat() {
fmt.Println("狗狗吃骨头")
}
//定义动物:猫
type Cat struct {
}
//重写接口Animal方法
func (cat Cat) eat() {
fmt.Println("猫猫吃老鼠")
}
func main() {
var animal Animal = new(Dog)
animal.eat()
animal = new(Cat)
animal.eat()
}
16.并发
Go语言支持并发,通过go关键字来开启goroutine.
goroutine是轻量级线程,它的调度是由Golang运行时进行管理的。
go function-name(params)
package main
import (
"fmt"
"time"
)
func say(str string){
for i:=0; i<5; i++ {
time.Sleep(100* time.Millisecond)
fmt.Println(str)
}
}
func main() {
go say("hahaha")
say("aaaaa")
}
go语言的线程间通过channel通道进行通信, <-用于执行通道方向,如果没有,则表示通道是双向通道。
package main
import (
"fmt"
"time"
)
func say(str string){
for i:=0; i<5; i++ {
time.Sleep(100* time.Millisecond)
fmt.Println(str)
}
}
func main() {
//创建一个channel
// ch := make(chan int)
// var i = 1
//ch <-i //把i放到通道
//v := <-ch //接收通道中的值
//fmt.Println(v)
//***fatal error: all goroutines are asleep - deadlock!
//***死锁,通道不带缓冲区时,发送方会阻塞,直到接收方接收数据
ch := make(chan int, 10)
var i = 1
ch <-i //把i放到通道
v := <-ch //接收通道中的值
fmt.Println(v)
//如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;
//如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。
//接收方在有值可以接收之前会一直阻塞。
}
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
v, ok <-ch
close(ch)