golang笔记

保留关键字(25个)

break 		//退出循环
default 	//选择结构默认项(switch、select)
func 		//定义函数
interface	//定义接口
select		//channel
case 		//选择结构标签
chan 		//定义channel
const 		//常量
continue 	//跳过本次循环
defer 		//延迟执行内容(收尾工作)
go 		//并发执行
map 		//map类型
struct		//定义结构体
else 		//选择结构
goto 		//跳转语句
package 	//包
switch		//选择结构
fallthrough 	//??
if 		//选择结构
range 		//从slice、map等结构中取元素
type		//定义类型
for 		//循环
import 		//导入包
return 		//返回
var		//定义变量

系统预定标识符

append	
bool	 byte	false	true	string				
cap	
close	
complex	complex64	complex128	
uint uint8 uint16 uint32 uint64
int	int8	int16	int32	int64
float32	float64
copy	imag	
iota	len	make	new	nil	panic	
print	println	real	recover	uintptr

字符串和基本数据类型转换

反引号,以字符串原生形式输出,不识别特殊字符,相当于输出源码
如果多行用 + 拼接, 换行时行末有要 + 号

基本数据类型转string

fmt.Sprintf

%v	值的默认格式表示
%T	值的类型的Go语法表示
%c	该值对应的unicode码值		byte --> string
%d	表示为十进制      int   -->  string
%t	单词true或false   bool --> string
%f	有小数部分但无指数部分,如123.456    float --> string

strconv

func FormatInt(i int64, base int) string
func FormatBool(b bool) string
func FormatFloat(f float64, fmt byte, prec, bitSize int) string

string转基本数据类型

string转成基本数据类型时,要确保能转成有效的数据, 否则会变成目标数据类型的默认值
如  "hello" --> int --> 0

strconv

func ParseBool(str string) (value bool, err error)
func ParseInt(s string, base int, bitSize int) (i int64, err error)
func ParseUint(s string, base int, bitSize int) (n uint64, err error)
func ParseFloat(s string, bitSize int) (f float64, err error)

流程控制

if

单分支

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}

双分支

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

多分支

多分支中  else不是必须的
if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else if  布尔表达式 {
......
}else {
  /* 在布尔表达式为 false 时执行 */
}

switch

  • 从上到下匹配,直到匹配到为止,不彡添加break语句,自动跳出

  • case后面可以有多个表达式 用逗号间隔, 表达式可以用变量 常量或有返回值的函数

  • case的数据类型必须和switch一样, 表达式如果是常量值则不能重复

  • default也不是必须的

  • switch也可以不要表达式, 只要在case后面加逻辑判断即可

  • switch后面也可以直接声明一个变量 加分号结尾

  • case后面加fallthrough会穿透,如果匹配了还会继续执行一次到下一个case

  • switch 支持 x.(type)进行类型判断 “x.(type)”, 只允许用到switch后面

     switch var1 {
         case val1:
             ...
         case val2:
             ...
         default:
             ...
     }
    
    switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )    
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
         }
    

select

类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

select {
    case 管道通信语句 :
       statement(s);      
    case 管道通信语句 :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}
  1. 每个case都必须是一个通信
  2. 所有channel表达式都会被求值
  3. 所有被发送的表达式都会被求值
  4. 如果任意某个通信可以进行,它就执行,其他被忽略。
  5. 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
    否则:
    如果有default子句,则执行该语句
    如果没有default,select将阻塞,直到某个通信可以执行,go不会重新对channel或值进行求值

for

for init; condition; post { }

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

for condition { }

sum := 1
   for ; sum <= 10; {
           sum ++
   }

for { }

sum := 0
for {
	if sum < 10{
    sum++ 
    }else {
	break
	}
}

for range{}

numbers := [6]int{1, 2, 3, 5}
    for index,value:= range numbers {
            fmt.Printf("第 %d 位 value 的值 = %d\n", index, value)
    } 

跳转控制-break

用于跳出for 或者switch
在多重循环中,可以用标号 label 标出想 break 的循环。
re:  //这是label  
for i := 1; i <= 3; i++ {
    fmt.Printf("i: %d\n", i)
    for i2 := 11; i2 <= 13; i2++ {
        fmt.Printf("i2: %d\n", i2)
        break re  //本次相当于跳出两层的循环
    }
}

跳转控制-continue

跳过当前循环
在多重循环中,可以用标号 label 标出想 continue的循环。
re: //这是label  
for i := 1; i <= 3; i++ {
    fmt.Printf("i: %d\n", i)
    for i2 := 11; i2 <= 13; i2++ {
        fmt.Printf("i2: %d\n", i2)
        continue re  // 当前的label就是外层循环三次,内层循环只一次就跳出外层了
    }
}

跳转控制-goto

 goto 语句可以无条件地转移到过程中指定的行。也是使用label

跳转控制-return

跳出所在的函数并返回值,如果在main函数中执行,则退出程序
支持return多个值, 如果想忽略某个值可以用 " _" 接收

函数和包

函数

  • 支持函数作为函数的参数
  • 简化数据类型定义 type 自定义数据类型名 数据类型(支持各种结构)
  • 对函数返回值命名
    func ABC(a int, b int)(aa int, bb int){
    aa = a + b
    bb = a * b
    return
    }
  • 使用_忽略返回值
  • 支持可变参数
    //0到多个参数
    func sum(args…int) int{
    }
    //1到多个参数
    func sum(a int, args…int) int{
    }
    args是个切片,可通过索引或者遍历取值
    如参数列表中有可变参数,需要放在最后
  • 首字母小写,只能在本包调用,大写可以跨包调用
  • 基本数据类型和数组当作参数都是值传递(副本拷贝)

基本语法

func function_name( [parameter list] ) [return_types] {
   函数体
}

package 包名
import 包的路径
import (
	包名
	包名
)
golang 在1.12版本之前是不做包管理的
在 import第三方包时需要遵循以下两条
	1, 一定要将项目代码放到$GOPATH/src目录下面
	2, 设置go 环境变量 // go env -w GO111MODULE=off
	// 否则编译时会报 xxx not in GOROOT
	//如果想把包管理转成go module的形式, 在项目根目录下执行命令 go mod init
如果import 包名过长,支持取别名
import xxx  aaa/bbb/ccc  //用xxx代替ccc

init函数

每一个源文件都可以包含一个init函数,init会在main函数前被调用.
全局变量定义-->init函数-->main函数

匿名函数

方式1.  只能使用一次
	res := func (n1 int) int {
		return n1+n1
	} (10)
方式2. 可以多次使用
	a := func (n1 int) int {
		return n1+n1
	}
	a(10)
	如果将匿名函数赋给一个全局变量,那么这个匿名函数就是全局匿名函数

闭包

func Test() func (int) int{
	var n int = 10
	return func (x int) int {
			n = n + x
			return n
		}
	}
调用:  每调用一次 f()  里面的值将进行累加
	f := Test()
	fmt.println(f(2))

函数的defer

当执行到defer时, 暂不执行压入到独立的栈,当函数执行完后再执行, 出栈(先入后出 )
如果涉及到变量, 则会将变量拷贝一份进栈, 当前函数变量的值修改后不会对栈内的值造成影响

函数参数传递方式

两种传递方式: 不管是值传递还是引用传递都是传递变量的拷贝,引用传递效率高,值传递取决于值的大小
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址 &, 函数内以指针的方式操作变量

值类型

基本数据类型 int float bool string 数组 结构体struct

引用类型

指针 slice切片 map 管道chan	 interface等都是引用类型

变量的作用域

函数内声明/定义的变量叫局部变量,作用域仅函数内部
函数外声明/定义的变量叫全局变量,作用域整个包都有效,如果首字母为大写则整个程序都有效
变量是在一个代码块中 for if ,则只作用域仅该代码块

字符串常用的系统函数

len

go统一为utf-8, len统计汉字占3

字符串遍历(处理中文问题),

r := []rune(str)

字符串转整数

n, err := strconv.Atoi("12")

整数转字符串

n, err := strconv.Itoa(123456)

字符串转[]byte

b := []byte("hello")

[]byte转字符串

str := string([]byte{97,98,99})

10进制转 2,8,16进制

a := strconv.FormatInt(123, 2)  // 结果类型是字符串

查找子串

strings.Contains("chinese", "es")  // true

统计子串数量

strings.Count("chinese", "es")  //1

不区分大小写比较

strings.EqualFold

查找索引位置

strings.Index("chinese", "es")  // 首次出现 如果没有返回 -1
strings.LastIndex("chinese", "es")  // 最后一次出现 如果没有返回 -1

替换

strings.Replace("abcabc", "a", "b", -1) // 数据, 查找目标, 替换成, 替换次数 -1为全部

分割成字符串数组

strings.Split("aaaa,ddd", ",")

大小写转换

strings.ToLower  strings.TuUpper

头尾去掉字符

strings.TrimSpace(str)  头尾去空格
strings.Trim(str, "char")  头尾去掉指定字符
strings.TrimLeft(str, "char")  左边去掉指定字符
strings.TrimRight(str, "char")  右边去掉指定字符

判断是否以 字符开头和结尾

strings.HasPrefix(str, "char") //以字符开头
strings.HasSuffix(str, "char") // 以字符结尾

时间包 time 类py

now := time.Now()
now.Year() //等
fmt.Printf或者fmt.Sprintf 格式化时间  now.Format(时间格式)
time.Sleep(time.Second * 2)
now.Unix()时间戳

内置函数

len

new

分配内存 分配值类型 如 int struct float32 返回的是指针

make

分配内存, 分配引用类型,比如 channel map slice

从键盘输入

fmt.Scanln()  //一行输入
fmt.Scanf()  //根据format参数获取输入以空格隔开的文本

错误处理

go不支持传统的try, 引入了defer panic recover
使用场景: 抛出一个panic异常, 然后通过defer + recover捕获处理 
func test() {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}
	res := 10 / 0
	fmt.Println(res)
}

自定义错误

errors.New("错误说明")
panic内置函数
接收一个interface{}类型的值作为参数, 可以接收error类型的变量,输出错误信息并退出程序

//返回一个error类型的值, 表示一个错误
func readConf(name string) (err error) {
		if name == "config.ini" {
			//读取...
			return nil
		} else {
			//返回一个自定义错误
			return errors.New("读取文件错误..")
		}
	}
	err := readConf("config2.ini")
		if err != nil {
			//如果读取文件发送错误,就输出这个错误,并终止程序
			panic(err)
		}

数组与切片

数组

数组是值类型,只能存放相同的一种数据
数组一旦声明定义了,其长度是固定的
数组的元素可以是任何数据类型, 但必须是同一种,不能混用
数组创建后如果没有赋值, 默认是零值
下标是0开始,有越界的异常,作为参数传递是默认是值拷贝
在其它函数中想去修改数组的值,可以使用引用传递(指针)

定义

方法一
	var str [2]string
    str[0] = "world"
    str[1] = "hello"
    fmt.Println(str)
方法二
	str1 := [2]string{"12", "safas"}
	var str1 [2]string = [2]string{"12", "safas"}
	var data [10]int
	data = [10]int{1, 2, 3, 4, 5}
方法三  [...]是规定写法
	var str1  = [...]string{"12", "safas"}
方法四
	var str1 = [...]string{1: "12", 0: "safas"}
	str1 := [...]string{1: "12", 0: "safas"}

数组在内存中的地址

&数组名就是地址, 第一个元素的地址也是数组的首地址,各元素地址间隔是依据数组的类型决定

使用

用下标访问
常规for遍历
for--range遍历
	for index,val := range array01 {
		...
	}

传递指针
arr := [3]int{11, 22, 33}

//函数1
	func test01(arr [3]int) {
		arr[0] = 88
	} 
	test01(arr)  //函数1不会改变原数组的值
//函数2
	func test02(arr *[3]int) {
		fmt.Printf("arr指针的地址=%p", &arr)
		(*arr)[0] = 88 //!!
	}
	test02(&arr)  //函数2 改变原数组的值

切片

切片是引用类型, 使用方法和数组类似,切片长度是可以变化的

基本语法

var 切片名 []类型
从底层来说其它就是一个数据结构体
type slice struct{
	ptr *[2]int
	len int
	cap int
}

引用

引用一个数组
	var slice = arr[1:3]
make一个切片
	var slice = make([]类型, len, [cap])
	//len为长度,cap是可选最大容量,要求cap >= len
直接定义一个切片
	var strSlice []string = []string{"tom", "jim", "mary"}
cap是个内置数据,可以统计切片的容量

追加

切片1 = append(切片1, 切片2...)//将切片2加入切片1
切片1 = append(切片1, 元素1)//将元素1加入切片1
切片1 = append(切片1, 元素1, 元素2, 元素3)//支持多个元素加入切片1

拷贝

copy(切片1, 切片2)  //将切片2的内容拷贝给切片1
// 如果切片2的长度大于切片1,则舍弃大于切片1长度的部分
//如果切片2的长度小于切片1,则把切片2内长度为切片1的部分覆盖

引用类型

切片是引用类型, 在传递到函数内修改后,原切片的内容并且包含引用数组的内容都会发生改变

string 和slice

string底层是个byte数组,因此也可以切片,string是不可变的,因此不能通过下标去修改其的值.如需修改可以先将string转成[]byte或者[]rune,修改后再重新转成string

map

基本语法

var 变量名 map[keytype]valuetype
map是引用数据类型, 传递的是地址,修改后会对原变量的值进行修改
map自动动态扩容
value使用struct比map更好
key的类型:
	允许的类型: bool, 数字, string,指针,channel还可以是只包含前面几个类型的接口,结构体,数组
	不允许的类型:slice map func
value的类型:
	和key基本一样
	通常为数字,string,map, struct,也可以是interface{}

声明

//map使用前一定要make
//方法一
var m map[string]string
m = make(map[string]string, 10)  //len是可选
m["a"] = "a"
//方法二
m := make(map[string]string)
m["a"] = "a"
//方法三
m := map[string]string{"a": "a"}

增删改查

修改和添加同其它语言

查询

a["a"] //如果不存在这个key也不会报错, 会返回value类型的默认值
//如果用变量来接收,可以判断key是否存在
b, ok := a["a"] //如果ok为真则存在该key

删除

delete(map, key)//delete是内置函数,如果key存在则删除key-value,如果不存在也不会报错
go没有一次性删除所有key-value的方法,只能遍历逐个删除,或者重新make一个空的指向变量

遍历

使用for range遍历

切片

var m []map[string]string
m = make([]map[string]string, 10)

排序

map是无序的,每次遍历map,输出的顺序可能会不一样
排序方法 先遍历map把key存入切片后排序,然后再遍历切片根据key去map里面取值

面向对象

golang是基于struct来实现面向对象,去掉了传统语言的继承,方法重载,构造函数,析构函数, this(self)等

定义结构体

type 结构体名称 struct {
   field 数据类型
   field 数据类型
   ...
   field 数据类型
}

创建和访问

结构体声明语法同变量
字段类型可以为基本数据类型,数组或引用类型
创建一个结构体后如果没有赋值,默认都是数据类型的默认值
指针 slice map默认值都是nil
结构体所有字段在内存都是连续的
两个结构体之间可以强转,但需要有完全相同的字段(名字,个数和类型)
使用type 给数据类型取别名时, 两者之间可以强转
struct每个字段上可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化, 序列化的时候使用该tag的标签作为json的key(默认是field)
tag样式 json: "name"

type Person struct{
Name string
Age int
}
// 方式一
var p Person
p.Name = "tom"
//方式二
p2 := Person{"tom", 12} // 或者
p2 := Person{"Name": "tom", "Age": 12}
//方式3
p3 := new(Person)// 或var p3 *Person = new(Person)
p3.Name = "tom" // 或 (*p3).Name = "tom"
//方式4
//p4 := &Person{"aa", 22}
p4 := &Person{}// var p4 *Person = &Person{}
(*p4).Name = "tom"//p4.Name = "tom"
// 方式三和方式四访问属于标准写法应该是(*p3).Name但go底层做了转化 自动把p3.Name转成了 (*p3).Name

方法声明和调用

声明

func (p Person) test(){
	fmt.Println(p.name)
}
// 把方法绑定在结构体上, p相当于其它语言的this(self), 是个形参
// 方法可以绑定在任何一个通过type 自定义的数据类型上
// test()不能直接调用,只能通过Person的实例变量来调用
//调用机制和函数一样,不一样的是实例变量本身也会作为this这个形参传递进去,如果变量是值类型,则进行值拷贝(结构体是属于值类型),如果是引用类型则进行地址拷贝
//如果需要修改值类型变量的值,变量的指针方式
type Integer int
func (i *Integer) test(){
	*i = *i + 1
}
//方法的访问控制范围和函数一样,按方法首字母大小写来控制
//如果一个类型实现了String()这个方法,那么fmt.Println的时候调用这个方法来打印

方法和函数的区别

调用方式:
函数: 函数名(实参列表)
方法: 变量.方法名(实参列表)
传参和接参
函数: 接收为值类型时,不能将指针类型的数据直接传递,反之亦然.
方法: (如struct) 接收为值类型时,可以用指针传递,反之也可以

三大特性–封装

工厂模式

当一个结构体首字母是小写时,但又需要在别的包调用,那么工厂模式刚好解决这个需求

type person struct{
	Name string
	age int
}
func NewPerson(n string, a int) *person{
	return &person{
		Name: n,
		age: a,  //逗号别忘记了
		}
	}

但在包外又访问不了age属性, 那么再添加一个set和get方法

func (p *person) SetAge(a int){
	p.age = a
}
func (p person) GetAge() int{
	return p.age
}

工厂模板刚好体现了封装的特性

三大特性–继承

基本语法

type Man struct{
	Person  //匿名结构体,即继承
	score int
}

结构体可以使用继承匿名结体的所有字段和方法(不管首字母大小写)
当访问结构体的一个属性(方法)时,按就近查找原则,
如果当前没有查到去上一级,一层层地查找直到找到为止,如果都找不到就会报错
赋值亦然
如果当前结构和一级结构体有同名的方法或属性
使用 m.age 访问的当前结构体的,如果想访问上层结构体
请使用 m.Person.age
当结构体继承了多个匿名结构体,并且多个结构体有相同的属性或方法时(结构体本身却没有),那么在访问时须明确指定匿名结构体的名字,否则编译报错
如果结构体里放了一个有名结构体,那么这就是组合关系,在访问有名结构体时必须带上结构体的名字

type Woman struct{
	p Person  //有名结构体,名字是p, 也叫组合关系
	score int
	int //支持匿名基本数据类型
}

创建匿名结构体实例时,直接指定各个匿名结构体字段的值

P1 := Woman{
	Person{"mary", 20},
	20,
}

支持匿名基本数据类型,但同个结构体里同样的数据类型只能存在一个,其它的必须有名
支持多重继承

三大特性–多态

多态参数

type Pad struct{
	Name string
}

func (p Pad) Start(){
	fmt.Println(p.Name, "Start Usb")
}

func Working(usb Usb) {
	//通过usb接口变量来调用Start和Stop方法
	usb.Start()
}

p1 := Phone{"苹果"}
p2 := Pad{"平板"}
Working(p1)
Working(p2)
// Usb接口和Phone结构体的代码请参照 接口 里面的代码

多态数组

var usbArr [2]Usb
usbArr[0] = p1
usbArr[1] = p2

接口

接口类型可以定义一些方法,但是不需要实现,并且接口不能包含任何变量,也不需要显式的实现,只要一个变量含有接口类型中的所有的方法,那么这个变量就实现了这个接口
所以所有的变量都实现了空接口, 那么任何的变量都可以赋给空接口

//空接口
type EmptyIf interface{}

基本语法

type 接口名 interface {
   方法名(参数列表) [返回值列表]
   方法名(参数列表) [返回值列表]
   方法名(参数列表) [返回值列表]
   ...
   方法名(参数列表) [返回值列表]
}

接口本身不能创建实例, 但可以指向一个实现了该接口的自定义类型的变量
一个自定义类型只有实现了某个接口,才能将这个实例的变量赋给接口类型
只要是自定义数据类型就可以实现接口,不仅仅是结构体
一个自定义类型可以实现多个接口
一个接口可以继承多个别的接口,如果一个自定义类型要实现A接口,那么连同A接口继承的B,C接口的所有方法都要全部实现
接口类型默认是一个指针(引用类型), 如果没有初始化那么输出是nil

type Usb interface{
	Start()
}

type Phone struct{
	Name string
}

func (p Phone) Start(){
	fmt.Println(p.Name, "Start Usb")
}
p1 := Phone{"苹果"}
var u Usb
u = p1  // 这两行可以合成一行  var u Usb = p1
u.Start()

类型断言

var a, b int
var c interface{}
a = 10
c = a
b = c //这是不允许的, 所以需要类型断言,即 c.(int)
fmt.Println(b)

应用一

	//类型断言(带检测的)
	var x interface{}
	var b2 float32 = 2.1
	x = b2  //空接口,可以接收任意类型
	// x=>float32 [使用类型断言]

	//类型断言(带检测的)
	//换种写法
	//y, ok := x.(float32)
	//if ok
	if y, ok := x.(float32); ok {
		fmt.Println("convert success")
		fmt.Printf("y 的类型是 %T 值是=%v", y, y)
	} else {
		fmt.Println("convert fail")
	}

应用二

//编写一个函数,可以判断输入的参数是什么类型
// 这个 .(type) 只允许在switch里使用
func TypeJudge(items... interface{}) {
	for index, x := range items {
		switch x.(type) {
			case bool :
				fmt.Printf("第%v个参数是 bool 类型,值是%v\n", index, x)
			case float32 :
				fmt.Printf("第%v个参数是 float32 类型,值是%v\n", index, x)
			case float64 :
				fmt.Printf("第%v个参数是 float64 类型,值是%v\n", index, x)
			case int, int32, int64 :
				fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)
			case string :
				fmt.Printf("第%v个参数是 string 类型,值是%v\n", index, x)
			case Student :
				fmt.Printf("第%v个参数是 Student 类型,值是%v\n", index, x)
			case *Student :
				fmt.Printf("第%v个参数是 *Student 类型,值是%v\n", index, x)
			default :
				fmt.Printf("第%v个参数是  类型 不确定,值是%v\n", index, x)
		}
	}
}

文件操作

文件在程序中是以流的形式来操作的
os.File(结构体)封装了所有文件相关操作

读取

读取文本行

file , err := os.Open("d:/test.txt")
defer err = file.Close()
reader := bufio.NewReader(file)
//循环的读取文件的内容
for {
	str, err := reader.ReadString('\n') // 读到一个换行就结束
	if err == io.EOF { // io.EOF表示文件的末尾
		break
	}
	//输出内容
	fmt.Printf(str)
}

一次性读取(小文件)

file := "d:/test.txt"
content, err := ioutil.ReadFile(file)
fmt.Printf("%v", string(content)) // []byte
// 没有显式open,所以也不需要close

写入

func OpenFile(name string, flag int, perm FileMode) (*File, error)
参数flag为常量
	const (
		// 必须指定 O_RDONLY、O_WRONLY 或 O_RDWR 中的一个。
		O_RDONLY int = syscall.O_RDONLY // 以只读方式打开文件。
		O_WRONLY int = syscall.O_WRONLY // 以只写方式打开文件。
		O_RDWR   int = syscall.O_RDWR   // 以读写方式打开文件。
		// 剩余的值可以被 or'ed 来控制行为。
		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  // 打开时截断常规可写文件。
	)

创建新文件

filePath := "d:/abc.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)
defer file.Close()
writer := bufio.NewWriter(file)
writer.WriteString("abcdefg")
writer.Flush()

覆盖原文件

// 代码同创建新文件 仅下行不同
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC, 0666)

在原文件追加

// 代码同创建新文件 仅下行不同
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND, 0666)

读取原文件并追加

// 代码同创建新文件 仅下行不同
file, err := os.OpenFile(filePath, os.O_RDWR | os.O_APPEND, 0666)
//读取代码
reader := bufio.NewReader(file)
for {
	str, err := reader.ReadString('\n')
	if err == io.EOF { //如果读取到文件的末尾
		break
	}
	//显示到终端
	fmt.Print(str)
}

读取A文件把内容写到B文件

	//会把B文件的内容给覆盖了
	file1Path := "d:/abc.txt" 
	file2Path := "d:/def.txt" 
	data, err := ioutil.ReadFile(file1Path)
	err = ioutil.WriteFile(file2Path, data, 0666)

拷贝文件

srcFile, err := os.Open(srcFileName)
defer srcFile.Close()
reader := bufio.NewReader(srcFile)
dstFile, err := os.OpenFile(dstFileName, os.O_WRONLY | os.O_CREATE, 0666)
writer := bufio.NewWriter(dstFile)
defer dstFile.Close()
return io.Copy(writer, reader)
//func Copy(dst Writer, src Reader)   io.Copy参数

判断文件(夹)是否存在

_, err := os.Stat(path)
// err == nil   文件存在
// os.IsNotExist(srr) == true  文件不存在
// 如果err是其它错误,则不确定是否存在  

命令行参数

os.Args

是一个string切片
遍历os.Args就可以获取命令行参数
索引0为程序名

flag包

var user string
var pwd string
var port int
flag.StringVar(&user,  "u" , "", "用户名,默认为空")
flag.StringVar(&pwd,  "p" , "", "密码,默认为空")
flag.IntVar(&port,  "P" , 3306, "端口,默认为3306")
//参数1: 变量地址, 参数2 命令行参数 带 - 后面的值 , 参数3如果没取到值变用这个值为默认值, 参数4 说明

json

序列化

json.Marshal(&结构体变量)
//如其它数据类型则传入变量即可(包括基本数据类型) 不用传地址
//map序列化之前一定要先make

反序列化

json.Unmarshal([]byte(str), &变量)
//反序列化map时不需要make, 因为make操作已经封装在Unmarshal里了
//要确保反序列化后的数据类型和原因序列化前的数据类型一致

单元测试

  1. 测试用例文件名必须以 _test.go结尾
  2. 测试用例函数必须Test开头
  3. 测试用例函数形参必须是 t *tesing.T
  4. 一个测试用例文件中,可以有多个测试函数
  5. 支持测试用例指令
    go test //运行正常时无日志,错误时才有日志
    go test -v //正确和错误都有日志
  6. 当出现错误时,可以使用t,Fatalf来格式化输出错误信息
  7. t.Logf方法可以输出相应的日志
  8. 运行结果PASS表示运行成功,FAIL表示出现错误
  9. 测试单个文件,一定要带上被测试的原文件
    go test -v xxx_test.go xxx.go
  10. 测试单个方法
    go test -v -test.run 函数名

goroutine和channel

goroutine 协程

一个线程可以起多个协程
如果主线程已经执行完了,而协程任务还没有执行完,那么主线程不会等待协程的结果,直接退出程序
  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 是轻量的线程

goroutine使用

go 函数名() //开启一个协程

goroutine的调度模型-MPG

M:操作系统主程序
P: 协程需要的上下文
G: 协程

channel管道

在运行某个程序,如何查看是否存在资源竞争问题, 在编译该程序增加一个参数 -race
如果出现资源争夺问题,代码会出现错误 提示 concurrent xxx writes
要解决这个问题就要用到同步锁
var lock sync.Mutex
lock.Lock() //锁定
执行共享资源的写入代码
lock.Unlock() //解锁
但又出现一个问题,主线程不知道协程全部完成的时间是多久,如果设定一个time.Sleep
时间设长了又增加等待时间,设短了又怕协程还没有全部执行完
用同步锁也不利于多个协程对全局变量的读写操作
所以channel由起而生

  1. channel本质就是一个数据结构–队列
  2. 数据是先进先出
  3. 线程安全,多协程访问时不需要加锁
  4. channel是有类型的,一个string的channel只能存放string类型数据

定义/声明 channel

var 变量名 chan 数据类型
//例:
var intChan chan int
var mapChan chan map[string]string
var perChan chan Person
var perChan2 chan *Person
var anyType chan interface{} // 可以存放任意数据类型

通道可以声明为只读或者只写属性(默认是双向)

var intChan chan<- int //只写
var intChan <-chan int //只读

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

var intChan chan int
intChan = make(chan int, 2) // 可以存放2个
intChan <- 10
intChan <- 20
//intChan <- 30 //如果满了会报 deadlock错误
n1 := <- intChan
n2 := <- intChan
//n3 := <- intChan //如果取完了也会报 deadlock错误
fmt.Println(n1, n2)

deadlock错误: fatal error: all goroutines are asleep - deadlock!
在没有使用协程的情况下 数据取完了再取会报 deadlock

channel关闭和遍历

用内置函数close可以关闭chan, 关闭后通道不能再写入,但还可以读取数据
chan的遍历不能使用普通的for循环
chan支持for range循环遍历,但有以下两个注意点
如果遍历时没有关闭chan,则会出现 deadlock错误
如果遍历时已经关闭chan,则正常遍历

func gr(exitChan chan bool)  {
	time.Sleep(time.Second * 10)
	exitChan <- true
	close(exitChan)
}
exitChan := make(chan bool, 1)
go gr(exitChan)
for {
	_, ok := <- exitChan
	fmt.Println(ok)
	if !ok{
		break
	}
}

使用recover解决协程中出现panic

反射

基本介绍

  • 反射可以在运行时动态获取变量的各种信息,如果变量的类型(type),类型(kind)
  • 如果是结构体变量,还可以获取结构体本身的信息(包括字段和方法)
  • 通过反射,可以修改变量的值,可以调用关联的方法
  • 使用反射,需要import “reflect”

反射各种类型转换

var num int
func test(i interface{}){
	rType := reflect.TypeOf(i) 
	//获取反射到的变量的类型,(看起来像是原变量类型,但实际是*reflect.rtype)
	rVal := reflect.ValueOf(i) //将interface{}转成reflect.Value
	iVal := rVal.Interface()  //将reflect.Value转成interface{}
	iVal.Kind() //显示值的类型的文本, 实际类型是reflect.Kind (常量)
	//通过 rVal.Kind() 也可以获取到 kind
	n := iVal.(int)  //将interface{} 断言转成 int
}

注意事项

Type是类型和Kind类别, Type和Kind可能相同也可能不同.
比如 var num int = 10, num的Type是int , Kind也是int
比如 var stu Student, stu的Type是 包名.Student, Kind是struct
可以通过kind判断对象的类别

kd = iVal.Kind()
if kd == reflect.Struct {
	//执行语句
}
num := iVal.NumField() //获取结构体有几个字段(属性)
//循环结构体,获取字段名
for i :=0; i < num; i++{
	iVal.Field(i)}
	//获取struct的标签,注意需要通过reflect.Type来获取tag标签的值
	tagVal := rType.Field(i).Tag.Get("json")  // 这个"json"我们可以自定义
	//但如果需要去进行json序列化时,就必须这么写
numMethod := iVal.NumMethod()  //获取结构体有几个方法
/执行方法
//方法的排序默认是按照 函数名的排序(ASCII码)
iVal.Method(1).Call(nil) //获取到第二个方法。调用它,
//Call方法必须需要一个参数,如果这个方法不需要参数,那么传入nil
	//调用结构体的第1个方法Method(0)
var params []reflect.Value  //声明了 []reflect.Value, 需要参数的方法 传入的是一个切片
params = append(params, reflect.ValueOf(10))  // 将int转成 reflect里面的类型
params = append(params, reflect.ValueOf(40))
res := iVal.Method(0).Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Value,也是切片
fmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value, 看函数的多少个值取对应的索引
// res[0].Int() 如果不确定返回是什么类型可以使用 res[0].(xxx)类型断言

//如果传入的对象是个指针
test(&num)
iVal.SetInt(10) //这个代码就会报错
iVal.Elem().SetInt(10)  //需要用到Elem取到值后再设置, 类似于 *ptr
//非指针的获取值
iVal.Int() 
iVal.SetInt(10) //设置值

网络编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值