是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>变量的类型零值
即初始化值。
类型 | 零值 |
---|---|
bool | false |
int、float、byte\rune | 0 |
complex | 0+0i |
string | “” |
pointer、function、interface、slice、channel、map | nil |
④标识符命名规则
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内置包计算虚部的函数
类型 | 字节长度 |
---|---|
complex64 | 8 |
complex128 | 16 |
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 | %U | Unicode格式: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操作功能。
1)内存分配机制
①源码文件
UTF-8格式。
②GC
标记-清除算法。
③内存函数
GO有两种分配内存机制,分别是使用内置函数new()和make()。
1>new()
用于给值类型的数据分配内存,调用成功后返回一个初始化的内存块指针,同时该类型被初始化为“0”.
2>make()
用于给引用类型分配内存空间。
make函数创建的是一个引用类型对象,而不是一个内存空间的指针。
2)并发程序设计
①基本概念
1>OS提供的并发基础
进程、
线程、
协程(用户态线程):不需要OS进行抢占式调度,在真正的实现中寄存于线程。
2>程序间的并发通信
- 锁原语操作
- 信号量机制
- 共享内存(Shared Memory)
多个并发单位在同时访问共享内存时,必须使用互斥锁等相关机制,保证对共享内存的互斥访问。这早就程序设计复杂。 - 消息通信机制(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之间传递消息。
注意:
- 同一时刻只有一个Goroutine能从Channel中接收数据,这就避免了互斥锁的问题。
- 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()方法