您好,登录后才能下订单哦!
本篇内容主要讲解“go语言基本语法有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“go语言基本语法有哪些”吧!
一、描述
go语言是直接将源码编译成二进制机器码的语言;它支持面向对象、也支持函数式编程;go是强类型语言;Go 编程语言原生支持并发。Go 使用 Go 协程(Goroutine) 和信道(Channel)来处理并发。
二、基本语法
1、切片与数组差别:切片是动态化数组,切片是注意两者初始化和函数的区别
a.初始化差别
数组需要指定大小,不指定也会根据初始化的自动推算出大小,不可改变
a:=[...] int {1,2,3} ; a:=[3] int {1,2,3}
切片不需要指定大小
a:=[] int {1,2,3}; a:=make([]int,3); a:=make([] int ,3,5)
b.函数传递:
数组需要明确指定大小,数组是值传递,不同的数组总是代表不同的存储。
切片不需要明确指定大小,切片是地址传递。多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的。
func changeArray(a [3]int) {//数组
a[0] = 100
}
func changeSlice(s []int) {//切片
s[0] = 100
}
func main(){
a := [...]int{1, 2, 3}
changeArray(a)
fmt.Println(a[0]) //值传递,输出结果:1
var s []int = []int{1, 2, 3, 4}
fmt.Println(len(s), cap((s)))
s = append(s, 6, 7, 8)
fmt.Println(len(s), cap(s))
changeSlice(s)
fmt.Println(s[0]) //地址传递,输出结果:100
}
c.切片和垃圾回收
切片的底层指向一个数组,该数组的实际容量可能要大于切片所定义的容量。只有在没有任何切片指向的时候,底层的数组内层才会被释放,这种特性有时会导致程序占用多余的内存。
示例 函数 FindDigits
将一个文件加载到内存,然后搜索其中所有的数字并返回一个切片。
var digitRegexp = regexp.MustCompile("[0-9]+") func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) return digitRegexp.Find(b) }
这段代码可以顺利运行,但返回的 []byte
指向的底层是整个文件的数据。只要该返回的切片不被释放,垃圾回收器就不能释放整个文件所占用的内存。换句话说,一点点有用的数据却占用了整个文件的内存。
想要避免这个问题,可以通过拷贝我们需要的部分到一个新的切片中:
func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = digitRegexp.Find(b) c := make([]byte, len(b)) copy(c, b) return c }
2.数组和Map,数组的输出按照数组的放置顺序,但是Map是无序输出的,每次遍历的顺序不一样;
var m = map[string]int{ "unix": 0, "python": 1, "go": 2, "javascript": 3, "testing": 4, "philosophy": 5, "startups": 6, "productivity": 7, "hn": 8, "reddit": 9, "C++": 10, } var keys []string for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println("Key:", k, "Value:", m[k]) }
3.go语言中的指针
指针是一种存储变量内存地址(Memory Address)的变量。
如上图所示,变量 b
的值为 156
,而 b
的内存地址为 0x1040a124
。变量 a
存储了 b
的地址。我们就称 a
指向了 b
。
指针变量的类型为 *T
,该指针指向一个 T 类型的变量。
&b 获取b的内存地址值;*a 则是获取a值对应物理地址值的b值:156;
向函数传递指针参数
func change(val *int) { *val = 55 } func main() { a := 58 fmt.Println("value of a before function call is",a) b := &a change(b) fmt.Println("value of a after function call is", a) }
4.那么什么时候使用指针接收器,什么时候使用值接收器?
/* 使用值接收器的方法。 */ func (e Employee) changeName(newName string) { e.name = newName } /* 使用指针接收器的方法。 */ func (e *Employee) changeAge(newAge int) { e.age = newAge }
一般来说,指针接收器可以使用在:对方法内部的接收器所做的改变应该对调用者可见时。
指针接收器也可以被使用在如下场景:当拷贝一个结构体的代价过于昂贵时。考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体,这是很昂贵的。在这种情况下使用指针接收器,结构体不会被拷贝,只会传递一个指针到方法内部使用。
在其他的所有情况,值接收器都可以被使用。
5.在方法中使用值接收器 与 在函数中使用值参数
这个话题很多Go语言新手都弄不明白。我会尽量讲清楚。
当一个函数有一个值参数,它只能接受一个值参数。
当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
让我们通过一个例子来理解这一点。
package main import ( "fmt" ) type rectangle struct { length int width int } func area(r rectangle) { fmt.Printf("Area Function result: %d\n", (r.length * r.width)) } func (r rectangle) area() { fmt.Printf("Area Method result: %d\n", (r.length * r.width)) } func main() { r := rectangle{ length: 10, width: 5, } area(r) r.area() p := &r /* compilation error, cannot use p (type *rectangle) as type rectangle in argument to area */ //area(p) p.area()//通过指针调用值接收器 }
6.在方法中使用指针接收器 与 在函数中使用指针参数
和值参数相类似,函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。
package main import ( "fmt" ) type rectangle struct { length int width int } func perimeter(r *rectangle) { fmt.Println("perimeter function output:", 2*(r.length+r.width)) } func (r *rectangle) perimeter() { fmt.Println("perimeter method output:", 2*(r.length+r.width)) } func main() { r := rectangle{ length: 10, width: 5, } p := &r //pointer to r perimeter(p) p.perimeter() /* cannot use r (type rectangle) as type *rectangle in argument to perimeter */ //perimeter(r) r.perimeter()//使用值来调用指针接收器 }
7.并行和并发
并行不一定会加快运行速度,因为并行运行的组件之间可能需要相互通信。在我们浏览器的例子里,当文件下载完成后,应当对用户进行提醒,比如弹出一个窗口。于是,在负责下载的组件和负责渲染用户界面的组件之间,就产生了通信。在并发系统上,这种通信开销很小。但在多核的并行系统上,组件间的通信开销就很高了。所以,并行不一定会加快运行速度!
8.线程与协程区别
线程切换从系统层面远不止 保存和恢复 CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。但是协程有一个问题,就是系统并不感知,所以操作系统不会帮你做切换。目前的协程框架一般都是设计成 1:N 模式。所谓 1:N 就是一个线程作为一个容器里面放置多个协程。那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,也就是每个协程池里面有一个调度器,这个调度器是被动调度的。意思就是他不会主动调度。协程的切换很轻。
9.缓冲信道和非缓冲信道
缓冲的channel:保证往缓冲中存数据先于对应的取数据,简单说就是在取的时候里面肯定有数据,否则就因取不到而阻塞。
非缓冲的channel:保证取数据先于存数据,就是保证存的时候肯定有其他的goroutine在取,否则就因放不进去而阻塞
到此,相信大家对“go语言基本语法有哪些”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。