Golang学习笔记

Golang study

语言结构

Go 语言的基础组成有以下几个部分:

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
// 以下copy于菜鸟教程

package main

import "fmt"

func main() {
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}

让我们来看下以上程序的各个部分:

第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

下一行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。

下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。

下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

下一行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
使用 fmt.Print("hello, world\n") 可以得到相同的结果。
Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误 (我就犯过这个错误,在那里debug半天)

变量

规则 :GO语言变量由字母 数字 下划线组成 首个字符不能为数字

布尔&数字&字符串
布尔:即bool
package main

import "fmt"

func main() {
	var a = true
	var b = false
	var c bool //声明 默认false
	c = true   //赋值

	fmt.Println(a, b, c)
}

数字:int 和 float
package main

import "fmt"

func main() {
	var a = 12244
	var b = 1245
	var c int //声明 默认0
	c = 14524 //赋值

	fmt.Println(a, b, c)
}

字符串:
package main

import "fmt"

func main() {
	var a = "小王学安全"

	var b = "Nigr-W"

	var c string   //声明 默认0
	c = "Nige-W 2" //赋值
	fmt.Println(a, b, c)
}

//简写都可以写作
a: = XX
数组
数组:和其他语言的差不多

var a = [9]bool{}
	var b = [...]int{1, 2, 3}
	var c = [...]int{1, 5, 3}
	c[0] = 6 //通过下标修改
	fmt.Println(a, b, c)

///输出结果 [false false false false false false false false false] [1 2 3] [6 5 3]

切片
切片:
//Go 语言切片是对数组的抽象。
/*Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。*/

var a = make([]string, 0) //切片定义
	a = append(a, "小王学安全")
	a = append(a, "Nige-W")
	var b = []int{1, 2, 3, 4, 5, 6}
	b = append(b, 7) //切片追加
	var c = make([]int, 6)
	copy(c, b)   //切片复制
	var d = make([]string, len(a))
	copy(d, a)
	fmt.Println(len(a), b, c, d)  // len代表 获取当前切片长度 
	fmt.Println(b[4:6])  //切片截取    规律:左包含又不包含

//输出结果 2 [1 2 3 4 5 6 7] [1 2 3 4 5 6] [小王学安全 Nige-W]
//[5 6]

//推荐讲解博客 https://cloud.tencent.com/developer/article/1627244
指针
指针:c语言简化版
var a = "小王学安全"
	var b *string //声明一个字符串指针
	b = &a        //指针进行赋值 &代表地址
	fmt.Println(b)

//输出结果 0xc000050050

var a = "小王学安全"
	var b *string //声明一个字符串指针
	b = &a        //指针进行赋值 &代表地址
	*b = "Nige-W"  //通过指针进行覆盖
	fmt.Println(a)

//输出结果 Nige-W

//通过这两段代码相信大家都理解了 在go语言中的指针 还是不清楚的可以看下 这段博客 http://c.biancheng.net/view/21.html
结构体
结构体:和C语言类似

type pepople struct {
		name     string  
		age      int
		birthday string
		gender   string
	}
	//在进行定义的同时直接进行赋值
	var a = pepople{
		name:     "小王学安全",
		age:      20,
		birthday: "1245/4545",
		gender:   "男",
	}
	//先进行定义再进行赋值
	var b = pepople{}
	b.name = "Nige-W"
	b.age = 20
	b.birthday = "125/545"
	b.gender = "男"
	
	
	fmt.Println(a)
	fmt.Println(b)
}

//输出结果:  {小王学安全 20 1245/4545 男}
//          {Nige-W 20 125/545 男}

var a = pepople{
		name:     "小王学安全",
		age:      20,
		birthday: "1245/4545",
		gender:   "男",
	}
	//先进行定义再进行赋值
	var b = pepople{}
	b.name = "Nige-W"
	b.age = 20
	b.birthday = "125/545"
	b.gender = "男"

	a.name = "Nige-W"
	fmt.Println(a)
	fmt.Println(b)

//输出结果:  {Nige-W 20 1245/4545 男}
//          {Nige-W 20 125/545 男}

map
var a map[string]string     //声明map集合变量
	a = make(map[string]string) //创建集合

	//对集合进行赋值
	a["name"] = "小王学安全"
	a["birthday"] = "124/1224"
	a["gender"] = "男"

	//声明变量并进行赋值
	var b = map[string]string{
		"name":     "Nige-W",
		"birthday": "1015/5545",
		"gender":   "男",
	}

	fmt.Println(a)
	fmt.Println(b)
//输出结果:map[birthday:124/1224 gender:男 name:小王学安全]
//        map[birthday:1015/5545 gender:男 name:Nige-W]

//输出的结果与json类似

常量

  • 常量名称与变量遵循相同的命名规则
  • 常量名称通常用大写字母进行书写
  • 常量可以在函数内部和外部进行声明
package main

import "fmt"

const aaa = 123456

func main() {
	fmt.Println(aaa)

}

//输出结果 123456 
//常量也可以定义在函数内部
枚举
package main

import "fmt"

const (
	aaa = 1
	ccc = 2
	ddd = 3
	eee = 4
)

func main() {

	fmt.Println(eee)
}

//输出结果:4
iota

修改编译器里面的常量

package main

import "fmt"

const (
	aaa = iota
	ccc
	ddd
	eee
)

func main() {

	fmt.Println(eee)
}

条件分支

语法格式和其他类语差不多,比较特殊的是 go 语言没有 while

if
if:和其他语言的一样 书写的时候需要注意go的方式

	var a, b = 11, 52
	if a > b {

		fmt.Println(a)
	} else {
		fmt.Println(b)
	}

}

//输出结果:52
switch
switch:和其他编程语言的差不多,注意go的一些格式写法
today := 3

	switch today {
	case 1:
		fmt.Println("周一")
	case 2:
		fmt.Println("周二")
	case 3:
		fmt.Println("周三")
	case 4:
		fmt.Println("周四")
	case 5:
		fmt.Println("周五")
	case 6:
		fmt.Println("周六")

		//fallthrough //这个意思是前面执行完了后 执行下面的语句。

	default:
		fmt.Println("周日")
	}

//输出结果:周三

for
for:循环判断

//一个简单的for循环

	for i := 1; i < 10; i++ {
		fmt.Println(i)
	}

//输出结果: 从 1 到 9

var i = 1
	for i < 10 {
		i++
		fmt.Println(i)
	}

//输出结果:2到10



//map 格式
// key and value
userpasmap := map[string]string{
		"uername":      "admin",
		"password":     "123456",
		"mobile_phone": "1258749",
	}
	fmt.Println("数据段1\t数据段2")

	for user, pass := range userpasmap {         //key, value := range userpasmap
		fmt.Printf("%s\t%s\n", user, pass)
	}

//only key
userpasmap := map[string]string{
		"uername":      "admin",
		"password":     "123456",
		"mobile_phone": "1258749",
	}
	fmt.Println("数据段1\t数据段2")

	//for user, pass := range userpasmap {
	//	fmt.Printf("%s\t%s\n", user, pass)
	//}
	for user := range userpasmap {
		fmt.Printf("%s\t%s\n", user, userpasmap[user]) // map可以使用key 取出 value的值 value = map[key]
	}

//这两段代码输出结果一致

for _, pass := range userpasmap {
		fmt.Printf("%s\n", pass)
	}for _, pass := range userpasmap {
		fmt.Printf("%s\n", pass)
	}
//在不想要前段数据的时候,通过 _ 舍去,程序只会输出后半段数据。

//嵌套
和其他语言一样 go的for也可以进行嵌套

//一段爆破代码
users := []string{
		"admin",
		"root",
		"test",
		"ubuntu",
	}

	passwords := []string{
		"admin",
		"root",
		"password",
		"123456",
	}
	fmt.Println("用户\t密码")
	for i, _ := range users {
		for x, _ := range passwords {
			fmt.Printf("%s\t%s\n", users[i], passwords[x])
		}

	}

输出结果就是 每段数据都进行整合 有点暴破脚本的感觉了。

循环控制
break : 强制退出for循环

users := []string{
		"admin",
		"root",
		"test",
		"ubuntu",
	}

	passwords := []string{
		"admin",
		"root",
		"password",
		"123456",
	}
	fmt.Println("用户\t密码")
	for i, _ := range users {
		if users[i] == "test" {
			break //退出 for 循环
		}
		for x, _ := range passwords {
			fmt.Printf("%s\t%s\n", users[i], passwords[x])
		}

	}

// 这段代码造成的后果就是 test及其以后的字段都不会进行暴破



users := []string{
		"admin",
		"root",
		"test",
		"ubuntu",
	}

	passwords := []string{
		"admin",
		"root",
		"password",
		"123456",
	}
	fmt.Println("用户\t密码")
	for i, _ := range users {
		
		for x, _ := range passwords {
			if users[i] == "test" {
				break //退出 for 循环
			}
			fmt.Printf("%s\t%s\n", users[i], passwords[x])
		}

	}
// 这段代码造成的后果就是只有 test字段不进行暴破。

break 被哪个循环包围了 就退出哪个循环

注意 在go语言的 switch  case 语句 当中虽然可以加入 break使用,但是go语言的 switch 只要执行成功了就不会向下执行,所以没有很大的实际意义。


contiue: 如果为真 强制重新循环 

users := []string{
		"admin",
		"root",
		"test",
		"ubuntu",
	}

	passwords := []string{
		"admin",
		"root",
		"password",
		"123456",
	}
	fmt.Println("用户\t密码")
	for i, _ := range users {
		if users[i] == "test" {
			continue //强制重新循环
		}
		for x, _ := range passwords {

			fmt.Printf("%s\t%s\n", users[i], passwords[x])
		}

	}

//这段代码造成的后果就是只有 test字段不进行暴破。



goto : 无条件转移到指定的代码片段中。

//一个简单的例子

fmt.Println("代码片段1")
	goto JUMP
	fmt.Println("代码片段2")
JUMP:
	{
		fmt.Println("代码片段3")
	}

//输出结果:
代码片段1
代码片段3


注意 goto 如果对程序判断失误,容易造成死循环 导致程序崩溃,所以需要慎用。

作用域

局部变量:定义在函数体里面只可以在当前函数里面进行调用
全局变量:定义在函数体外。可以在整段程序中调用
形式参数:和局部变量类似

//一个形式参数的简单例子

func test (a int){    //这里的a就是形式参数
    fmt.Println(a)
}

访问权限:首字母大写的函数默认可以跨包调用 即 xx.xx
         首字母小写的只能在当前包里面调用 即 xx

函数

func : 函数使用func声明
sub  : 函数名称
(x,y int) :  参数列表 函数也可以不包括参数
返回类型 : 函数返回一列值 函数可以没有返回值
函数体 : 函数的代码集合
package main

import "fmt"

func main() {

	d := sub("3", "5")
	fmt.Println(d)
}
func sub(x string, y string) string {
	c := x + y
	return c
	//不会再继续向下执行了
}
输出结果 :35
//在C语言中学习过的一个小坑

package main

import "fmt"

func main() {

   x := 15
   fmt.Println(x)
   c := sub(&x)
   fmt.Println(x, c)
}
func sub(x *int) int {
   *x = 2
   return *x
   //不会再继续向下执行了
}

//在这里通过修改地址将x的值修改为了2


package main

import "fmt"

func main() {

   x := 15
   fmt.Println(x)
   c := sub(x)
   fmt.Println(x, c)
}
func sub(x int) int {
   x = 2
   return x
   //不会再继续向下执行了
}

//如果是第二段代码这样的 x的值仍然为15

方法

// 一个简单的例子
package main

import (
    "fmt"
)

type Employee struct {
    name     string
    salary   int
    currency string
}

/*
  displaySalary() 方法将 Employee 做为接收器类型
*/
func (e Employee) displaySalary() {
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}

func main() {
    emp1 := Employee {
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    emp1.displaySalary() // 调用 Employee 类型的 displaySalary() 方法
}

//输出结果 Salary of Sam Adolf is $5000
//不使用方法 只使用函数的一个例子 
package main

import (
    "fmt"
)

type Employee struct {
    name     string
    salary   int
    currency string
}

/*
displaySalary()方法被转化为一个函数,把 Employee 当做参数传入。
*/
func displaySalary(e Employee) {
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}

func main() {
    emp1 := Employee{
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    displaySalary(emp1)
}

//输出结果一样。

这里将来的方法转换为函数,将结构体中的值作为参数传入函数当中。得到的结果一致

思考: 为什么有了函数,我们还需要使用方法呢。

//原因 (中文社区给出的答案)
/*Go 不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。

相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。假设我们有一个 Square 和 Circle 结构体。可以在 Square 和 Circle 上分别定义一个 Area 方法。见下面的程序。*/

package main

import (
    "fmt"
    "math"
)

type Rectangle struct {
    length int
    width  int
}

type Circle struct {
    radius float64
}

func (r Rectangle) Area() int {
    return r.length * r.width
}

func (c Circle) Area() float64 {
    return math.Pi * c.radius * c.radius
}

func main() {
    r := Rectangle{
        length: 10,
        width:  5,
    }
    fmt.Printf("Area of rectangle %d\n", r.Area())
    c := Circle{
        radius: 12,
    }
    fmt.Printf("Area of circle %f", c.Area())
}

//输出结果
/*Area of rectangle 50
Area of circle 452.389342*/
//上述代码中方法的属性在接口中会有用到。

接口

很惭愧,第一次看这个知识点的时候没有认真看代码,那个文档里的代码,翻来覆去看了几遍才看懂。demo是一个印度程序员写的。

//一个小 demo 感受下接口
package main

import (  
    "fmt"
)

//interface definition
type VowelsFinder interface {  
    FindVowels() []rune
}

type MyString string

//MyString implements VowelsFinder
func (ms MyString) FindVowels() []rune {  
    var vowels []rune
    for _, rune := range ms {
        if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
            vowels = append(vowels, rune)
        }
    }
    return vowels
}

func main() {  
    name := MyString("Sam Anderson")
    var v VowelsFinder
    v = name // possible since MyString implements VowelsFinder
    fmt.Printf("Vowels are %c", v.FindVowels())

}

//输出结果 Vowels are [a e o]
//实际项目中会用到的技术
//比如 一家公司需要根据公司员工的个人薪资,计算公司的总支出。为了简单起见,我们假定支出的单位都是美元

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    CalculateSalary() int
}

type Permanent struct {  
    empId    int
    basicpay int
    pf       int
}

type Contract struct {  
    empId  int
    basicpay int
}

//salary of permanent employee is sum of basic pay and pf
func (p Permanent) CalculateSalary() int {  
    return p.basicpay + p.pf
}

//salary of contract employee is the basic pay alone
func (c Contract) CalculateSalary() int {  
    return c.basicpay
}

/*
total expense is calculated by iterating though the SalaryCalculator slice and summing  
the salaries of the individual employees  
*/
func totalExpense(s []SalaryCalculator) {  
    expense := 0
    for _, v := range s {
        expense = expense + v.CalculateSalary()
    }
    fmt.Printf("Total Expense Per Month $%d", expense)
}

func main() {  
    pemp1 := Permanent{1, 5000, 20}
    pemp2 := Permanent{2, 6000, 30}
    cemp1 := Contract{3, 3000}
    employees := []SalaryCalculator{pemp1, pemp2, cemp1}
    totalExpense(employees)

}

/* demo 作者的代码讲解 
上面程序的第 7 行声明了一个 SalaryCalculator 接口类型,它只有一个方法 CalculateSalary() int。

在公司里,我们有两类员工,即第 11 行和第 17 行定义的结构体:Permanent 和 Contract。长期员工(Permanent)的薪资是 basicpay 与 pf 相加之和,而合同员工(Contract)只有基本工资 basicpay。在第 23 行和第 28 行中,方法 CalculateSalary 分别实现了以上关系。由于 Permanent 和 Contract 都声明了该方法,因此它们都实现了 SalaryCalculator 接口。

第 36 行声明的 totalExpense 方法体现出了接口的妙用。该方法接收一个 SalaryCalculator 接口的切片([]SalaryCalculator)作为参数。在第 49 行,我们向 totalExpense 方法传递了一个包含 Permanent 和 Contact 类型的切片。在第 39 行中,通过调用不同类型对应的 CalculateSalary 方法,totalExpense 可以计算得到支出。

这样做最大的优点是:totalExpense 可以扩展新的员工类型,而不需要修改任何代码。假如公司增加了一种新的员工类型 Freelancer,它有着不同的薪资结构。Freelancer只需传递到 totalExpense 的切片参数中,无需 totalExpense 方法本身进行修改。只要 Freelancer 也实现了 SalaryCalculator 接口,totalExpense 就能够实现其功能。

该程序输出 Total Expense Per Month $14050。*/
//内部表示
//我们可以把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。
package main

import (
	"fmt"
)

type Test interface {
	Tester()
}

type MyFloat float64

func (m MyFloat) Tester() {
	fmt.Println(m)
}

func describe(t Test) {
	fmt.Printf("Interface type %T value %v\n", t, t)
}

func main() {
	var t Test
	f := MyFloat(89.7)
	t = f
	describe(t)
	t.Tester()
}
/*
*Test 接口只有一个方法 Tester(),而 MyFloat 类型实现了该接口。在第 24 行,我们把变量 f(MyFloat 类型)赋值给了 t(Test 类型)。
*现在 t 的具体类型为 MyFloat,而 t 的值为 89.7。第 17 行的 describe 函数打印出了接口的具体类型和值
**/
//空接口
package main

import (  
    "fmt"
)

func describe(i interface{}) {  
    fmt.Printf("Type = %T, value = %v\n", i, i)
}

func main() {  
    s := "Hello World"
    describe(s)
    i := 55
    describe(i)
    strt := struct {
        name string
    }{
        name: "Naveen R",
    }
    describe(strt)
}

//类型断言
/*类型断言用于提取接口的底层值(Underlying Value)。

在语法 i.(T) 中,接口 i 的具体类型是 T,该语法用于获得接口的底层值。*/
package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) //get the underlying int value from i
    fmt.Println(s)
}
func main() {  
    var s interface{} = 56
    assert(s)
}
package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) 
    fmt.Println(s)
}
func main() {  
    var s interface{} = "Steven Paul"
    assert(s)
}
/*在上面程序中,我们把具体类型为 string 的 s 传递给了 assert 函数,试图从它提取出 int 值。该程序会报错:panic: interface conversion: interface {} is string, not int.*/

v, ok := i.(T)

/*如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。

如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错。*/

package main

import (
	"fmt"
)

func assert(i interface{}) {
	v, ok := i.(int)
	fmt.Println(v, ok)
}
func main() {
	var s interface{} = 56
	assert(s)
	var i interface{} = "Steven Paul"
	assert(i)
}

//输出结果
/*56 true
0 false
*/
//类型和接口相比较
package main

import "fmt"

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe() {  
    fmt.Printf("%s is %d years old", p.name, p.age)
}

func findType(i interface{}) {  
    switch v := i.(type) {
    case Describer:
        v.Describe()
    default:
        fmt.Printf("unknown type\n")
    }
}

func main() {  
    findType("Naveen")
    p := Person{
        name: "Naveen R",
        age:  25,
    }
    findType(p)
}

//输出结果
/*Sam is 25 years old  
James is 32 years old  
State Washington Country USA*/
//实现多个接口
package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    DisplaySalary()
}

type LeaveCalculator interface {  
    CalculateLeavesLeft() int
}

type Employee struct {  
    firstName string
    lastName string
    basicPay int
    pf int
    totalLeaves int
    leavesTaken int
}

func (e Employee) DisplaySalary() {  
    fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {  
    return e.totalLeaves - e.leavesTaken
}

func main() {  
    e := Employee {
        firstName: "Naveen",
        lastName: "Ramanathan",
        basicPay: 5000,
        pf: 200,
        totalLeaves: 30,
        leavesTaken: 5,
    }
    var s SalaryCalculator = e
    s.DisplaySalary()
    var l LeaveCalculator = e
    fmt.Println("\nLeaves left =", l.CalculateLeavesLeft())
}
//接口的嵌套
package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    DisplaySalary()
}

type LeaveCalculator interface {  
    CalculateLeavesLeft() int
}

type EmployeeOperations interface {  
    SalaryCalculator
    LeaveCalculator
}

type Employee struct {  
    firstName string
    lastName string
    basicPay int
    pf int
    totalLeaves int
    leavesTaken int
}

func (e Employee) DisplaySalary() {  
    fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {  
    return e.totalLeaves - e.leavesTaken
}

func main() {  
    e := Employee {
        firstName: "Naveen",
        lastName: "Ramanathan",
        basicPay: 5000,
        pf: 200,
        totalLeaves: 30,
        leavesTaken: 5,
    }
    var empOp EmployeeOperations = e
    empOp.DisplaySalary()
    fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft())
}

一个小tips

接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil

小结:这些大概就是我认为的一些go语言基础了。后面会总结下并发编程和网络编程的一些东西,再发出来。