这篇文章只是作为GoLang的基础语法的一个大概记录,基本不具备什么深入讨论,适合初学者浏览GoLang的基本语法而写。整个文章也花费了好几天才整理出来,如果有哪里不恰当之处,还请各位大佬指正。
GoLang作为当前最火的开发语言之一,自然拥有很多优秀的特性,包括跨平台、高并发、自动内存回收等等。作为初学者,感受最深的应该就是defer延迟调用带来的便利了。(其他优点请自行百度吧,其他博主已经总结得很细致了)
defer延迟调用的妙用 类似java的finally 用于处理一些必要的收尾工作,如:文件的关闭、连接的关闭。而defer的优雅之处在于简洁、与位置无关、与代码无关 例如以下代码:
import (
"fmt"
"os"
)
func main() {
file,err := os.Open("test.txt")
if err != nil {
fmt.Println("os.Open err : ",err)
}
defer file.Close()
//defer 语句可以放在file被声明和赋值后的任意位置而不影响file的关闭操作
}
import java.io.*;
public class FileTest {
public static void main(String[] args) throws IOException {
BufferedReader bfw = null;
try{
bfw = new BufferedReader(new FileReader(new File("text.txt")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != bfw){
bfw.close();
}
}
}
}
通过一段go语言代码和一段java代码可以看出,同样是打开并关闭一个文件,go语言的写法就显得优雅很多,而java语言就显得代码臃肿了不少。
作为初学者来说,我自己学习的时候体会比较大的就这几点,下面我做一个说明
把接口设计成了结构类型: 这一点让java开发者很迷惑,在java中接口是用来定义行为准则的,而在go语言中,接口却被定义成了万能的,类似于java的Object类,而又超越了java的Object类的范畴
没有枚举类型 虽然go语言中提供了iota来协助定义枚举类,但是iota的重置特性让很多新手开发者很容易犯错。
import "fmt"
func main() {
const (
a = iota // 0
b = iota // 1
c = iota // 2
)
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
const (
b1 = iota //0
b2 //1
b3 //2
)
fmt.Println(b1)
fmt.Println(b2)
fmt.Println(b3)
const (
c1 = iota
c2,c3 = iota,iota
c4,c5
)
fmt.Println(c1) //0
fmt.Println(c2) //1
fmt.Println(c3) //1
fmt.Println(c4) //2
fmt.Println(c5) //2
}
对于最后一段代码来说,很多新手开发者是很容易犯错的。而且,如果要定义其他数据类型的枚举的情况下,go语言就显得不好处理了(当前还没想到好的方案)
而对于java来说,这就显得很容易了:
import org.apache.commons.lang.StringUtils;
/**
* 报损单状态
* @author
*/
public enum EnumDamagedStatus {
outline("201004001", "草稿"),
complete("201004002", "已报损");
private String code;
private String text;
EnumDamagedStatus(String code, String text) {
this.code = code;
this.text = text;
}
//此处省略get/set方法和其他方法
}
var age int //通过var关键字进行变量声
name := "张三" //通过类型推导声明并初始化变量(name此时的变量类型为string)
/*对于庞大的系统来说,开发者是不可能记住全部自定义类型的,
所以类型推导方便了开发者,不用他们再去寻找数据类型是什么了*/
a,b = b,a //通过这一特性,可以轻松交换两个变量的值,而不需要通过中间变量
数据类型是每个编程语言的必备要素,是必须掌握的基础知识之一。go语言中数据类型如下:
数据类型名 | 描述 | 取值范围 |
---|---|---|
uint8 | 无符号 8 位整型 | 0 到 255 |
uint16 | 无符号 16 位整型 | 0 到 65535 |
uint32 | 无符号 32 位整型 | 0 到 4294967295 |
uint64 | 无符号 64 位整型 | 0 到 18446744073709551615 |
int8 | 有符号 8 位整型 | -128 到 127 |
int16 | 有符号 16 位整型 | -32768 到 32767 |
int32 | 有符号 32 位整型 | -2147483648 到 2147483647 |
int64 | 有符号 64 位整型 | -9223372036854775808 到 9223372036854775807 |
float32 | IEEE-754 32位浮点型数 | 保留到小数点后面7位,末位不精准 |
float64 | IEEE-754 64位浮点型数 | 保留到小数点后面15位,末位不精准 |
complex64 | 32 位实数和虚数 | |
complex128 | 64 位实数和虚数 | |
byte | 类似 uint8 | |
rune | 类似 int32 | |
uint | 32 或 64 位 | 根据实际系统环境决定 |
int | 与 uint 一样大小 | |
uintptr | 无符号整型,用于存放一个指针 |
数据类型表 用于在未来开发过程中针对一些极值的设计参考
var test1 int = 10
var test2 float64 = 3.14
test2 + test1
//代码会报错 : Invalid operation: test2 + test1 (mismatched types float64 and int)
强制数据类型转换
既然没有隐式数据类型转换,那么肯定存在强制数据类型转换。其语法是:数据类型名(变量名)
var test1 int = 10
var test2 float64 = 3.14
test3 := test2 + float64(test1) //将test1数据类型强制转换成float64类型
fmt.Println(test3)
str := strconv.Itoa(100)
number := "80"
number_int, error := strconv.Atoi(number)
if error == nil {
fmt.Println("转换成功",number_int)
}else {
fmt.Println("转换错误,",error)
}
//断言变量v是否是int类型
if _, ok := v.(int); ok {
fmt.Printf("%d\n", v)
}
相比起java来说,go的函数比较有特点的就是支持多返回 和指针
package main
import "fmt"
//交换两个变量的值
func exchange(a,b int) (int,int){
return b,a
}
//交换两个变量的值(在返回参数列表中定义要返回的变量)
func exchangeNew(a,b int) (c , d int) {
c,d = b,a
return //返回列表中定义了c、d后,return语句中就可以直接不带c、d
// return c,d // 也可以在返回列表上写出c,d
/*
这种方式主要用于返回带error的函数中,如果遇到错误无法进行下去,
则赋值error后直接通过return结束函数,此时error就是赋值好的错误,
而其他返回值则是默认值(通常检查到error后其他返回值就是无用的)
*/
}
func main() {
a,b := 10,20
a,b = exchange(a,b)
fmt.Println("a = ",a , "b = ",b)
a,b = exchangeNew(a,b)
fmt.Println("a = ",a , "b = ",b)
}
/*最终控制台输出为:
a = 20 b = 10
a = 10 b = 20
*/
隔壁的Python也宣布函数支持多返回,看样子这是一个好特性啊,哈哈
def funcTest()
str = "小明"
age = 12
sex = "男"
return [str,age,sex]
//通过返回一个列表来返回多个返回值,这让强类型语言开发者看起来很是别扭,对于他们来说,更愿意把这段代码改成返回一个对象的方式来做。
def funcTest2()
str = "小明"
age = 12
sex = "男"
return str,age,sex
//通过返回元组的方式返回多个参数,这一点倒是和go很接近了
//Python还可以通过返回字典、对象的方式来返回,这里就不多说了
对于java开发者来说,实现多参数返回,还是老老实实用对象比较靠谱
作为go语言中一个复杂数据类型之一的数组,在使用中存在一些让人觉得不舒适的地方:
数组作为不可变长度数据结构来说,相比起java来说显得更加严格。
var arr1 [5]int
var arr2 [6]int = [6]int{1,2,3,4,5,6}
arr1 = arr2 //error
//第三行代码在go中是不允许的,因为arr1被指定了只能是5个长度的int类型的数组,而arr2长度为6
int arr1[] = new int[23];
int arr2[] = new int[90];
arr1[0] = 10;
System.out.println(arr1[0]);
arr1 = arr2;
arr1[0] = 100;
System.out.println(arr1[0]);
//而在java中,这段代码是成立的,控制台分别输出10,100
对于go开发者来说,更喜欢使用切片来代替数组。因为切片简直是太好用了。切片的出现是为了解决数组长度固定的问题,所以切片的长度是不固定的
定义语法:
- var 切片名 []数据类型
eg: var s1 []int
主意:和数组不同,[]中不输入任何值- 通过make创建一个切片
切片名 := make([]数据类型,切片大小)
eg: s2 := make([]int,5)
虽然切片长度可以扩展,但是初始化长度为n后,不能直接操作s[m] (m>=n)的,会报数组下标越界error
追加语法:
切片的截取是未来开发中经常使用的操作,可参考java中list的sublist来学习
- 切片名1 := 切片名2[开始下标:] //包含开始下标的值
eg: s2 := s1[0:] //表示完全复制切片s1
效果和 s2 := s1[:] 一样- 切片名1 := 切片名2[:结束下标] //不包含结束下标
eg: s2 := s1[:4]- 切片名1 := 切片名2[开始下标:结束下标]
eg: s2 := s1[1:4]- 生成切片并设置新切片的容量
切片名1 := 切片名2[开始下标:结束下标:切片名1的容量]
主意:
4.1 切片名1的容量不写的时候默认为 切片名2的容量
4.2 切片名1的容量不能大于切片名2的容量,可以小于 切片名2的容量- 修改新切片中的内容,会同步修改老的切片中的内容(这一点和java的list不同)
- 切片名本身就是一个数据地址,所以在获取切片内存地址的时候,不用使用&符号
对于java开发者来说,无疑再熟悉不过了,字典是以key-value这样的键值对方式存储的。
- map [keyType] [valueType]
eg: var m map[string]string- 使用make进行定义
eg: m := make(map[string]int)- 直接初始化map
eg: m := map[string]int{“张三”:10,“李四”:20}
注意:在初始化的时候,map中的key不可以重复
package main
import (
"fmt"
"math/rand"
"time"
)
func addStudent(dict map[string]int) {
dict["new_stu"] = 666
}
func main() {
//通过make定义一个string-int类型的字典
dict := make(map[string]int)
//定义一个切片作为学生学号列表
arr := []string{"stu001","stu002","stu003","stu004"}
//随机数种子
rand.Seed(time.Now().UnixNano())
//通过随机函数随机生成成绩后存入字典
for _,stuNo := range arr {
dict[stuNo] = rand.Intn(750)
}
//通过函数操作map
addStudent(dict)
//循环获取学生的成绩值
for key,val := range dict {
fmt.Printf("学生【%s】的成绩为:%d\n",key,val)
}
//事实证明函数对map的操作将会体现到实参当中
}
/*
可能的运行结果(因为前面4个学生成绩是随机生成的)
学生【stu001】的成绩为:625
学生【stu002】的成绩为:553
学生【stu003】的成绩为:259
学生【stu004】的成绩为:148
学生【new_stu】的成绩为:666
*/
import java.util.HashMap;
import java.util.Map;
public class MapTest {
private static void addStudent(Map<String,Integer> map) {
map.put("new_stu",666);
}
public static void main(String[] args) {
Map<String,Integer> map = new HashMap();
String[] arr = {"stu001","stu002","stu003","stu004"};
for (String stu : arr ) {
map.put(stu, (int)(Math.random()*750));
}
//调用函数添加学生信息
addStudent(map);
for (String key : map.keySet()) {
System.out.printf("学生【%s】的成绩为:%d\n",key,map.get(key));
}
}
}
/*
输出结果可能是:
学生【stu004】的成绩为:291
学生【new_stu】的成绩为:666
学生【stu001】的成绩为:211
学生【stu003】的成绩为:244
学生【stu002】的成绩为:202
*/
import random
def add_student(stu_dict):
stu_dict['new_stu'] = 666
def main():
student_list = ['stu001', 'stu002', 'stu003', 'stu004']
stu_dict = {}
for stu in student_list:
stu_dict[stu] = random.randint(0, 750)
#调用函数添加学生信息
add_student(stu_dict)
for (key, value) in stu_dict.items():
print("学生【" + key + "】的成绩为:", value)
if __name__ == '__main__':
main()
'''
可能的运行结果为:
学生【stu001】的成绩为: 742
学生【stu002】的成绩为: 138
学生【stu003】的成绩为: 302
学生【stu004】的成绩为: 462
学生【new_stu】的成绩为: 666
'''
function addStudent(studentScore){
studentScore["new_stu"] = 666
}
function random(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
let studentScore = {}
let studentList = ["stu001","stu002","stu003","stu004"]
for (stu in studentList) {
studentScore[stu] = random(0, 750)
}
//调用函数添加一个学生信息
addStudent(studentScore)
for (stu in studentScore) {
console.log("学生【" + stu + "】的成绩为:" + studentScore[stu] )
}
/*
js中对对象属性的处理,还可以使用(.)的方式,例如上述代码中:
console.log("学生【" + stu + "】的成绩为:" + studentScore[stu] ) 改写成
console.log("学生【" + stu + "】的成绩为:" + studentScore.stu ) 也是成立的
因为有[]操作方式,而且js的对象属性key不可以重复,可以动态添加,所以写到这里做一个参考复习
*/
/*
可能的运行结果为:
学生【stu001】的成绩为:736
学生【stu002】的成绩为:463
学生【stu003】的成绩为:326
学生【stu004】的成绩为:249
学生【new_stu】的成绩为:666
*/
熟悉C语言的同学一定清楚这个东西,作为复杂信息体封装载体而出现的一种数据结构,在GoLang中,结构体是面向对象编程的基础
结构体定义语法:
type 结构体名 struct{
属性名1 数据类型
属性名2 数据类型
属性名3 数据类型
...
属性名n 数据类型
}
type 关键字在go中用于定义自定义类型,其中包括:结构体类型、函数类型、为已经定义的数据类型取别名
函数类型
package main
import "fmt"
func fun1(){
fmt.Println("Hello")
}
func helloWorld(){
fmt.Println("Hello word")
}
func fun2(a,b int){
fmt.Println(a + b)
}
//为指定格式的函数类型取别名
type SayHello func()
type getSumNew func(int,int)
func main() {
//声明一个函数类型变量
var sayHello SayHello
//为函数类型变量赋值
sayHello = fun1
//通过自定义名称调用函数
sayHello()
var getSum getSumNew
getSum = fun2
getSum(1,2)
//重新为函数类型变量赋值一个相同格式的函数
sayHello = helloWorld
sayHello()
}
为已经定义的数据类型取别名
package main
import "fmt"
//为int类型取名为MyInt
type MyInt int
func main() {
var a MyInt
a = 100
fmt.Printf("a的数据类型是%T,a的值是%d",a,a)
}
/*
运行结果为:
a的数据类型是main.MyInt,a的值是100
*/
package main
import "fmt"
type vip struct {
id int //用户id
name string //用户名
lv int //vip等级
}
func main() {
//几种结构体初始化及赋值写法
var p1 vip
p1.name = "宋江"
p1.id = 1001
p1.lv = 1
var p2 = vip{1002,"李逵",2}
p3 := vip{1003,"武松",3}
p4 := vip{name:"林冲",id:1004,lv:2}
//初始化一个cap = 0的切片
slice := make([]vip,0)
//将所有用户添加到切片中
slice = append(slice,p1,p2,p3,p4)
//循环切片打印用户信息
for _,item := range slice {
fmt.Printf("用户【%s】,id为【%d】,会员等级为【%d】\n",item.name,item.id,item.lv)
}
}
/*
输出结果为:
用户【宋江】,id为【1001】,会员等级为【1】
用户【李逵】,id为【1002】,会员等级为【2】
用户【武松】,id为【1003】,会员等级为【3】
用户【林冲】,id为【1004】,会员等级为【2】
*/
用于保存某些变量的地址的变量类型叫做指针
package main
import "fmt"
func main() {
//定义一个变量并初始化
var a int = 10
//定义一个指针变量并初始化
var pa *int = &a
fmt.Println("指针为:",pa)
fmt.Println("a的值为",a)
fmt.Println("通过指针来访问a的值:",*pa)
}
package main
import "fmt"
func main() {
//声明一个指针变量,但是没有赋值
var p *int
//使用new操作为指针p创建一个int类型大小的一个空间
p = new(int)
//打印p保存的地址信息
fmt.Println(p)
fmt.Println("初始化后的值为",*p)
//操作申请的空间
*p = 100
fmt.Println("操作后的值为:",*p)
}
go语言的数组指针,感觉就是一个坑,不同大小的数组的指针变量不能互相赋值
intArr := [5]int{1,2,3,4,5}
var p1 *[5]int
p1 = &intArr
fmt.Println(*p1)
// var p2 *[6]int
// p2 = &intArr // err: cannot use &intArr (type *[5]int) as type *[6]int in assignment
package main
import "fmt"
func main() {
var arr [5]int = [5]int{1,2,3,4,5}
var parr = &arr
fmt.Println(parr) //&[1 2 3 4 5]
fmt.Println(arr) //[1 2 3 4 5]
fmt.Println(*parr) //[1 2 3 4 5]
// fmt.Println(*parr[0]) //语法错误 (运算符优先级问题)
fmt.Println((*parr)[0]) // 1
//因为parr是指向arr的指针,所以可以使用len来获取数组的长度
for i := 0;i<len(parr);i++ {
fmt.Println(parr[i])
}
}
package main
import "fmt"
func main() {
slice := []int{1}
var p *[]int = &slice
fmt.Printf("切片堆区地址:%p\n",slice)
fmt.Printf("切片变量地址:%p\n",p)
fmt.Println(*p)
slice = append(slice,2,3,4,5,6)
fmt.Printf("切片堆区地址:%p\n",slice)
fmt.Printf("切片变量地址:%p\n",p)
fmt.Println(*p)
}
切片指针作为函数变量
切片指针作为函数变量的时候,append操作会直接影响到实参中的数据
package main
import "fmt"
func sliceFuncTest(p *[]int) {
*p = append(*p,2,3,4,5,6)
}
func main() {
slice := []int{1,2,3}
p := &slice
fmt.Println(slice)
sliceFuncTest(p)
fmt.Println(slice)
}
通过前面的学习后,结构体指针就容易多了,当拿到结构体的地址之后,就能获取、改变结构体中的相关内容。
不绑定方法的类,实际上就是结构体
type Person struct {
id int
name string
age int
}
type Student struct {
Person //继承的父类作为匿名字段存在(只写类名,不写属性名)
score int //子类自己的字段
}
//定义类中的方法使用的是定义函数的形式,不过函数名前面加了一个说明 (p *Person)
//意思是将这个方法绑定到结构体Person中,这样就区别于普通的函数了
func (p *Person) sayHello(){
fmt.Printf("大家好,我是%s,我今年%d岁 !!!\n",p.name,p.age)
}
//这种方式也是可以使用,但是存在实参和形参的问题,所以开发中用得不是很多
func (p Person) sayHello(){
fmt.Printf("大家好,我是%s,我今年%d岁 !!!\n",p.name,p.age)
}
type IntegerfaceName interface {
functions ...
}
代码示例:
package main
import "fmt"
//定义一个父类
type Person2 struct {
name string
}
//学生类继承父类
type Student2 struct {
Person2
score int
}
//老师类继承父类
type Teacher2 struct {
Person2
subject string
}
//学生类SayHello方法
func (s *Student2) SayHello() {
fmt.Printf("大家好,我是%s,我的成绩是%d\n", s.name, s.score)
}
//老师类SayHello方法
func (t *Teacher2) SayHello() {
fmt.Printf("大家好,我是%s,我的学科是%s\n", t.name, t.subject)
}
//定义一个接口
type Human interface {
SayHello()
}
//定义一个多态函数
func SayHello(h Human) {
h.SayHello()
}
func main() {
//声明一个接口类型变量
var h Human
h = &Student2{Person2{"小明"}, 90}
SayHello(h)
h = &Teacher2{Person2{"张老师"}, "语文"}
SayHello(h)
}
* **同类的继承语法类似,父类接口在子类接口以匿名方式存在**
* **子类接口可以转换为父类接口,父类接口不能转换为子类接口**
type InterfaceA interface {
func1
}
type InterfaceB interface {
InterfaceA
func2
}
空接口可以接收任意类型数据,这意味着我们需要做数据类型判断,所以就有了类型断言
package main
import "fmt"
func main() {
slice := make([]interface{},0)
slice = append(slice,1,3.14,"Hello")
for i,v := range slice {
//if判断里面做类型断言
if data,ok := v.(int); ok {
fmt.Printf("第%d个数据是int类型,值为%d\n",i,data)
} else if data,ok := v.(float64); ok {
fmt.Printf("第%d个数据是float类型,值为%f\n",i,data)
}else if data,ok := v.(string); ok {
fmt.Printf("第%d个数据是string类型,值为%s\n",i,data)
}else {
fmt.Println("第",i,"个数据类型未知,值为:",v)
}
}
}
//官方定义
type error interface {
Error() string
}
package main
import (
"errors"
"fmt"
)
func test(a, b int) (value int, err error) {
if b == 0 {
//创建一个错误信息
err = errors.New("0不能作为除数")
return
}
value = a / b
return
}
func main() {
value,err := test(10,0)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("value = ",value)
}
当程序无法进行下去的时候,就会出现panic异常,go语言自己调用的,如果自己手动调用,也能停止程序
import "fmt"
func main() {
fmt.Println("Hello World")
panic("手动调用panic")
//这两个函数是用于错误信息处理,不建议使用
// print()
// println()
fmt.Println("Hello World last")
}
func recover() interface{}
package main
import "fmt"
func demo(i int) {
var arr [10]int
//通过匿名函数和defer配合进行错误的拦截
defer func() {
res := recover()
if res != nil {
fmt.Println(res)
}
}()
arr[i] = 100
}
func main() {
demo(11)
fmt.Println("执行完了.... ")
}
在开发中,经常忘记做一些必须要做的事情,所以go中提供了defer关键字来做延迟调用,减少这样的事情发生
1s= 1000ms 1秒等于1000毫秒
1ms = 1000um 1毫秒等于1000微秒
1um = 1000nm 1微秒等于1000纳秒
并行:同一时刻有多条指令在多个CPU中同时执行。必须在多核的情况下才可以完成
并发:
宏观:在用户角度来看,程序是在同时执行的
微观:多个计划任务顺序执行,是串行的一个过程
中断:CPU硬件的一个切换任务机制,具有强制性。当一个任务执行的CPU时间片结束的时候,中断机制会强行收回该任务的CPU执行权,并进行下一轮的时间片的分配。
进程:资源分配的最小单位
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
for i := 0;i<100;i++ {
fmt.Println("....这里是匿名函数....",i)
time.Sleep(100*time.Microsecond)
}
}()
for i:=0;i<100;i++ {
if i % 3 == 0 {
fmt.Println(".... 我让出CPU时间片给你 ....")
runtime.Gosched()
}
fmt.Println("...这里是main程在跑 。。。 " , i)
time.Sleep(100*time.Microsecond)
}
}
package main
import (
"fmt"
"runtime"
)
func test() {
defer fmt.Println("bbbbbbbbbbbb")
//使用Goexit退出当前go程
runtime.Goexit()
//因为这行代码在Goexit后面,来不及执行,所以不会输出ddd
fmt.Println("ddddddddddddddddd")
}
func main() {
go func() {
defer fmt.Println("aaaaaaaaaaaaaaaaaaaaa")
test()
//因为test中有Goexit,直接退出了当前go程,所以cccc也来不及输出
fmt.Println("cccccccccccccccccc")
}()
for {
;
}
}
//设置最大核心数为m,返回之前核心数n
n := runtime.GOMAXPROCS(m)
//从channel中读取数据
data := <- ch
//向channel中写数据
ch <- data
1.默认的channel是双向的
var ch chan int
ch = make(chan int)
2.单向写channel
var sendCh chan <- int
sendCh = make(chan <- int)
3.单向读channel
var recCh <- chan int
recCh = make( <- chan int)
4.双向channel可以转换为任意一种单向channel,单向channel不能转换为双向channel
5.传参 【引用】
package main
import "fmt"
func main() {
ch := make(chan int)
var sendCh chan <- int = ch //声明一个写入单向channel并将ch赋值给sendCh
sendCh <- 123 // fatal error: all goroutines are asleep - deadlock!
/*
造成死锁的原因是:不能在同一个go程中对同一个channel进行(写入 + 读取操作),
因为写入后就阻塞了,同样读取后也会阻塞
*/
var recCh <- chan int = ch //声明一个读取单向channel并将ch赋值给recCh
num := <- recCh
fmt.Println("读取到参数",num)
}
双向channel可以转换成任意的单向channel,但是单向channel就不能转换成双向channel
单向channel使用示例
package main
import "fmt"
//单向channel作为一个参数传递
func sendChTest( sendCh chan<- int ){
fmt.Println("我写入了一个数:100")
sendCh <- 100
}
func recChTest(recCh <-chan int) {
result := <-recCh
fmt.Println("我是读取端,我读取到了:",result)
}
func main() {
ch := make(chan int) //声明一个双向channel
//开启一个子go程
go func() {
sendChTest(ch) //在子go程里面调用函数
}()
recChTest(ch) //在主go程里面调用读取函数
}
//系统中Timer定义
type Timer struct {
C <-chan Time
r runtimeTimer
}
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("time.now = " , time.Now())
//创建一个定时器
myTimer := time.NewTimer(time.Second * 2)
fmt.Println("现在时间 =", <- myTimer.C)
}
func main() {
//3种定时方法
//1.使用sleep
time.Sleep(time.Second * 2) //使用sleep函数,传递sleep时长
//2.使用Timer
myTimer := time.NewTimer(time.Second * 2)
fmt.Println("现在时间 =", <- myTimer.C)
//3.使用time.After
nowTime := <-time.After(time.Second)
fmt.Println("现在时间2 = ",nowTime)
}
func main() {
myTimer := time.NewTimer(time.Second ) //创建定时器
go func() {
<- myTimer.C
fmt.Println("子go程读取完毕")
}()
myTimer.Stop() //设置定时器停止
/*
定时器在主go程中Stop后,上面的语句就不会被打印了
但是也不会造成死锁。因为这里只是关闭了定时器,并没有关闭channel,
而channel现在被系统把持着,所以不会造成死锁
*/
for {
;
}
}
func main() {
myTimer := time.NewTimer(time.Second *3 ) //创建定时器
myTimer.Reset(time.Second) //重置定时器为1秒
go func() {
<- myTimer.C
fmt.Println("子go程读取完毕")
}()
for {
;
}
}
###### 2.2.5.3.3 周期定时器
func main() {
quit := make(chan bool)
myTimer := time.NewTicker(time.Second)
var i int
go func() {
for {
nowTime := <-myTimer.C
fmt.Println("nowTime = ",nowTime)
i++
if i == 3 {
quit <- true //向quit中写入标记
myTimer.Stop()
break
}
}
}()
<- quit //主go程里面读取quit channel中的标记,如果没有写入标记,则一直阻塞而不会退出主go程
//这种写法比死循环优雅
fmt.Println("这里。。。主go程退出")
}
func main() {
ch := make(chan int) //创建一个channel
quit := make(chan bool) //用来标记是否退出的channel
go func() {
for i := 0;i<5;i++ {
ch <- i
time.Sleep(time.Second)
}
quit <- true
close(ch)
runtime.Goexit()
}()
for {
select {
case num := <- ch:
fmt.Println("读取到数据 ... ",num)
case <- quit:
//1. break // break只能退出select,不能退出for循环
//2. return //return返回main函数
//3. runtime.Goexit() //退出go程(但是不能用在主go程里面)
return
}
}
}
//死锁1(单go程自己死锁)
//不能在同一个go程里面对同一个channel同时进行读和写操作
func main() {
ch := make(chan int)
ch <- 89 //写端阻塞
num := <- ch
fmt.Println("num = ",num)
}
//死锁2(go程间channel访问顺序导致死锁)
func main (){
ch := make(chan int)
num := <- ch //读端先于写端将channel锁死。写端没有机会写入数据
//主go程阻塞后,下面的子go程没有创建
fmt.Println("num = ",num)
go func() {
ch <- 789
}()
}
//死锁3 (多go程,多channel交叉死锁)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for {
select {
case num := <- ch1 :
ch2 <- 999
fmt.Println(num)
break
}
}
}()
for {
select {
case num := <- ch2:
fmt.Println(num)
ch1 <- 888
break
}
}
}
在开发过程中,有些操作是互斥的,为了数据一致性,这个时候通常会使用到互斥锁。go语言中的互斥锁,还是通过channel来实现的。
现在模拟打印机功能进行打印,分别有两个用户进行打印操作。
package main
import (
"fmt"
"time"
)
var ch = make(chan int)
func printString(str string){
for _,c := range str {
fmt.Printf("%c",c)
time.Sleep(time.Millisecond * 300)
}
}
func p1() {
printString("hello")
ch <- 89
}
func p2() {
<-ch
printString(" world")
}
func main() {
go p1()
go p2()
for {
;
}
}
/*
你会发现打印出来的数据是乱的,而真实的打印机,一定是一个打印任务完成后,才会打印下一个任务
*/
//声明一个互斥锁
var mutex sync.Mutex
func printString(str string){
mutex.Lock() //加锁
for _,c := range str {
fmt.Printf("%c",c)
time.Sleep(time.Millisecond * 300)
}
mutex.Unlock() //解锁
}
func p1() {
printString("hello")
}
func p2() {
printString(" world")
}
func main() {
go p1()
go p2()
for {
;
}
}
//现在打印就正常了,不会出现两个人的内容穿插的情况
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
//定义一个全局的条件变量
var condition sync.Cond
//生产者
func producer02(ch chan<- int,no int,cap int){
for {
//加锁锁定公共区域
condition.L.Lock()
for len(ch) == cap{
condition.Wait()
}
/*
这里没有使用if来判断的原因是我们使用for来进行循环判断
if len(ch) == 3{
condition.Wait()
}*/
num := rand.Intn(1000)
fmt.Printf("生产者【%d】生产:%d\n",no,num)
ch <- num
condition.L.Unlock()
condition.Signal()
time.Sleep(time.Second)
}
}
//消费者
func consumer02(ch <-chan int,no int){
for {
condition.L.Lock()
for len(ch) == 0 {
condition.Wait()
}
num := <-ch
fmt.Printf("----消费者【%d】消费了%d\n",no,num)
condition.L.Unlock()
condition.Signal()
time.Sleep(time.Second)
}
}
func main() {
//随机因子
rand.Seed(time.Now().UnixNano())
//公共区(商品,缓冲区3个)
product := make(chan int,3)
//为全局变量的L赋值一个互斥锁(创建互斥锁和条件变量)
condition.L = new(sync.Mutex)
//开启go程开始生产
for i:=0;i<5;i++{
go producer02(product,i+1,cap(product))
}
//开启go程开始消费
for i:=0;i<5;i++{
go consumer02(product,i+1)
}
for {
;
}
}
socket编程分为服务端、客户端两个
并发服务器演示
package main
import (
"fmt"
"net"
"strings"
)
func main() {
//tcp必须小写
server,err := net.Listen("tcp",":8000")
if err != nil {
fmt.Println("开启TCP服务失败",err)
return
}
//延迟关闭
defer server.Close()
fmt.Println("服务器等待客户端建立连接。。。。。。")
//阻塞客户端连接请求
for {
conn,connErr := server.Accept()
if connErr != nil {
fmt.Println("server.Accept() err" , connErr)
return
}
//延迟关闭
fmt.Println("服务器和客户端成功建立连接")
go handleConn(conn) //使用go程去处理
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
//读取客户端发送的数据
buf := make([]byte , 4096)
for {
n,err := conn.Read(buf)
if n == 0 {
fmt.Println("客户端强制关闭了连接 。。。")
return
}
if err != nil {
fmt.Println(" conn.Read err ",err)
return
}
fmt.Println("服务读取到数据,",string(buf[:n]))
conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
}
}
package main
import (
"fmt"
"net"
"os"
)
func main() {
conn,err := net.Dial("tcp","127.0.0.1:8000")
if err != nil {
fmt.Println("连接服务器失败",err)
return
}
//延迟关闭客户端
defer conn.Close()
go func() {
str := make([]byte , 4096)
for {
n,err := os.Stdin.Read(str)
if err != nil {
fmt.Println("os.Stdin.Read err ",err)
continue
}
conn.Write(str[:n])
}
}()
buf := make([]byte,4096)
for {
n,err := conn.Read(buf)
if n == 0 {
fmt.Println("服务器关闭连接")
return
}
if err != nil {
fmt.Println("读取服务器返回错误",err)
return
}
if n > 0 {
fmt.Println("服务器返回:",string(buf[:n]))
}
}
}
go语言自己提供了相关http编程的封装,其中著名的Beego、Gin等一众GoLang的web框架,都是在这基础之上封装的优秀框架。
简单服务器示例
package main
import "net/http"
func myHandler(w http.ResponseWriter,r *http.Request){
w.Write([]byte("hello world"))
}
func main() {
//注册回调函数。该回调函数会在服务器被访问时自动被调用
http.HandleFunc("/test",myHandler)
//绑定服务器监听地址
http.ListenAndServe("127.0.0.1:9002",nil) //如果这里传递nil,则会调用默认的回调函数
}
简单客户端示例
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp,err := http.Get("http://www.baidu.com")
if err != nil {
fmt.Println("http.Get err:",err)
return
}
defer resp.Body.Close()
//简单查看应答包
fmt.Println("Header",resp.Header)
buf := make([]byte,4096)
var result string
for {
n,err := resp.Body.Read(buf)
if n == 0 {
if err != nil && err != io.EOF{
fmt.Println("read err : ",err)
break
}
break
}
result += string(buf[:n])
}
fmt.Println(result)
}
在go语言中,正则表达式操作需要分两步:
package main
import (
"fmt"
"regexp"
)
func main() {
//最终测试字符串
str := "abc a7c mfc cat hello sdlfwejoi "
//编译正则表达式
//res := regexp.MustCompile(`a.c`)
res := regexp.MustCompile(`a[0-9]c`)
//匹配div中间的数据 (但是不包含换行符)
res = regexp.MustCompile(`(.*)`)
//?s 表示单行模式,使得后面的(.)可以匹配换行符
res = regexp.MustCompile(`(?s:(.*?))`)
//提取用到的信息
arr := res.FindAllStringSubmatch(str,-1)
fmt.Println(arr)
}
String str = "an";
str = str.replaceAll("\\","_"); //正则表达式在字符串里面还需要转义
str = str.replaceAll("\\<\\/sub>","");//正则表达式在字符串里面还需要转义
说实话,GoLang中的json就优点坑了,原因在于:
type PersonInfo struct {
//前面是正常的结构体属性声明,后面则是json字段的对应
Name string `json:"name"`
Age int `json:"age"`
}
func main(){
// 创建数据
p := personInfo{Name: "张三", Age: 8}
// 序列化(返回:[]byte, error)
data, _ := json.Marshal(&p)
fmt.Println(string(data))
// 反序列化
var p1 PersonInfo
err := json.Unmarshal([]byte(data), &p1) // 貌似这种解析方法需要提前知道 json 结构
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Printf("name=%s, Age=%d\n", p1.Name, p1.Age)
}
}