0
点赞
收藏
分享

微信扫一扫

【Golang 快速入门】基础语法 + 面向对象

Sikj_6590 2022-02-06 阅读 94

Golang 快速入门

Golang 语言特性

Golang 的优势

极简单的部署方式:可直接编译成机器码、不依赖其他库、直接运行即可部署。

静态类型语言,编译的时候可以检查出大多数问题。

语言层面的并发:天生的基因支持、充分的利用多核

// Go 语言实现并发的代码
func goFunc(i int) {
	fmt.Println("goroutine ", i, " ...")
}

func main() {
	for i := 0; i < 1000; i++ {
		go goFunc(i) // 开启一个并法协程
	}
	time.Sleep(time.Second)
}

强大的标准库:runtime 系统调度机制、高效的 CG 垃圾回收、丰富的标准库

“大厂” 领军:Google、facebook、Tencent、Baidu、七牛、字节 …

Golang 的应用场景

1、云计算基础设施领域

代表项目:docker、kubernetes、etcd、consul、cloud flare CDN、七牛云存储 等。

2、基础后端软件

代表项目:tidb、influxdb、 cockroach 等。

3、微服务

代表项目:go-kit、 micro、 monzo bank 的 typhon、bilibili 等。

4、互联网基础设施

代表项目:以太坊、hyperledger 等。


Golang 明星作品:DockerKubernetes

Golang 的不足

1、包管理,大部分包都托管在 Github 上。

2、无泛化类型。

3、所有 Exception 都用 Error 来处理(有争议)。

4、对 C 的降级处理,并非无缝,没有 C 降级到 asm 那么完美。(序列化问题)

基础语法

main

package main

// 导多个包
import (
	"fmt"
	"time"
)

// Golang 有无 ; 都可,建议不加
func main() { // 函数的 { 必须和函数名同行, 否则编译报错
	fmt.Println("Hello Go!")
	time.Sleep(1 * time.Second)
}

变量

局部变量的声明:

// 方法一:声明一个变量 默认的值是0
var a int 

// 方法二:声明一个变量,初始化一个值
var b int = 100

// 方法三:初始化的时候, 省去数据类型,通过值自动匹配当前的变量的数据类型
var c = 100

// 方法四:(常用) 省去var关键字,直接自动匹配
d := 100

全局变量的声明:以上只有方法四不支持(编译会报错)

多变量的声明:

// 单行写法
var xx, yy int = 100, 200
var kk, ll = 100, "Aceld"

// 多行写法
var (
  vv int  = 100
  jj bool = true
)

常量与 iota

使用 const 定义常量,常量是只读的,不允许修改。

const a int = 10

const (
	a = 10
  b = 20
)

const 可以用来定义枚举:

const {
  BEIJING = 0
  SHANGHAI = 1
  SHENZHEN = 3
}

const 可以和 iota 一起使用来定义有规则的枚举

const (
	// 可以在const() 添加一个关键字 iota, 每行的iota都会累加1, 第一行的iota的默认值是0
	BEIJING = iota	// iota = 0
	SHANGHAI 		  	// iota = 1
	SHENZHEN      	// iota = 2
)

const (
	a, b = iota+1, iota+2 // iota = 0, a = iota + 1, b = iota + 2, a = 1, b = 2
	c, d				  // iota = 1, c = iota + 1, d = iota + 2, c = 2, d = 3
	e, f				  // iota = 2, e = iota + 1, f = iota + 2, e = 3, f = 4

	g, h = iota * 2, iota *3  // iota = 3, g = iota * 2, h = iota * 3, g = 6, h = 9 
	i, k					   // iota = 4, i = iota * 2, k = iota * 3 , i = 8, k = 12
)

函数

多返回值

单返回值的函数:

func foo1(a string, b int) int {
	return 100
}

多返回值的函数:

// 返回多个返回值,匿名的
func foo2(a string, b int) (int, int) {
	return 666, 777
}

// 返回多个返回值,有形参名称的
func foo3(a string, b int) (r1 int, r2 int) {
	// r1 r2 属于foo3的形参,初始化默认的值是0
	// r1 r2 作用域空间 是foo3 整个函数体的{}空间
	fmt.Println("r1 = ", r1) // 0
	fmt.Println("r2 = ", r2) // 0

	// 给有名称的返回值变量赋值
	r1 = 1000
	r2 = 2000

	return
}

func foo4(a string, b int) (r1, r2 int) {
	// 给有名称的返回值变量赋值
	r1 = 1000
	r2 = 2000

	return
}

init 函数

每个 go 程序都会在一开始执行 init() 函数,可以用来做一些初始化操作:

package main

import "fmt"

func init() {
	fmt.Println("init...")
}

func main() {
	fmt.Println("hello world!")
}
init...
hello world!

如果一个程序依赖了多个包,它的执行流程如下图:

制作包的时候,项目路径如下:

$GOPATH/GolangStudy/5-init/ 
├── lib1/
│ └── lib1.go
├── lib2/
│ └── lib2.go 
└── main.go

lib1 .init() ...
lib2 .init() ...
lib1Test()
lib2Test()

import 导包

  • import _ "fmt"

    给 fmt 包一个匿名, ⽆法使用当前包的⽅法,但是会执行当前的包内部的 init() 方法

  • import aa "fmt"

    给 fmt 包起一个别名 aa,可以用别名直接调用:aa.Println()

  • import . "fmt"

    将 fmt 包中的全部方法,导入到当前包的作用域中,全部方法可以直接调用,无需 fmt.API 的形式

指针

经典:在函数中交换两数的值

func swap(pa *int, pb *int) {
	var temp int
	temp = *pa
	*pa = *pb
	*pb = temp
}

func main() {
	var a, b int = 10, 20

	swap(&a, &b) // 传地址

	fmt.Println("a = ", a, " b = ", b)
} 

defer

defer 声明的语句会在当前函数执行完之后调用:

func main() {
	defer fmt.Println("main end")
	fmt.Println("main::hello go ")
}
main::hello go main end

如果有多个 defer,依次入栈,函数返回后依次出栈执行:

上图执行顺序:func3() -> func2() -> func1()


关于 defer 和 return 谁先谁后:

func deferFunc() int {	fmt.Println("defer func called...")	return 0}func returnFunc() int {	fmt.Println("return func called...")	return 0}func returnAndDefer() int {	defer deferFunc()	return returnFunc()}func main() {	returnAndDefer()}
return func called...defer func called...

结论:return 之后的语句先执⾏,defer 后的语句后执⾏

切片 slice

数组

声明数组的方式:(固定长度的数组)

var array1 [10]intarray2 := [10]int{1,2,3,4}array3 := [4]int{1,2,3,4}

数组的长度是固定的,并且在传参的时候,严格匹配数组类型

// 传入参数的数组长度为4,则只能传递长度为4的数组func printArray(myArray [4]int) {	fmt.Println(myArray) // [1 2 3 4]	myArray[0] = 666     // 数组是值传递}func main() {	myArray := [4]int{1, 2, 3, 4}	printArray(myArray)	fmt.Println(myArray) // [1 2 3 4]}

声明动态数组和声明数组一样,只是不用写长度。

// 不指定长度则是动态数组func printArray(myArray []int) {	fmt.Println(myArray) // [1 2 3 4]	myArray[0] = 10      // 动态数组是引用传递}func main() {	myArray := []int{1, 2, 3, 4}	printArray(myArray)	fmt.Println(myArray) // [1 2 3 4]}

slice

slice 的声明方式:通过 make 关键字

// 1 声明一个切片,并且初始化,默认值是1,2,3,长度是3slice1 := []int{1, 2, 3} // [1 2 3]// 2 声明一个切片,但是没有给它分配空间var slice2 []int // slice2 == nil// 开辟3个空间,默认值是0slice2 = make([]int, 3) // [0 0 0]// 3 声明一个切片,同时给slice分配3个空间,默认值是0var slice3 []int = make([]int, 3) // [0 0 0]// 4 声明一个切片,同时给slice分配3个空间,默认值是0,通过:=推导出slice是一个切片slice4 := make([]int, 3) // [0 0 0]

len() 和 cap() 函数:

  • len:长度,表示左指针⾄右指针之间的距离。
  • cap:容量,表示指针至底层数组末尾的距离。

切⽚的扩容机制,append 的时候,如果长度增加后超过容量,则将容量增加 2 倍。

var numbers = make([]int, 3, 5)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)// 向numbers切片追加一个元素1, len = 4, [0,0,0,1], cap = 5numbers = append(numbers, 1)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)// 向numbers切片追加一个元素2, len = 5, [0,0,0,1,2], cap = 5numbers = append(numbers, 2)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)// 向一个容量cap已经满的slice 追加元素, len = 6, cap = 10numbers = append(numbers, 3)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
len = 3, cap = 5, slice = [0 0 0]len = 4, cap = 5, slice = [0 0 0 1]len = 5, cap = 5, slice = [0 0 0 1 2]len = 6, cap = 10, slice = [0 0 0 1 2 3]

slice 操作

可以通过设置下限以及上限设置截取切片 [lower-bound: upper-bound],实例:

func main() {	/* 创建切片 */	numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}	fmt.Println(numbers)	/* 打印原始切片 */	fmt.Println("number ==", numbers)	/* 打印子切片从索引1(包含)到索引4(不包含) */	fmt.Println("numbers[1:4] ==", numbers[1:4])	/* 默认下限为 0 */	fmt.Println("numbers[:3] ==", numbers[:3])	/* 默认上限为 len(s) */	fmt.Println("numbers[4:] ==", numbers[4:])	numbers1 := make([]int, 0, 5)	fmt.Println(numbers1)	/* 打印子切片从索引 0(包含) 到索引 2(不包含) */	numbers2 := numbers[:2]	fmt.Println(numbers2)	/* 打印子切片从索引 2(包含) 到索引 5(不包含) */	numbers3 := numbers[2:5]	fmt.Println(numbers3)}
[0 1 2 3 4 5 6 7 8]number == [0 1 2 3 4 5 6 7 8]numbers[1:4] == [1 2 3]numbers[:3] == [0 1 2]numbers[4:] == [4 5 6 7 8][][0 1][2 3 4]

利用 copy 函数拷贝切片,是深拷贝。

slice1 := []int{1, 2, 3}slice2 := make([]int, 3)copy(slice2, slice1)slice2[0] = 10fmt.Println(slice1) // [1 2 3]

直接赋值是切片,是浅拷贝。

slice1 := []int{1, 2, 3}slice2 := slice1slice2[0] = 10fmt.Println(slice1) // [10 2 3]

map

map 的声明

map 的第一种声明方式:

// 声明myMap1是一种map类型 key是string,value是stringvar myMap1 map[string]stringfmt.Println(myMap1 == nil) // true// 使用map前,需要先用make给map分配数据空间myMap1 = make(map[string]string, 10)myMap1["one"] = "java"myMap1["two"] = "c++"myMap1["three"] = "python"fmt.Println(myMap1)// map[one:java three:python two:c++]

map 的第二种声明方式:

myMap2 := make(map[int]string)myMap2[1] = "java"myMap2[2] = "c++"myMap2[3] = "python"fmt.Println(myMap2)// map[1:java 2:c++ 3:python]

map 的第三种声明方式:

myMap3 := map[string]string {  "one":   "php",  "two":   "c++",  "three": "python",}fmt.Println(myMap3)// map[one:java three:python two:c++]

map 的使用

func printMap(cityMap map[string]string) {
	for key, value := range cityMap {
		fmt.Println("key = ", key+", value = ", value)
	}
}

func AddValue(cityMap map[string]string) {
	// map 是引用传递
	cityMap["England"] = "London"
}

func main() {
	cityMap := make(map[string]string)
	// 添加
	cityMap["China"] = "Beijing"
	cityMap["Japan"] = "Tokyo"
	cityMap["USA"] = "NewYork"
	// 删除
	delete(cityMap, "China")
	// 遍历
	printMap(cityMap)
	fmt.Println("-------")

	// 修改
	cityMap["USA"] = "DC"
	// 利用函数添加 - map 是引用传递
	AddValue(cityMap)
	// 遍历
	printMap(cityMap)
}
key =  Japan, value =  Tokyo
key =  USA, value =  NewYork
-------
key =  England, value =  London
key =  Japan, value =  Tokyo
key =  USA, value =  DC

面向对象编程

利用 type 可以声明某个类型的别名(理解为声明一种新的数据类型)

type myint int

func main() {
  	var a myint = 10
		fmt.Println("a = ", a)
		fmt.Printf("type of a = %T\n", a)
}
a =  10
type of a = main.myint

struct

func changeBook(book Book) {
	// 传递一个book的副本
	book.price = "666"
}

func changeBook2(book *Book) {
	// 指针传递
	book.price = "777"
}

func main() {
	var book Book
	book.title = "Golang"
	book.price = "111"
	fmt.Printf("%v\n", book) // {Golang 111}

	changeBook(book)
	fmt.Printf("%v\n", book) // {Golang 111}

	changeBook2(&book)
	fmt.Printf("%v\n", book) // {Golang 777}
}

封装

Golang 中,类名、属性名、⽅法名 首字⺟大写 表示对外(其他包)可以访问,否则只能够在本包内访问。

// 如果类名首字母大写,表示其他包也能够访问
type Hero struct {
	// 如果类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
	Name  string
	Ad    int
	level int // 只能本包访问
}
func (h *Hero) Show() {
	fmt.Println("Name = ", h.Name)
	fmt.Println("Ad = ", h.Ad)
	fmt.Println("Level = ", h.level)
	fmt.Println("---------")
}

func (h *Hero) GetName() string {
	return h.Name
}

// 不用指针则传递的是副本,无法赋值
func (h *Hero) SetName(newName string) {
	h.Name = newName
}

func main() {
	hero := Hero{Name: "zhang3", Ad: 100}
	hero.Show()

	hero.SetName("li4")
	hero.Show()
}
Name =  zhang3
Ad =  100
Level =  0
---------
Name =  li4
Ad =  100
Level =  0
---------

继承

父类:

type Human struct {
	name string
	sex  string
}

func (h *Human) Eat() {
	fmt.Println("Human.Eat()...")
}

func (h *Human) Walk() {
	fmt.Println("Human.Walk()...")
}

子类:

type SuperMan struct {
	Human // SuperMan类继承了Human类的方法
	level int
}

// 重定义父类的方法Eat()
func (s *SuperMan) Eat() {
	fmt.Println("SuperMan.Eat()...")
}

// 子类的新方法
func (s *SuperMan) Fly() {
	fmt.Println("SuperMan.Fly()...")
}

定义子类:

// 定义一个子类对象
// s := SuperMan{Human{"li4", "female"}, 88}
var s SuperMan
s.name = "li4"
s.sex = "male"
s.level = 88

s.Walk() // 父类的方法
s.Eat()  // 子类的方法
s.Fly()  // 子类的方法

s.Print()
Human.Walk()...
SuperMan.Eat()...
SuperMan.Fly()...
name =  li4
sex =  male
level =  88

多态

Golang 中多态的基本要素:

  • 有一个父类(有接口)
// 本质是一个指针
type AnimalIF interface {
	Sleep()
	GetColor() string //  获取动物的颜色
	GetType() string  // 获取动物的种类
}
  • 有子类(实现了父类的全部接口)
// 具体的类
type Cat struct {
	color string // 猫的颜色
}

func (c *Cat) Sleep() {
	fmt.Println("Cat is Sleep")
}

func (c *Cat) GetColor() string {
	return this.color
}

func (c *Cat) GetType() string {
	return "Cat"
}
  • 父类类型的变量(指针)指向(引用)子类的具体数据变量
// 接口的数据类型,父类指针
var animal AnimalIF
animal = &Cat{"Green"}
animal.Sleep() // 调用的就是Cat的Sleep()方法, 多态

通用万能类型

interface{} 表示空接口,可以用它引用任意类型的数据类型。

// interface{}是万能数据类型
func myFunc(arg interface{}) {
	fmt.Println(arg)
}

type Book struct {
	auth string
}

func main() {
	book := Book{"Golang"}

	myFunc(book)
	myFunc(100)
	myFunc("abc")
	myFunc(3.14)
}

Golang 给 interface{} 提供类型断言机制,用来区分此时引用的类型:

func myFunc(arg interface{}) {
  // 类型断言
  value, ok := arg.(string)
  if !ok {
    fmt.Println("arg is not string type")
  } else {
    fmt.Println("arg is string type, value = ", value)
    fmt.Printf("value type is %T\n", value)
  }
}
举报

相关推荐

0 条评论