Golang语言学习
花了两天时间看了下Golang
语言,发现这语言很有趣,主要是一个土拨鼠形象的语言在goland
中可以看到每一个文件都会有一个小图标,感觉还是挺可爱的,这门语言估计以后还是要拥抱了,CPP
学了一段时间,从Java
转过去着实是不简单,估计还是拥抱go
了。
主要是通过菜鸟教程来学习的,主要学习了go
的语言相关特性,go
中保留了指针,可以通过指针操作对象,同时也增加了许多的新特性,在学习的过程中,首先是关于类型定义,一般的都是前面类型后面参数,但是在go
中增加了更为人性化一面的变量定义,即通过var varName varType
的形式定义类型。
同时在定义类型的时候我们可以使用a := 1
这个可以自动识别a为一个整型变量。且其中的整型有四种有符号的整型,以及四种无符号的整型,在java
中整型的话有short/int/long
且大的可以囊括小的,但是在go中只要有类型出现变化就必须显示声明,否则不可成功。
还有就是在go
中定义方法的话是使用func methodName(param1 paramType) returnResult Type{}
这种形式来定义方法,即先声明名称,然后是参数,最后是返回值,然后再写具体的方法实现。在go
中,有个make
用来制作动态的切片,且make
只能定义slice、map、chan
三种,在go
中所有的实现都是格局数组来的,就算是实例化什么的都是这样来的,保持了语言的原生特性,很多东西没有封装,需要自己在写的时候自行实现。
在go
中声明类的话是使用struct
关键字来声明一个类,因为是结构化的语言,在go
中是没有构造方法这一说的而且在struct
中是默认实现了类似于java中的toString()
方法。还有就是接口,功能和java interface
差不多,都是方法抽象出来,等待实现自己的类进行实现,但是这里的实现需要使用new(subClass SubClass)
的时候会触发方法实现的提示goland会提示
。
最后的话就是go
的协程,协程的话是轻量级的线程,隶属于线程,需要线程作为载体支撑协程的运行。这里的话,协程一般用作对chan
写入数据,但是注意,如果写入chan
和读取chan
是一个线程直接下去的话,会造成死锁,需要切换协程进行数据的写入和读取,且如果当前协程中的chan
读取不到数据了,此时可以使用close(c)
来关闭当前携程中的通道。
package main
import (
"fmt"
"time"
)
var (
a int
b bool
)
var c, d int = 1, 2
//var e, f = 3, "hello world"
var ptr *int
// var e, f := 123,"hello" :=这种形式只能在函数体中出现
func main() {
//g, h := 100, "200"
//fmt.Println(a, b, c, d, e, f, g, h)
//fmt.Println(&a)
可以针对当前的指针类型的赋予其地址,*ptr则表示取出当前地址的值即*&d
//ptr = &d
//fmt.Println(*ptr)
//fmt.Println(&*&d)
//fmt.Println(add(c, d))
//fmt.Println(getBol(1, 2))
/* 定义局部变量 */
//selectMethod()
//loopMethod()
//gotoMethod()
//var a, b = 100, 200
//fmt.Printf("值交换之前的值输出: %d %d\n", a, b)
//swap(a, b)
//fmt.Printf("值交换之后的值输出: %d %d\n", a, b)
//fmt.Println("外部a的地址:", &a)
//ptrSwap(&a, &b)
//fmt.Printf("引用交换之后的值输出: %d %d\n", a, b)
//var p *int
//p = &a
//fmt.Println(p) // 输出的就是a的地址,那为什么方法却接收的就是值
//var d = 100
//var c *int
//c = &d
//setValue(c)
//fmt.Println(*c)
//ptrMethod()
//ptrPtrMethod()
//structMethod()
//sliceMethod()
//rangeMethod()
//mapMethod()
//fmt.Println(recursion(15))
//fmt.Println(getFibonacci(10))
//typeTransMethod()
//interfaceMethod()
//errorMethod()
//errorProcess()
//goroutine()
chanMethod()
channelBuffer()
}
/*
通道方法
通道channel是用来传递数据的一个数据结构
通道可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯。
操作符<-用于指定通道的方向,发送或接收,如果未指定方向,则为双向通道
注意:make只能用来初始化slice,map,channel三种类型
*/
func chanMethod() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
//fmt.Println(s[0])
//fmt.Println(len(s))
c := make(chan int)
go getSum(s[:len(s)/2], c)
go getSum(s[len(s)/2:], c)
x, y := <-c, <-c
fmt.Println(x, y)
openAndCloseChan()
}
func getSum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将sum发送到c这个通道中去
}
/*
可以设置通道缓冲区
*/
func channelBuffer() {
c := make(chan int, 100)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
func openAndCloseChan() {
c := make(chan int)
go proc(c) // 协程的话如果需要再中途做某些的话,因为是共用线程中的资源了,此时一个协程不能既实现读又实现写的功能
v, ok := <-c
fmt.Println(ok, v)
}
func proc(c chan int) {
c <- 1
}
/*
go中的goroutine机制
go关键字用于定义一个goroutine
同一个程序中所有的goroutine共享一个地址空间
goroutine就是一个协程,也被称作轻量级的线程
Golang在runtime,系统调用等多方面对goroutine调度进行了封装和处理,
即goroutine不完全是由用户控制,一定程度上由go运行时runtime管理
当某个goroutine阻塞时,会让出cpu给其他goroutine
*/
func goroutine() {
go say("hello") // 第一个goroutine
say("world") // 第二个goroutine
}
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func errorProcess() {
fmt.Println("外层开始")
defer func() {
fmt.Println("外层准备recover")
// recover类似于java中的finally
// 即这里有异常打印异常,没有异常的话正常执行后续的一个元素打印即可
if err := recover(); err != nil {
fmt.Printf("%#v-%#v\n", "外层", err) // err已经在上一级的函数中捕获了,这里没有异常,只是例行先执行defer,然后执行后面的代码
} else {
fmt.Println("外层没做啥事")
}
fmt.Println("外层完成recover")
}()
fmt.Println("外层即将异常")
f()
fmt.Println("外层异常后")
defer func() {
fmt.Println("外层异常后defer")
}()
}
func f() {
fmt.Println("内层开始")
defer func() {
fmt.Println("内层recover前的defer")
}()
defer func() {
fmt.Println("内层准备recover")
if err := recover(); err != nil {
fmt.Printf("%#v-%#v\n", "内层", err) // 这里err就是panic传入的内容
}
fmt.Println("内层完成recover")
}()
defer func() {
fmt.Println("内层异常前recover后的defer")
}()
panic("异常信息")
//defer func() {
// fmt.Println("内层异常后的defer")
//}()
//
//fmt.Println("内层异常后语句") //recover捕获的一级或者完全不捕获这里开始下面代码不会再执行
}
/*
go的错误处理
*/
func errorMethod() {
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10=", result)
}
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is:", errorMsg)
}
}
type DivideError struct {
dividee int // 除数
divider int // 被除数
}
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return 0, errorMsg
} else {
return varDividee / varDivider, ""
}
}
/**
golang的接口相关内容
golang的接口不像java,在java中接口与抽象类差不多,只不过是抽象出所有的公共方法
需要显式的去使用关键字implements来作为类实现接口的声明
在go中,只需要使用new关键字new相应的struct即可,会默认提示你去实现当前的接口方法
或者自己提前实现方法即可
*/
func interfaceMethod() {
var school School
school = new(NJUPT)
fmt.Println(school.getSchoolName())
}
type NJUPT struct {
}
func (N NJUPT) getSchoolName() string {
fmt.Println("my school is njupt")
return "南京邮电大学"
}
func (N NJUPT) getStudentNum() int {
fmt.Println("my student number is 100000")
return 10000
}
type School interface {
getSchoolName() string
getStudentNum() int
}
/*
类型转换
注意:go不支持隐式类型转换,只支持显式类型转化
下面关于int64转成int32需要显式声明转类型
如果是java的话
int a = 32
long b = 64
b = a
此时b就等于32了是不需要强制转换的
*/
func typeTransMethod() {
var sum int = 17
var count int = 5
//var mean float32 // 相当于java中的float,单精度的
var mean float64 // 相当于java中的double,双精度的
mean = float64(sum) / float64(count)
fmt.Printf("mean的值为%v\n", mean)
var a int64 = 100
var b int32
b = int32(a)
fmt.Printf("b的值为:%v\n", b)
}
/**
递归函数
实现阶乘
*/
func recursion(num int) int {
if num == 1 {
return 1
}
return num * recursion(num-1)
}
/**
这里需要注意如果使用数组定义的话,当前的数组长度是固定的,且返回的数组也必须是长度固定的
但是如果使用make来操作的话,此时就不需要使用固定长度的数组返回了
*/
func getFibonacci(num int) []int {
//n := num + 1
var dp = make([]int, num)
dp[0] = 1
dp[1] = 1
if num <= 1 {
return dp
}
for i := 2; i < num; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
return dp
}
/**
golang中的集合元素,定义Map操作
Map的整体操作
var map_variable map[key_data_type]value_data_type
*/
func mapMethod() {
map1 := map[int]string{1: "caoduanxi", 2: "wangluyao"}
for k, v := range map1 {
fmt.Printf("%d -> %s\n", k, v)
}
// 定义
var countryCapitalMap map[string]string
// 初始化
countryCapitalMap = make(map[string]string)
countryCapitalMap["China"] = "北京"
countryCapitalMap["France"] = "巴黎"
countryCapitalMap["American"] = "纽约"
fmt.Println(countryCapitalMap)
// 查看元素在集合中是否存在
capital, ok := countryCapitalMap["American"]
fmt.Println(ok)
if ok {
fmt.Printf("American's captial: %s\n", capital)
} else {
fmt.Printf("Americal's captial is not exist!")
}
// 删除元素
delete(countryCapitalMap, "American")
}
/*
golang数据范围方法
range是关键字
*/
func rangeMethod() {
nums := []int{1, 2, 3, 4, 5}
sum := 0
// 这里传入的是index:nums[index],由于不需要使用该元素的符号,所以采用空白符_省略
for _, num := range nums {
sum += num
}
fmt.Printf("sum: %d\n", sum)
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
mapKv := map[string]string{"a": "caoduanxi", "b": "wangluyao"}
for k, v := range mapKv {
fmt.Printf("%s -> %s\n", k, v)
}
}
/**
切片方法
对数组的抽象,Go数组的长度不可变,在特定场景中不太适用,Go提供了一种灵活,功能强悍的内置类型切片,动态数组
这种可以实现数组的动态增加
*/
func sliceMethod() {
// make([]int, len, cap)
var numbers = make([]int, 3, 5)
printSlice(numbers)
// 这里相当于重新赋值了
numbers = []int{1, 2, 3, 4, 5}
printSlice(numbers)
// 其实这里表示的是2->5之间的cap即从cap中可以看到当前的元素是否还可以存元素
// len = 2 cap = 3 slice = [3 4] => 表示当前3 4还可以被拼接一个元素
printSlice(numbers[2:4])
printSlice(numbers[2:5])
printSlice(numbers[1:5])
numbers = append(numbers, 10, 11, 12)
printSlice(numbers)
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
// 用于参数的赋值
copy(numbers1, numbers)
printSlice(numbers1)
}
func printSlice(x []int) {
fmt.Printf("len = %d cap = %d slice = %v\n", len(x), cap(x), x)
}
/**
测试结构体的方法
golang是结构化的语言
*/
type Student struct {
name string
age int
address string
}
/**
结构体指针
*/
func structMethod() {
var student Student
student.name = "曹端喜"
student.age = 24
student.address = "广东省深圳市南山区"
fmt.Println(student)
}
/**
指针的指针的方法
*/
func ptrPtrMethod() {
var a int
var ptr *int
var pptr **int
a = 1
ptr = &a
pptr = &ptr
fmt.Printf("变量a的地址: %d\n", &a)
fmt.Printf("变量a的值: %d\n", a)
fmt.Printf("指向a的指针的地址: %d\n", &ptr)
fmt.Printf("指向a的指针的值: %d\n", *ptr)
fmt.Printf("指向a的指针的指针的地址: %d\n", &pptr)
fmt.Printf("指向a的指针的指针的值: %d\n", **pptr)
}
const MAX int = 10
func ptrMethod() {
var ptr *int
fmt.Printf("当前ptr指针的值: %d\n", ptr)
// %t表示true or false => bool
fmt.Printf("当前ptr是否为空指针: %t\n", ptr == nil)
var arr [10]int
for i := 0; i < 10; i++ {
arr[i] = i + 10
}
for j := 0; j < 10; j++ {
fmt.Printf("Element[%d] = %d\n", j, arr[j])
}
// 定义数组指针类型
var ptrArr [MAX]*int
for i := 0; i < MAX; i++ {
ptrArr[i] = &arr[i]
}
for i := 0; i < MAX; i++ {
fmt.Printf("ptrArr[%d] = %d\n", i, *ptrArr[i])
}
}
/**
数组方法:
主要是针对一维数组和多维数组来实现的
*/
func arrMethod() {
var arr [10]int
for i := 0; i < 10; i++ {
arr[i] = i + 10
}
for j := 0; j < 10; j++ {
fmt.Printf("Element[%d] = %d\n", j, arr[j])
}
// 这种属于可以自动识别当前的数组的元素长度
var arr2 = [...]int{1, 2, 3, 454365, 75, 676}
var len2 = len(arr2)
for k := 0; k < len2; k++ {
fmt.Printf("arr2[%d] = %d\n", k, arr2[k])
}
// 多维数组
var arr3 = [2][3]int{{1, 2, 3}, {4, 5, 6}}
var len31 = len(arr3)
var len32 = len(arr3[0])
for k := 0; k < len31; k++ {
for l := 0; l < len32; l++ {
fmt.Printf("arr3[%d][%d] = %d\n", k, l, arr3[k][l])
}
}
}
func setValue(c *int) {
*c = 200
}
/**
引用传递
*/
func ptrSwap(a *int, b *int) {
fmt.Println(a)
var temp = *a
*a = *b
*b = temp
}
func swap(a int, b int) {
fmt.Println("值交换的a地址: ", &a)
var temp = a
a = b
b = temp
}
func gotoMethod() {
var a = 10
LABEL:
for a < 20 {
if a == 15 {
a += 1
goto LABEL
}
fmt.Printf("a's value is: %d\n", a)
a++
}
}
/**
循环方法
*/
func loopMethod() {
var i int
// 第一种形式:经典的遍历形式
//for i = 1; i < 10; i++ {
// fmt.Println(i)
//}
// 类似于while形式的循环
for i < 10 {
fmt.Println(i)
i++
}
}
func selectMethod() {
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:
if ok {
fmt.Printf("received", i3, " from c3\n")
} else {
fmt.Printf(" c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
func switchMethod() {
var grade string = "B"
var marks int
// 给当前的地址注入一个值
fmt.Scanln(&marks)
switch marks {
case 90:
grade = "A"
case 80:
grade = "B"
case 50, 60, 70:
grade = "C"
default:
grade = "D"
}
switch {
case grade == "A":
fmt.Printf("优秀!\n")
case grade == "B", grade == "C":
fmt.Printf("良好\n")
case grade == "D":
fmt.Printf("及格\n")
case grade == "F":
fmt.Printf("不及格\n")
default:
fmt.Printf("差\n")
}
fmt.Printf("你的等级是 %s\n", grade)
}
/**
采用的是方法第一位与第二位有差距
*/
func getBol(a int, b int) bool {
if a < b {
return true
} else {
return false
}
}
/**
注意在go语言中,也使用了&符号和*符号
这些符号的作用就是&符号是获取当前的变量的地址
而*符号则表明当前变量是一个指针
指针变量保存的是一个地址值,会分配独立的内存在存储一个整型数字
*/
/**
声明方法
*/
func add(int, int) int {
x, y := 1, 2
return x + y
}
Keep thinking! Keep coding!
加油,前进的路从来不是一帆风顺的,遇到坎坷便是成功的冲锋号!加油! 2020-8-27 16:02:15 写于深圳