go语言初解
go 语言初解
注释
- 注释用于说明不太容易理解的业务和逻辑,多写注释。
- c++注释方法 //
- c注释方法 /* */
变量
-
电脑识别的是机器码,程序是和计算机的沟通桥梁【go程序--汇编--机器码】
-
程序是一些定义的东西
// 定义一个名字的变量 // string 字符串类型 var name string = "go语言" 或者 name := "go语言" // int 数字类型 var age int = 12 或者 age := 12
-
go语言默认零值可用 -- 定义可以不用赋值但必须使用
-
变量交换
理解取地址符 &
-
匿名变量 -- 任何赋值给匿名变量的值都会被丢弃
_ 下划线 例如 if err,_ := func()(error, int)
-
全局变量只能用
var
定义
占位符
常用
- 详细参考fmt库的内容
1. %d 数字
2. %p 内存地址
3. %s 字符串
4. \n 换行
5. %t 布尔
6. %T 类型
7. %f 浮点数 %.1f 保留两位小数
常量
const
定义常量(建议大写),如若改变常量内容,需要进入内存修改
iota
特殊的常量计数器,在一组const中,自动加1
基本数据类型
字符串类型
- unicode编码
转义字符
数据类型转换
a := 5.0 // float
b := int(a) // b int类型的a=5
c := 1 // int
d := float64(c) // d float64
flag := true //bool类型不能转换成数值类型
运算符
- 算术运算符
- [ + - * / ++ -- % ]
- 关系运算符
- [ > < == != >= <= ] 布尔值
- 逻辑运算符
- [ &&与 ||或 !非 ]
- 位运算符
- 二进制、加密解密 [&按位与 |按位或 ^按位异或]
- &^位清空
- <<左移运算符
- 右移运算符 >>
- 赋值运算符
- =
- +=
- -=
- *=
- /=
- %=
- <<=
- 右移后赋值 >>=
- &=
- ^=
- |=
- 其他运算符
- & 返回变量存储地址
- 指针变量 *
流程控制 (参考c语言)
顺序结构:自上而下运行
选择结构:满足某些条件才会执行
if
switch:case
循环结构
for
面向过程编程 (参考c语言中的函数)
自上而下而下的执行
函数区别于面向对象中的方法
- go语言中的可变参数
//可变参数:函数的参数类型可以确定,参数的个数不确定
func varg(num ...any)(any)
- defer
// 函数被延迟执行
defer func();
当多个defer添加,在退出当前接口时,defer会进行逆序
执行
主要用来处理 错误、文件、网络流关闭等的操作
- go语言中函数可以进行赋值给另一个函数,可以看作一个特殊变量,本质上是一段代码的地址
- 匿名函数
func() {
fmt.println("我是匿名函数")
}()
- 回调函数
高阶函数:可以将一个函数作为另一个函数的参数
func1() func2(func1)
func1函数作为func2函数的参数
func2函数,叫做高阶函数,接受了另外一个函数作为参数的函数
func1函数,叫做回调函数,作为另外一个函数的参数 - 闭包
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部
并且该外层函数的返回值就是这个内层函数。这个内层函数和外层函数的局部变量,称为闭包结构。
闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用。 - 函数中的参数传递
- 值类型 (拷贝) int string bool float64 array
- 引用类型 (操作数据的地址) 切片slice map chan
切片
是对数组的操作
[数组是定义的数据,切片是对数组的操作]
data := make([]slice,len,cap)
append(data, slice1)
append(data, slice2)
map
map是一种无序的键值对
map := make(map[any]any) | &map{key:value}
delete(map,key)
- 遍历
for k,v := range map{ fmt.Println(k, v) }
指针
&data // 取地址
*data // 取地址的值
// 指针的套娃 *指针类型 *(*int)-->*int是指针对应的类型
var ptr **int
// 数组指针 --> 指针
var ptr *[10]int
*ptr[1] = 1 //语法糖 ptr[1] = 1
// 指针数组 --> 数组
var arr [2]*int{&a,&b}
*arr[0] = 0
// 指针函数 --> 函数
func ptr() *int{return &a} // 返回一个指针
结构体
// 结构体名称首写字母小写-->privte 首写字母大写-->public
type User struct{
name string
age int
sex string
}
// 结构体内容名称首字母大小写同结构体相同
// 内置函数 new 创建对象。返回指针,而不是结构体对象
面向对象编程
将世界抽象为一个个的对象,然后用程序模拟这些对象,然后进行一些人工化的操作实现
- go语言不是面向对象语言,但可以通过一些方法来模拟面向对象
封装
- 模版 -- 产品
继承
- 父子
- 父类更通用,子类更具体
- 子类会具有父类的一般特性也会有具有自身的特性
type Person struct{}
// 继承
type User struct{
Person
}
多态
- 一个事物可能拥有多种形态: 动物(猫、狗)
方法
需要指定调用者,约定这个方法属于谁的 对象.方法()
- 函数:不需要指定调用者,定义了函数就可以直接函数名()调用
type Dog struct {
name string
age int
}
// 方法定义 ,需要增加一个调用者
func (dog Dog) eat() {
fmt.Println("我是狗的方法-->吃东西")
}
func main() {
dog := new(Dog)
dog.eat()
}
//函数
func eat(){
fmt.Println("我是函数吃东西")
}
方法的重写
- 需要和继承集合
- 子类可以重写父类的方法 override
- 子类还可以新增自己的方法
- 子类可以访问父类中的属性和方法
接口
- 将一些共性的方法集合在一起。如果有实习类将接口定义的方法
全部实现了
,那么代表实现了这个接口
type j struct {
}
func (j j) input() {
println("输入")
}
func (j j) output() {
println("输出")
}
// 接口是方法的集合
type i interface {
input()
output()
}
// 测试接口
func test(j j) {
j.input()
j.output()
}
func main() {
// 通过传入接口实现类来进行调用
s := j{}
test(s)
}
- 空接口:所有结构体都默认实现了空接口,空接口可以存储任何类型
interface{} == any
接口断言
- 检查一个接口类型的变量是不是符合预期值
断言 t:=i.(T) t:t就是i接口是T类型的 i:接口 T:类型
-
// 判断一个变量的类型 func asserts(i any) { v, ok := i.(string) if ok { fmt.Println("类型是 int") fmt.Println(v) } } // 判断一个变量的类型 func asserts(i any) { switch i.(type) { case string: fmt.Println("类型为string类型") case int: fmt.Println("类型为int类型") case bool: fmt.Println("类型为bool类型") case nil: fmt.Println("类型为nil类型") default: fmt.Println("不知道的类型") } }
异常和错误
- 错误:指的是程序中预期结果会发生的结果、预料之中
- 打开一个文件:文件正在被占用,可以预知
- 异常:不该出现问题的地方出现了问题,预料之外
- 调用一个对象,发现这个对象是个空指针,发生错误
错误是业务的一部分,异常则不是
errors.New("自己定义的错误")
strconv
- 字符串转换
string convert
time
- 获取当前时间
- 格式化
- 定时
package time
const DateTime = "2006-01-02 15:04:05"
// 使用
println(time.Now().Format(time.DateTime))
//随机数
// 时间戳
timestamp := time.Now().Unix()
// 设置随机数种子,使用时间戳,种子只需要设置一次
rand.Seed(timestamp) // 每次执行都不同
定时器 - 本质是一个通道
- time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间。以纳秒为单位,可表示最长时间段大约290年。
- time包中定义的时间间隔类型常量如下
const( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute ) // time.Duration 表示1纳秒 // time.Second 表示1秒
-
// 定时器: 每隔xxx s执行一次 ticker := time.Tick(time.Second) // 每一秒都会触发 for i := range ticker { fmt.Println(i) }
I/O
获取文件信息file
- 计算机中的文件是存储在外部介质上的数据,文件分为
文本文件
和二进制文件
- file类在os包中,封装了底层的文件描述符和相关信息,同时封装了Read:读和Write:写的实现
- 获取文件信息
- 创建目录,创建文件
- IO读写
- 问价复制
- 断点续传
- bufio
FileInfo 接口 os.stat() //获取文件信息
fileinfo,_ := os.Open("文件地址") fileinfo.Name() fileinfo.IsDir() fileinfo.ModTime() fileinfo.Size() fileinfo.Mode()
- 创建文件、目录
os.Mkdir()
os.Create()
- 路径
- 相对路径:相对当前目录的路径
- ./ 当前目录
- ../ 上一级目录
- 绝对路径
- 从盘符开始的路径
IO读
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_RDONLY, os.ModePerm)
- 读取file.Read([]byte),将file中的数据读取到[]byte中,n,err n读取到的行数,err错误,EOF错误,就代表文件读取完毕了一直调用read,就代表光标往后移动...
IO写(权限问题)
- 建立连接()
- 关闭连接
- 写入file.write
Seeker接口
- 设置光标的位置
const( SeekStart = 0 // 表示相对于文件开头 SeekCurrent = 1 // 表示相对于当前光标所在位置 SeekEnd = 2 // 表示相对于文件的末尾 ) type Seeker interface{ //1.offset偏移量 //2.whence 如何设置,当前光标的位置 0,1,2 Seek(offset int64, whence int) (int64, error) }
断点续传
遍历文件夹
bufio (io缓冲区)
Groutine
进程
- 程序执行的过程
线程
- 一个进程中可以有多个线程
协程
- 一种用户态的轻量级线程
Groutine
- 当新的Groutine开始时,Groutine调用立即返回。
- 当Groutine调用,并且Groutine的任何返回值被忽略之后,go立即执行到下一行代码
- main的Groutine应该为其他的Groutine执行。如果main的Groutine终止了,程序将被终止,而其他Groutine将不会运行。
runtime包
- 获取系统信息
channel
不通过共享内存来通信(锁),而应该通过通信来共享内存
var ch chan bool ch = make(chan bool) go func() { ch <- true //将数据放进通道 }() data := <-ch // 将数据从通道中取出 fmt.Println(data)
- 缓冲通道
- 通道带了一个缓冲区,发送的数据直到缓冲区满为止,才会被阻塞,接收的也是,只有缓冲区清空,才会阻塞
ch := make(chan string, 5)
- 定向通道
- 双向通道
- channel是用来实现gorutine
通信的。一个写,一个读,这是双向通道
ch <- data data := <- ch
- channel是用来实现gorutine
- 单向通道
ch := make(chan <- int, 1) // 只能写数据,不能读 ch := make(<- chan int, 1) // 只能读数据,不能写
- 双向通道
- select
// select 在通道中使用 select { case num1 := <- ch1: fmt.Println(num1) case num2 := <- ch2: fmt.Println(num2) default: fmt.Println("default") } // 如果有多个case都可以运行,select是随机选取一个执行
反射
- 正常开发很少用到。因为效率低下
- 开发一些脚手架,自动实现一些底层的判断
- interface{} = any,由于这种动态类型是不确定的,我们可能在底层代码进行判断,从而选择使用什么来处理
反射机制可以在程序运行的过程中,获取信息(变量:类型、值。结构体:字段、方法)
我们可以通过反射来修改变量和值、通过反射来调用方法
reflect包
- reflect.Type类型 reflect.Value值
- 类型Type(在编程中我们使用Type)、种类:Kind(反射更多时候用kind来区分)
- 数据类型一般用kind
- 在反射过程中,编译的时候知道变量类型的就是静态、如果在运行时候才知道类型的就是动态
- go是静态类型
- 动态类型:在运行的时候可能发生变化,主要考虑赋值问题
为什么需要反射?
- 编写一个函数,但不知道函数传递给我的参数是什么?没有约定好,传入的类型太多,这些类型不能统一表示,反射
- 某些时候,需要根据条件判断来具体使用那个函数处理问题,根据用户输入来决定,这时候需要对函数进行反射,在运行期间动态处理。
为什么不建议使用反射? - 和反射相关的代码,不方便阅读,开发中,代码可读性很重要
- go是静态类型语言,编译器可以找出开发时候的错误,如果代码中有大量的反射代码,随时可能存在安全问题,panic,项目就终止
- 反射性能很低,相对于正常开发,至少慢2-3个数量级。项目关键位置,一定不能使用发射。更多时候使用约定。
反射获取变量信息
- reflect.TypeOf(v):获取变量的类型Type
- Type.kind,找到种类
- Type.NumField(),找到里面的字段数量
- Type.Filed(i)
- Type.NumMethod(),找到里面的方法的数量
- Type.Method(i)
- reflect.ValueOf(v):获取变量的值Value
- Value.Field(i).Interface()
type User struct {
Name string
Age int
Sex string
}
func (user User) Say(msg string) {
fmt.Println("User 说:", msg)
}
func (user User) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s", user.Name, user.Age, user.Sex)
}
func main() {
user := User{Name: "dadsda", Age: 92, Sex: "男"}
reflectGetInfo(user)
}
func reflectGetInfo(v interface{}) {
//1.获取参数的类型
getType := reflect.TypeOf(v)
fmt.Println(getType.Name())
fmt.Println(getType.Kind())
// 获取值
getValue := reflect.ValueOf(v)
fmt.Println(getValue)
// 获取字段
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface() //value
//打印
fmt.Printf("字段名:%s,字段类型:%s,字段值:%s\n", field.Name, field.Type, value)
}
// 获取这个结构体方法
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Printf("方法名字:%s,方法类型:%s\n", method.Name, method.Type)
}
}
反射修改变量信息
func main() {
var num float64 = 3.24
update(&num)
fmt.Println(&num)
}
// 通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象
func update(v any) {
pointer := reflect.ValueOf(v)
newValue := pointer.Elem()
fmt.Println("类型:", newValue.Type())
fmt.Println("判断该类型是否可以修改:", newValue.CanSet())
if newValue.Kind() == reflect.Float64 {
//通过反射对象给变量赋值
newValue.SetFloat(2.21)
}
if newValue.Kind() == reflect.Int {
newValue.SetInt(2)
}
}
泛型(1.18)
- go在迭代优化功能
- 泛型很少使用
//泛型也是使用[]
func printSlice[T any](s []T){
for _, v := range s{
fmt.Println(v)
}
}
/// printSlice,[T any]泛型约束|我们不确定这个函数的参数类型,希望用户传递这个值
print[T string](name T)
print[T int|string](name T)
func Min[T int|int8|int16|int32|int64](a,b T){}
- 泛型的作用:
- 减少重复性代码。有相同逻辑可以使用泛型来简化
- 在1.18版本之前用反射实现。泛型不能完全取代反射
- 泛型:类型不确定
- 类型推断(简化开发)
网络编程
- 网络编程-web开发!
- 一切的网络开发,本质上就是服务的请求与响应处理
- 静态web
- html/css
- 提供给所有人看的,数据几乎不发生变化
- 动态web
- 提供给所有人的数据都不一致,会分生变化。不同时间不同地点都可能不同
- web应用程序:指的是可以给浏览器访问的程序
- B/S
- C/S
- HTTP
- 文本:string、txt
- http:超文本传输协议(图片、视频、音频、定位、地图)。建立在TCP之上,默认端口:80
- https:安全http协议。(加上了一个ssl,保证安全)。默认端口:443
- 请求和响应