1,Go介绍

是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

1)环境搭建

①标准包安装

go1.11.2.darwin-amd64.pkg
检查安装成功:在终端输入:go,出现go命令列表。

②配置环境变量

终端输入以下命令。显示go运行所需的系统环境变量。

go env

③安装集成开发工具(LiteIDE、Sublime Text、Vim、Emacs、Eclipse、vscode、goland)

Sublime Text3的gosublime这个插件目前不能用,但是可以在github上下载,相比起来vscode有智能提示所以采用vscode。

goland使用:
goland应该是最好用的,很少的配置,收费,不缺钱可以支持下正版。
i> 官网下载goland。
ii> 新建一个project,选择项目location。
如果go环境安装成功goroot可以选择。然后create项目。
在项目根目录创建directory:main。然后在其下方写一个新的程序:test.go。

package main
import (
       "fmt"
)
func main(){
       fmt.Println("hello world")
}

Control + shift + R运行即可。#

④工作空间

即创建GOPATH目录,并在GOPATH下建立三个目录:

bin:存放编译后生成的可执行文件(.exe)
pkg:存放编译后生成的包文件(.a)
src:存放项目源码(.go)
2,基本语法

1)常量、变量与命名规则

①可见性规则

函数名首字母小写,表示private;
函数名首字母大写,表示public。

②常量 const

运行期间不可改变值。
可以涉及计算,但每一个值必须是编译期就能获得。

1>内置常量

true、false、iota。

iota

初值为0,作为常量组中常量个数的计数器。

2>定义

const PI float32 = 3.1415926
const PI = 3.1415926   //隐式类型常量定义(省略常量类型,根据赋值自动判断类型)
const a,b,c = 1, "Go", 'c'  //整型常量、字符串常量、字符常量 多个常量一起定义

3>枚举

枚举指一系列的常量。

	const (
		Sunday  = iota //0  iota生成了从0开始自动增长的枚举值
		Monday         //1  后续的iota可以省略
		Tuesday        //2
	)
	const (
		A = iota //iota每遇到一个const关键字,就重置为0
		B
	)
	fmt.Println(Sunday);  //0
	fmt.Println(Monday);  //1
	fmt.Println(Tuesday); //2

	fmt.Println("-------");
	fmt.Println(A); //0
	fmt.Println(B); //1

③变量 var

&

1>定义

var count int = 10   //格式为:var <variableName> [variableType]
var count = 10 //缺省
count: = 10  //用:代替var的缺省

2>变量的类型零值

即初始化值。

类型零值
boolfalse
int、float、byte\rune0
complex0+0i
string“”
pointer、function、interface、slice、channel、mapnil

④标识符命名规则

i> 以字母或下划线开始
ii> 由字母、数字、下划线组成
iii> 避免关键字
iv> 大小写区分为不同的变量

2)数据类型

①基本数据类型

1> 布尔型 bool (1 Byte)

取值范围:true | false。

2> 整型

进制(4种):

123  //十进制
0123  //八进制 以0开头
0x123  //十六进制  以0x开头
1e3   //指数形式 由数字和e组成  1*(10^3) = 1000

有无符号整型:

int   //有符号整型
uint //无符号整型

精度控制整数:

^^^^^^^^^^
字节型 byte(1byte) == uint8

本质是uint8的类型,使用效果完全一样。
使用该别名是为了增强代码的可读性,告知现在是进行字节处理。
主要作用:表示和存储ASCII码,即处理字符。

字符型

字符以ASCII的形式存储到内存中。所以一个byte型数据直接输出就是uint8整数形式;也可以将对应的ASCII码转换成相应的字符。

rune类型 == int32

Go语言处理Unicode时专门的数据类型,完全等价于int32。
使用该别名是为了增强代码的可读性,告知现在是进行Unicode字符处理。

3> 浮点型(实数)

表示形式:

1.25
1.23e3 
类型字节长度精确位数
float32 32位浮点型4精确到小数点后7位
float64 64位浮点型8精确到小数点后15位

浮点数存在舍入误差,所以避免极大数和极小数相加减丢失极小数。

4> 复数型

var cp complex64 = 1.2 + 3.4i    //复数:a+bi形式的数(i = 根号-1)。
real(cp)   //即1.2,fmt内置包计算实部的函数
imag(cp)   //即3.4,fmt内置包计算虚部的函数
类型字节长度
complex648
complex12816

5> uintptr 指针类型

uintptr是可以保存指针的无符号整数类型,可以保存32位或64位的指针。根据操作系统决定指针位数。

//一般表现形式:var <指针变量名> * <基类型>   
var i_pointer * int                      //指向整型的变量的指针i_pointer
var i int = 100; i_pointer = &i             //将i的内存地址存放到i_pointer中
fmt.Println( * i_pointer )           //通过指针i_pointer读取对应地址存放的数据,结果输出为100。

使用注意:

→.

②值类型和引用类型

1>值类型

包括:bool、int、float、byte、复数型(complex)、字符串(string)、数组、结构体、错误类型(error)。
值拷贝传递。
字符串(string)、数组、结构体又称为构造类型。

2>引用类型

包括:指针、切片(slice)、字典(map)、通道(channel)、接口(interface)、函数(function)。

③字符串(string)

1> 字符串内容不可变

2> 字符串操作

       var str string = "hello"; //字符串初始化
	fmt.Println(str[2]);      //取字符,以0开始。'A' = 65,'a' = 97, 输出为l =  97+12 = 108
	fmt.Println(len(str));    //len()函数  输出为5
	fmt.Println(str + " world");

3>字符串遍历

支持2种方式。

       var str string = "hello";
	//字节数组方式遍历:(每一个字符类型为byte)
	for i := 0; i < len(str); i++ {
		fmt.Printf("str[%d] = %v\n", i, str[i]);
	}

	fmt.Println("-----------");
	//Unicode字符方式遍历:(每一个字符类型为rune)
	for i, ch := range str {      
		fmt.Printf("str[%d] = %v\n", i, ch);
	}

4>strings 包

Go标准库包。
包含了字符串查找函数、字符串比较函数、字符串位置索引函数、字符串追加和替换函数

5>strconv 包

Go标准库包。
提供了字符串与基本数据类型相互转化的基本函数。

④数组类型

1>定义

数组是一组具有相同类型和名称的变量的集合。

var arr1 [5] int

数组的元素类型必须是基本数据类型。

2>初始化

       var arr1 = [2] int {1,2}
	var arr2 = [2] int {1}
	var arr3 = [...] int {2,2} //...就是三个英文句号。这个不能省略,否则就成切片了

3>多维数组

var a[3][4] int  //二维数组定义
var a = [3][4] int {{1,2,3}, {4,5}, {6}} 

⑤切片(slice)(变长数组)

是数组的一个引用,它会生成一个指向数组的指针,并通过切片长度关联到底层数组部分或者全部元素。
切片还提供了一系列对数组的管理功能,可以随时动态的扩充存储空间,并且可以被随意传递而不会导致所管理的数组元素被重复赋值。
故切片通常用于实现变长数组。

1>切片原型

    type slice struct {
        array unsafe.Pointer      //Pointer 是指向一个数组的指针
        len   int     //len 代表当前切片的长度
        cap   int   //cap 是当前切片的容量。cap 总是大于等于 len 的
    }

2>声明

var slice1 [] int  //不要指定数组长度即切片

3>初始化

默认为nil,长度为0.

var slice1 = [] int {1,2,3,4}
var slice1 = make([] int, 4)  //通过内置函数make创建有4个元素的整型切片,元素初始值为int的初始值0.
var slice1 = make([]int, 4, 10) //预留10个元素的存储空间。

4>操作数组

slice1 = array1         //指定切片引用范围为数组全部
slice1 = array1[ : ]   //指定切片引用范围从第一个元素到最后一个元素
slice1 = array1[m:n]  //指定切片引用范围从第m个元素到第n个元素

5>操作

  • 访问:利用下标
  • 元素增加:append()
  • 元素复制:copy()

⑥字典(Map,Dictionary,哈希表Hash table)

由键值对组成。

1>初始化和创建

未初始化时值为nil。

var map1 map[string] int {}   //创建并初始化。键用[]包起来。
map1["key1"] = 1

或者用make函数

var map1 map[string] int      //创建
map1 = make( map[string] int)   //初始化,给map1分配存储空间

2>访问和操作

查找:

	var map1 = map[string]int{"key1": 100, "key2": 200}
	v, OK := map1["key1"] //查找一个特定的键值对。如果key值存在,则将Key对应的Value值赋予v,OK为true。否则v=0,OK为false。
	if OK {
		fmt.Println(v, OK) //输出:100 true
	} else {
		fmt.Println(v)
	}

删除:

delete(map1,"key1")

⑦通道类型

⑧错误类型

⑨类型别名

使用type关键字为类型定义别名。

	type(
		word uint //定义word是uint的别名
		小数 float32 //Go支持UTF-8编程格式,所以定义时可用非英文字符。
	)
	var f 小数 = 3.14
	var i word = 3
	fmt.Println(f)
	fmt.Println(i)

⑩类型转换

<变量A>[:] = <变量A的类型>(<变量B>)
	var a int
	var f float32 = 3.14
	a = int(f)   //如果变量A已经定义过了,可以省略`:`
	b := int(f)  
	fmt.Println(a)
	fmt.Println(b)

3)运算符与表达式

①分号

用分号来终止语句,但这个分号可以由词法分析器扫描源代码过程中自动插入(给每一行末尾根据简单的规则进行判断是否需要加分号,所以出现了左大括号约定)。

②注释

//
或
/*   */

③位运算符

|
	var a, b byte = 10, 4 //1010

	fmt.Println(a)      //10b
	fmt.Println(^a)     //11110101b   2^8-1-10 = 256-11 = 245
	fmt.Println(a &^ b) //1010b 清除 10b = 1000b = 8 ;1010b 清除 100b = 不变 = 1010b = 10
	fmt.Println(a << 1) //10*2 = 20

④通道运算符(< -)

详细用法可见通道。

4)输入输出

①标准输出函数:

1>Print

2>Println

3>Printf 格式化输出

func Printf( format string, a...interface{} ) (n int, err error)
作用对象格式字符说明
-%v以基本格式输出
-%#v输出数据,同时也输出Go语法表示
-%T输出数据类型
-%%输出%
bool%t输出布尔值true flase
int%b、%d、%o、%x、%X以二进制、十进制、八进制、十六进制(小写)、十六进制(大写) 输出
int%c以Unicode字符格式输出(否则直接输出为ASCII码)
int%q输出的每个字符自动加单引号
int%UUnicode格式:U+1234 == U+%04X
浮点型、复数%b无小数部分、两位指数的科学记数法
浮点型、复数%e、%E科学计数法(小写e、大写E)
浮点型、复数%f有小数部分,但无指数部分
浮点型、复数%g、%G根据实际情况采用%e(%E)或%f
字符串、切片%s直接输出字符串或切片
字符串、切片%q输出字符串的同时加双引号
字符串、切片%x、%X每个字节用两字符的十六进制数表示
指针%p以0x开头的十六进制数表示
-+输出数值正负号对%q(%+q)按ASCII码输出
--使用空格填充右侧空缺(默认为左侧)
-#
-‘ ’
-0用前置0代替空格填补空缺
fmt.Printf("str[%d] = %v\n", i, str[i]);

②标准输入函数

1>Scan

原型:

func Scan(a ...interface{} )( n int, err error )

将使用空格分割的连续数据顺序存入到参数中(换行也被视为空格)。返回参数的数量n。

	var a,b,c int
	fmt.Scan(&a,&b,&c)

2>Scanln

原型:

 func Scanln(a ...interface{} ) ( n int, err error)

同Scan,不同的是Scanln读取到换行符才终止录入。

3>Scanf

原型:

func Scanf( format string, a ...interface{} )( n int, err error}

按照格式化字符读取数据。

       var a,b,c int
	fmt.Scanf("%d,%d %d",&a,&b,&c)  //严格按照格式输入才可以读取,如‘1,2 3’
	fmt.Print(a)
	fmt.Print(b)
	fmt.Print(c)

5)顺序结构程序

复合语句

{}

6)选择结构程序

①if-else

if 表达式1 {
} else if 表达式2 {
} else

注意:if语句块中不允许执行return语句。

②switch

switch 条件表达式 {
case 常量1:
       语句1
      ……
default:
       语句n
}
或者:
switch{
case 条件表达1:
    语句1
      ……
default:
       语句n
}
fallthrough
	var op byte = '+';
	switch op {
	case '+':
		fmt.Println(1);
		fallthrough
	case '-':
		fmt.Println(2)
	}

7)循环结构程序

只有for循环,没有do和while循环。
注意for语句后面没有括号。

好的编程习惯:

  • 循环变量名尽量短,如:i, j, z, ix等。
  • 不要在循环中修改循环变量

①for基本循环结构

      var str string = "hello";
	for i := 0; i < len(str); i++ {
		fmt.Printf("str[%d] = %v\n", i, str[i]);
	}

执行顺序:表达式1,表达式2,表达式4,表达式3

for 表达式1; 表达式2; 表达式3 {
      表达式4
}

②for条件循环结构

类似while循环。

for 条件表达式{
}

③for无限循环结构

用break跳出循环。

for{
}
for true{}
for ; ; {}

④for range语句

相当于迭代器,可以对数组(Array)、切片(Slice)、字典(Map)、通道(Channel)等进行遍历。在遍历时会返回一个键值对。

       var str string = "hello";
	for i, ch := range str { //返回的键值对i,ch  i为下标,ch为值。
		fmt.Printf("str[%d] = %v\n", i, ch);
	}

可以使用占位符过滤掉不需要数据。

for _,ch := range str{}   //只需要值不需要键

for i, _ := range str {}   //只需要键不需要值
for i := range str {}   //只需要键不需要值

⑤跳转语句

1> goto

与LABEL搭配使用。

       var a int = 1;
	goto LABEL1

	a++

	LABEL1:
		fmt.Println(a);

2> break

3> continue

8)函数

①包声明部分

包是组成的Go程序的基本单位。每个Go源代码都要进行包声明说明当前文件属于哪个包。

package <pkgName>

可执行Go程序有且仅有一个main包,有且仅有一个main函数(在main包中)。
源码编译后都会生成*.a文件。

②第三方包导入部分

分为如下三种模式。导入的包没有使用编译会报错(保证代码体积小,加快编译速度)。

1>正常模式

import <pkgName>  

举例:

import "fmt" //导入包
fmt.Println(v, OK) //输出:100 true   //包内函数调用

2>别名模式

包名相近或相同用别名区分。

import 别名 <pkgName>

3>简便模式

import . <pkgName>

③函数声明部分

func为关键字。允许多返回值。

func funcName(参数列表)(返回值列表){
       return result1, result2
}

main函数没有参数也没有返回值,如果要传入参数,可在os.Args变量中保存。

④变参传递

任意数量:

func main() {
	a(1,2,3,4,5);
}
func a(args ... int){ 
	fmt.Println(args) //输出[1 2 3 4 5]
	fmt.Println(args[2:]) //输出[3 4 5] 从索引2开始的数字
}

任意类型的变参,类型指定为空接口interface{}:

func main() {
	a(1,2,"s",4,5);
}
func a(args ... interface{}){
	fmt.Println(args) //输出[1 2 s 4 5]
}

⑤闭包(Closure)

内部函数通过某种方式使其可见范围超出了其定义范围。
在go语言中,闭包可以作为函数对象或匿名函数。确保只要闭包还被使用,那么被闭包引用的变量会一直存在。

⑥defer语句

通过defer向函数注册退出调用,当主调函数退出时,defer后的函数才会被执行(不管是否出现异常都会被执行)。

1>最后调用

	defer fmt.Println("defer ")
	fmt.Println("main ")

输出:
main defer

2>倒序输出

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

输出:3210

3>匿名函数调用

func main() {
	fmt.Println("f1 = ",f1())
	fmt.Println("f2 = ",f2())
}
//一个延迟执行的函数的变量的值在声明时,值不延迟
func f1() int{
	var i int
	defer fmt.Println(i)
	i = 1
	return 1
}
//被延迟的匿名函数会读取f2的返回值,或对f2的返回值赋值
func f2()(i int){
	defer func(){
		i++
	}()
	return 1
}

4>用于清理工作

defer语句常用来进行函数的清理、释放资源等工作。

srcFile,err := os.Open("myFile")
defer srcFile.Close

9)结构体

①使用

Go没有类的概念,通过结构体来实现面向对象编程。

type date struct {//定义
	year int
}
type student struct {
	id int
	name string
	birthday date
}
func main() {
	stu := new(student)
	stu.name = "a"    //访问
	stu.id = 0
	stu.birthday.year = 5

	fmt.Println(stu)
}
stu := student{0,"a",date{1}}  //对象初始化

②嵌入式结构

③匿名字段

④方法

1>定义

在普通的函数名前增加绑定类型参数。


type student struct {
	id int
	name string
	fee int
}
type teacher struct {
	id int
	name string
	fee int
}
func main() {
	stu := student{0,"a",10}
	t1 := teacher{0,"a",10}

	fmt.Println(stu.getFee())
	fmt.Println(t1.getFee())
}
func (revc student) getFee() int{    //rev为变量,student为struct类型。
	return 10+100
}
func (revc teacher) getFee() int{    //类似java中的多态。接收类型不同,方法名可以一样
	return 10+1000
}

如果指针作为一个receiver,那么是一个引用传递。

2>匿名Receiver

3>继承和重写

type people struct {
	id int
	name string
	fee int
}
type student struct {
	people
	school string
}
func main() {
	stu := student{people{0,"a",10},"ut"}

	fmt.Println(stu.getFee())
}
func (revc student) getFee() int{ //重写了方法,隐藏了people的getFee方法
	return 10+100
}
func (revc people) getFee() int{
	return 10+1000
}

4>字段标签

10)接口(Interface)

是一组Method的组合,通过Interface来定义对象的一组行为。

①定义

type Speaker interface {  //关键字:type interface
	sayHi()
}
func (s student)sayHi(){
	
}
func (s student)study(){

}

②接口组合

集合其他接口的功能

type SpeakerLearner interface {
	Speaker
	study()
}

③空接口

表示任何数据类型。

interface{}

④使用

⑤反射

3,包

1)fmt

是format缩写。内置基本包。

Package fmt implements formatted I/O with functions analogous to C’s printf and scanf. The format ‘verbs’ are derived from C’s but are simpler.

2)math

①Min、Max

Max(int8)  Min(int8)  //数据的最大值最小值

3)strings包

4)strconv包

5)bytes包(字节切片标准库)

提供了对字节切片进行读写操作的一系列函数和方法。包括:字节切片处理函数、Buffer对象和Reader对象,类似于strings包。

6)bufio包

实现了对数据I/O接口的缓冲功能。
封装于接口io.ReadWriter、io.Reader和io.Writer中,在提供缓冲的同时实现了一些文本的基本I/O操作功能。

4,加强

1)内存分配机制

①源码文件

UTF-8格式。

②GC

标记-清除算法。

③内存函数

GO有两种分配内存机制,分别是使用内置函数new()和make()。

1>new()

用于给值类型的数据分配内存,调用成功后返回一个初始化的内存块指针,同时该类型被初始化为“0”.

2>make()

用于给引用类型分配内存空间。
make函数创建的是一个引用类型对象,而不是一个内存空间的指针。

2)并发程序设计

①基本概念

1>OS提供的并发基础

进程、
线程、
协程(用户态线程):不需要OS进行抢占式调度,在真正的实现中寄存于线程。

2>程序间的并发通信

  1. 锁原语操作
  2. 信号量机制
  3. 共享内存(Shared Memory)
    多个并发单位在同时访问共享内存时,必须使用互斥锁等相关机制,保证对共享内存的互斥访问。这早就程序设计复杂。
  4. 消息通信机制(Message Communication Mechanism)
    Go语言在处理程序的并发模型时,主要采用消息机制。
    消息机制规定每个并发单位是自包含的、独立的个体,并且都有自己的变量,这些变量不能在不同的并发单位之间共享。每个并发单位的输入输出只有一种:消息,即Channel。(类似进程的感觉)

②Goroutine(轻量级线程)

是Go语言运行库的功能(由Go运行时管理(Runtime)),不是OS提供的功能(不是用线程实现的,所以支持跨平台)。
Goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。因为节省了频繁创建和销毁线程的开销,所以对于进程、线程的开销非常小。可以轻松创建上百万个Goroutine,但它们不是被OS所调度执行的。
Go语言标准库提供的所有系统调用操作(包括同步I/O操作),都会让出处理机给其他Goroutine。

1>创建(与Channel一起使用)

go func()
import (
	"fmt"
	"math/rand"
)
func Test(ch chan int){
	ch <- rand.Int()  //随机数获取并写入
	fmt.Println("Test")
}

func main() {
	chs := make([]chan int , 10) //定义一个包含10个Channel的数组chs
	for i:=0;i<10;i++{
		chs[i] = make(chan int)
		go Test(chs[i]) //启动10个Goroutine,对应每一个Goroutin分配一个Channel
	}
	for _,ch := range chs{
		value := <-ch     //从通道读取数据
		fmt.Println(value)
	}
	fmt.Println("main")  //因为通道的写入和读取都是阻塞的,从而保证了即使没有锁,所有的Goroutin执行完后才main才返回。即:main总是在最后输出的。
}

③通道(Channel)

1>概念

是Go提供的消息通信机制。主要用于并发通信,类似于单双向数据管道(Pipe),用户可以通过Channel在两个或多个Goroutine之间传递消息。

注意:

  1. 同一时刻只有一个Goroutine能从Channel中接收数据,这就避免了互斥锁的问题。
  2. Channel中数据的发送和接收都是原语操作,不会中断,只会失败。

2>声明和初始化

Channel是引用类型,一个Channel只能传递一种类型的值,这个类型需要在声明Channel时指定。
支持的类型:基本类型、指针、Array、Slice、Map

var chanName chan ElementType
var ch chan int   //一个传递int类型的Channel。chan为关键字。
ch := make(chan int)   //用make函数直接声明

3>数据接收和发送

ch < - value      //通道接收数据。ch表示通道,value表示数据。
value = < - ch  //通道发送数据

注意:
3. 向Channel写入数据通常会导致程序阻塞,直到有其他Goroutine从这个Channel中读取数据。
4. 如果Channel中之前没有写入数据,那么从Channel中读取数据也会导致阻塞,直到Channel中被写入数据为止。

4>close()

throw : all goroutines are asleep-deadlock!

5>单向通道

声明时可以将Chanel制定为单向通道:只能接收或只能发送。

var chanName chan <- ElementType  //只能接收
var chanName <- chan ElementType //只能发送

6>异步通道

给Channel设定一个Buffer值,用于持续传输大量数据。
在Buffer未写满之前,不阻塞发送操作(即使没有读取方,发送方也不断写入数据);
在Buffer未读完之前,不阻塞接收操作。

ch := make(chan int, 1024) //创建一个大小为1024的int类型Channel。

7>select机制

主要用于解决通道通信中的多路复用问题。

select语句(类似switch):

select{
	case <- chan1:   //如果 chan1 成功读取数据,则进行该case处理语句
	case <- chan2:  
	……
	default:
}

select直接检测case语句,每个case语句必须是一个面向Channel的操作.
只要其中一个case完成,程序就会继续向下执行。利用这个特性可以可以为Channe实现超时处理功能。

8>超时机制

由于通道的接收是阻塞式的,为了将阻塞式的通信转换为非阻塞式,将select机制和超时机制配合使用,以提高系统通信效率。
在Go语言并发编程的通信过程中,所有错误处理都由超时机制来完成。
超时机制一般用来解决通信死锁,通常设置一个超时参数,通信双方如果在设定的时间内仍然没处理完成任务,则该处理过程会立即被终止,并返回对应的超时信息。
Go没有提供直接的超时处理机制。利用select机制可以实现。

3)网络编程

传统的Socket网络编程分为:流式套接字(基于TCP)、数据报套接字(基于UDP)、原始式套接字(基于IP)。

①Dial()

Go语言对socket进行了封装,无论期望用什么协议都只需要调用Dial函数即可。
支持网络协议:tcp、tcp4、tcp6、udp、udp4、udp6、ip、ip4、ip6。
创建连接:

func Dial ( net, addr string) (Conn, error)  //net是网络协议名,addr是IP地址或域名,后面可跟随端口号。
conn,err := net.Dial ("tcp", "192.168.0.1:5000")
conn,err := net.Dial ("ip4:icmp", "www.baidu.com")

发送数据:使用conn对象的Write()方法
接收数据:使用conn对象的Read()方法