赞
踩
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
第一种:先通过go build xxx编译成 .exe 文件再运行这个exe文件(不依赖环境);
第二种:直接使用 go run xxx命令
golang社区:https://golang.google.cn/dl/
在社区中能下载不同版本的golang
1、配置GOROOT系统环境变量
2、配置go语言SDK环境变量
1、在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件
变大了很多。生成可执行文件后,这个文件可以在没有go开发环境下运行;
2、使用go run xxx执行代码时,如果要在另外一个机器上这么运行,则需要go
开发环境,否则无法执行。
go语言具有中文社区(https://studygolang.com/),在其中可以看到Go语言提供的标准库以及相关用法。
package main import "fmt" func main() { // 声明后赋值 var a int a = 10 fmt.Println(a) // 声明并赋值 var b int = 20 fmt.Println(b) // 语法糖声明赋值 c := 30 fmt.Println(c) }
使用整数类型声明变量
浮点数类型主要分为了float32和float64,如果不给定类型则默认为float64类型,浮点数可能会存在精度缺失的问题。
在Golang中如果要存储单个字符,一般使用byte保存,使用的是UTF-8字符编码:
package main
import "fmt"
func main() {
var a byte = 'a'
fmt.Println(a) //97
var b byte = '('
fmt.Println(b + 20) //40
var c int = '中'
fmt.Println(c) //20013
var d byte = 'A'
fmt.Printf("c5对应的具体的字符为:%c", d) //A
}
占用一个字节。
如果字符串类型中字符含有特殊字符则可以使用反引号``包裹。
声明变量实际上我们的变量是保存在一个内存地址中的
func main(){
var a int = 10
//&符号+变量 就可以获取这个变量内存的地址
fmt.Println(&a) // 0xc0000a2058
// 定义指针类型,想要定义指针类型使用 *数据类型的方法
var b *int = &a
fmt.Println(b) // 0xc0000a2058
fmt.Println("%v",*b) // 10 ===> 使用*内存地址可以得到源地址
}
Go在不同类型的变量之间赋值时需要显式转换,并且只有显式转换(强制转换)
2.1、基本数据类型转string
方式1:fmt.Sprintf(“%参数”,表达式)
这里第一个参数传递的是需要转换的参数类型例如:%d(整数)、%f(浮点数)、%t(布尔值)、%c(字符类型)
var a int = 10
var a1 string = fmt.Sprintf("%d",a) // '10'
var b float32 = 10.01
var b1 string = fmt.Sprintf("%f",b) // '10.01'
var c bool = false
var c1 string = fmt.Sprintf("%t",c) // 'false'
var d byte = 'd'
var d1 string = fmt.Sprintf("%c",d) // 'd'
方式2:使用strconv包的函数
strconv包提供了FormatInt、FormatFloat、FormatBool等格式化方法,但使用方式较为复杂,限制较多一般不使用。
var a int = 18
var a1 string = strconv.FormatInt(int64(a),10)
//参数:第一个参数必须转为int64类型 ,第二个参数指定字面值的进制形式
2.2、string转基本数据类型
string转基本数据类型使用到了strconv包的函数
以上函数会返回两个值,value数据,和err出现的错误
var a string = "19"
var a1 int64
a1,_ = strconv.ParseInt(a,10,64)
fmt.Printf("类型是:%T,a1=%v \n",a1,a1)
工具包
package test
var TestA int = 10
主包
package main
import (
"fmt"
"module02/test"
)
func main() {
fmt.Println(test.TestA)
}
**算术运算符规则和JS基本一致,只有++,–有区别,**区别如下:
go语言里,++,–操作非常简单,只能单独使用,不能参与到运算中去,++,--只能在变量的后面,不能写在变量的前面 --a ++a
错误写法。
func main(){
var a int = 10
a++
fmt.Println(a) //11
a--
fmt.Println(a) //10
var b int = a + a-- // syntax error: unexpected -- at end of statement
fmt.Println(b)
}
&返回变量的存储地址
*取指针变量对应的数值
package main
import "fmt"
func main(){
var a int = 10
fmt.Println(&a) //0xc0000100b0
var b *int = &a
fmt.Println(b)
fmt.Println(*ptr) //10
}
有两种方式:
//方式1:Scanln var age int fmt.Println("请录入学生的年龄:") //传入age的地址的目的:在Scanln函数中,对地址中的值进行改变的时候,实际外面的age被影响了 fmt.Scanln(&age)//录入数据的时候,类型一定要匹配,因为底层会自动判定类型的 var name string fmt.Println("请录入学生的姓名:") fmt.Scanln(&name) var score float32 fmt.Println("请录入学生的成绩:") fmt.Scanln(&score) var isVIP bool fmt.Println("请录入学生是否为VIP:") fmt.Scanln(&isVIP) //将上述数据在控制台打印输出: fmt.Printf("学生的年龄为:%v,姓名为:%v,成绩为:%v,是否为VIP:%v",age,name,score,isVIP)
//方式2:Scanf
fmt.Println("请录入学生的年龄,姓名,成绩,是否是VIP,使用空格进行分隔")
fmt.Scanf("%d %s %f %t",&age,&name,&score,&isVIP)
//将上述数据在控制台打印输出:
fmt.Printf("学生的年龄为:%v,姓名为:%v,成绩为:%v,是否为VIP:%v",age,name,score,isVIP)
分为了if语句和switch语句
if a := 20; a > 30 { fmt.Println("A") } else { fmt.Println("B") } var b int = 20 switch b { case 10, 20: fmt.Println("10") case 30: fmt.Println("20") fallthrough //switch穿透 default: fmt.Println("其他") } var c int = 30 switch { case c > 20: fmt.Println("10") case c > 30: fmt.Println("20") fallthrough //switch穿透 default: fmt.Println("其他") }
普通的for循环或for range循环。for range类似于js中的foreach方法。
for e := 0; e < 5; e++ {
fmt.Println(e)
}
var str = "hello World!"
for index, value := range str {
fmt.Printf("%d,%c\n", index, value)
}
使用方式和js一致,但添加了两个新的用法
label: for e := 0; e < 5; e++ { for f := 0; f < 5; f++ { if f == 3 { continue label } fmt.Println(f) } } // continue的作用是结束离它近的那个循环,继续离它近的那个循环,但这里结束的是里层循环,继续的是外层循环。 // goto fmt.Println(1) goto label fmt.Println(2) fmt.Println(3) fmt.Println(4) label: fmt.Println(5) // 最终打印1 5,会跳过2 3 4
package utils import "fmt" func AddNum (a int, b int) int { sum := a + b return sum } func AddNumOrigin (a int, b int) (int,int,int) { sum := a + b return sum, a, b } func MulPrint (args...int) { for i := 0; i < len(args); i++ { fmt.Println(args[i]) } } ________________________________________________________ package main import ( "fmt" "module05/utils" ) func main() { var res = utils.AddNum(12,23) var res2,or1,or2 = utils.AddNumOrigin(12,23) fmt.Println(res) fmt.Println(res2, or1, or2) utils.MulPrint(1,2,3,4) }
package main import "fmt" //定义一个函数: func test(num int) { fmt.Println(num) } func test02(num1 int, num2 float32, testFunc func(int)) { testFunc(1) fmt.Println("-----test02") } func main() { a := test fmt.Printf("a的类型是:%T,test函数的类型是:%T \n", a, test) //a的类型是:func(int),test函数的类型是:func(int) a(10) test02(10, 3.19, test) test02(10, 3.19, test) test02(10, 3.19, a) }
func test03(num1 int, num2 int) (int, int) {
sub := num1 - num2
sum := num1 + num2
return sum, sub
}
func test04(num1 int, num2 int) (sum int,sub int) {
sum = num1 - num2
sub = num1 + num2
return
}
什么是包?
在程序层面,所有使用相同 package 包名 的源文件组成的代码模块,在同一个包下不能有重复的声明。
使用包的原因:
包引用时也能起别名
package main
import (
"fmt"
alias "module05/utils"
)
func main() {
var res = alias.AddNum(12,23)
var res2,or1,or2 = alias.AddNumOrigin(12,23)
fmt.Println(res)
fmt.Println(res2, or1, or2)
}
先执行引入包中的函数、再执行当前包的函数
而这里针对于函数的优先级为:变量定义 > init函数 > main函数
Go支持匿名函数,如果希望函数只是使用一次,可以考虑使用匿名函数,在声明时就调用
针对于闭包的话,按照下面的例子解释更加清晰:
package main import "fmt" func getSum() func (int) int { var sum int = 0 return func (num int) int{ sum = sum + num return sum } } func getSum01(sum int,num int) int{ sum = sum + num return sum } func main(){ f := getSum() fmt.Println(f(1))//1 fmt.Println(f(2))//3 fmt.Println(f(3))//6 fmt.Println(f(4))//10 fmt.Println(getSum01(0,1))//1 fmt.Println(getSum01(1,2))//3 fmt.Println(getSum01(3,3))//6 fmt.Println(getSum01(6,4))//10 }
len() :统计字符串的长度
str := "golang你好"
fmt.Println(len(str)) // 12 (因为在golang中,汉字是三个字节)
for-range
str := "golang"
for i, v := range str {
fmt.Printf("%d,%c", i, v)
}
res := []rune(str)
str := "golang"
res := []rune(str)
fmt.Println(res) //[103 111 108 97 110 103]
for i := 0; i < len(res); i++ {
fmt.Printf("%c\n", res[i])
}
/*
g
o
l
a
n
g
*/
字符串转整数
n, err := strconv.Atoi("10")
整数转字符串
str := strconv.Itoa(10)
查找子串是否在指定的字符串中
strings.Contains("golanggolang", "go")
统计一个字符串有几个指定的子串
strings.Count("golanggolang","a")
不区分大小写的字符串比较
strings.EqualFold("go" , "Go")
返回子串在字符串第一次出现的索引值,如果没有返回-1
strings.lndex("golanggolang" , "a")
时间和日期的函数,需要导入time包,所以你获取当前时间,就要调用**time.Now()**函数。
package main import ( "fmt" "time" ) func main(){ now := time.Now() fmt.Printf("%v ~~~ 对应的类型为:%T\n",now,now) //2021-02-08 17:47:21.7600788 +0800 CST m=+0.005983901 ~~~ 对应的类型为:time.Time fmt.Printf("年:%v \n",now.Year()) fmt.Printf("月:%v \n",now.Month()) //月:February fmt.Printf("月:%v \n",int(now.Month())) //月:2 fmt.Printf("日:%v \n",now.Day()) fmt.Printf("时:%v \n",now.Hour()) fmt.Printf("分:%v \n",now.Minute()) fmt.Printf("秒:%v \n",now.Second()) }
格式化日期
//Printf将字符串直接输出:
fmt.Printf("当前年月日: %d-%d-%d 时分秒:%d:%d:%d \n",now.Year(),now.Month(),
now.Day(),now.Hour(),now.Minute(),now.Second())
//Sprintf可以得到这个字符串,以便后续使用:
dateStr := fmt.Sprintf("当前年月日: %d-%d-%d 时分秒:%d:%d:%d \n",now.Year(),now.Month(),
now.Day(),now.Hour(),now.Minute(),now.Second())
fmt.Println(dateStr)
按照指定格式输出,这里每个数字都是固定的不能改变,但是可以随意组合。
//这个参数字符串的各个数字必须是固定的,必须这样写
datestr2 := now.Format("2006/01/02 15/04/05")
fmt.Println(datestr2)
//选择任意的组合都是可以的,根据需求自己选择就可以(自己任意组合)。
datestr3 := now.Format("2006 15:04")
fmt.Println(datestr3)
Golang设计者为了编程⽅便,提供了⼀些函数,这些函数不⽤
导包可以直接使⽤,我们称为Go的内置函数/内建函数。
常⽤函数:
(1)len函数:
统计字符串的⻓度,按字节进⾏统计
(2)new函数:
分配内存,主要⽤来分配值类型(int系列, float系列, bool,
string、数组和结构体struct),返回的是Type的指针
(3)make函数:
分配内存,主要⽤来分配引⽤类型(指针、slice切⽚、map、管
道chan、interface 等),返回的是Type本身
defer是Go里面的一个关键字,用在方法或函数前面,作为方法或函数的延迟调用;
作用是进行优雅释放资源。
官方对defer的解释中写到每次defer语句执行的时候,会把函数压栈,同时函数参数会被拷贝下来。这两点很重要:
一是说明当一个函数中有多个defer的时候,执行顺序是先进后出
二是说明延迟函数的参数在defer语句出现时就已经确定下来了
func CopyFile(dstFile, srcFile string) (wr int64, err error) {
src, err := os.Open(srcFile)
if err != nil {
return
}
dst, err := os.Create(dstFile)
if err != nil {
return
}
wr, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
func main() { deferRun() deferRun2() } func deferRun() { var num = 1 defer test(&num) num = 2 return } func test (b *int) { fmt.Println(*b) } func deferRun2() { var arr = [4]int{1, 2, 3, 4} defer printArr(&arr) arr[0] = 100 return } func printArr(arr *[4]int) { for i := range arr { fmt.Println(arr[i]) } }
程序中出现错误/恐慌以后,程序被中断,⽆法继续执⾏。这里可以引入机制:defer+recover机制处理错误;
func main() { test() fmt.Println("除法成功") fmt.Println("正常执行") } func test() { defer func() { err := recover() if err != nil { fmt.Println("错误信息", err) } }() num1 := 10 num2 := 0 // 0不能做除数 result := num1 / num2 fmt.Println(result) }
package main import ( "fmt" "errors" ) func main() { err := test() if err != nil { fmt.Println("自定义错误",err) panic(err) //还有可能的情况就是,遇到错误后直接结束。 } fmt.Println("除法成功") fmt.Println("正常执行") } func test()(err error){ num1 := 10 num2 := 0 if num2 == 0 { return errors.New("除数不能为0") }else { result := num1 / num2 fmt.Println(result) return nil } }
var 数组名 [数组⼤⼩]数据类型
func main() {
var scores [5]int
scores[0] = 95
scores[1] = 91
scores[2] = 39
scores[3] = 60
scores[4] = 21
fmt.Println(scores)
}
func main() {
//第⼀种:
var arr1 [3]int = [3]int{3, 6, 9}
fmt.Println(arr1)
//第⼆种:
var arr2 = [3]int{1, 4, 7}
fmt.Println(arr2)
//第三种:
var arr3 = [...]int{4, 5, 6, 7}
fmt.Println(arr3)
//第四种:
var arr4 = [...]int{2: 66, 0: 33, 1: 99, 3: 88}
fmt.Println(arr4)
}
func main() {
// 二维数组的定义
var arr1 [3][2]int
// fmt.Printf("%T",arr1)
arr1[0][0] = 10
arr1[1][0] = 11
arr1[2][0] = 12
fmt.Println(arr1)
// 二维数组初始化
var arr2 = [2][2]int{{1,2},{3,4}}
fmt.Println(arr2)
}
for key, val := range 数组 {
...
}
切片(slice)是golang中⼀种特有的数据类型;
切片是⼀种建⽴在数组类型之上的抽象,它构建在数组之上并且提供更强⼤的能⼒和便捷;
切片(slice)是对数组⼀个连续片段的引用,所以切片是⼀
个引用类型。这个片段可以是整个数组,或者是由起始和终止索
引标识的⼀些项的子集。需要注意的是,终止索引标识的项不包
括在切片内。
方式一
var 切⽚名 []类型 = 数组的⼀个⽚段引⽤
func main() {
var arr = [5]int{1,2,3,4,5}
slice1 := arr[1:3]
fmt.Println(slice1) //[2,3]
// 切片的长度
fmt.Println(len(slice1)) //2
// 获取切片的容量,这个是可以动态变化的
fmt.Println(cap(slice1)) //4
}
方式二
var 切⽚名 type = make([], len,[cap])
slice2 := make([]int,4,20) //类型,长度,类型
fmt.Println(slice2)
// 切片的长度
fmt.Println(len(slice2))
// 获取切片的容量,这个是可以动态变化的
fmt.Println(cap(slice2))
方式三
slice3 := []int{1,2,3} //类型,长度,类型
fmt.Println(slice3)
// 切片的长度
fmt.Println(len(slice3))
// 获取切片的容量,这个是可以动态变化的
fmt.Println(cap(slice3))
package main import "fmt" func main(){ //定义切片: slice := make([]int,4,20) slice[0] = 66 slice[1] = 88 slice[2] = 99 slice[3] = 100 //方式1:普通for循环 for i := 0;i < len(slice);i++ { fmt.Printf("slice[%v] = %v \t" ,i,slice[i]) } fmt.Println("\n------------------------------") //方式2:for-range循环: for i,v := range slice { fmt.Printf("下标:%v ,元素:%v\n" ,i,v) } }
1、简写方式:
2、切片不能越界
3、切片后还能继续切片
4、切片增加元素
//定义数组:
var intarr [6]int = [6]int{1,4,7,3,6,9}
//定义切片:
var slice []int = intarr[1:4]
slice2 := append(slice,88,50)
fmt.Println(slice2) //[4 7 3 88 50]
slice2 = append(slice2,slice...)
fmt.Println(slice2)//[4 7 3 88 50 4 7 3]
5、切片的拷贝
func main() {
//定义切片:
var a []int = []int{1, 4, 7, 3, 6, 9}
//再定义一个切片:
var b []int = make([]int, 10)
//拷贝:
copy(b, a)
//将a中对应数组中元素内容复制到b中对应的数组中
//元素多了会丢失多余的,元素少了会补0
fmt.Println(b) //[1 4 7 3 6 9 0 0 0 0]
}
映射(map), Go语言中内置的一种类型,它将键值对相关联,我们可以通过键 key来获取对应的值 value;
// 声明map,但暂未分配空间 var map变量名 map[key type]value type PS:key、value的类型:bool、数字、string、指针、channel 、还可以是只包含前面几个类型的接口、结构体、数组 PS:key通常为int 、string类型,value通常为数字(整数、浮点数)、string、map、结构体 PS:key:slice、map、function不可以 map的特点: (1)map集合在使用前一定要make (2)map的key-value是无序的 (3)key是不可以重复的,如果遇到重复,后一个value会替换前一个value (4)value可以重复的 package main import "fmt" func main(){ //定义map变量: var a map[int]string a = make(map[int]string,10) //map可以存放10个键值对 //将键值对存入map中: a[20095452] = "张三" a[20095387] = "李四" a[20097291] = "王五" a[20095337] = "朱六" a[20096699] = "张三" //输出集合 fmt.Println(a) // map[20095385:朱六 20095387:李四 20095452:张三 20096699:张三20097291:王五] }
package main import "fmt" func main() { //方式1: //定义map变量: var a map[int]string //只声明map内存是没有分配空间 //必须通过make函数进行初始化,才会分配空间: a = make(map[int]string, 10) //map可以存放10个键值对 //将键值对存入map中: a[20095452] = "张三" a[20095387] = "李四" //输出集合 fmt.Println(a) //方式2:使用语法糖 b := make(map[int]string) b[20095452] = "张三" b[20095387] = "李四" fmt.Println(b) //方式3:设置初始值 c := map[int]string{ 20095452: "张三", 20098765: "李四", } c[20095387] = "王五" fmt.Println(c) }
(1)Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。
(2)Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Gelang是基于struct来实现OOP特性的。
(3)Golang面向对象编程非常简洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏的this指针等等
(4)Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承:Golang没有extends 关键字,继承是通过匿名字段来实现。
方式一:直接创建
package main import "fmt" type Teacher struct { Name string Age int School string } func main() { var t1 Teacher fmt.Println(t1) t1.Name = "terry" t1.Age = 23 t1.School = "东软" fmt.Println(t1) fmt.Println(t1.Age + 10) }
方式二:创建时携带初始值
var t1 Teacher = Teacher{"terry", 23, "东软"}
方式三:返回的是结构体指针
var t1 *Teacher = &Teacher{"terry", 23, "东软"}
(*t1).Name = "张三"
fmt.Println(*t1)
方式四:返回的是结构体指针
var t1 *Teacher = &Teacher{"terry", 23, "东软"}
(*t1).Name = "张三"
fmt.Println(*t1)
package main
import "fmt"
type Student struct {
Age int
}
type Stu Student
func main(){
var s1 Student = Student{19}
var s2 Stu = Stu{19}
s1 = Student(s2)
fmt.Println(s1)
fmt.Println(s2)
}
//声明:
type A struct {
Num int
}
func (a A) test() {
fmt.Println(a.Num)
}
//调用:
var a A
a.test()
package main import "fmt" type integer int func (i integer) print(){ i = 30 fmt.Println("i = ",i) } func (i *integer) change(){ *i = 30 fmt.Println("i = ",*i) } func main(){ var i integer = 20 i.print() i.change() fmt.Println(i) }
【1】方式1:按照顺序赋值操作
缺点:必须按照顺序有局限性
【2】方式2:按照指定类型
【3】方式3:想要返回结构体的指针类型
type Student struct { Name string Age int } func main() { // 方式一 var s1 Student = Student{"小李", 19} fmt.Println(s1) // 方式二 var s2 Student = Student{Name: "Terry", Age: 18} fmt.Println(s2) // 方式三 var s3 *Student = &Student{Name: "Terry", Age: 18} fmt.Println(*s3) }
package main import ( "fmt" ) //定义动物结构体: type Animal struct{ Age int Weight float32 } //给Animal绑定方法:喊叫: func (an *Animal) Shout(){ fmt.Println("我可以大声喊叫") } 给Animal绑定方法:自我展示: func (an *Animal) ShowInfo(){ fmt.Printf("动物的年龄是:%v,动物的体重是:%v",an.Age,an.Weight) } //定义结构体:Cat type Cat struct{ //为了复用性,体现继承思维,嵌入匿名结构体:——》将Animal中的字段和方法都达到复用 Animal } //对Cat绑定特有的方法: func (c *Cat) scratch(){ fmt.Println("我是小猫,我可以挠人") } func main(){ //创建Cat结构体示例: cat := &Cat{} cat.Age = 3 cat.Weight = 10.6 cat.Shout() cat.ShowInfo() cat.scratch() }
package main import ( "fmt" ) type A struct{ a int b string } type B struct{ c int d string a int } type C struct{ A B } func main(){ //构建C结构体实例: c := C{A{10,"aaa"},B{20,"ccc",50}} fmt.Println(c.b) fmt.Println(c.d) fmt.Println(c.A.a) fmt.Println(c.B.a) }
package main import "fmt" type SayHello interface { sayHello() } type Chinese struct { } func (person Chinese) sayHello() { fmt.Println("你好") } type American struct { } func (person American) sayHello() { fmt.Println("hi") } func greet(s SayHello) { s.sayHello() } func main() { c := Chinese{} a := American{} greet(a) greet(c) }
可以按照统一的接口来调用不同的实现
例如上述案例,greet方法的参数s就是个多态参数;
判断是否是该类型的变量: value, ok := element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型
func greet(s SayHello) {
s.sayHello()
//断言:
ch, flag := s.(Chinese) //看s是否能转成Chinese类型并且赋给ch变量
if flag == true {
ch.niuYangGe()
} else {
fmt.Println("美国人不会扭秧歌")
}
}
Type Switch
Type Switch 是 Go 语言中一种特殊的 switch 语句,它比较的是类型而不是具体的值。它判断某个接口变量的类型;
func greet(s SayHello) {
s.sayHello()
switch s.(type) { //type属于go中的一个关键字,固定写法
case Chinese:
ch := s.(Chinese)
ch.niuYangGe()
case American:
am := s.(American)
am.disco()
}
}
os包下的File结构体封装了对文件的操作
File结构体—打开文件和关闭文件:
(1)打开文件,用于读取,传入一个字符串(文件的路径),返回的是文件的指针,和是否打开成功
OpenFile可配置文件打开模式,(可以利用"|"符号进行组合)
(2)关闭文件:(方法)
package main import ( "fmt" "os" ) func main() { //打开文件: file, err := os.Open("C:/Users/admin/Desktop/test.txt") if err != nil { //出错 fmt.Println("文件打开出错,对应错误为:", err) } fmt.Printf("文件=%v", file) //文件指针 //关闭文件: err2 := file.Close() if err2 != nil { fmt.Println("关闭失败") } }
package main import ( "fmt" "io/ioutil" ) func main() { //备注:在下面的程序中不需要进行 Open\Close操作, //因为文件的打开和关闭操作被封装在ReadFile函数内部了 //读取文件: content, err := ioutil.ReadFile("C:/Users/admin/Desktop/test.txt") //返回内容为:[]byte,err if err != nil { //读取有误 fmt.Println("读取出错,错误为:", err) } //如果读取成功,将内容显示在终端即可: fmt.Printf("%v", content) //因为它是一个字节数组,所以需要转为string fmt.Printf("%v", string(content)) }
package main import ( "bufio" "fmt" "io" "os" ) func main() { //打开文件: file, err := os.Open("C:/Users/admin/Desktop/test.txt") if err != nil { //打开失败 fmt.Println("文件打开失败,err=", err) } //当函数退出时,让file关闭,防止内存泄露: defer file.Close() //创建一个流: reader := bufio.NewReader(file) //读取操作: for { str, err := reader.ReadString('\n') //读取到一个换行就结束 如果读取完毕err会返回EOF if err == io.EOF { //io.EOF 表示已经读取到文件的结尾 break } //如果没有读取到文件结尾的话,就正常输出文件内容即可: fmt.Println(str) } //结束: fmt.Println("文件读取成功,并且全部读取完毕") }
package main import ( "bufio" "fmt" "os" ) func main() { //写入文件操作: //打开文件: file, err := os.OpenFile("C:/Users/admin/Desktop/test.txt", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) if err != nil { //文件打开失败 fmt.Println("打开文件失败", err) return } //及时将文件关闭: defer file.Close() //写入文件操作:---》IO流---》缓冲输出流(带缓冲区) writer := bufio.NewWriter(file) for i := 0; i < 10; i++ { writer.WriteString("你好 马士兵\n") } //流带缓冲区,刷新数据--->真正写入文件中: writer.Flush() s := os.FileMode(0666).String() fmt.Println(s) }
这里0666代表的是权限:
四位数代表意思: 特殊权限用户,用户,组用户,其它用户
每位值代表意思:读是4,写是2,执行是1
0666 就是用户,组用户,其它用户都只有读写权限;
package main import( "fmt" "io/ioutil" ) func main(){ //定义源文件: file1Path := "C:/Users/admin/Desktop/test.txt" //定义目标文件: file2Path := "C:/Users/admin/Desktop/test2.txt" //对文件进行读取: content,err := ioutil.ReadFile(file1Path) if err != nil { fmt.Println("读取有问题!") return } //写出文件: err = ioutil.WriteFile(file2Path,content,0666) if err != nil { fmt.Println("写出失败!") } }
是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。
是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期 : 有它自身的产生、存在和消亡的过程 。
进程可进一步细化为线程, 是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的。
又称为微线程,纤程,协程是一种用户态的轻量级线程
作用:在执行A函数的时候,可以随时中断,去执行B函数,然后中断继续执行A函数(可以自动切换),注意这一切换过程并不是函数调用(没有调用语句),过程很像多线程,然而协程中只有一个线程在执行(协程的本质是个单个线程)
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就将寄存器上下文和栈保存到某个其他地方,然后切换到另外一个任务去计算。在任务切回来的时候,恢复先前保存的寄存器上下文和栈,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态(注意:线程是CPU控制的,而协程是程序自身控制的,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级)
package main import ( "fmt" "strconv" "time" ) func test() { for i := 1; i <= 10; i++ { fmt.Println("hello golang + " + strconv.Itoa(i)) //阻塞一秒: time.Sleep(time.Second) } } func main() { //主线程 go test() //开启一个协程 for i := 1; i <= 10; i++ { fmt.Println("hello msb + " + strconv.Itoa(i)) //阻塞一秒: time.Sleep(time.Second) } }
主死从随
WaitGroup的作用:
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。—》解决主线程在子协程结束后自动结束;
package main import ( "fmt" "sync" ) var wg sync.WaitGroup //只定义无需赋值 func main() { //启动五个协程 for i := 1; i <= 5; i++ { wg.Add(1) //协程开始的时候加1操作 go func(n int) { fmt.Println(n) wg.Done() //协程执行完成减1 }(i) } //主线程一直在阻塞,什么时候wg减为0了,就停止 wg.Wait() }
当多个协程同时操作统一数据时,会出现更新错误的情况
package main import( "fmt" "sync" ) //定义一个变量: var totalNum int var wg sync.WaitGroup //只定义无需赋值 func add(){ defer wg.Done() for i := 0 ;i < 100000;i++{ totalNum = totalNum + 1 } } func sub(){ defer wg.Done() for i := 0 ;i < 100000;i++{ totalNum = totalNum - 1 } } func main(){ wg.Add(2) //启动协程 go add() go sub() wg.Wait() fmt.Println(totalNum) }
按理说结果应该是0,但是结果却天差地别,是因为同时更新会导致数据有误。
加入互斥锁,确保:一个协程在执行逻辑的时候另外的协程不执行
package main import( "fmt" "sync" ) //定义一个变量: var totalNum int var wg sync.WaitGroup //只定义无需赋值 //加入互斥锁: var lock sync.Mutex func add(){ defer wg.Done() for i := 0 ;i < 100000;i++{ //加锁 lock.Lock() totalNum = totalNum + 1 //解锁: lock.Unlock() } } func sub(){ defer wg.Done() for i := 0 ;i < 100000;i++{ //加锁 lock.Lock() totalNum = totalNum - 1 //解锁: lock.Unlock() } } func main(){ wg.Add(2) //启动协程 go add() go sub() wg.Wait() fmt.Println(totalNum) }
golang中sync包实现了两种锁Mutex (互斥锁)和RWMutex(读写锁);
其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别
----性能、效率相对来说比较低
RWMutex是一个读写锁,其经常用于读次数远远多于写次数的场景.
—在读的时候,数据之间不产生影响,写和读之间才会产生影响
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup //只定义无需赋值 // 加入读写锁: var lock sync.RWMutex func read() { defer wg.Done() lock.RLock() //如果只是读数据,那么这个锁不产生影响,但是读写同时发生的时候,就会有影响 fmt.Println("开始读取数据") time.Sleep(time.Second) fmt.Println("读取数据成功") lock.RUnlock() } func write() { defer wg.Done() lock.Lock() fmt.Println("开始修改数据") time.Sleep(time.Second) fmt.Println("修改数据成功") lock.Unlock() } func main() { wg.Add(6) //启动协程 ---> 场合:读多写少 for i := 0; i < 5; i++ { go read() } go write() wg.Wait() }
var 变量名 chan 数据类型
PS1:chan管道关键字
PS2:数据类型指的是管道的类型,里面放入数据的类型,管道是有类型的,int类型的管道只能写入整数int
PS3:管道是引用类型,必须初始化才能写入数据,即make后才能使用
package main import ( "fmt" ) func main() { //定义管道 、 声明管道 ---> 定义一个int类型的管道 var intChan chan int //通过make初始化:管道可以存放3个int类型的数据 intChan = make(chan int, 3) //证明管道是引用类型: fmt.Printf("intChan的值:%v\n", intChan) // 0xc000112080 //向管道存放数据: intChan <- 10 num := 20 intChan <- num intChan <- 40 //注意:不能存放大于容量的数据: //intChan<- 80 //在管道中读取数据: num1 := <-intChan num2 := <-intChan num3 := <-intChan fmt.Println(num1) fmt.Println(num2) fmt.Println(num3) //注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错: // num4 := <-intChan // fmt.Println(num4) //输出管道的长度: fmt.Printf("管道的实际长度:%v,管道的容量是:%v", len(intChan), cap(intChan))
使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据。
close(intChan)
管道支持for-range的方式进行遍历,请注意两个细节
package main import( "fmt" ) func main(){ //定义管道 、 声明管道 var intChan chan int //通过make初始化:管道可以存放3个int类型的数据 intChan = make(chan int,100) for i := 0;i < 100;i++ { intChan<- i } //在遍历前,如果没有关闭管道,就会出现deadlock的错误 //所以我们在遍历前要进行管道的关闭 close(intChan) //遍历:for-range for v := range intChan { fmt.Println("value = ",v) } }
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup //只定义无需赋值 // 写: func writeData(intChan chan int) { defer wg.Done() for i := 1; i <= 10; i++ { intChan <- i fmt.Println("写入的数据为:", i) time.Sleep(time.Second) } //没关闭前读线程已经开始工作 //如果没有关闭,编译器会一直等待下一个写入的内容,导致死锁 close(intChan) } // 读: func readData(intChan chan int) { defer wg.Done() //遍历: for v := range intChan { fmt.Println("读取的数据为:", v) time.Sleep(time.Second) } } func main() { //主线程 //写协程和读协程共同操作同一个管道-》定义管道: intChan := make(chan int, 50) wg.Add(2) //开启读和写的协程: go writeData(intChan) go readData(intChan) //主线程一直在阻塞,什么时候wg减为0了,就停止 wg.Wait() }
//默认情况下,管道是双向的--》可读可写:
var intChan1 chan int
//声明为只写:
var intChan2 chan <- int // 管道具备<- 只写性质
//声明为只读:
var intChan3 <- chan int // 管道具备<- 只读性质
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。