GoLang基础语法分享

文章目录

  • GoLang 基础知识分享
    • 1.GoLang的优点缺点对比
      • 1.1 GoLang的优点
      • 1.2 GoLang的缺点
    • 2.GoLang 基础语法介绍(部分)
      • 2.1 一些典型的语法介绍
        • 2.1.1 通过**var**进行变量声明以及类型推导
        • 2.1.2 **多重赋值**
        • 2.1.3 **数据类型**
          • 2.1.3.1 数据类型转换
          • 2.1.3.2 类型断言
        • 2.1.4 **函数**
        • 2.1.5 **数组**
        • 2.1.6 **切片**
          • 2.1.6.1 在切片末尾追加数据
          • 2.1.6.2 切片的长度和容量
          • 2.1.6.3 切片的截取
          • 2.1.6.4 切片的追加和复制特性
          • 2.1.6.5 切片在函数中的运用中的注意事项
        • 2.1.7 字典
          • 2.1.7.1 字典的定义
          • 2.1.7.2 获取map中的值
          • 2.1.7.3 删除map中的值
          • 2.1.7.4 map在函数中的使用特性
          • 2.1.7.5 字典代码示例和对比
            • 2.1.7.5.1GoLang中的map基本操作
            • 2.1.7.5.2 java中map的基本操作
            • 2.1.7.5.3 python中字典的基本操作
            • 2.1.7.5.4 javascript中的对象基本操作方式之一
        • 2.1.8 结构体
          • 2.1.8.1 自定义类型
          • 2.1.8.2 结构体的基本操作
      • 2.2 进阶语法介绍
        • 2.2.1 指针
          • 2.2.1.1 普通类型数据的指针
          • 2.2.1.2 数组指针(指向数组的指针)
          • 2.2.1.3 切片指针
          • 2.2.1.4 结构体指针
        • 2.2.2 面向对象编程
          • 2.2.2.1 类与结构体
          • 2.2.2.2 类的继承
          • 2.2.2.3 类的方法
        • 2.2.3 接口和多态
          • 2.2.3.1 接口定义的语法为:
          • 2.2.3.2 接口的实现和使用
          • 2.2.3.3 多态的实现方式是**通过一个以接口为参数的函数来实现的**
          • 2.2.3.4 接口的继承
          • 2.2.3.5 空接口(万能接口)与类型断言
        • 2.2.4 异常处理
          • 2.2.4.1 error
          • 2.2.4.2 panic异常
          • 2.2.4.3 recover
          • 2.2.4.4 defer延迟调用
        • 2.2.5 并发(goroutine)
          • 2.2.5.1 runtime 包里面的几个方法
          • 2.2.5.2 channel
            • 2.2.5.2.1 无缓冲channel
            • 2.2.5.2.2 有缓冲channel
            • 2.2.5.2.3 关闭channel
            • 2.2.5.2.4 单向channel
          • 2.2.5.3 Timer定时器
            • 2.2.5.3.1 三种延时方法
            • 2.2.5.3.2 定时器重置和停止
          • 2.2.5.4 select关键字
          • 2.2.5.5 死锁
          • 2.2.5.6 互斥锁(sync.Mutex)
          • 2.2.5.7 读写锁(sync.RWMutex)
          • 2.2.5.8 条件变量(sync.Cond)
            • 2.2.5.8.1 条件变量方法:Wait()
            • 2.2.5.8.2 条件变量方法:Signal
            • 2.2.5.8.3 条件变量:Broadcast
            • 2.2.5.8.4 完整版的生产者、消费者模型实现
          • 2.2.5.9 网络编程
            • 2.2.5.9.1 socket 编程
            • 2.2.5.9.2 http编程
        • 2.2.6 正则表达式基本操作
        • 2.2.7 GoLang中的json

GoLang 基础知识分享

1.GoLang的优点缺点对比

这篇文章只是作为GoLang的基础语法的一个大概记录,基本不具备什么深入讨论,适合初学者浏览GoLang的基本语法而写。整个文章也花费了好几天才整理出来,如果有哪里不恰当之处,还请各位大佬指正。

1.1 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语言就显得代码臃肿了不少。

1.2 GoLang的缺点

作为初学者来说,我自己学习的时候体会比较大的就这几点,下面我做一个说明

  • 把接口设计成了结构类型: 这一点让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方法和其他方法
    }
    

2.GoLang 基础语法介绍(部分)

2.1 一些典型的语法介绍

2.1.1 通过var进行变量声明以及类型推导

var age int  //通过var关键字进行变量声
name := "张三"	//通过类型推导声明并初始化变量(name此时的变量类型为string)
/*对于庞大的系统来说,开发者是不可能记住全部自定义类型的,
所以类型推导方便了开发者,不用他们再去寻找数据类型是什么了*/

2.1.2 多重赋值

a,b = b,a  //通过这一特性,可以轻松交换两个变量的值,而不需要通过中间变量

2.1.3 数据类型

数据类型是每个编程语言的必备要素,是必须掌握的基础知识之一。go语言中数据类型如下:

  1. 布尔型 bool
  2. 数字类型
  3. 字符串类型(string)
  4. 其他复杂数据类型(slice、map)
数据类型名 描述 取值范围
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 无符号整型,用于存放一个指针

数据类型表 用于在未来开发过程中针对一些极值的设计参考

2.1.3.1 数据类型转换
  • 目前为止,并没有发现golang中有隐形的数据类型转换
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)
  • int数字转换成字符串(strconv.Itoa)
str := strconv.Itoa(100)
  • 字符串转换成int (strconv.Atoi)
number := "80"
number_int, error := strconv.Atoi(number)
if error == nil {
	fmt.Println("转换成功",number_int)
}else {
	fmt.Println("转换错误,",error)
}
2.1.3.2 类型断言
  • interface{}作为GoLang中的Object全能接口,可以接收任何数据,在开发过程中需要判断数据类型的时候,就需要用到类型断言
//断言变量v是否是int类型
if _, ok := v.(int); ok {
    fmt.Printf("%d\n", v)
}  

2.1.4 函数

​ 相比起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开发者来说,实现多参数返回,还是老老实实用对象比较靠谱

2.1.5 数组

作为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

2.1.6 切片

对于go开发者来说,更喜欢使用切片来代替数组。因为切片简直是太好用了。切片的出现是为了解决数组长度固定的问题,所以切片的长度是不固定的

定义语法:

  1. var 切片名 []数据类型
    eg: var s1 []int
    主意:和数组不同,[]中不输入任何值
  2. 通过make创建一个切片
    切片名 := make([]数据类型,切片大小)
    eg: s2 := make([]int,5)
2.1.6.1 在切片末尾追加数据

虽然切片长度可以扩展,但是初始化长度为n后,不能直接操作s[m] (m>=n)的,会报数组下标越界error
追加语法:

  1. 切片名 = append(切片名,追加元素)
    eg: s = append(s,123)
  2. 可以一次性添加多个值
    eg: s = append(s,1,2,3,4,5…)
2.1.6.2 切片的长度和容量
  • 因为切片的长度是可以动态扩展,所以就存在着内存申请,和java的ArrayList类似,在申请内存空间的时候,会多申请部分空间以减少切片在扩展的时候申请空间的次数。
  • 切片的长度用len来获取,切片的容量用cap来获取
    cap(s) >= len(s)
  • 切片扩展空间的时候,如果整体数据没有超过1024,则每次扩展为原来的n倍(n>=2),如果超过1024,则每次扩展原来的1/4
2.1.6.3 切片的截取

切片的截取是未来开发中经常使用的操作,可参考java中list的sublist来学习

  1. 切片名1 := 切片名2[开始下标:] //包含开始下标的值
    eg: s2 := s1[0:] //表示完全复制切片s1
    效果和 s2 := s1[:] 一样
  2. 切片名1 := 切片名2[:结束下标] //不包含结束下标
    eg: s2 := s1[:4]
  3. 切片名1 := 切片名2[开始下标:结束下标]
    eg: s2 := s1[1:4]
  4. 生成切片并设置新切片的容量
    切片名1 := 切片名2[开始下标:结束下标:切片名1的容量]
    主意:
    4.1 切片名1的容量不写的时候默认为 切片名2的容量
    4.2 切片名1的容量不能大于切片名2的容量,可以小于 切片名2的容量
  5. 修改新切片中的内容,会同步修改老的切片中的内容(这一点和java的list不同)
  6. 切片名本身就是一个数据地址,所以在获取切片内存地址的时候,不用使用&符号
2.1.6.4 切片的追加和复制特性
  1. append操作后的切片内存地址可能发生改变,因为切片的内存是连续的
  2. 切片的拷贝使用copy函数,返回拷贝成功的元素数量
    copy(target,source)
  3. 在拷贝的时候,如果目标切片容量小于拷贝的元素个数,则会丢弃多余的元素
  4. 在拷贝切片的时候,切片的截取语法完全适用
2.1.6.5 切片在函数中的运用中的注意事项
  • 因为切片存储的是地址信息,所以在函数中的操作可能会影响到实参的切片数据
  • 在使用append操作切片后,实参可能不会发生改变,所以在想要反映到实参,则需要使用函数返回append后的切片(如果append操作发生了内存申请,那么函数内的切片地址将会是新的切片地址)

2.1.7 字典

​ 对于java开发者来说,无疑再熟悉不过了,字典是以key-value这样的键值对方式存储的。

2.1.7.1 字典的定义
  1. map [keyType] [valueType]
    eg: var m map[string]string
  2. 使用make进行定义
    eg: m := make(map[string]int)
  3. 直接初始化map
    eg: m := map[string]int{“张三”:10,“李四”:20}
    注意:在初始化的时候,map中的key不可以重复
2.1.7.2 获取map中的值
  • 在go中,使用map[key]来获取map中的值
    eg: v,ok := m[key]
    • 如果key对应的值存在,则v为对应的值,ok = true
    • 如果key对应的值不存在,则v为,ok = false
2.1.7.3 删除map中的值
  • delete(mapName,key)
  • 如果key不存在,也不会报错
2.1.7.4 map在函数中的使用特性
  • 因为map是引用传递,所以函数中对map的操作,会反映到实参当中
2.1.7.5 字典代码示例和对比
2.1.7.5.1GoLang中的map基本操作
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
*/
2.1.7.5.2 java中map的基本操作
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
*/
2.1.7.5.3 python中字典的基本操作
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
'''
    
2.1.7.5.4 javascript中的对象基本操作方式之一
		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
*/

2.1.8 结构体

​ 熟悉C语言的同学一定清楚这个东西,作为复杂信息体封装载体而出现的一种数据结构,在GoLang中,结构体是面向对象编程的基础

结构体定义语法:

type 结构体名 struct{
		属性名1 数据类型
		属性名2 数据类型
		属性名3 数据类型
		...
		属性名n 数据类型
	}
2.1.8.1 自定义类型

type 关键字在go中用于定义自定义类型,其中包括:结构体类型函数类型为已经定义的数据类型取别名

  1. 函数类型

    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()
    }
    
    
  2. 为已经定义的数据类型取别名

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
*/
2.1.8.2 结构体的基本操作
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】
*/

2.2 进阶语法介绍

2.2.1 指针

用于保存某些变量的地址的变量类型叫做指针

  • 指针极大的方便了程序快速定位变量以及避免了函数调用过程中值传递带来的内存空间申请的额外消耗,所以在高级框架中,经常推荐使用指针作为参数传递
  • 指针分为一级指针多级指针,因此也带来了复杂性
  • 函数名数组切片字典 变量保存的都是地址,可以认为是隐形的指针,但是切片作为参数切片指针作为参数在append操作后所产生的结果可能是不相同的,原因是内存模型不同,需要理解底层内存模型。
  • 指针定义的语法: var 变量名 *数据类型名
2.2.1.1 普通类型数据的指针
  • 定义指针变量并对其赋值操作
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)
}
  • 使用new定义指针并对其进行操作
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)
}

2.2.1.2 数组指针(指向数组的指针)

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])
	}
}
2.2.1.3 切片指针
  • 切片名自身保存的就是一个地址,这个地址指向了堆区的一个地址
  • 切片指针里面保存的是切片变量的地址,所以append的时候,切片在扩展空间的时候,虽然切片变量里面保存的地址变了,但是切片变量的地址并没有发生变化,所以切片指针还是可以定位到新的切片的地址。
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)
}

2.2.1.4 结构体指针

​ 通过前面的学习后,结构体指针就容易多了,当拿到结构体的地址之后,就能获取、改变结构体中的相关内容。

2.2.2 面向对象编程

  • GoLang中的面向对象是基于结构体来实现的,结构体用来保存对应的属性信息,再给结构体绑定对应的函数,就给结构体赋予了行为能力,这就实现了对象。
  • GoLang中没有java里面的public、private、protect 修饰词,是用过作用域来实现控制的。
    1. 在同一个package内,类的属性是公开的
    2. 在不同package之间,类的属性分两种情况:首字母大写表示公开属性首字母小写表示私有属性
2.2.2.1 类与结构体

不绑定方法的类,实际上就是结构体

type Person struct {
    id int
    name string
    age int
}
2.2.2.2 类的继承
  • 类的继承是通过结构体嵌套来实现的,在子类定义的时候,将要继承的父类作为子类的匿名字段存在即可,也可以不是匿名字段
  • 类的继承是多继承
type Student struct {
    Person //继承的父类作为匿名字段存在(只写类名,不写属性名)
    score int //子类自己的字段
}
  • 当遇到同名属性的时候,子类默认就近原则使用的是子类的同名字段信息
    1. 此时父类同名字段默认为该数据类型的默认值
    2. 如果要使用父类的同名字段,则应该使用 子类变量名.父类类名.父类同名字段
2.2.2.3 类的方法
//定义类中的方法使用的是定义函数的形式,不过函数名前面加了一个说明 (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)
}

2.2.3 接口和多态

2.2.3.1 接口定义的语法为:
type IntegerfaceName interface {
    functions ... 
}
2.2.3.2 接口的实现和使用
  1. 定义接口
  2. 声明一个接口变量
  3. 为接口变量绑定实现类
  4. 调用接口方法
2.2.3.3 多态的实现方式是通过一个以接口为参数的函数来实现的

​ 代码示例:

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)
}

2.2.3.4 接口的继承
* **同类的继承语法类似,父类接口在子类接口以匿名方式存在**
* **子类接口可以转换为父类接口,父类接口不能转换为子类接口** 
type InterfaceA interface {
    func1
}
type InterfaceB interface {
    InterfaceA
    func2
}
2.2.3.5 空接口(万能接口)与类型断言
  • 空接口定义格式为 var i interface{}
  • 空接口可以接收任意值:简单数据类型数据、复杂数据类型数据、函数
  • 空接口实际上是一个万能指针

空接口可以接收任意类型数据,这意味着我们需要做数据类型判断,所以就有了类型断言

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)
		}
	}
}

2.2.4 异常处理

2.2.4.1 error
//官方定义
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)
}
2.2.4.2 panic异常

当程序无法进行下去的时候,就会出现panic异常,go语言自己调用的,如果自己手动调用,也能停止程序

import "fmt"

func main() {
	fmt.Println("Hello World")
	panic("手动调用panic")
	//这两个函数是用于错误信息处理,不建议使用
	//	print()
	//	println()
	fmt.Println("Hello World last")
}

2.2.4.3 recover
  • recover可以让程序从运行时panic的状态中恢复并重新获得流程控制权
  • 一定是先拦截错误,再recover
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("执行完了.... ")
}
2.2.4.4 defer延迟调用

在开发中,经常忘记做一些必须要做的事情,所以go中提供了defer关键字来做延迟调用,减少这样的事情发生

2.2.5 并发(goroutine)

1s= 1000ms		1秒等于1000毫秒
1ms = 1000um	1毫秒等于1000微秒
1um = 1000nm	1微秒等于1000纳秒
并行:同一时刻有多条指令在多个CPU中同时执行。必须在多核的情况下才可以完成
并发:
	宏观:在用户角度来看,程序是在同时执行的
	微观:多个计划任务顺序执行,是串行的一个过程
中断:CPU硬件的一个切换任务机制,具有强制性。当一个任务执行的CPU时间片结束的时候,中断机制会强行收回该任务的CPU执行权,并进行下一轮的时间片的分配。
进程:资源分配的最小单位
  • 在go语言中,并发使用goroutine来实现的,其思想就是协程,一种比线程更加轻量级的程序处理单位
  • go语言中,并发包都在runtime包里面
  • go语言中,go程间通讯同步使用channel
2.2.5.1 runtime 包里面的几个方法
  • Gosched : 暂时让出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)
	}

}

  • Goexit : 退出当前go程
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  {
		;
	}

}

  • GOMAXPROCS : 设置go程运行时使用最大CPU核心数量
//设置最大核心数为m,返回之前核心数n
n := runtime.GOMAXPROCS(m)
2.2.5.2 channel
  • channel是一个数据类型,主要用于go程之间的同步问题以及数据共享。
  • goroutine运行在相同的地址空间,因此内存共享必须做好。goroutine奉行的是通过通信来共享内存,而不是通过共享内存来通信。
  • 引用类型channel可用于多个goroutine通讯,其内部实现了同步,确保并发安全。
  • channel的初始化方法:
    • make(chan Type) //初始化一个没有缓冲区的channel
    • make(chan Type , capacity) //初始化一个channel并指定缓冲区大小
  • 从channel中读写语法:
//从channel中读取数据
data := <- ch
//向channel中写数据
ch <- data
2.2.5.2.1 无缓冲channel
  • len = 0,cap = 0
  • 应用于两个go程中,一个读取,一个写入。
  • 具备同步能力,读写同步
2.2.5.2.2 有缓冲channel
  • cap > 0
  • 缓冲区可以进行数据存储,存储到容量上限后,才阻塞。
  • 具备异步能力,不需要同时操作缓冲区
2.2.5.2.3 关闭channel
  • 使用**close(ch)**来关闭一个channel,关闭channel后,对端可以判断是否关闭channel.
  • 当我确定不再向对端发送数据的时候,关闭channel
  • 已经关闭的channel不能再写入数据,但是可以从里面把剩余的数据读取出来,如果是无缓冲channel,读取到的数据为默认值
  • 有缓冲channel和无缓冲channel都可以关闭
2.2.5.2.4 单向channel
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程里面调用读取函数
}
2.2.5.3 Timer定时器
  • time.Timer是一个定时器,代表未来的一个单一事件,你可以告诉timer需要等待多长时间。
  • Timer提供一个channel,在定时事件到达之前,没有数据写入time.C会一直阻塞。直到定时事件到,系统会自动向timer.C这个channel中写入当前时间,阻塞即被解除。
//系统中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)
}
2.2.5.3.1 三种延时方法
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)
}
2.2.5.3.2 定时器重置和停止
  • 定时器的停止 Stop()
func main()  {
	myTimer := time.NewTimer(time.Second ) //创建定时器
	go func() {
		<- myTimer.C
		fmt.Println("子go程读取完毕")
	}()
	myTimer.Stop()	//设置定时器停止
	/*
		定时器在主go程中Stop后,上面的语句就不会被打印了
		但是也不会造成死锁。因为这里只是关闭了定时器,并没有关闭channel,
		而channel现在被系统把持着,所以不会造成死锁
	*/
	for  {
		;
	}
}
  • 定时器的重置**Reset() **
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 周期定时器
  • 周期循环定时器,我们使用 : NewTicker 来做,参数是循环间隔时间
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程退出")
}
2.2.5.4 select关键字
  • select是go里面的一个关键字,是用来监听channel中的数据流动的。
  • select的用法和switch类似,但是select的case语句里面必须是一个IO操作.
  • 一个select可以同时监听多个channel,系统会自动判断,如果任意一个语句可以继续执行,那么系统就会从这些case中任意挑选一个来运行。
  • 如果没有一条语句可以执行
    • 如果有default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复
    • 如果没有default语句,那么select语句将会被阻塞,直到至少有一个通信可以进行下去。
  • 在实际开发中,select一般不搭配default使用,因为如果要一直用select监听的时候,有default的select会形成:忙轮询 ,导致cpu没有闲暇来处理真正业务
  • select 自身不带循环机制,需要借助for来实现循环监听
  • break跳出select中的一个case,类似于switch中的用法
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
		}
	}
}
2.2.5.5 死锁
  • 造成死锁的情况大概分为以下几种:
    • 单go程自己死锁
    • go程间channel访问顺序导致死锁
    • 多go程,多channel交叉死锁
  • 在go语言中,尽量不要将互斥锁、读写锁和channel混用 — 容易造成隐性死锁
//死锁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
		}
	}
}
2.2.5.6 互斥锁(sync.Mutex)

​ 在开发过程中,有些操作是互斥的,为了数据一致性,这个时候通常会使用到互斥锁。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  {
		;
	}
}
//现在打印就正常了,不会出现两个人的内容穿插的情况
2.2.5.7 读写锁(sync.RWMutex)
  • 读写锁的用法和互斥锁一样
  • 读写锁在写的时候,会阻塞所有读取操作,在读取的时候不会阻塞其他go程的读取操作
2.2.5.8 条件变量(sync.Cond)
2.2.5.8.1 条件变量方法:Wait()
  • 阻塞等待条件变量满足
  • 释放已经掌握的互斥锁(相当于cond.L.Unlock()) 主意:这两步操作是原子操作,不可阻断
  • 当被唤醒,Wait函数返回时,解除阻塞并重新获取互斥锁。相当于cond.L.Lock()
  • 调用Wait之前,必须先加锁。
  • 条件变量里面的L就是一个锁,可以用来加锁,解锁
2.2.5.8.2 条件变量方法:Signal
  • 唤醒一个阻塞在同一条件变量上面的一个go程(唤醒对端,也就是说唤醒wait挂起的go程)
2.2.5.8.3 条件变量:Broadcast
  • 唤醒所有阻塞在条件变量上面的go程
2.2.5.8.4 完整版的生产者、消费者模型实现
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  {
		;
	}

}
2.2.5.9 网络编程
2.2.5.9.1 socket 编程
  • 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]))
		}
	}
}
2.2.5.9.2 http编程

​ 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)
}

2.2.6 正则表达式基本操作

​ 在go语言中,正则表达式操作需要分两步:

  1. 编译正则表达式
    1. 正则表达式为了防止转义字符带来的麻烦,所以通常使用两个反引号将表达式引起来
    2. 为了让(.)可以匹配换行符,可以使用单行模式
  2. 使用正则匹配
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) }
  1. 在java里面没有反引号表达式带来的麻烦
String str = "an";
str = str.replaceAll("\\","_");  //正则表达式在字符串里面还需要转义
str = str.replaceAll("\\<\\/sub>","");//正则表达式在字符串里面还需要转义

2.2.7 GoLang中的json

​ 说实话,GoLang中的json就优点坑了,原因在于:

  • GoLang中的类没有public、private、protect这些访问权限控制,所以采用的是首字母大小写来控制访问权限,首字母大写表示public权限,首字母小写就只能在包内直接使用了。
  • 而描述对象的json字段,就不一定是首字母大写了,于是GoLang官方就出了一个encoding/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)
	}
    
}


  • 第三方json插件go-simplejson,个人使用后发现,还是没有java的各大json插件好用

你可能感兴趣的:(GoLang,菜鸟笔记,golang)