Golang 基础之一
文件目录
- GOROOT Go的主程序目录
- GOPATH Go的第三方组件目录
- 项目目录
初始化一个项目,名称为 hello
系统环境需要先添加key-value: GO111MODULE=auto 或 GO111MODULE=on
go mod init hello
包
- 每一个应用程序都应包含一个main包: package main
- 目录下的所有文件名可以任意,但 package 名必须与所在目录一直
import "fmt"
import (
"fmt"
"os"
"net/http"
"github.com/gorilla/websocket"
)
编译与执行
go run hello.go
go build hello.go
go install
go get
常量与变量
- 常量用const关键字定义,变量用var关键字定义
- Go语言不允许出现未被使用的变量
- 常量类型编译器会自动判断,变量必须显式指定类型
- 一般使用
fmt包的Scanln()放法从键盘获取输入,也可用 bufio 的 Reader 对象从 os.stdin 读取
const Pi = 3.14159
var a int
var b float
# 不能以 x := nil (无类型)初始化变量,会报错:use of untyped nil,非要使用,写法如下:
var x interface{} = nil
# 普通输入
fmt.Scanln(&a, &b)
# 输入时必须按格式来,包含逗号
fmt.Scanf("%d,%f", &a, &b)
reader := bufio.NewReader(os.stdin)
# 以回车作为读取结束
res := reader.ReadString('\n')
- 常量批量赋值
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6
const (
a = 0
b = 1
)
const (
a = iota //iota每遇到一次const自动归零,a=0
b //b=1, iota=1
c //c=2, iota=2
d = "haha" //d="haha", iota=3
e //e="haha", iota=4
f = 10 //f=10, iota=5
g //g=10, iota=6
h = iota //h=iota=7
i //i=iota=8
)
const (
j = iota //j=0
)
- 变量批量定义与赋值
var n int64 = 2
var a, b, c = 5, 7, "abc"
var (
a = 15
b = false
)
a := true
a, b, c := 5, 7, "abc" //多变量一次性定义并赋值 `:=` ,左边需至少有一个变量未被定义
内置函数
| 名称 | 说明 |
|---|---|
| close | 用于关闭管道 |
| len | 返回某个类型的长度或数量(字符串、数组、切片、map 和管道) |
| cap | 返回某个类型的最大容量(只能用于切片和 map) |
| new | 为值类型和用户定义的类型分配内存,返回地址 |
| make | 为内置引用类型(slice、map、chan)分配内存,返回值 |
| copy、append | 用于复制和连接切片 |
| panic、recover | 两者均用于错误处理机制 |
| print、println | 底层打印函数 |
| complex、real imag | 用于创建和操作复数 |
数组
-
在golang中,array和struct都是值类型的,而slice、map、chan是引用类型。所以我们写代码的时候,基本不使用array,而是用slice代替它,对于struct则尽量使用指针,这样避免传递变量时复制数据的时间和空间消耗,也避免了无法修改原数据的情况。
-
使用 Copy() 函数进行引用类型的值(深)拷贝
-
定义与赋值
var a = []int
var b = [3]byte{'a', 'b', 99}
c := []string{"abc", "def"}
d := new([6]float32) //值类型可以用new()初始化,需指定长度
- 数组切片
//start_length 初始长度(一般为0)
//capacity (相关数组)最大长度
slice0 := make([]type, start_length, capacity)
arr := [6]int{1,2,3,4,5,6}
slice1 := arr[2:5] 索引>=2, <5
slice2 := arr[:5] 索引<5
map类型
- map即键值对类型
- 要比数组切片慢,但可以动态增加。
- 未初始化的 map 的值是 nil
- 可以被for...range循环
- key 可以是任意可以用 == 或者 != 操作符比较的类型
- key 也可以是指针和接口类型
- key 不能是数组、切片和结构体
- value 可以是任意类型
var map1 map[string]int //key类型string,值类型int
//或
map1 := make(map[string]float32, lenth) //长度lenth选填
- map遍历
for key, value := range map1{
}
- map判断key的值是否存在
if a, ok := map1[key1]; ok {
//a是value
//如果key1存在则ok == true,否在ok为false
}
- map删除键值
delete(map1, key1) //键不存在也不会出错
map底层使用了array存储数据,并且没有容量限制,随着map元素的增多,需要创建更大的array来存储数据,那么之前的地址就无效了,因为数据被复制到了新的更大的array中,所以不允许直接修改map中的元素,需要通过指针来修改。
type person struct {
name string
age byte
isDead bool
}
func main() {
p1 := &person{name: "zzy", age: 100}
p2 := &person{name: "dj", age: 99}
p3 := &person{name: "px", age: 20}
people := map[string]*person{
p1.name: p1,
p2.name: p2,
p3.name: p3,
}
whoIsDead(people)
if p3.isDead {
fmt.Println("who is dead?", p3.name)
}
}
func whoIsDead(people map[string]*person) {
for name, _ := range people {
if people[name].age < 50 {
people[name].isDead = true
}
}
}
- map无法直接排序,需先取所有的key到数组里,用sort.String()排序,再通过数组映射map。
keys := make([]string, len(map1))
i := 0
for k, _ := range map1 {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("Key: %v, Value: %v / ", k, map1[k])
}
字符串
- 字符必须以单引号包含, 字符串必须用双引号包含
- 基本定义与拼接
s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 “hello, world!”
- bytes.Buffer 高效率拼接
var buf bytes.Buffer
buf.WriteString("12312")
buf.WriteString("werwer")
buf.String()
buf := bytes.Buffer{}
b := []byte{97, 98}
c := []byte{99, 100}
buf.Write(b)
buf.Write(c)
buf.Bytes() //返回 []byte{ 97, 98, 99 100 }
buf.String() //返回 "abcd"
- Strings.Builder() 在一次性分配好足够的内存下性能比bytes.Buffer好
func StringBuilder(p []string) string {
var b strings.Builder
l:=len(p)
for i:=0;i<l;i++{
b.WriteString(p[i])
}
return b.String()
}
- 字符串和字符数组共享内存
bytes := []byte("I am byte array !") //字符串转字符数组 () 不是 {}
str := (*string)(unsafe.Pointer(&bytes))
bytes[0] = 'i'
fmt.Println(*str)
- Sprintf输出字符串变量
var s string = "12312sf"
s = fmt.Sprintf("%s %s",s,"123123")
- strings.Join 处理字符串切片数组效率最好
s := "1231"
strings.Join([]string{s,"ewrwer"},"")
- append
var s []string
s = append(s,"123123")
strings.Join(s,"")
字符串与数字转换
//string到int
int,err:=strconv.Atoi(string)
//string到int64
int64, err := strconv.ParseInt(string, 10, 64)
//int到string
string:=strconv.Itoa(int)
//int64到string
string:=strconv.FormatInt(int64,10)
遍历含中文的字符串
- byte 类型以单字节 ASCII 码方式处理字符串,只能存储 ASCII 码
- rune 类型以四字节 utf-8 方式处理字符串,
[]rune("hello 中文")可实现字符串转字符切片,可存储 Unicode 全字符
func traverseStrings() {
//方式一:传统方法
var str string = "hello,world!北京"
str_cpy := []rune(str) //把str转成切片[]rune(str),可以解决字符串的中文乱码(中文字符是占3个字节)
for i := 0; i < len(str_cpy); i++ {
fmt.Printf("%c\n", str_cpy[i])
}
//方式二:for..range
str1 := "ABCDEFG上海"
//用这种方式不会出现乱码
for index, val := range str1 {
fmt.Printf("index = %d, val = %c \n", index, val)
}
}
控制结构
- 循环
循环可遍历array、slice、map等结构
循环内部是值拷贝,因此无法直接修改原变量,如需修改,则应当循环地址的集合。
for {
}
for i:=1; i<10; i++ {
}
for i := range arr {
}
- 判断
if val := 10; val > max {
// do something
}
if condition1 {
} else if condition2 {
}else {
}
- switch
switch i {
case 0:
…………
case 1:
…………
default:
…………
}
函数
- 函数参数也必须指定参数类型和返回值类型:
func a(n int) float32 {
return float32(n)
}
- 函数可以传变长参数, 参数可被range循环:
func b(n ...int){
for i := range n{
}
}
- 函数可以返回多个值:
func c(n int) (int, float){
return 1, 3.14
}
d, e := c(1)
自定义类型 struct
- 相当于定义一个类(名)和类里的属性
type Person struct{
name string
age int
}
- 结构体打tag
type Person struct{
name string `json:"user_name"`
age int `json:"user_age"`
}
//转成json后会变成
{
user_name : "xxx",
user_age : "xx"
}
- 编写类方法
func (p *Person) sayName(){
println("name is " + p.name)
return
}
- 函数内简单实例化一个类
zhangSan := &Person{
name : "张三",
age : 20,
}
- 构造函数实例化一个类
func NewPerson(name string, age int) *Person{
if age <= 0 {
panic("error age")
}
return &Person{
name:name,
age:age,
}
}
- 调用实例方法
zhangSan = NewPerson("张三", 12)
zhangSan.sayName()
- go没有static关键字,也没有静态方法或属性
管道与异步
- 无缓冲区的管道读和写都会阻塞。
- 有缓冲区的管道已满再写入,或已空再读取,才会阻塞。
- 管道是不同线程之间数据通信的通道,原型为队列。
- 关键字 go 用来异步执行函数。
创建管道:
// 无缓冲管道(双向管道)
ch1 := make(chan int)
// 有缓冲(容量)管道(双向管道)
ch2 := make(chan int, 10)
// 只读管道(单向管道)
ch3 := make(<- chan int)
// 只写管道(单向管道)
ch4 := make(chan <- int)
// 定义函数只写管道参数,可传入双向管道,但函数内只能写不能读
func writePipe(ch chan <- int){}
- 写入数据到管道
ch1 <- 1
- 从管道取出一个数据
b, ok := <- ch1
//ok 为 true 表示通道正常,为 false 表示通道已关闭
- 关闭管道
defer close(ch1)
- 遍历管道数据
- 通过显式的比较管道取出的状态值是否为false判断管道已关闭,也可通过放入——取出一个约定的值判断
// 无阻塞方式遍历,适用于有缓冲管道
for {
if value, ok := <- ch1; ok == true {
println(value)
}
}
// 或
for value := range ch1 {
println(value)
}
- 一个函数内不允许同时出现对同一管道的读和写,否则会出现 deadlock 死锁,因为读一个空的管道,或写一个满的管道,程序都会阻塞,无法继续执行下去,必须用关键字 go 开启新的线程处理读或写。
- 一个无缓冲管道也可以实现数据同步传递
package main
import "time"
func aa(ch chan int){
for{
if value, ok := <- ch; ok == true {
println(value)
}
}
}
func main() {
ch1 := make(chan int)
go aa(ch1)
go func(ch chan int){
for i:=0;i<10;i++ {
ch <- i
}
}(ch1)
time.Sleep(1 * time.Second)
}
- 主函数不会等待异步函数结束才退出,使用 WaitGroup 正确退出
var wg sync.WaitGroup
func aa() {
………………
wg.Done()
return
}
func main() {
wg.Add(1)
go aa()
wg.Wait()
}
- select 切换协程
func suck(ch1, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("Received on channel 1: %d\n", v)
case v := <-ch2:
fmt.Printf("Received on channel 2: %d\n", v)
}
}
}
- select 与 sync 综合示例
package main
import (
"strconv"
"sync"
)
var wg sync.WaitGroup
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
ch3 := make(chan int)
wg.Add(4)
go foo1(ch1, 10)
go foo1(ch2, 20)
go foo2(ch1, ch2, ch3)
go foo3(ch3)
wg.Wait()
}
func foo1(ch chan int, n int) {
i := 1
for ; i <= n; i++ {
ch <- i
}
ch <- -1
close(ch)
wg.Done()
}
func foo2(ch1, ch2, ch3 chan int) {
close1 := false
close2 := false
for {
select {
case v, ok := <-ch1:
if ok && v > 0 {
ch3 <- v
} else if v == -1 {
println("ch1 Closed")
close1 = true
}
case v, ok := <-ch2:
if ok && v > 0 {
ch3 <- v
} else if v == -1 {
println("ch2 Closed")
close2 = true
}
}
if close1 && close2 {
break
}
}
close(ch3)
wg.Done()
}
// 有返回值的函数,在 goroutine 中返回值会被抛弃
func foo3(ch chan int) int {
count := 0
for {
if v, ok := <-ch; ok == true {
count++
println("ch3: " + strconv.Itoa(v))
} else if ok == false {
break
}
}
println("Count: " + strconv.Itoa(count))
wg.Done()
return count
}
接口概念 interface
- 实现一个功能的函数集合
- 当某个功能方法需要以实现了某个接口的实例为参数时使用
- 接口可以被多种类实现
空接口
- 一般用Any命名 : type Any interface
- 可以给一个空接口类型的变量 var val interface { } 赋任何类型的值。

浙公网安备 33010602011771号