Golang笔记-基础语法

Golang基础语法

1 Go 程序开发的注意事项

  1. Go 源文件以 “go” 为扩展名。
  2. Go 应用程序的执行入口是 main()函数。
  3. Go 方法由一条条语句构成,每个语句后不需要分号(Go 语言会在每行后自动加分号),这也体现出 Golang 的简洁性。
  4. Go 编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错
    在这里插入图片描述
  5. go 语言定义的变量或者 import 的包如果没有使用到,代码不能编译通过
    在这里插入图片描述
  6. Go语言不允许大括号换行
    在这里插入图片描述

2 Golang 变量

2.1 Golang 变量使用的三种方式

(1) 第一种:指定变量类型,声明后若不赋值,使用默认值

package main
import "fmt"
func main() {
	var i int
	fmt.Println("i = ", i) //i = 0
}

(2) 第二种:根据值自行判定变量类型(类型推导)

package main
import "fmt"
func main() {
	var num = 10.11
	fmt.Println("num =", num)//num = 10.11
}

(3) 第三种:省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误

package main
import "fmt"
func main() {
	name := "tom"
	fmt.Println("name= ", name)//name=  tom 
}

(4) 多变量声明

package main

import "fmt"

//定义全局变量
var (
	n4    = 100
	name2 = "mary"
)

func main() {
	//声明局部变量
	n1, name, n3 := 100, "tom", 888
	fmt.Println("n1 = ", n1, "name = ", name, "n3 = ", n3)
	fmt.Println("n4 = ", n4, "name2 = ", name2)
}

(5) 该区域的数据值可以在同一类型范围内不断变化(重点)

package main
import "fmt"
func main() {
	var i int = 10
	i = 30
	i = 50
	fmt.Println(i)
	i = 1.2//报错,因为不能改变数据类型
}

2.2 Golang 数据类型的基本介绍

在这里插入图片描述

2.2.1 整数类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2.1.1 整型的使用细节

(1)Golang 的整型默认声明为 int 型
(2)如何在程序查看某个变量的字节大小和数据类型 (使用较多)

package main

import (
	"fmt"
	"unsafe"
)
func main() {
	var i = 100
	fmt.Printf("i的类型为 %T i的占用字节数为 %d", i, unsafe.Sizeof(i))//i的类型为 int i的占用字节数为 8
}

2.2.2 小数类型分类

在这里插入图片描述

2.2.3 字符类型

Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存

package main

import "fmt"

func main() {
	var c1 byte = 'a'
	var c2 byte = 'b'
	fmt.Println("c1 = ", c1)//97
	fmt.Println("c2 = ", c2)//98
	fmt.Printf("c1=%c c2=%c", c1, c2)//a  b
	var c3 int = '北'
	fmt.Printf("c3=%c c3对应的码值=%d", c3, c3)//北 21271
}
  1. 如果我们保存的字符在 ASCII 表的,比如[0-1, a-z,A-Z…]直接可以保存到 byte
  2. 如果我们保存的字符对应码值大于 255,这时我们可以考虑使用 int 类型保存
  3. 如果我们需要安装字符的方式输出,这时我们需要格式化输出,即 fmt.Printf(“%c”, c1)
  4. 字符型 存储到 计算机中,需要将字符对应的码值(整数)找出来
    存储:字符—>对应码值---->二进制–>存储
    读取:二进制----> 码值 ----> 字符 --> 读取
  5. Go 语言的编码都统一成了 utf-8。非常的方便,很统一,再也没有编码乱码的困扰了

2.2.4 string 类型

字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本

2.2.4.1 string 使用注意事项和细节
  1. Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本,这样 Golang 统一使用 UTF-8 编码,中文乱码问题不会再困扰程序员。
  2. 反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果。

3)当一行字符串太长时,需要使用到多行字符串,可以如下处理
在这里插入图片描述

2.2.1 基本数据类型的相互转换

Golang 和 java / c 不同,Go 在不同类型的变量之间赋值时需要显式转换。也就是说 Golang 中数据类型不能自动转换。

package main

import "fmt"

func main() {
	var i int32 = 100
	var n1 float32 = float32(i)
	var n2 int8 = int8(i)
	var n3 = int64(i)
	fmt.Printf("i=%v n1=%v n2=%v n3=%v n3=%T", i, n1, n2, n3, n3)
}

2.2.2 基本数据类型和 string 的转换

package main

import "fmt"

func main() {
	var num1 int = 99
	var num2 float64 = 23.22
	var b bool = true
	var mychar byte = 'h'
	var str string
	//使用第一种方式转为string
	str = fmt.Sprintf("%d", num1)
	fmt.Printf("str的type=%T str的值=%q", str, str)
	str = fmt.Sprintf("%f", num2)
	fmt.Printf("str的type=%T str的值=%q", str, str)
	str = fmt.Sprintf("%t", b)
	fmt.Printf("str的type=%T str的值=%q", str, str)
	str = fmt.Sprintf("%c", mychar)
	fmt.Printf("str的type=%T str的值=%q", str, str)
}

2.2.3 string 类型转基本数据类型

package main

import (
	"fmt"
	"strconv"
)

func main() {
	//转换类型要匹配
	var str string = "true"
	var b bool
	b, _ = strconv.ParseBool(str)
	fmt.Printf("b的type=%T b=%v", b, b)
	fmt.Printf("str的type=%T b=%v", str, str)
	var str2 = "123"
	var n1 int64
	var n2 int
	n1, _ = strconv.ParseInt(str2, 10, 64)
	//string转换为int返回值类型为int64,如果转换为其他需要显示转化内
	n2 = int(n1)
	fmt.Printf("n1的type=%T n1=%v", n1, n1)
	fmt.Printf("n2的type=%T n2=%v", n2, n2)
	var str3 string = "123.22"
	var f1 float64
	f1, _ = strconv.ParseFloat(str3, 64)
	fmt.Printf("f1的type=%T f1=%v", f1, f1)
}

注意:string转换为整形的时候,返回的类型为int64或float64,因此如果需要转换为int或float32需要显示转换

2.3 指针

  1. 基本数据类型,变量存的就是值,也叫值类型,包括:int 系列, float 系列, bool, string 、数组和结构体 struct
  2. 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值
    比如:var ptr *int = &num
  3. 获取指针类型所指向的值,使用:*,比如:var ptr *int, 使用*ptr 获取 ptr 指向的值
    在这里插入图片描述

2.3.1 值类型和引用类型

  1. 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
  2. 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
  3. 值类型:变量直接存储值,内存通常在栈中分配
  4. 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收

2.4 标识符的命名规范

  1. 包名:保持 package 的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,不要和标准库不要冲突 fmt
    在这里插入图片描述
  2. 如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用 ( 注:可以简单的理解成,首字母大写是公开的,首字母小写是私有的) ,在 golang 没有public , private 等关键字。

2.5 运算符

  1. Golang 的自增自减只能当做一个独立语言使用时,不能这样使用
    在这里插入图片描述
  2. Golang 的++ 和 – 只能写在变量的后面,不能写在变量的前面,即:只有 a++ a-- 没有 ++a--a

2.5.1 for 循环的使用注意事项和细节讨论

  1. Golang 提供 for-range 的方式,可以方便遍历字符串和数组
func main() {
	str := "asc!ddd"
	for index, val := range str {
		fmt.Printf("index=%d,val=%c \n", index, val)
	}
}
  1. for-range 遍历方式而言,是按照字符方式遍历。因此如果有字符串有中文,也是 ok(传统的是按照字节的方式,因此中文会报错)
package main

import "fmt"

func main() {
	str := "asc!ddd北京"
	for index, val := range str {
		fmt.Printf("index=%d,val=%c \n", index, val)
	}
}

在这里插入图片描述

3 函数、包和错误处理

3.1 Golang中函数的基本语法

在这里插入图片描述

package main

import "fmt"

func cal(n1 float64, n2 float64, operator byte) float64 {
	var res float64
	switch operator {
	case '+':
		res = n1 + n2
	case '-':
		res = n1 - n2
	case '*':
		res = n1 * n2
	case '/':
		res = n1 / n2
	default:
		fmt.Println("操作错误")
	}
	return res
}
func main() {
	var n1 float64 = 1.2
	var n2 float64 = 0.6
	var operator byte = '+'
	res := cal(n1, n2, operator)
	fmt.Printf("res=%v", res)
}

3.1.1 Golang中函数的注意事项

  1. Go函数支持多个返回值
    在这里插入图片描述
  2. 希望忽略某个返回值,则使用 _ 符号表示占位忽略
    在这里插入图片描述
  3. 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
  4. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。
  5. Go 函数不支持函数重载
  6. 在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
    在这里插入图片描述
  7. 函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用
    在这里插入图片描述
  8. 为了简化数据类型定义,Go 支持自定义数据类型
    基本语法:type 自定义数据类型名 数据类型 // 理解: 相当于一个别名
    在这里插入图片描述
    在这里插入图片描述
  9. 支持对函数返回值命名
    在这里插入图片描述

3.1.2 Golang中匿名函数

匿名函数使用方式 1

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次

package main

import "fmt"
func main() {
	res1 := func(n1 int, n2 int) int {
		return n1 + n2
	}(12, 20)
	fmt.Println("res1=", res1)
}

匿名函数使用方式 2

将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数

package main

import "fmt"
func main() {
	a := func(n1 int, n2 int) int {
		return n1 - n2
	}
	res2 := a(10, 30)
	fmt.Println("res2=\n", res2)
	res3 := a(21, 22)
	fmt.Println("res3=", res3)
}

全局匿名函数

如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。

package main

import "fmt"
var (
	Fun1 = func(n1 int, n2 int) int {
		return n1 * n2
	}
)

func main() {
	res4 := Fun1(4, 2)
	fmt.Println("res3=", res4)
}

3.2 Golang包的相关说明

  1. 在给一个文件打包时,该包对应一个文件夹,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。
  2. 当一个文件要使用其它包函数或变量时,需要先引入对应的包
    引入方式 1:import “包名”
    引入方式 2:
    import (
    “包名”
    “包名”
    )
  3. 在 import 包时,路径从 $GOPATH 的 src 下开始,不用带 src , 编译器会自动从 src 下开始引入
  4. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的 public ,这样才能跨包访问
  5. 在访问其它包函数,变量时,其语法是 包名.函数名
  6. 如果包名较长,Go 支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了
    在这里插入图片描述
  7. 在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义
  8. 如果你要编译成一个可执行程序文件,就需要将这个包声明为 main , 即 package main .这个就是一个语法规范,如果你是写一个库 ,包名可以自定义

3.3 Golang的闭包

基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

package main

import "fmt"

func AddUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}

func main() {
	f := AddUpper()
	fmt.Println(f(1))
	fmt.Println(f(2))
	fmt.Println(f(3))

}

返回的是一个匿名函数, 但是这个匿名函数引用到函数外的 n ,因此这个匿名函数就和 n 形成一个整体,构成闭包。

3.4 函数的 defer

在函数执行完毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。

package main

import "fmt"

func AddUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}
func sum(n1 int, n2 int) int {
	defer fmt.Println("ok1")
	defer fmt.Println("ok2")
	res := n1 + n2
	fmt.Println("res1=", res)
	return res
}

func main() {
	res := sum(10, 20)
	fmt.Println("res=", res)

}

在这里插入图片描述

3.4.1 函数的 defer注意事项

  1. 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中,然后继续执行函数下一个语句。
  2. 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),
  3. 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。

3.5 字符串常用的系统函数

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func AddUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}
func sum(n1 int, n2 int) int {
	defer fmt.Println("ok1")
	defer fmt.Println("ok2")
	res := n1 + n2
	fmt.Println("res1=", res)
	return res
}

func main() {
	str := "hello 北京"
	//统计字符串的长度(字节)
	fmt.Println("str的长度=", len(str), "\n")
	//字符串转成整数int
	n, _ := strconv.Atoi("1222")
	fmt.Printf("n的type=%T n的值=%d\n", n, n)
	//整数转成字符串
	str1 := strconv.Itoa(12345)
	fmt.Printf("str1的type=%T str1的值=%v\n", str1, str1)
	//字符串转[]byte
	var bytes = []byte("hellogo")
	fmt.Printf("bytes的type=%T bytes=%v\n", bytes, bytes)
	//[]byte 转 字符串
	str3 := string([]byte{97, 98, 99})
	fmt.Printf("str3的type=%T str3=%v\n", str3, str3)
	//查找字串是否在指定的字符串中
	b := strings.Contains("seafood", "foo")
	fmt.Printf("b=%v\n", b) //true
	//统计一个字符串有几个指定的子串
	count := strings.Count("cehessas", "e")
	fmt.Printf("count=%v\n", count)
	//不区分大小写的字符串比较(==是区分字母大小写的)
	fold := strings.EqualFold("abc", "ABC")
	fmt.Printf("fold=%v\n", fold) //ture
	//返回子串在字符串第一次出现的 index 值,如果没有返回-1
	index := strings.Index("asdasda", "as")
	fmt.Printf("index=%v\n", index)
	//返回子串在字符串最后一次出现的 index,如没有返回-1
	lastIndex := strings.LastIndex("golang go", "go")
	fmt.Printf("lastindex=%v\n", lastIndex)
	//将指定的子串替换成 另外一个子串
	replace := strings.Replace("go go go 你好", "go", "o", -1)
	fmt.Printf("replace=%v\n", replace)
	//按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 ,
	//将 一 个 字 符 串 拆 分 成 字 符 串 数 组
	strArr := strings.Split("hello,world,go", ",")
	fmt.Printf("strArr的type=%T str=%v\n", strArr, strArr)
	//判断字符串是否以指定的字符串开头
	prefix := strings.HasPrefix("ftp://11111", "ftp")
	fmt.Printf("prefix=%v\n", prefix)
}

3.5 时间函数–日期格式化

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	fmt.Printf(now.Format("2006-01-02 15:04:05"))
	fmt.Println()
	fmt.Printf(now.Format("2006.01.02"))
	fmt.Println()
	fmt.Printf(now.Format("15:04:05"))
}

“2006/01/02 15:04:05” 这个字符串的各个数字是固定的,必须是这样写。

时间常量

const (
	Nanosecond Duration = 1 //纳秒
	Microsecond = 1000 * Nanosecond //微秒
	Millisecond = 1000 * Microsecond //毫秒
	Second = 1000 * Millisecond //秒
	Minute = 60 * Second //分钟
	Hour = 60 * Minute //小时
)

常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到 100 毫秒,100 * time. Millisecond

4 错误处理

  1. Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
  2. Go 中引入的处理方式为:defer, panic, recover
  3. 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
package main

import (
	"fmt"
	"time"
)
func test() {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("err=", err)
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}
func main() {
	test()
	for {
		fmt.Println("main")
		time.Sleep(time.Second)
	}
}

4.1 自定义错误

Go 程序中,也支持自定义错误, 使用 errors.New 和 panic 内置函数。

  1. errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
  2. panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序
package main

import (
	"errors"
	"fmt"
)

func readConf(name string) (err error) {
	if name == "hhhh" {
		return nil
	} else {
		return errors.New("读取失败")
	}
}
func test() {
	err := readConf("hhss")
	if err != nil {
		panic(err)
	}
	fmt.Println("test继续执行")
}
func main() {
	test()
	for {
		fmt.Println("main")
	}
}

5 数组与切片

5.1 数组

数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型

  1. 数组的定义
var 数组名 [数组大小]数据类型
var a [5]int
赋初值 a[0] = 1 a[1] = 30
  1. 数组初始化的方式
package main

import (
	"fmt"
)


func main() {
	var numArr01 [3]int = [3]int{1, 2, 3}
	fmt.Println("01=", numArr01)
	var numArr02 = [3]int{5, 6, 7}
	fmt.Println("02=", numArr02)
	var numArr03 = [...]int{8, 9, 10}
	fmt.Println("03=", numArr03)
	var numArr04 = [...]int{1: 11, 0: 6, 2: 7}
	fmt.Println("04=", numArr04)
	numArr05 := [...]string{1: "11", 0: "6", 2: "7"}
	fmt.Println("05=", numArr05)
}
  1. 数组的遍历–for-range
package main

import "fmt"

func main() {
	heros := [...]string{"松江", "吴用", "李白"}
	for i, v := range heros {
		fmt.Printf("i=%v v=%v", i, v)
	}
}

5.2 切片

  1. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  2. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
  3. 切片的长度是可以变化的,因此切片是一个可以动态变化数组
  4. 切片定义的基本语法:
var 切片名 []类型
比如:var a [] int

5.2.1 切片在内存中形式

在这里插入图片描述
slice 从底层来说,其实就是一个数据结构(struct 结构体)

5.2.1 切片的使用

  1. 定义一个切片,然后让切片去引用一个已经创建好的数组
package main

import "fmt"

func main() {
	intArr := [...]int{1, 2, 3, 4, 5, 6, 7}
	slice := intArr[1:3]
	fmt.Printf("s=%v", slice)

}
  1. 通过 make 来创建切片
    基本语法:var 切片名 []type = make([]type, len, [cap])
package main

import "fmt"

func main() {
	var slice []float64 = make([]float64, 5, 10)
	slice[1] = 10
	fmt.Printf("s=%v", slice)
}

通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素.
3. 方式 3 定义一个切片,直接就指定具体数组,使用原理类似 make 的方式

package main

import "fmt"

func main() {
	var slice []string = []string{"s1", "s2"}
	fmt.Println("s=", slice)
}

5.2.2 切片的使用的注意事项和细节讨论

  1. 用 append 内置函数,可以对切片进行动态追加
    在这里插入图片描述
  2. 切片的拷贝操作
    在这里插入图片描述

5.2.3 string 和 slice

在这里插入图片描述
string 是不可变的,如果想要改变类型,需要转换为其他类型转换

package main
import "fmt"
func main() {
	str := "hhhh 你好"
	arr1 := []rune(str)
	arr1[0] = '你'
	str = string(arr1)
	fmt.Println("str= ", str)//str=  你hhh 你好
}

5.3 Map

5.3.1 map的基本语法

var map 变量名 map[keytype]valuetype
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string

注意:声明是不会分配内存的,初始化需要 make ,分配内存后才能赋值和使用。

5.3.2 map 的使用

方式 1
在这里插入图片描述
方式 2
在这里插入图片描述
方式 3
在这里插入图片描述

5.3.3 map 的增删改查操作

  1. map[“key”] = value //如果 key 还没有,就是增加,如果 key 存在就是修改。
    在这里插入图片描述

  2. map 删除
    delete(map,“key”) ,delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在,不操作,但是也不会报错
    在这里插入图片描述
    注意:如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历一下 key, 逐个删除或者 map = make(…),make 一个新的,让原来的成为垃圾,被 gc 回收
    在这里插入图片描述

  3. map 查找
    在这里插入图片描述
    说明:如果 heroes 这个 map 中存在 “no1” , 那么 findRes 就会返回 true,否则返回 false

  4. map 遍历
    map 的遍历使用 for-range 的结构遍历
    在这里插入图片描述

  5. map 切片
    切片的数据类型如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 个数就可以动态变化了。
    在这里插入图片描述
    在这里插入图片描述

  6. map 排序
    (1) golang 中没有一个专门的方法针对 map 的 key 进行排序
    (2) golang 中的 map 默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出可能不一样
    (3) golang 中 map 的排序,是先将 key 进行排序,然后根据 key 值遍历输出即可
    在这里插入图片描述
    在这里插入图片描述

5.3.4 map 使用细节

  1. map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来的 map
  2. map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长

6 面向对象编程

  1. Golang 没有类(class),Go 语言的**结构体(struct)**和其它编程语言的类(class)有同等的地位, Golang 是基于 struct 来实现 OOP 特性的。
  2. Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等
  3. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现

6.1 结构体

  1. 基本语法
    定义结构体
type 结构体名称 struct {
	field1 type
	field2 type
}

创建结构体变量
方式 1-直接声明

package main

import "fmt"

type Persion struct {
	Name string
	Age  int
}

func main() {
	var persion Persion
	persion.Name = "ss"
	persion.Age = 12
	fmt.Println("p=", persion)
}

方式 2-{}

package main

import "fmt"

type Persion struct {
	Name string
	Age  int
}

func main() {
	p2 := Persion{"mary", 20}
	fmt.Println("p2=", p2)
}

方式 3-&

package main

import "fmt"

type Persion struct {
	Name string
	Age  int
}

func main() {
	var p3 *Persion = new(Persion)
	(*p3).Name = "smish"
	p3.Age = 11
	fmt.Println("p3=", *p3)
}

方式 4-{}

package main

import "fmt"

type Persion struct {
	Name string
	Age  int
}

func main() {
	var p4 *Persion = &Persion{}
	(*p4).Name = "kobe"
	p4.Age = 99
	fmt.Println("p4=", *p4)
}

6.1.1 结构体使用注意事项和细节

  1. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
  2. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
  3. struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

6.2 方法

Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct。

6.2.1 方法的声明和调用

type A struct {
	Num int
}
func (a A) test() {
	fmt.Println(a.Num)
}
  1. func (a A) test() {} 表示 A 结构体有一方法,方法名为 test
  2. (a A) 体现 test 方法是和 A 类型绑定的
package main

import "fmt"

type Persion struct {
	Name string
}

func (p Persion) test() {
	fmt.Println("test name=", p.Name)
}
func main() {
	var p Persion
	p.Name = "tom"
	p.test()
}

  1. test 方法和 Person 类型绑定
  2. test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用

6.2.2 方法的注意事项

  1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
  2. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
  3. 如果一个类型实现了 String()这个方法,那么 fmt.Println 默认会调用这个变量的 String()进行输出,类似tostring方法

6.2.3 方法和函数区别

  1. 调用方式不一样
    函数的调用方式: 函数名(实参列表)
    方法的调用方式: 变量.方法名(实参列表)
  2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
  3. 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
    在这里插入图片描述
    在这里插入图片描述
    总结:
  1. 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
  2. 如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则是地址拷贝。

6.3 封装

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
	//加入数据验证的业务逻辑
	var.字段 = 参数
}
  1. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值
func (var 结构体类型名) GetXxx() {
	return var.age;
}
package model

import "fmt"

type person struct {
	Name string
	age  int
	sal  float64
}

//编写一个工厂模式,返回结构体对象
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

//为了访问age和sal,设置set和get方法
func (p *person) SetAge(age int) {
	if age > 0 && age < 150 {
		p.age = age
	} else {
		fmt.Println("年龄范围不对")
	}
}
func (p *person) GetAge() int {
	return p.age
}
func (p *person) Setsal(sal float64) {
	p.sal = sal
}
func (p *person) Getsal() float64 {
	return p.sal
}

package main

import (
	"fmt"
	"test/model"
)

func main() {
	p := model.NewPerson("mary")
	p.SetAge(18)
	p.Setsal(100000)
	fmt.Println(*p)
	fmt.Println(p.Name, "age=", p.GetAge(), "sal=", p.Getsal())
}

6.4 继承

在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性

6.4.1 基本语法

type Goods struct {
	Name string
	Price int
}
type Book struct {
	Goods //这里就是嵌套匿名结构体 Goods
	Writer string
}
package main

import "fmt"

//定义一个学生的共有属性
type Student struct {
	Name  string
	Age   int
	Score int
}

//将公有方法也绑定到student
func (stu *Student) ShowInfo() {
	fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",
		stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
	stu.Score = score
}

//小学生
type Pupil struct {
	Student
}

//这是 Pupil 结构体特有的方法,保留
func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中.....")
}

//大学生
type Graduate struct {
	Student
}

func (p *Graduate) testing() {
	fmt.Println("大学生正在考试中.....")
}
func main() {
	pupil := &Pupil{}
	pupil.Student.Name = "tom~"
	pupil.Student.Age = 8
	pupil.testing()
	pupil.Student.SetScore(70)
	pupil.Student.ShowInfo()
	graduate := &Graduate{}
	graduate.Student.Name = "mary~"
	graduate.Student.Age = 28
	graduate.testing()
	graduate.Student.SetScore(90)
	graduate.Student.ShowInfo()
}

6.4.2 继承的使用细节

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
  2. 匿名结构体字段访问可以简化
    在这里插入图片描述
  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
  4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
    在这里插入图片描述
  5. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
    在这里插入图片描述
  6. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    在这里插入图片描述
    在这里插入图片描述

6.5 接口

6.5.1 基本语法

type 接口名 interface{
	method(参数列表) 返回值列表
}
  1. interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型(比如结构体 )要使用的时候,在根据具体情况把这些方法写出来(实现)。
  2. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。
  3. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字
  4. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
  5. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口

6.6 类型断言

由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言
在这里插入图片描述
在这里插入图片描述

7 文件操作

7.1 打开文件和关闭文件

在这里插入图片描述

7.2 读文件操作

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	//打开文件
	file, err := os.Open("d:/test.txt")
	if err != nil {
		fmt.Println("open file err=", err)
	}
	//当函数退出时,要及时的关闭 file
	defer file.Close() //要及时关闭 file 句柄,否则会有内存泄漏. // 创建一个 *Reader ,是带缓冲的
	/*
	   const (
	   defaultBufSize = 4096 //默认的缓冲区为 4096
	   )
	*/
	reader := bufio.NewReader(file)
	//循环的读取文件的内容
	for {
		str, err := reader.ReadString('\n') // 读到一个换行就结束
		if err == io.EOF {                  // io.EOF 表示文件的末尾
			break
		}
		//输出内容
		fmt.Print(str)
	}
	fmt.Println("文件读取结束...")
}

7.3 写文件操作

  1. 创建一个新文件,写入内容 5 句 “hello, Gardon”
    在这里插入图片描述
    在这里插入图片描述
  2. 打开一个存在的文件中,将原来的内容覆盖成新的内容 10 句 “你好!”
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	//打开一个存在的文件中,将原来的内容覆盖成新的内容 10 句 "你好,尚硅谷!"
	//创建一个新文件,写入内容 5 句 "hello, Gardon"
	//1 .打开文件已经存在文件, d:/abc.txt
	filePath := "d:/abc.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC, 0666)
	if err != nil {
		fmt.Printf("open file err=%v\n", err)
		return
	}
	//及时关闭 file 句柄
	defer file.Close()
	//准备写入 5 句 "你好,尚硅谷!"
	str := "你好!\r\n" // \r\n 表示换行
	//写入时,使用带缓存的 *Writer
	writer := bufio.NewWriter(file)
	for i := 0; i < 10; i++ {
		writer.WriteString(str)
	}
	//因为 writer 是带缓存,因此在调用 WriterString 方法时,其实
	//内容是先写入到缓存的,所以需要调用 Flush 方法,将缓冲的数据
	//真正写入到文件中, 否则文件中会没有数据!!!
	writer.Flush()
}

  1. 打开一个存在的文件,在原来的内容追加内容 ‘ABC! ENGLISH!’
    在这里插入图片描述
    在这里插入图片描述

8 goroutine 和 channel

8.1 协程-goroutine

在这里插入图片描述

  1. 主线程是一个物理线程,直接作用在 cpu 上的。是重量级的,非常耗费 cpu 资源。
  2. 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
  3. Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。
  4. 如果主线程退出了,则携程即使还没有结束,也会退出

8.2 channel(管道)

goroutine之间通信-解决资源竞争问题

  1. channle 本质就是一个数据结构-队列
  2. 数据是先进先出【FIFO : first in first out】
  3. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  4. channel 有类型的,一个 string 的 channel 只能存放 string 类型数据。

8.2.1 定义/声明 channel

var 变量名 chan 数据类型

var intChan chan int (intChan 用于存放 int 数据)

8.2.2 管道的注意事项

  1. channel 是引用类型
  2. channel 必须初始化才能写入数据, 即 make 后才能使用
package main

import (
	"fmt"
)

func main() {
	//演示一下管道的使用
	//1. 创建一个可以存放 3 个 int 类型的管道
	var intChan chan int
	intChan = make(chan int, 3)
	//2. 看看 intChan 是什么
	fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan)
	//3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- 50
	// intChan<- 98//注意点, 当我们给管写入数据时,不能超过其容量
	//4. 看看管道的长度和 cap(容量)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3
	//5. 从管道中读取数据
	var num2 int
	num2 = <-intChan
	fmt.Println("num2=", num2)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3
	//6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock
	num3 := <-intChan
	num4 := <-intChan
	num5 := <-intChan
	fmt.Println("num3=", num3, "num4", num4, "num5=", num5)
}

  1. channel 中只能存放指定的数据类型
  2. channle 的数据放满后,就不能再放入了
  3. 如果从 channel 取出数据后,可以继续放入
  4. 在没有使用协程的情况下,如果 channel 数据取完了,再取,就会报 dead lock

8.2.3 channel 的遍历和关闭

8.2.3.1 关闭

使用内置函数 close 可以关闭 channel, 当 channel 关闭后,就不能再向 channel 写数据了,但是仍然可以从该 channel 读取数据
在这里插入图片描述

8.2.3.2 channel 的遍历

channel 支持 for–range 的方式进行遍历,请注意两个细节

  1. 在遍历时,如果 channel 没有关闭,则回出现 deadlock 的错误
  2. 在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
    在这里插入图片描述

8.2.4 注意事项

  1. channel 可以声明为只读,或者只写性质
    在这里插入图片描述
  2. goroutine 中使用 recover,解决协程中出现 panic,导致程序崩溃问题
package main

import (
	"fmt"
	"time"
)

//函数
func sayHello() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("hello,world")
	}
}

//函数
func test() {
	//这里我们可以使用 defer + recover
	defer func() {
		//捕获 test 抛出的 panic
		if err := recover(); err != nil {
			fmt.Println("test() 发生错误", err)
		}
	}()
	//定义了一个 map
	var myMap map[int]string
	myMap[0] = "golang" //error
}
func main() {
	go sayHello()
	go test()
	for i := 0; i < 10; i++ {
		fmt.Println("main() ok=", i)
		time.Sleep(time.Second)
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值