Go语言学习笔记

Golang学习笔记

Golang的优势

  • 极简单的部署方式
    • 可直接编译成机器码
    • 不依赖其他库
    • 直接运行即可部署
  • 静态类型语言
    • 编译的时候能够直接检查出隐藏的错误
  • 语言层面的并发
    • 语言特点支持
    • 充分利用多核
  • 强大的标准库支撑
    • runtime系统调度机制
    • 高效的GC垃圾回收机制
    • 丰富的标准库
  • 简单、容易上手
    • 只有25个关键字
    • 语法跟C语言有相似点
    • 有面向对象特征(继承、多态、封装)
    • 支持跨平台开发
  • 大厂
    • Google (k8s)
    • 腾讯(docker)百度 京东 小米 七牛云 滴滴
    • 美团 阿里巴巴 字节跳动 抖音 哔站

Golang适合做什么

优点

  • 云计算基础设施领域:docker、kubernetes、etcd、consul、cloudflare CDN、七牛云存储
  • 基础后端软件:tidb、influxdb、cockroachdb
  • 微服务:go-kit、micro、monzo bank的typhon、bilibili
  • 互联网基础设施:以太坊、hyperledger

不足

  • 大部分包管理在github上面
  • 无泛化类型,有些相同功能的代码需要重复写(go 1.18已加上)
  • 对于所有的Exception都用Error来处理
  • 对C的降级处理并非无缝,没有C降级到asm那么完美

Golang文件
bin:编译好的代码文件存放处
pkg:依赖的存放位置
src:源码的存放位置

  • 语法规范
    花括号要跟函数定义处在同一行
  • 运行.go文件:
    • go run main.go 直接运行main.go文件,文件存储在临时目录
    • go build main.go 生成可执行main.exe,再通过./main.go执行

Golang变量声明
go局部变量声明有四种方式,全局变量声明只有三种方式,:=符号不能声明全局变量。对于多变量的声明跟单变量声明类似,多一种多行声明的方式。

package main

import "fmt"

//全局变量声明
//方法一到三都适用
var A int
var B int = 100
var C = 100

func main() {
	//局部变量声明
	//方法一
	var a int
	a = 10
	fmt.Println(a)
	//方法二
	var b int = 10
	fmt.Println(b)
	//方法三
	var c = 10
	fmt.Println(c)
	//方法四
	d := 10
	fmt.Println(d)

	fmt.Println("下面是全局变量的输出结果:")
	//方法一
	A = 100
	fmt.Println(A)
	//方法二
	fmt.Println(B)
	//方法三
	fmt.Println(C)

	fmt.Println("下面是多变量的声明和输出结果")
	//单行写法
	var Q1, Q2 int = 1000, 2000
	fmt.Println(Q1, Q2)
	//多行写法
	var ( //注意此处是圆括号
		S1 int    = 1000
		S2 string = "hello go"
	)
	fmt.Println(S1, S2)
}

在进行多变量多行声明时,要特别注意var关键字后的括号为()。

Golang常量声明
使用const关键字进行常量的声明:

//常量声明	const a = 10
	const (
		b = 15
		c = 20
	)

const声明中使用iota关键字进行逐行+1。

//iota在const中的使用
	//iota初始值=0,下面每一行会默认+1,只能在const里使用
	const (
		A = 2 * iota
		B
		C
	)

上述代码的运行结果如下:

0 2 4

iota第一行初始值为0,每多一行常量值会+1,可以对iota关键字进行运算操作。
Golang函数多个返回值
Golang的函数可以有多个返回值,可以直接

return a,b

来返回a和b的值,也可以直接使用return,后面不需要跟变量。

Golang关键字

defer

defer关键字在Golang中常常用于代码最后执行的部分,如果有多个defer语句,则执行过程依照压栈、出栈的形式进行运行。

func main{
	defer fun1()
	defer fun2()
	defer fun3()
}

参考上述代码,将fun1到fun3依次压栈,执行时从fun3到fun1依次出栈,所以打印顺序为fun3到fun1。

如果一个函数体里有返回值,那么先执行return还是defer呢?

我们来看下面的代码:

package main

import "fmt"

func printdefer() int {//defer输出函数
	fmt.Println("defer running!")
	return 0
}

func printreturn() int {//return输出函数
	fmt.Println("return running!")
	return 0
}

func goget() int {//同时调用
	defer printdefer()
	return printreturn()
	return 0
}

func main() {
	goget()
}

通过这个代码,我们可以得到defer和return的执行顺序,程序执行结果如下:

return running!
defer running!

可知defer永远是在最后执行。

range

Golang数组

  • 数组的声明
//方法一
var Array1 [10] int
//方法二
array1 := [10] int {0,1,2,3}
//方法三
array1 := [4] {0,1,2,3}

程序运行结果如下:

[0 0 0 0 0 0 0 0 0 0]
[0 1 2 3 0 0 0 0 0 0]
[0 1 2 3]

由程序运行的结果可知,当只声明数组时,数组内的数值会自动初始化为0。我们也可以对数组的一部分进行赋值。

func arrayDemo(array1 [4]int){//值拷贝
}

当数组作为函数参数时,进行的是值拷贝,不会改变原函数中数组的值。

  • 动态数组

slice(动态数组)的声明结构有以下几种

//声明slice是一个切片,并且默认值为1,2,3,长度为3
slice1 := []int {1,2,3}
%v可以查看所有信息

//声明slice是一个切片,无默认值
var slice1 [] int
slice1 =make  ([]int ,3 )//通过make给slice分配地址空间

slice1:=make([]int ,3)

//判断slice是否为空
if slice == nil

在作为函数参数时,进行的是引用传递

func arrayDemo(array1 [] int){//引用传递
}

slice的长度的追加

//make(type,len,cap) type为类型,len为长度,cap为切片容量
var character = make([] int , 3, 5)
//通过append(slice,element)slice为操作的切片,element为添加的元素来进行元素的追加
character = append(character,2)

来看下面这段代码及输出:

var character = make([]int, 3, 5)
fmt.Printf("slice = %v,len = %d,cap = %d\n", character, len(character), cap(character))
//通过append(slice,element)slice为操作的切片,element为添加的元素来进行元素的追加
character = append(character, 2)
fmt.Printf("slice = %v,len = %d,cap = %d\n", character, len(character), cap(character))
输出结果:
slice = [0 0 0],len = 3,cap = 5
slice = [0 0 0 2],len = 4,cap = 5

当len==cap时,若想继续追加元素,slice会自动扩容,增加当前cap长度的内存空间,即当前内存空间为2*cap长度。

character = append(character, 4)
fmt.Printf("slice = %v,len = %d,cap = %d\n", character, len(character), cap(character))

输出结果:
slice = [0 0 0 2 3 4],len = 6,cap = 10

slice的截取
1、采用左闭右开

var slice = []int{1, 2, 3}
fmt.Printf("slice = %v\n", slice)

s := slice[0:2]
fmt.Printf("s = %v\n", s)

输出结果为:
slice = [1 2 3]
s = [1 2]

2、通过copy函数

var slice = []int{1, 2, 3}
fmt.Printf("slice = %v\n", slice)

s := slice[0:2]
fmt.Printf("s = %v\n", s)

s1 := make([]int, 3)
copy(s1, s)
fmt.Printf("s1 = %v\n", s1)

输出结果为:
slice = [1 2 3]
s = [1 2]
s1 = [1 2 0]

第一种方式可以看成是浅拷贝,采用copy函数则为深拷贝。

map

  • map的声明
    map的声明需要给map分配地址空间,一般为:make(map[int]string)的方式,即通过make进行分配。
    下面提供几种声明方式:
func main() {
	//第一种声明
	var map1 = make(map[int]string, 10)//声明开辟的空间个数
	map1[1] = "h"
	map1[2] = "he"
	map1[3] = "hel"
	fmt.Println(map1)

	//第二种声明
	var map2 = make(map[int]string)//不声明空间个数,根据定义开辟
	map2[1] = "wanzi"
	map2[2] = "yongzai"
	map2[3] = "halo"
	map2[4] = "readme"
	fmt.Println(map2)

	//第三种声明
	map3 := make(map[int]string)
	map3[1] = "re"
	map3[2] = "er"
	fmt.Println(map3)
}

  • map的使用
    下面演示对map进行赋值、遍历、删除和函数参数的传递
func change(map_class_add map[string]string) {
	//进行的是引用传递,即传入参数的指针地址
	map_class_add["qianrushi"] = "qianrushi1"
}
	//赋值
	map_class := make(map[string]string)
	map_class["ruanjian"] = "runajian1"
	map_class["jisuanji"] = "jisuanji1"
	fmt.Println(map_class)
	
	//遍历
	for key, value := range map_class {//用range遍历map的每一组键值对
		fmt.Println(key + ":" + value)
	}

	//引用函数进行传参
	fmt.Println("——————————————————")
	change(map_class)
	for key, value := range map_class {
		fmt.Println(key + ":" + value)
	}
	
	//删除
	fmt.Println("——————————————————")
	delete(map_class, "ruanjian")
	for key, value := range map_class {
		fmt.Println(key + ":" + value)
	}

输出结果如下:

map[jisuanji:jisuanji1 ruanjian:runajian1]
jisuanji:jisuanji1
ruanjian:runajian1
——————————————————
ruanjian:runajian1
jisuanji:jisuanji1
qianrushi:qianrushi1
——————————————————
jisuanji:jisuanji1
qianrushi:qianrushi1

面向对象的表示
go语言中类的定义采用的是下面这种格式

type student struct {
	Name  string
	Age   int
	Class int
}

其中student为类名,{}内的为属性值

类方法格式如下:

func (s *student) Setstuname(newname string) string {
	s.Name = newname
	return s.Name
}

func 后面跟的()内的值意为该方法是student类的方法,s即为一个对象,后面的内容与go语言函数的定义相同。
在此需要注意,类方法描述捆绑的类有两种形式,一种为(s student),另一种为(s *student)。在语法上两种形式都是可行的,但是在实际应用中会有很大的不同。我们通过下面的例子来讲解:

`Example1`

package main

import "fmt"

type student struct {
	Name  string
	Age   int
	Class int
}

func (stu student) Setstuname(newname string) {
	stu.Name = newname
	stu.Age = 17
	stu.Class = 5
}

func (stu student) show() {
	fmt.Println("姓名:" + stu.Name)
	fmt.Println("年龄:", stu.Age)
	fmt.Println("班级:", stu.Class)
}

func main() {
	s := student{}
	s.show()
	name := "李四"
	s.Setstuname(name)
	s.show()
}

`Example2`

package main

import "fmt"

type student struct {
	Name  string
	Age   int
	Class int
}

func (stu *student) Setstuname(newname string) {
	stu.Name = newname
	stu.Age = 17
	stu.Class = 5
}

func (stu *student) show() {
	fmt.Println("姓名:" + stu.Name)
	fmt.Println("年龄:", stu.Age)
	fmt.Println("班级:", stu.Class)
}

func main() {
	s := student{}
	s.show()
	name := "李四"
	s.Setstuname(name)
	s.show()
}

看了上面两个例子,大家可以自己运行一下代码看看有什么区别,并思考一下原因

在Example1中,当我们进行第一次show操作时,打印的是初始化之后的s对象。在进行修改name操作后,s对象被拷贝到Setstuname方法中,stu的变量生命周期也只限于这一个方法内,所以我们在方法内的操作只会随着方法调用完而消失。
而在Example2中,进行修改name操作后,传入方法的是主函数中s的地址,赋值给stu对象,所以进行的所有修改操作都会更改s的值。
两个例子的打印结果如下:

`Example1`
姓名:
年龄: 0
班级: 0
姓名:
年龄: 0
班级: 0
`Example2`
姓名:
年龄: 0
班级: 0
姓名:李四
年龄: 17
班级: 5

总的来说,根本区别就是选择值的拷贝还是地址的传递。在进行读操作时,其实两种方法都可以;但是在进行写操作时,只有地址的传递才能够进行修改。

面向对象的封装
类名、属性名、方法名如果是大写,则其他包可以对其进行调用。如果是小写,只能在本包内进行访问。

  • 38
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值