Golang简单语法
文章目录
GO语言
简介
“Go是一种开源的程序设计语言,它意在使得人们能够方便地构建简单、可靠、高效率的软件”(来自go官网golang.org)
我们程序员在开发程序,开发软件时都会选择一门编程语言,那么我们应该怎样进行选择呢?可能有同学会说,我们要选择一门简单的,容易学习的,而且开发效率高的,能够在很短的时间内开发完成一个软件,这样老板会非常的满意,能够升职加薪,Python语言或者Ruby语言就非常适合这种快速开发。但是问题是,用这种语言开发的软件,当用户量多了,运行的速度会非常慢,给人的感觉就是非常卡,大家想一下这种软件还有人愿意使用吗?那可能又有同学说了,我们要学习运行速度快的编程语言,例如C或者是C++,但是这类编程语言学习难度是非常大的。那么有没有一种编程语言,学习非常简单,开发速度非常快,开发出的软件电脑运行速度非常快呢?有,就是我们今天开始学习的GO语言。GO
语言借鉴了Python等其它编程语言简单,易学,生产效率高,同时GO语言专门针对多处理器(多核CPU,在这里可以给学生看一下windows下的多核CPU,)系统的程序进行了优化(让每核CPU都能够执行GO语言开发的程序),这样使用GO语言开发的程序运行的速度可以媲美C或C++开发程序的速度。
总结:
- 第一:并发上有天然的优势,
- 第二:运行速度在C之下python之上
Golang的格式检查
- 强制 { 放置位置 以及不可省去
正确使用
package main
func main(){
a := 10
//这个 { 必须在这个位置 不可以省去 不可以放到下面
if a == 10 {
a= 20
}else {
a =1100
}
}
错误使用一
package main
func main(){
a := 10
if a == 10
{
a= 20
}else
{
a =1100
}
}
错误使用二
package main
func main(){
a := 10
if a == 10
a= 20
else
a =1100
}
- 每行代码占一行 ;也就是说一行代码不能写两行或者多行,
比如 a = 10 不可以a =在第一行而10在第二行 - Golang要求不允许代码中有 变量 引入等等 比如你定义一个变量 a 之后的操作不再受用到a 也就是说在项目中你只定义了a这里golang会抛出没有使用的错误,这也体现了GOlang代码的简洁.这一点在项目的迭代中可以方便我们排除一些没有必要的存在的代码.
注释
单行注释 // XXX
多行注释 /** XXX*/
主函数模板
func main(){
}
运行方式
方式一 :
go build -o 别名/地址 需要编译的地址
- 先到对应XX.go的文件目录
- 之后在执行 go build XX.go [可选参数 -o 生成的别名.exe]
- 最后直接运行对应的exe文件
方式二 :
go run 需要运行的地址
- 先到对应XX.go的文件目录
- 执行 go run XX.go
变量
包的介绍
包的作用
1、区分相同名字的函数,变量等标识符
2、当程序文件很多时可以很好的管理项目
3、控制函数、变量等访问范围,即作用域
打包基本语法
package util
引入包二点基本语法
import 包的路径
输入和输出语句
package main
import (
"fmt"
)
func main(){
var i int
fmt.Printf("%T\n",i)
fmt.Println("输出语句")
// 输入
// Sprintf 返回一个格式化的字符串
str := fmt.Sprintf("格式化测试 %d。",10)
fmt.Println(str)
// Scanln()获取
// Scanf()获取
var sal float32
var name string
fmt.Scanln(&name)
fmt.Scanf("%s %f" ,&name,&sal)
fmt.Println(name,"\t",sal)
}
变量的类型
- 基本类型 (值类型)
- 整数类型
int int8 int16 int32 int64 uint uint8 uint32 uint64 byte - 浮数类型 float32 float64
- 字符型 : 没有专门的字符型,使用byte来保存单个字符不像C那样有char类型
- 布尔型:bool
- 字符串:string 注意:这里的字符串是基本类型存储方式是值,不是字符串的地址
- 整数类型
- 派生/复杂数据类型
- 指针(Pointer)
- 数组
- 结构体(struct)
- 管道(Channel)
- 函数(也是一种类型
- 切片(slice)
- 接口(interface)
- map
值类型 : 基本数据类型 int系列 float系列 bool string 数组和结构体struct |
---|
引用类型: 指针 slice系列 map 管道chan interface等都是引用类型 |
注意:
对于int32和int64不是同一个类型 这是两个类型,这里不想一些语言可以实现自动转换,和数据的截断等等.
变量的定义和赋值
/**
变量的三种方式:
1、指定变量类型,声明后不赋值 使用默认值
2、根据值自行判断变量类型
3、省去var,注意 := 左侧得变量不应该是已经声明过的,
*/
package main
import "fmt"
// 定义 1 个变量
var q1 =10
// 会类型推导 整数默认推导成 int类型
// 定义 n个 变量
var (
w1 = 10
w2 = 100
w3 = "hello wrold"
)
var i int // 定义一个int类型的
对于赋值得操作
var q1 =10 // 这里使用类型推导
var q2 int = 10 // 这里注意,因为你定义了q2的变量的数据类型因此数据两边的类型需要一致
q3 := 10 // 这种方式使用类型推导,但是前提在之前q3没有被定义过。
// 这种方式在for循环中常用到或者接收函数的返回值等等
var a int = 10
var b int = 20
a , b = b , a // 进行a b 的互换,这中写法在python中也支持
常量
常量的简单六要素
1、常量使用const修改
2、常量在定义的时候,必须初始化
3、常量不能修改
4、常量只能修改bool 、数值类型(int、float系列)、string类型
5、语法 const identifier [type] = value
6、是首字母控制范围和 变量 一样
**简单案例说明**
package main
import(
"fmt"
)
func main(){
var num int
const tax int =10
const b = 9/3 // 可以 但是const b = m/2 因为m是变量则不可以
fmt.Println(num , tax , b)
const (
a1 = iota // 0 iota 从a开始依次 +1 =》 本质是显示第几行 -1(从0开始) 如果一行有两个常量,两个常量的值一样
a2 // 1
a3 // 2
a4 , a5 = iota ,iota
)
fmt.Println(a1,a2,a3,a4,a5)
}
标注
iota
这里可以简单理解为的当前的变量的在第几行相对于其它一起被定义的变量 从0开始 比如 a1在第一行则表示0 a2 在第二行等等一次类推
流程控制
if else 语言
语法:
if isVaish1{
}else if isVaish2{
}else{
}
简单案例
package main
import(
"fmt"
)
func main(){
var a1 int
var a2 int = 10
if a1 == a2 {
fmt.Println("两个数相同")
}else{
fmt.Println("a1为int系列的默认值 0 : ",a1,a2)
}
}
switch语句
switch 常量/有返回值的函数等等{
case a1,b2...:
// 这个位置也可以是变量,但是需要age的类型和这里的类型一致
// switch的类型需要和case类型匹配
// 常量不可以重复一样在case但是变量如果一样但是可以
case b1,b2...:
case b1,b2...:
default:
}
for语句
// 循环10次
for t:= 0 ; t < 10 ; t++{
}
for{
// 死循环 可以使用break出去
}
for-range
for index ,value := range XXX{
// index 下标
// value 对应下标的值
}
for key ,value := range map{
// key
// key 对应的 value
}
for value := range XXX{
// value : XXX的元素
}
注意:
- for-range循环 可以遍历任意类型不想 for循环遍历只能
- for-range循环 左边值得数量看range 后面得类型
- for-range循环 可以使用 _ 来占位,这样可以解决不想使用一些变量得烦恼
函数
包的介绍
包的作用
1、区分相同名字的函数,变量等标识符
2、当程序文件很多时可以很好的管理项目
3、控制函数、变量等访问范围,即作用域
打包基本语法
package util
引入包二点基本语法
import 包的路径
内置函数
- len() 求长度
- new 用于分配内存主要用来分配值类型 比如int float32 struct返回的是指针
- make 用于分配内存,主要用来分配应用类型比如 chan map slice
init()函数
package main
import(
"fmt"
)
var age = test()
func test() int{
fmt.Println("test()...")
return 90
}
func init(){
// init函数完成初始化工作
fmt.Println("init()....")
}
func main(){
fmt.Println("mian()...")
}
解析:
- 第一个输出 是因为给全局变量赋值
- 第二个输出 是因为加载好参数之后,对文件进行初始化
- 都三个输出 当文件初始化好之后,执行主方法
自定义函数
语法
func 函数名 (形参…) (返回值){
函数执行体
}
函数的几种案例
package main
import (
"fmt"
)
func CaseOne(){
fmt.Println("没有参数,没有返回值的函数")
}
func CaseTwo(i int ){
fmt.Println("有一个形参,没有返回值的函数")
}
func CaseThreee(i int ,j int){
fmt.Println("多个形参,没有返回值的函数")
}
func CaseFour (i int ,args ... int )(){
fmt.Println("又可变形参的函数")
}
// 对于返回值的问题
func CaseFive()(sum int,err error ){
fmt.Println("没有形参,又两个返回的函数")
fmt.Println("返回值处理方式: 使用return直接返回")
return 10 , nil // 顺序要和返回的位置一样,
}
func CaseSix() (sum int ,err error){
err = nil // 设置返回err 的值,在return 返回
sum = 1000// 设置返回sum 的值,在return 返回
// 因为这里err sum 都是返回值,这里需要和返回值的变量名一样
return
}
func main(){
i ,err := CaseFive()
if err != nil{
return
}
fmt.Println(i)
}
defer的使用
package main
import(
"fmt"
)
func sum (n1 int ,n2 int )int {
// 当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈中(defer栈)
// 当函数执行完毕后,再从defer栈,按照先入后出的方式出栈
// 但是后面的值变化不会影响defer栈的变量的值,因为也会将值也会压入栈中
defer fmt.Println("ok n1 = ",n1)
defer fmt.Println("ok n2 = ",n2)
res := n1+n2
fmt.Println("res = ",res)
return res
}
func main(){
res := sum(10,20)
fmt.Println("Res = ",res)
}
匿名函数
在Go中函数也是一种变量类型
// 匿名函数
// 在GO中函数也是一种数据类型
fmt.Println("匿名函数")
res1 := func(n1 int,n2 int) int{
return n1 + n2
}(10,20)
fmt.Println("res1 = ",res1)
日期时间函数
Golang的时间是参考的时间是 :Mon Jan 2 15:04:05 MST 2006
2006年1月2日星期一15:04:05 MST
首先需要引入time包
time.Tme()类型
获取当前时间:
time.Now()
具体使用如下
now := time.Now()
fmt.Println(now)
fmt.Printf("time的类型时%T\n",now)
fmt.Printf("年=%v\n",now.Year())
fmt.Printf("月=%v\n",now.Month())
fmt.Printf("月=%v\n",int(now.Month()))
fmt.Printf("日=%v\n",now.Day())
fmt.Printf("时=%v\n",now.Hour())
fmt.Printf("分=%v\n",now.Minute())
fmt.Printf("秒=%v\n",now.Second())
格式化日期和时间
方式一:使用 fmt.Sprintf()格式化
fmt.Printf("当前年月日%02d-%02d-%02d %02d:%02d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
dataStr := fmt.Sprintf("当前年月日%02d-%02d-%02d %02d:%02d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
fmt.Println(dataStr)
方式二:使用Format()进行格式化
/**
语法:
Time.Format(格式)
*/
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::05"))
fmt.Println()
time包下的Sleep()
说明: 执行这个方法可以让当前的协程进行休眠
// 时间的常量 纳秒...
// 休眠 func Sleep(d Duration) ..
// time.Sleep(100 * time.Millisecond) 休眠100毫秒
time.Sleep(1 * time.Second)
获取时间的时间戳
UnixNano() 返回时间的纳秒数目
Unix() 返回时间的秒数
返回的结果是从时Time的的时间 减去 2006年1月2日星期一15:04:05 MST的结果。
随机函数
对于时间函数的使用,在随机函数中也常常使用到
随机函数的需要引入的包是 ”math/rand“
// 获取当前unix时间戳 秒
// 和unixnano时间戳 纳秒(作用是可以获取随机数字)
rand.Seed(time.Now().UnixNano())
// 如果不设置rand.Seed的种子,那么下面随机生成的数字都是一样的
a := rand.Int()
b := rand.Intn(100) // [0,99]的整数
fmt.Println("随机数一:",a,"随机数二:",b)
字符串函数
strconv包下中常用的函数和一些其它函数介绍介绍
函数作用 | 函数名及其参数使用 |
---|---|
10进制转换 2 8 16 | str = strconv.FormatInt(123,2/8/16) |
字符串转换为整数 | n,err := strconv.Atoi(str) |
查找子串是否在指定的字符串中 | strings.Contains(str,str1) str包含str1返回true |
统计字符串中子串的个数 | stringsCount(str,str1) str包含n个str1则返回n |
不区分大小写的字符串比较 | strings.EqualFold(str1,str2) 。 == 是区分大小写的比较 |
返回子串在字符串第一次出现的index值,如果没有返回 -1 | strings.Index(“awer”,“er”) // 2 |
返回指定子串最后出现的index 如果没有返回 -1 | strings.LastIndex(“go goland”,“go”) // 3 |
将指定子串替换成另一个子串 | strings.Replace(“go goland”,“go”,“go语言”,n)[n是指你想替换几个,n=-1表示全部替换] |
按照指定的某个字符分割表示,将一个字符串拆分成字符串数组 | strings.Split(“heel”,“e”) |
将字符串的字母进行大小写转换 | strings.ToLower(“Go”)//go strings.ToUpper(“go”) // GO |
将字符串左右两边的空格去掉 | stringsTrimSpace(" hello wordl ")//hello wordl |
判断字符串是否以指定的字符串开头 | strings.HasPrefix(“skspacve”,“s”) // true |
判断字符串是否以指定的字符串结束 | strings.HasSuffix(“fasdD”,“D”) // true |
将字符串左右两边指定的字符串去掉n | strings.Trim("!space!","!") //space) // true |
将字符串左边指定的字符去掉 | strings.TrimLeft("!space!","!") // space! |
将字符串右边指定的字符去掉 | strings.TrimRight("!space!","!") // !space |
整数转字符串 | str = strconv.Itoa(21) |
字符串转[]byte | var bytes = []byte(str) |
[]byte转字符串 | str := string([]byte{12,32,43}) |
字符串转换一个切片 | r := []rune(str) |
返回一个字符串的长度 | len(string) |
在Golang中中文占3个字节这是因为Golang的编码统一为uft-8
(ascii的字符(字母和数字)占一个字节,汉字占用3个字节)
上述部分函数案例
// 注意 中文3个因为golang的编码统一为uft-8,(ascii的字符(字母和数字)占一个字节,汉字占用3个字节)
// 对于字符的编码golang做了统一,特别注意中文,因为在文件操作中中文的编码很重要
str := "hello世"
fmt.Println(len(str))
// 字符串的遍历
for i := 0 ;i<len(str) ;i++{
fmt.Printf("字符=%c\t",str[i])
}
r:= []rune(str)
for i := 0 ;i<len(r) ;i++{
fmt.Printf("字符=%c\t",r[i])
}
fmt.Println()
// 字符串转整数 n,err := strconv.Atoi("123",2)
n,err := strconv.Atoi("123")
if err==nil{
fmt.Println("转换的结果是:",n)
}else{
fmt.Println("转换错误",err)
}
// 整数转换字符串
str = strconv.Itoa(1243)
fmt.Println(str)
// 字符串转换 []byte
var bytes = []byte("helle go")
fmt.Println("bytes=%v",bytes)
// 把byte[]转换字符串
str = string([]byte{98,97,99})
fmt.Println(str)
package strconv的全部函数
数组 map和切片
数组 和切片
定义一个数组: var 数组名称[定义的数组长度] 数组元素类型
数组案例
package main
import(
"fmt"
)
func main(){
// GO中数组是值类型
// 定义一个数组
var hen[3] float64
// 给数组的每个元素赋值
hen[0] = 3.0
hen[1] = 3.1
hen[2] = 3.2
var res = 0.0
for i :=0 ; i < len(hen) ;i++ {
res += hen[i]
}
fmt.Println("数组的元素的数值的总和",res)
}
定义一个切片 : var 数组名称[] 数组元素类型
注意:
数组是值类型 而切片是引入类型
数组和切片定义时的区别:
- 数组需要定义的时候确定好长度,并并且确定好长度不可改变
- 切片在定义的时候不能在 [ ]中添加数字,添加数字定义的是数组而不是切片
数组和切片的区别:
简单的理解:
切片是 数组的一个引用 并且可以动态变化的数组
定义并使用切片案例
package main
import(
"fmt"
)
func main(){
var intArr [8]int = [...]int{0,1,2,3,4,5,6,7}
// 声明/定义一个切片
// 引用intArr数组的起始下标为1,最后的下标为3(但是不包含3)
slices := intArr[1:3]
fmt.Println("intArr = ",intArr)
fmt.Println("silce 的元素= ",slices)
fmt.Println("silce 的元素个数= ",len(slices))
fmt.Println("silce 的容量= ",cap(slices)) // slice的容量是动态变化的
// 使用make创建切片对应的数组是由make维护,对外不可见,只能使用slice去操作数组
var silceMake [] int = make([]int,10,20)
fmt.Println(silceMake)
var sliceZH []int = []int{1,2,3}
fmt.Println(sliceZH)
// slice的遍历
for i:= 0 ;i < len(sliceZH) ;i++{
fmt.Printf("sliceZH[%d] = %v\n",i,sliceZH[i])
}
for index,value := range sliceZH{
fmt.Printf("index = %d value = %v\n",index,value)
}
// append()的使用
sliceZH = append(sliceZH,200,400,300)
sliceZH = append(sliceZH,sliceZH...) // 注意 ...需要带上,是切片不能放数组
fmt.Println(sliceZH)
// 字符串的改变
str := "nihao"
stringArr :=[]byte(str)
stringArr[0] = 'z'
str = string(stringArr)
fmt.Println("str = ",str)
// 使用[]byte后,可以处理英文和数字但是不能处理中文
// 原因是[]byte字节处理,而一个汉字,是三个字节,因此会出现乱码
// 处理方法 string 转成[]rune,因为[]rune是按照字符处理,兼容汉字
str = "你好世界 sdf"
stringRune := []rune(str)
stringRune[0] = '师'
str = string(stringRune)
fmt.Println("Str = ",str)
}
总结
1、slice是一个引用类型
2、slice从底层来说是一个结构体
type slice struct{
ptr *[2]int
len int
cap int
}
切片的使用的三种方式
1、定义一个切片 ,然后让切片去引用一个已经创建的数组
2、通过make来创建
基本语法: var slice []type = make([]type,len,[cap])
注意:cap指定切片容量,可选 如果分配了cap则需要cap >= len
3、定义一个切片,直接就指定具体数据,使用原理类似make的方式
1和2的区别:
方式一是直接应用数组是事先存在的,可见
方式二是通过make创建切片,make会创建一个数组,是由切片底层进行维护的 不可见
切片的遍历
1、基本for
2、for-range
切片的使用范围是[0,len(slice)]之间,但是可以动态增长
1、var slice = arr[0:end] => var slice = arr[:end]
2、var slice = arr[start:len(arr)] => var slice = arr[start:]
3、var slice = arr[0:len(arr)] => var slice = arr[:]
cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组或者make一个空间来供切片来使用
切片可以再进行切片
append内置函数 ,可以对切片进行动态追加
1、切片append操作的本质就是对数组扩容
2、go底层会创建一个新的数组newArr(安装扩容后大小)
3、将slice原来包含的元素拷贝到新的数组newArr
4、slice重新引入到newArr
5、注意newArr是在底层维护的,不可见
切片的拷贝操作 copy(slice,a) a拷贝给slice 注意: a和slice都是切片
string和slice的关系
1、string底层是一个byte数组,因此string可以进行slice操作
2、string和切片在内存的形式
3、string是不可变的,也就是说不能通过string[0] = 's’的方式来修改字符串
4、如果需要修改字符串,可以先将string -> []byte/[]rune -> 修改 -> 重新转成string
map(字典)
map 采用 key - value 的数据结构
基本语法
var mapName = map[keyType]valueType
注意事项:
- 中对于map中的key的数据类型要求是可以做 ==比较的,对于
slice map function 不可以做key 原因则是 : 没有办法使用==来判断
对于map类型使用前需要make
,不能像值类型那样定义之后可以直接赋值
map的使用方式
1、先声明 再make
2、声明,就直接make
3、声明直接赋值
map的使用方式具体代码
// 方式一
var a map[string]string
// 在使用map前,需要先make,
// make的作用就是给map分配空间 key-value是无序的
a = make(map[string]string,10) // 最大放10对key-value 之后可以动态变化
a["12"] = "oneAndTwo1" //
a["12"] = "oneAndTwo2" // 覆盖前一个
a["we"] = "fasdf"
fmt.Println(a)
// 方式二
a1 := make(map[string]string) // 默认为 1
a1["12"] = "123"
a1["124"] = "1234"
fmt.Println(a1)
// 方式三
var a2 map[string]string = map[string]string{
"12":"124",
"qwe" :"wqe",
}
fmt.Println(a2)
a3 := map[string]string{
"12":"124",
"qwe" :"wqe",
"fasdfasd":"Fasdf",
}
fmt.Println(a3)
map的CRUD操作
- map[“key”] = value
- delete()内置函数 按照指定的key从映射中删除,若key为nil或无此元素,delete不进行操作
// 删除操作
delete(studentMap,"stu1")
delete(studentMap,"stu4") // 删除没有的,不会报错
fmt.Println(studentMap)
// for-range遍历map
for key,value := range a3{
fmt.Printf("key = %v value = %v\n",key,value)
}
注意: 如果删除所有的key-value需要遍历一下删除,因为没有专门的方法一次删除;或者map = make(...),make一个新的,让原本的称为垃圾,被gc回收
- map的查询
value,findRes = map[key] // 如果有key则findRes是true
- map的遍历
-不能使用for遍历,使用for-range遍历
- map的长度 len(map)
- map切片
- map排序
1、没有专门的方法针对map的key进行排序
2、map默认是无序的,注意也不是按照添加顺序存放的
3、先将key进行排序,然后根据key值遍历输出即可
// map排序
mapSort := make(map[int]int,10)
mapSort[10] = 11
mapSort[1] = 20
mapSort[4] = 17
mapSort[6] = 6
fmt.Println(mapSort)
/**
按照map的key排序进行输出
1、先将map的key放入 切片中
2、对切片排序
3、遍历切片,然后按照key来输出map的值
*/
var keys []int
for k ,_ :=range mapSort{
keys = append(keys,k)
}
// 排序 递增
sort.Ints(keys)
for _ , k :=range keys{
fmt.Printf("map[%v] = %v \n",k,mapSort[k])
}
面向对象
结构体
定义方式语法
type 结构体名称 struct{
继承的类
字段名 字段类型
}
使用案例
type StructCaseOne struct{
Name string
Age int
}
方法
方法的定义: Golang中的方法是作用在指定的数据类型上的(即和指定的数据类型绑定),因此自定义类型,都可以由方法不仅仅是struct
方法声明(定义)
func (recevier type
) methodName(参数列表
) (返回值列表
){ 方法体 return 返回值}
说明:
1、recevier type
:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
2、recevier typ
e: type可以是结构体,也可以其它自定义类型 type int
3、receiver:
就是type类型的一个变量(实例)
4、参数列表
:表示方法的输入
5、返回值列表
:表示返回的值,可以多个
6、方法主题
;表示为了实现某个功能代码块
7、return
语句不是必须的
方法和函数的区别
:
声明 | 函数 | 方法 |
---|---|---|
调用方式不一样 | 函数的调用方式 函数名(参数列表) | 方法的调用方式 变量.方法名(实参列表) |
对普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
对于方法(struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
type Student struct{
Name string
Age int
}
// 定义一个方法
func (stu * Student) String() string{
// 传递指针,这里改变circle的字段值,main的也会发生改变
return fmt.Sprintf("Name=[%v] Age=[%v]",stu.Name,stu.Age)
}
func (stu Student) String1() string{
return fmt.Sprintf("Name=[%v] Age=[%v]",stu.Name,stu.Age)
}
异常处理机制
defer
Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先defer的后执行,后defer的,先执行。
error
当我们在自定义函数是,某些情况下会产生错误。我们可以使用error 对象返回这些错误:
Panic
Panic是程序运行中突然产生未经处理的异常。在Go中,panic不是合理处理异常的方式,推荐使用error对象代替。当Panic产生时,程序将会暂停运行。当panic被defer之后,程序才能继续运行。
package main
import(
"fmt"
)
func main(){
i:= 10
fmt.Println("test is 1")
defer fmt.Println("defer i is :" ,i)
fmt.Println("test is 2")
i = 100
defer fmt.Println("defer i is :" ,i)
}
defer是执行到这里,将其压入栈中,当当先所在的线程执行完毕之后出栈执行,出栈的顺序是先进后出
。
JSON的转化
需要导入的包:encoding/json
- JSON序列化
func Marshal(v interface{}) ([]byte, error)
- JSON的反序列化
func Unmarshal(data []byte, v interface{}) error
序列化和反序列化这是JSON转换中的两个重要过程
要将json数据解码写入一个接口类型值,函数会将数据解码为如下类型写入接口: |
Bool 对应JSON布尔类型 |
float64 对应JSON数字类型 |
string 对应JSON字符串类型 |
[]interface{} 对应JSON数组 |
map[string]interface{} 对应JSON对象 |
nil 对应JSON的null |
序列化案例
package main
import(
"fmt"
"encoding/json"
)
func main(){
num := 10
data , err := json.Marshal(&num)
if err != nil{
fmt.Println("整数num 序列化失败")
return
}
fmt.Printf("基本类型序列化的结果 : %v\n",string(data))
JsonTStructCase := JsonTStruct{
Name : "JsonNameCase",
Age : 10 ,
}
data , err = json.Marshal(&JsonTStructCase)
fmt.Printf("结构体序列化的结果 : %v\n",string(data))
mapJsonCase := make(map[string]string,2)
mapJsonCase["name"] = "Hello World!"
mapJsonCase["age"] = "8"
data , err = json.Marshal(&mapJsonCase)
fmt.Printf("map字典序列化的结果 : %v\n",string(data))
var arrJsonCase[3] float64
arrJsonCase[0] = 0.0
arrJsonCase[1] = 1.0
arrJsonCase[2] = 2.0
data , err = json.Marshal(arrJsonCase)
fmt.Printf("数组序列化的结果 : %v\n",string(data))
var silceJsonCase [] int
silceJsonCase = make([]int,10,20)
silceJsonCase[0] = 0
silceJsonCase[1] = 1
silceJsonCase[2] = 2
silceJsonCase[3] = 3
silceJsonCase[4] = 4
data , err = json.Marshal(silceJsonCase)
fmt.Printf("切片的序列化的结果 : %v\n",string(data))
// 反序列案例
var sliceUnjsonCase [] int
sliceUnjsonCase = make([] int ,10 ,23)
err = json.Unmarshal([]byte(data),&sliceUnjsonCase)
if err != nil{
fmt.Println("切片反序列化失败")
return
}
for index ,value := range sliceUnjsonCase{
fmt.Printf("sliceUnjsonCase[%d] = %v \n",index,value)
}
UnjsonTStructStr := "{\"name\":\"JsonNameCase\",\"age\":10}"
var UnjsonTStructCase JsonTStruct
UnjsonTStructCase = JsonTStruct{}
json.Unmarshal([]byte(UnjsonTStructStr),&UnjsonTStructCase)
fmt.Printf("结构体的反序列化结果是 %v \n",UnjsonTStructCase)
}
type JsonTStruct struct{
Name string `json:"name"`
// 这个的字段名需要首字母大写,如果不是首字母大写,则字段是私有的
// JSON序列化不能序列化私有的字段
Age int `json:"age"`
// `json:"XXX"` 是为了让本来为给字段起别名,最好json序列化显示的别名
// 这使用web开发的中一些习惯,因为在web开发中,json的中的字段一般是小写
// 我所见的大写的基本很少
}
封装
Golang的封装方式和Python的封装很相似,采用字段的首字母是否大写
首字母大写可以直接访问,首字母的小写的不能直接被访问
,小写的使用一些其它手段也是能访问,这种现象在其它面向对象的面向对象也存在。
不论方法还是字段都遵循这种方式
案例
package main
import(
"fmt"
)
type persion struct {
Name string
age int
sal float64
}
func NewPersion(name string) *persion{
return &persion{
Name : name,
}
}
func (persion *persion)SetSal(sal float64){
persion.sal = sal
}
func (persion *persion) SetAge(age int){
persion.age = age
}
func (persion *persion) SetName (name string){
persion.Name = name
}
func (persion *persion) GetName () string{
return persion.Name
}
func (persion *persion) GetAge()int{
return persion.age
}
func (persion *persion)GetSal() float64{
return persion.sal
}
func main(){
persion1 := NewPersion("小红")
fmt.Println(persion1)
}
继承
继承的方式 Golang的继承外表看起来像成一种结构体的嵌套这是因为:golang 使用组合的方式实现继承
摘要
golang并非完全面向对象的程序语言,为了实现面向对象的继承这一神奇的功能,golang允许struct间使用匿名引入的方式实现对象属性方法的组合
// 基本语法:
type Goods struct{
Name string
Price int
}
type Book struct{
Goods // 这里就是嵌套匿名结构体
Write string
}
/*
结构体可以使用匿名结构体所有的字段和方法,(首字母大小写都可以使用)
不像Java那样private的不能使用
匿名结构体字段访问可以简化
出现相同的字段,就近原则
*/
接口
接口关键字 interface
但是接口不能创建实例,只能用于指向一个实现接口的自定义类型的变量
接口中所有的方法都没有方法体
- Golang 中,一个自定义类型需要讲某个接口二点所有方法都是实现,我们说这个自定类型实现了该接
- 只要自定义类型都可以实现接口,不知有结构体可以实现
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口可以继承多个别的接口
interface类型默认是一个指针,如果intface没有初始化使用,则会输出nil
空接口interface{} 没有任何方法,所以所有类型都实现了空接口
// 使用类型断言
// 接口类型转换之前的类型
B = inteface.(B) 类型断言 B转换为接口类型
// 只接收B的话 -> 如果转换失败,会报错
// 写法一
B ,ok = inteface.(B)
if ok == true{
// 转换成功
}
// 写法二
if B ,ok = inteface.(B);ok == true{
// 转换成功
}
一个结构体/其它类型 实现同一个文件的接口的方法,就视为继承这个接口;不需要使用其它字段来实现实现这个接Golang底层实现自动实现接口这个步骤。这也看出了GOlang语言的简洁
这里也表明了一个信息: interface{}(空接口) 可以接收任意类型的值。
定义接口实例代码
// 声明/定义一个接口
type Use1 interface{
// 声明了两个 没有实现的方法
Start()
Stop()
}
type phone struct{
}
func (phones phone) Start(){
fmt.Println("手机开始工作")
}
func (phones phone) Stop(){
fmt.Println("手机停止工作")
}
type Compter struct{
}
func (cm Compter) Working(user Use1){
fmt.Println("...开十四")
user.Start()
user.Stop()
}
func main(){
// 测试床架结构体变量
phoneTesy := phone{}
phoneTesy .Start()
phoneTesy.Stop()
com := Compter{}
com.Working(phoneTesy)
}
文件操作
Read操作
os包提供了操作系统函数的不依赖平台的接口
。设计为Unix风格
的,虽然错误处理是go风格
的;失败的调用会返回错误值
而非错误码。通常错误值里包含更多信息。例如,如果某个使用一个文件名的调用(如Open、Stat)失败了,打印错误时会包含该文件名,错误类型将为*PathError,其内部可以解包获得更多信息。
os包的接口规定为在所有操作系统中都是一致的。非公用的属性可以从操作系统特定的syscall包获取。
os.Open(name string)(*File,error)
- name: 表示文件的路径
- 返回值
- 文件指针
- 如果获取文件指针失败,则返回错误信息,否则返回 nil
打开文件的模式参数
const (
SEEK_SET int = 0 // 相对于文件起始位置seek
SEEK_CUR int = 1 // 相对于文件当前位置seek
SEEK_END int = 2 // 相对于文件结尾位置seek
)
关闭一个文件 `File.Close()` 使用的时候,个人感觉在创建之后加一个defer xxx.Close() (前提是知道这个在这关闭,比如 在函数/方法中从主协程获取的一般情况下是在函数/放啊中不能操作传过来参数的关闭操作)
file,err := os.Open("case1.txt")
if err !=nil{
fmt.Println("文件错误")
}
fmt.Printf("file = %v",file)
err = file.Close()
if err!=nil{
fmt.Println("关闭文件错误")
}
读写操作
注意: 不管读取到缓存区再写入还是直接读取,取出计算机内存的次数都是一次,而区分的程序的读取的次数
解析:
- 程序的多次读取或者写入操作的次数是用户空间(可以简单的理解成我们的引用程序)和内核空间的操作(可以简单的理解为我们的操作系统)的多次读取和操作。
- 第二点,最后不论应用程序读取或者写入操作是否使用到缓冲区,磁盘的读取或者写入都是一次。
- 对于操作的系统的设计为什么这么设计,简单来说就是一句话,提高性能。
对于开发来说提高项目性能一般采用使用缓存,操作系统也不例外。
总所周知计算机的内存操作比磁盘操作快了很多,如果我们写入或者去读一次文件需要多次磁盘操作,这样显示性能是很低,而且这样CPU的利用也会很低,因此磁盘操作真的太慢了。因此操作系统这一设计边孕育而生
GOlang的缓存区的使用
// 读取case.txt的内容使用缓冲区
file1,err1 := os.Open("case1.txt")
defer file1.Close() // 即使关闭file源 ,否则会有内存泄漏
if(err1 != nil){
fmt.Println("使用缓冲区读取文件中的打开文件出现错误")
}
reader := bufio.NewReader(file1) // 这里便把文件 case1.txt存储到 reader 的缓冲区中
// 循环的读取文件的内容
for{
str ,err2 :=reader.ReadString('\n')
if err2 == io.EOF{
break
}
fmt.Print(str)
}
ioutil.ReadFile一次性将文件读取到位
使用"io/ioutil"包下的ReadFile()一次性读取文件,不需要Open()操作和Close()操作
注意: 使用这种一次性读取的方式,不建议使用在大文件去读操作
使用案例
content,err3 := ioutil.ReadFile("case1.txt")
if err3 !=nil{
fmt.Printf("content = %v",err3)
}
fmt.Printf("%v\n",content) // []byte
fmt.Printf("%v\n",string(content))
func OpenFile(name string ,flag int ,perm FileModel)(file *File ,err error)
解释:isOpenFile是一个更一般性的文件打开函数,它会使用指定的选项、指定的模式、打开指定名称的文件。 如果操作成功,返回的文件对象可用于I/O错误底层类型是*PathError |
---|
os.Stat()
函数返回的 错误值进行判断:
- 如果返回的错误是nil ,说明文件或问价夹存在
- 如果返回的错误类型使用os.IsNotExist()判断为true ,说明文件或文件夹不存在
- 如果返回的错误为其它类型,则不确定是否在存在
拷贝文件
func Copy(dist Writer,src Reader)(written int64,err error)
使用案例
package main
import(
"fmt"
"os"
)
Write操作
打开文件的模式
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
向文件写入数据的方式
package main
import(
"os"
"fmt"
"io/ioutil"
"bufio"
)
func main(){
// 写数据到文件中 创建一个文件case1_1.txt
file2 , err4 := os.OpenFile("case1_1.txt",os.O_WRONLY | os.O_CREATE , 0666)
if err4 != nil{
fmt.Printf("open file err =%v\n",err4)
return
}
defer file2.Close()
str := "helle world!\n"
// 写入时,使用带缓存的 *Writer
write := bufio.NewWriter(file2)
for i := 0 ; i < 5 ;i++{
// fmt.Println(str)
write.WriteString(str)
}
// 因为writer是带缓存的,因此调用writerString方法时
// 内容是写道缓存的,所以需要调用Flush方法,将缓冲的数据真正写入到文件中,否则文件中会没有数据
write.Flush()
// 打开一个存在的文件,在原来的内容追加内容
file3,err5 := os.OpenFile("case1.txt",os.O_WRONLY | os.O_APPEND,0666)
if err5 != nil{
fmt.Println("open file error5 = %v",err5)
return
}
defer file3.Close()
writer3 := bufio.NewWriter(file3)
writer3.WriteString("追加的内容")
writer3.Flush()
// 复制文件内容到另外一个文件中
data,err6 := ioutil.ReadFile("case1.txt")
if err6 != nil{
fmt.Println("open file error6")
return
}
err6 = ioutil.WriteFile("case1复制.txt",data,0666)
if err6 != nil{
fmt.Println("writer is error6",err6)
}
}
命令行中参数获取
mysql命令行登录页面参数
这个参数mysql是如何获取的呢?
- 首先获取一行字符串
- 之后对字符串进行处理
(核心)
对字符串进行处理,最容易的能想到的是进行分割,之后进行设置的规则进行匹配.
那么在Golang中是否又封装好的方法可以处理这个问题呢,对,在flag包
中分装了一些方法可以处理这个问题
使用flag包进行获取参数
package main
import(
"fmt"
"flag"
"os"
)
func main(){
var user string
var pwd string
var host string
var port int
flag.StringVar(&user,"u","","用户名,默认为空")
flag.StringVar(&pwd,"pwd","","密码,默认为空")
flag.StringVar(&host,"h","localhost","主机名,默认为localhost")
flag.IntVar(&port,"port",3306,"端口号,默认为3306")
// 最重要的操作,转换
flag.Parse()
// 输出结果
fmt.Printf("用户:%v \t 密码:%v \t 主机号:%v \t 端口号:%v\n",user,pwd,host,port)
// 获取命令行的参数
fmt.Println("命令行的参数有",len(os.Args))
}
管道、Goroutine和单元测试
单元测试
要开始一个单元测试,需要准备一个 go 源码文件,在命名文件时需要让文件必须以_test结尾。默认的情况下,go test命令不需要任何的参数,它会自动把你源码包下面所有 test 文件测试完毕,当然你也可以带上参数。
案例
package main
import (
"testing"
)
func TestAddUpper(t *testing.T){
res := addUpper(10)
if res != 55{
t.Fatalf("出现错误")
}
// 如果正确,输出日志
t.Logf("执行 is rangeig")
}
func addUpper(n int)int{
res := 0
for i := 1; i <= n ;i++{
res +=i
}
return res
}
-
首先需要引入testing包
-
第二点方法名需要以Test- 开头
-
执行命令: go test 文件名.go
-
如果像看执行的详细信息
- 执行 go -v test 文件名.go
- 执行 go -v test 文件名.go
使用注意事项:
1、测试用例的文件名必须以 _test.go结尾
2、测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名
3、TestXXX(t * testing.T)
4、一个测试用例文件中,可以有多个测试用例函数
5、运行测试用例指令
(1)cmd> go test[如果运行正确,无日志,错误时,会输出日志]
(2)cmd> go test -v [运行正确或者错误,都输出日志]
6、当出现错误时,可以使用 t.Fatalf来格式化输出错误信息,并退出程序
7、t.Logf 方法可以输出相应的日志
8、测试用例函数,并没有放在main函数中,也执行了,这就是测试用例的方便指出
9、PASS表示测试用例运行成功,FAIL表示测试用例运行失败
10、测试单个文件 ,一定要带上被测试的原文件
go test -v cal_test.go cal.go
11、测试单个方法
go test -v -test.run 方法名
Goroutine
MPG模式
解析:
- M : 操作系统的主线程(物理线程)
- P : 协程执行需要的上下文
- G : 协程
Go线程和协程的简单介绍
1、Go 的主线程
一个Go线程可以有多个协程,协程是轻量级的线程[编译器做优化]
2、Go协程的特点
(1)有独立的栈空间
(2)共享程序堆空间
(3)调度由用户控制
(4)协程是轻量级的线程
3、主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常耗费cpu
4、协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小。
5、Golang的协程机制是重要的特点,可以轻松的开启上万个协程。
其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显了Golang的并发的优势
简单案例
package main
import "fmt"
import "time"
func TestGoroutine()(err error){
for i := 10 ; i< 1000 ; i++{
fmt.Printf("%d ",i)
}
return
}
func main (){
fmt.Println("在主线程中开启一个协程运行TestGoroutine()函数")
go TestGoroutine()
time.Sleep(10 * time.Second)// 主线程等待10秒让执行TestGoroutine()函数的协程执行完毕
}
不加休眠时间可能出现的问题
主线程中不加上休眠时间可能会出现,主线程执行完毕之后,协程没有执行的问题,这回导致主线程已经 关闭了,导致协程不会执行的问题
当然在以后的开发中可能不能使用休眠的方式来处理这个问题,一般自研的话会又使用类似监听的方式处理这个问题,
runtime包
runtime包提供和go运行时环境的互操作,如控制go程的函数。它也包括用于reflect包的低层次类型信息;参见reflect报的文档获取运行时类型系统的可编程接口 |
---|
NumCPU() 返回本地机器逻辑CPU个数,这个一般情况下是你电脑/服务器的CPU个数两倍的数目
GOMAXPROCS 设置可同时
版本:
1、go1.8后,默认让程序运行在多个核上,可以不用设置了
2、go1.8前,还需要设置一下,可以更高效益的利用cpu
在并发这个环境变量GOMAXPROCS限制可以同时运行用户层次的go代码的操作系统进程数。没有对代表go代码的、可以在系统调用中阻塞的go程数的限制;那些阻塞的go程不与GOMAXPROCS限制冲突。本包的GOMAXPROCS函数可以查询和修改该限制。
环境变量GOTRACEBACK控制当go程序因为不能恢复的panic或不期望的运行时情况失败时的输出 。失败的程序默认会打印所有现存go程的堆栈踪迹(省略运行时系统中的函数),然后以状态码2退出。如果GOTRACEBACK为0,会完全忽略所有go程的堆栈踪迹。如果GOTRACEBACK为1,会采用默认行为 。如果GOTRACEBACK为2,会打印所有现存go程包括运行时函数的堆栈踪迹 。如果GOTRACEBACK为crash,会打印所有现存go程包括运行时函数的堆栈踪迹,并且如果可能会采用操作系统特定的方式崩溃,而不是退出 。例如,在Unix系统里,程序会释放SIGABRT信号以触发核心信息转储。 |
---|
package main
import "fmt"
import(
"runtime"
)
func main (){
num := runtime.NumCPU()
fmt.Println("当前电脑的逻辑CPU数目:",num)
}
管道Channel
同步处理
(1)全局变量互斥锁
(2)管道:
为什么使用:
(1)主线程在等待所有的goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算
(2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可以还有goroutine处于工作状态,这时也会随主线程的退出而销毁
(3)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全部变量的读写操作
(4)通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作
channle介绍
(1)channle 本质就是一个数据结构-队列
(2)数据先进先出
(3)线程安全,多goroutine,不需要加锁,因为channele本身就是线程安全的
(4)channle有类型的,一个string 和channle只能存放string类型数据
channle使用
1、定义/声明channle
注意:
(1)channle是引用类型
(2)channle必须处初始化才能写入数据,即make后才能使用
(3)管道是有类型的 intChan 只能写入整数 int
2、channle的关闭
使用内置函数close可以关闭channle,当channle关闭后,就不能再向channle写数据了,但是仍然可以从channle读取数据
3、channle的遍历
channle支持for-range的方式进行遍历
注意:
(1)如果channle没有关闭,则会出现deadlock的错位
(2)关闭了,正常遍历数据,遍历完后,就会退出遍历
for v := range intChan{}
package main
import(
"fmt"
"runtime"
"sync"
"time"
)
var (
mayMap = make(map[int]int,10)
// 声明一个全局的互斥锁
// lock 是一个全部的互斥锁
// sync 是包:synchornized 同步
// Mutex :是互斥
lock sync.Mutex
)
func test(n int){
res := 1
for i :=1 ; i<= n;i++{
res *=i
}
// 加锁
lock.Lock()
mayMap[n] = res
// 解锁
lock.Unlock()
}
func main(){
nun := runtime.NumCPU()
fmt.Println("本电脑CPU的数目 ",nun)
// 自己设置使用多少个CPU
runtime.GOMAXPROCS(nun-1)
for i:= 1 ; i< 10;i++{
go test(i)
}
time.Sleep(time.Second * 10)
lock.Lock()
for k,v := range(mayMap){
fmt.Printf("myMap[%d] = %d \n",k,v)
}
lock.Unlock()
// channle使用
var intChan chan int
intChan = make(chan int ,3)
fmt.Printf("intChan的值是:%v\n",intChan)
// 写入数据
intChan <- 10
num := 100
intChan <- num
// 读取数据
num = <-intChan
fmt.Printf("intChan中取出的数据 = %v\n",num)
// 管道不会自动增长
fmt .Printf("intChan len =%v cap = %v\n",len(intChan),cap(intChan))
// 在没有使用协程的情况下,如果我们的管道数据已经全部取出 ,再取会报告 deadlock
}
channle使用细节和注意事项
1、channle可以声明只读 、只写和可读可写
(1) var chan1 chan int 可读可写
(2) var chan2 chan<- int 可写
(3) var chan3 <-chan int 只读
2、使用select可以解决从管道取数据的阻塞问题
阅读代码
package main
import(
"fmt"
"time"
)
func putNum(intChan chan int){
for i:= 0 ;i <=8000 ;i++{
intChan<-i
}
close(intChan)
}
func primeNum(intChan chan int ,primeChan chan int , exitChan chan bool){
var flag bool
for {
flag = true
num ,ok := <-intChan
if !ok { // intChan取不到...
break
}
// 判断是否为素数
for i:=2 ; i < num ;i++{
if num %i ==0 {
flag =false
break
}
}
if flag{
primeChan <- num
}
}
fmt.Println("有一个primeNum协程因为娶不到数据,退出")
// 向退出
exitChan<-true
}
func main(){
intChan := make(chan int , 1000)
primeChan := make(chan int ,2000)
exitChan := make(chan bool ,4)
stat := time.Now().Unix()
// 开启一个协程,向intChan放入1-8000个数
go putNum(intChan)
// 开启4个协程,从intChan取出数据,并判断是否为素数
for i:=0 ;i < 4 ;i++{
go primeNum(intChan,primeChan,exitChan)
}
go func(){
for i := 0 ;i < 4 ;i++{
<-exitChan
}
// 当从exitChan取出四个值
close(primeChan)
end:= time.Now().Unix()
fmt.Println("运行时间?:",end-stat)
}()
// 遍历primeChan 将结果输出
for{
res ,ok := <-primeChan
if !ok{
break
}
// 结果输出
fmt.Printf("素数是%v\n",res)
}
}
使用案例
package main
import(
"fmt"
)
// 函数
func sayHello(){
for i:=0;i < 10 ;i++{
time.Sleep(time.Second * 1)
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] - "fsd"
}
func main(){
go sayHello()
go test()
}
反射
反射基本介绍
1、反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)
2、如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
3、通过反射,可以修改变量的值,可以调用关联的方法
4、使用反射,需要import(“reflect”) package reflect包 实现运行反射
package reflect包 实现运行反射,中其中重要的一个类型如下
reflect.Value类型
reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。 |
---|
反射中的类型的之间的互换
interface{} 和reflect.Value的转换
// b 类型的变量转换为 reflect.Value类型
rVal := reflect.ValueOf(b)
// reflect.Value类型转换为 interface类型
iVal := rVal.interface() // 这里使用的是reflect.Value类型的方法不是类型断言
// 使用类型断言讲interface类型转换为b类型
v := iVal.(b)
在reflect.Value类型中有两个重要的类型一个是Kind
还有一个是Type
类型
Kind代表Type类型值表示的具体分类。零值表示非法分类。
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
那么Type是呢:
Type类型用来表示一个go类型。
在go中
不是所有go类型的Type值都能使用所有方法
。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类
。调用该分类不支持的方法会导致运行时的panic。
Type中所有的方法
type Type interface {
// Kind返回该接口的具体分类
Kind() Kind
// Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
Name() string
// PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
// 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
PkgPath() string
// 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
// 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。
String() string
// 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
Size() uintptr
// 返回当从内存中申请一个该类型值时,会对齐的字节数
Align() int
// 返回当该类型作为结构体的字段时,会对齐的字节数
FieldAlign() int
// 如果该类型实现了u代表的接口,会返回真
Implements(u Type) bool
// 如果该类型的值可以直接赋值给u代表的类型,返回真
AssignableTo(u Type) bool
// 如该类型的值可以转换为u代表的类型,返回真
ConvertibleTo(u Type) bool
// 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic
Bits() int
// 返回array类型的长度,如非数组类型将panic
Len() int
// 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
Elem() Type
// 返回map类型的键的类型。如非映射类型将panic
Key() Type
// 返回一个channel类型的方向,如非通道类型将会panic
ChanDir() ChanDir
// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
NumField() int
// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
Field(i int) StructField
// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
FieldByIndex(index []int) StructField
// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
FieldByName(name string) (StructField, bool)
// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
// 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
// 如非函数类型将panic
IsVariadic() bool
// 返回func类型的参数个数,如果不是函数,将会panic
NumIn() int
// 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
In(i int) Type
// 返回func类型的返回值个数,如果不是函数,将会panic
NumOut() int
// 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
Out(i int) Type
// 返回该类型的方法集中方法的数目
// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
// 匿名字段导致的歧义方法会滤除
NumMethod() int
// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
Method(int) Method
// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
MethodByName(string) (Method, bool)
// 内含隐藏或非导出方法
}
注意
如果使用Go开发一些框架或者看Go的一些框架的源码中反射是必不可少的.重点有一下几个方面 方法的使用
类型的实例
方法的参数
字段的获取
等等
Type和Kind的区别
- Type 是类型
- Kind 是类别
举个栗子
var stu Student stu
Type是pkg.Student
Kind是struct:
案例代码观看
package main
import(
"reflect"
"fmt"
)
func reflectTest1(b interface{}){
// 通过反射获取的传入的变量的 type , kind ,值
// 1、先获取到 reflect.Type
rType := reflect.TypeOf(b);
fmt.Println("rType = ",rType)
// 2、获取到reflect.Value
rVal := reflect.ValueOf(b)
fmt.Printf("rVal = %v \t rVal的类型:%T\n",rVal,rVal)
// n1 := 10
n2 := 2 + rVal.Int() //
fmt.Printf("n2 =%v \t n2 Type is equlas %T\n",n2,n2)
iV := rVal.Interface() // 将reflect.Value转换为interface
num2 := iV.(int) // 使用断言 转换成需要的类型
fmt.Println(num2)
}
// 演示反射[对结构体的反射]
func reflectTest2(b interface{}){
// 通过反射获取的传入的变量的 type , kind ,值
// 1、先获取到 reflect.Type
rType := reflect.TypeOf(b);
fmt.Println("rType = ",rType)
// 2、获取到reflect.Value
rVal := reflect.ValueOf(b)
// 3、获取 变量对应的Kind
// (1) rVal.Kind() ==>
kind1 := rVal.Kind()
// (2) rTyp.Kind() ==>
kind2 := rVal.Kind()
fmt.Printf("kind = %v kind %v \n",kind1,kind2)
iV := rVal.Interface()
fmt.Printf("iV的类型是 : %T \n",iV)
// 将interface{}通过断言转换需要的类型
// 可以使用switch 的断言方式来做灵活的转换
stu1 , ok := iV.(Student)
if ok {
fmt.Printf("stu.Name = %v\n",stu1.Name)
}
}
type Student struct{
Name string
Age int
}
func main(){
var num int = 100
reflectTest1(num)
// 2、
stu := Student{
Name : "Lxy",
Age :12,
}
reflectTest2(stu)
}
在reflect.Value类型设置值func(Value) Elem
方法
案例
package main
import(
"reflect"
"fmt"
)
func reflect01 (b interface{}){
rVal :=reflect.ValueOf(b)
fmt.Printf("rVal kind = %v\n",rVal.Kind())
rVal.Elem().SetInt(23)
}
func main(){
var nuim int = 10
reflect01(&nuim)
fmt.Println("nuim = ",nuim)
}
网络编程
Golang的网络编程的包在net
包中
net 包中包提供了可移植的网络I/O接口,包括TCP/IP、UDP、域名解析和Unix域socket.
net包提供了可移植的网络I/O接口,包括TCP/IP、UDP、域名解析和Unix域socket。 |
虽然本包提供了对网络原语的访问,大部分使用者只需要Dial、Listen和Accept函数提供的基本接口;以及相关的Conn和Listener接口。crypto/tls包提供了相同的接口和类似的Dial和Listen函数。 |
func Dial(network, address string) (Conn, error)
在网络network上连接地址address,并返回一个Conn接口 |
---|
可用网络有 | 简要说明 |
---|---|
tcp、tcp4、tcp6 | 面向连接的、可靠的、基于字节流的传输层通信协议, |
udp、udp4、udp6 | 应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包 |
ip、ip4、ip6 | 跟冒号和协议号或者协议名,地址必须是IP地址字面值 |
监听方法 net.Listen()
func Listen(net, laddr string) (Listener, error)
返回在一个本地网络地址laddr上监听的Listener。网络类型参数net必须是面向流的网络:
“tcp”、“tcp4”、“tcp6”、“unix"或"unixpacket”。参见Dial函数获取laddr的语法。
返回一个Listener结构体
Listener是一个用于面向流的网络协议的公用的网络监听器接口。多个线程可能会同时调用一个Listener的方法。 |
---|
type Listener interface {
// Addr返回该接口的网络地址
Addr() Addr
// Accept等待并返回下一个连接到该接口的连接
Accept() (c Conn, err error)
// Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
Close() error
}
Listener结构体的方法
- func (l *TCPListener) Accept() (Conn, error)
Accept用于实现Listener接口的Accept方法;他会等待下一个呼叫,并返回一个该呼叫的Conn接口。 |
Conn接口代表通用的面向流的网络连接。多个线程可能会同时调用同一个Conn的方法。 |
type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Write(b []byte) (n int, err error)
// Close方法关闭该连接
// 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误
Close() error
// 返回本地网络地址
LocalAddr() Addr
// 返回远端网络地址
RemoteAddr() Addr
// 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline
// deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞
// deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作
// 参数t为零值表示不设置期限
SetDeadline(t time.Time) error
// 设定该连接的读操作deadline,参数t为零值表示不设置期限
SetReadDeadline(t time.Time) error
// 设定该连接的写操作deadline,参数t为零值表示不设置期限
// 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
SetWriteDeadline(t time.Time) error
}
对于Conn结构体中方法
func (c *IPConn) Write(b []byte) (int, error)
参数b : 是你发送的数据的字节
返回值int : 是你通过Conn发送的数据的字节数目
func (c *TCPConn) Read(b []byte) (int, error)
参数b : 是你从这个Conn获取数据的缓冲区,这个和文件读取的方式基本一致。只是一个是从文件中获取数据一个是从网络获取数据
返回值 int : 是你这次读取了多个字节的数据
服务端简单案例一
package main
import(
"fmt"
"net" // net包提供了可移植的网络I/O,包括TCP/IP、UDP、域名解析和Unix域socket
)
func process(con net.Conn){
defer con.Close()
for{
buf := make([]byte,1024)
// con.Read(buf)
// 1、等待客户端通过con发送信息
// 2、如果客户端没有write[发送] ,那么协程就阻塞在这里
fmt.Printf("服务器在等待客户端%s 发送信息 \n" ,con.RemoteAddr().String())
n,err :=con.Read(buf)
if(err != nil){
if err == io.EOF{
fmt.Println("客户端退出")
}else{
fmt.Println("服务器的Read err = ",err)
}
return
}
// 3、服务器显示客户端的数据、到服务的终端
fmt.Print(string (buf[:n])) // 不要加ln因为客户端的数据将那个换行也输出进来 ; 加[:n]是为了buf有多余的
}
}
func main(){
fmt.Println("服务器开始监听.....")
// net.Listen("tcp","127.0.0.1:8888") // 这样写只支持TPV4
listen,err := net.Listen("tcp","0.0.0.0:8888") //这样写支持IPV4和IPV6两种
if err !=nil{
fmt.Println("listen err = ",err)
return
}
defer listen.Close() // 延迟关闭 listen
// 循环等待客户端来连接我
for{
fmt.Println("等待客户端的连接")
con,err :=listen.Accept() // 等待客户端连接
if err != nil{
fmt.Println("listen Accept is err",err)
}else{
fmt.Printf("Accept() suc con = %v 客户端的ip = %v\n",con,con.RemoteAddr().String())
}
// 启动一个协程 ,为客户服务
go process(con)
}
fmt.Printf("listen suc=%v\n",listen)
}
客户端简单案例一
package main
import(
"fmt"
"net"
"bufio"
"os"
)
func main(){
conn,err :=net.Dial("tcp","127.0.0.1:8888")
if err !=nil{
fmt.Println("clinet dial err = ",err)
return
}
fmt.Println("conn 成功",conn)
// os.Stdin -> 标准输入
reader :=bufio.NewReader(os.Stdin)
line ,err := reader.ReadString('\n')
if err !=nil{
fmt.Println("读取失败")
}
// 发送到服务器
n,err :=conn.Write([]byte(line))
if err != nil{
fmt.Println("conn.Write err=",err)
}
fmt.Printf("客户端发送了 %d 字节的数据", n)
}
总结
有过有小伙伴觉得有扩展,可以留言,