Go学习笔记
【摘要】 概念基础07年出现go语言的优势:均衡(开发效率高,运行性能好),强大的并发能力go语言的优势:均衡(开发效率高,运行性能好),强大的并发能力开发语言:c c++ java go python jsgoroot go安装路径gopath go代码目录go build (-o 文件名.exe) 编译成可执行程序/指定名称go run 文件名称 编译并执行 两种执行流程的方式区别1) 如...
概念基础
07年出现
go语言的优势:均衡(开发效率高,运行性能好),强大的并发能力go语言的优势:均衡(开发效率高,运行性能好),强大的并发能力
开发语言:c c++ java go python js
goroot go安装路径
gopath go代码目录
go build (-o 文件名.exe) 编译成可执行程序/指定名称
go run 文件名称 编译并执行
两种执行流程的方式区别
1) 如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有 go 开发环境的机
器上,仍然可以运行
2) 如果我们是直接 go run go 源代码,那么如果要在另外一个机器上这么运行,也需要 go 开发
环境,否则无法执行。
3) 在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了
很多。
快捷键 作用
Shift +Alt+上/下键 快速复制
Ctrl + / 单行注释
Ctrl + Shift + / 多行注释
Ctrl + D 复制当前光标所在行
Ctrl + X 删除当前光标所在行
Ctrl + Alt + L 格式化代码
Ctrl + Shift + 方向键上或下 将光标所在的行进行上下移动(也可以使用 Alt+Shift+方向键上或下)
Ctrl + Alt + left/right 返回至上次浏览的位置
Ctrl + R 替换
Ctrl + F 查找文本
Ctrl + Shift + F 全局查找
一、基础语法:
1.变量
先声明再赋值 var x int
声明并赋值 var y = "hello yuan!"
声明多个变量 var name,age = "yuan",22
// 声明多个相同类型变量
var x,y int
// 声明多个不同类型变量
var (
name string
age int
isMarried bool
)
值拷贝 相互独立不受影响
匿名变量
变量命名规则
1、变量名称必须由数字、字母、下划线组成。
2、标识符开头不能是数字。
3、标识符不能是保留字和关键字
2.基本数据类型
整形 1字节=8位 ini8 -128至127 uint8 0至255
浮点型 float32 float64
布尔类型 true false(默认)
字符串 默认空值
索引
切片左闭右开
转义符 \n 换行
字符串的常用方法
方法 介绍
len(str) 求长度
strings.ToUpper,strings.ToLower 生成一个新的全部大写的字符串,生成一个新的全部小写的字符串
strings.ReplaceAll 生成一个新的原字符串被指定替换后的字符串
strings.Contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Trim、 去除字符串两端匹配的内容
strings.Index(),strings.LastIndex() 子串出现的位置
strings.Split 分割,将字符串按指定的内容分割成数组
strings.Join(a[]string, sep string) join操作,将数组按指定的内容拼接成字符串
3.数据类型转化
3.1整形之间的转换 int8 int16 int32
3.2字符串与整形之间的转换 strconv
strconv.Atoi 字符串-->整型
strconv.Itoa 整型--> 字符串
strconv.ParseInt 字符串-->任意整型
strconv.ParseFloat字符串-->浮点型
strconv.ParseBool 字符串-->布尔型
`strconv.Atoi` 函数只能将字符串转换为 `int` 类型,而 `strconv.ParseInt` 函数可以将字符串转换为任意整数类型,如 `int8`、`int16`、`int32`、`int64` 等。此外,`strconv.ParseInt` 还可以指定转换的进制数,而 `strconv.Atoi` 只能将字符串解析为十进制的整数。
4.运算符
4.1 算数运算符
+ - * %
4.2 关系运算符
== != > < >= <=
4.3 逻辑运算符
&&:与运算 真与真为 真 真与假为假 假与真为假 假与假为假
||:或运算 真或真为 真 真或假为真 假或者真为真 假或假为假
!:非运算 非真为假 非假为真
4.4 赋值运算符
= 简单的赋值运算符,将一个表达式的值赋给一个左值
+= 相加后再赋值
-= 相减后再赋值
++ 自增
-- 自减
4.5 运算符优先级
=最后级
5.输入输出函数
5.1 输出函数
5.1.1 Print 和Println
Print不换行 Println换行
5.1.2 格式化输出(Printf)
fmt.Printf("姓名:%#v 年龄:%#v 婚否:%#v 薪资:%#v\n", name, age, isMarried, salary)
5.1.3 Sprintf Printf和Sprintf都是替换字符串,Printf是直接输出到终端,Sprintf是不直接输出到终端,而是返回该字符串
%s 输出字符串
%d 输出不符号的整形
%+d 输出带符号的整形
%b 打印整形的二进制
%x 打印整形的十六进制
%o 打印不带0的八进制
%f 打印浮点型
%.2f 打印浮点型 保留2位小数
%T 打印类型
%t 打印布尔值
%#v 打印任意类型 原始形
%p 打印地址
5.2 输入函数
5.2.1 fmt.Scan 等待用户在命令输入一个值 fmt.Scan(&name, &age, &isMarried) // 输入类型不一致,按默认值
5.2.2 fmt.Scanln 换行输入
5.2.3 fmt.Scanf 按格式输入 fmt.Scanf("%d+%d", &a, &b)
二、流程控制语句
6.分支语句
6.1 单分支 if
if username == "yuan" && password=="123"{
fmt.Println("登录成功!")
}
6.2 双分支 if else
if age >= 18 {
fmt.Println("恭喜,你已经成年,可以观看该影片!")
} else {
fmt.Println("抱歉,你还未成年,不宜观看该影片!")
}
6.3 if多分支语句 if...else if..else..
if score > 100 && score < 0 {
fmt.Println("输入数字应该在1-100之间")
} else if score > 90 {
fmt.Println("成绩优秀!")
} else if score > 80 {
fmt.Println("成绩良好!")
} else if score > 60 {
fmt.Println("成绩及格!")
} else {
fmt.Println("请输入一个数字!")
}
6.4 switch多分支语句
if week > 0 && week <= 7 {
switch week {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3:
fmt.Println("星期三")
case 4:
fmt.Println("星期四")
case 5:
fmt.Println("星期五")
case 6:
fmt.Println("星期六")
case 7:
fmt.Println("星期日")
}
} else {
fmt.Println("输入不合法!")
}
6.5 分支嵌套
var user string
var pwd int
fmt.Printf("请输入用户名:")
fmt.Scanln(&user)
fmt.Printf("请输入密码:")
fmt.Scanln(&pwd)
if user == "yuan" && pwd==123{
var score int
fmt.Printf("请输入成绩:")
fmt.Scanln(&score)
if score >= 90 && score<=100 {
fmt.Println("成绩优秀!")
} else if score >= 80 {
fmt.Println("成绩良好!")
} else if score >= 60 {
fmt.Println("成绩及格")
} else {
fmt.Println("不及格!")
}
}else {
fmt.Println("用户名或者密码错误!")
}
7 循环语句
7.1 简单循环案例:
count := 0 // 初始化语句
for count<10 { // 条件判断
fmt.Println(count)
count ++ // 步进语句
}
7.2.1 无限循环案例:
for true{
}
7.2.2 循环中嵌入分支语句:
func main() {
fmt.Printf(`
1、普通攻击
2、超级攻击
3、使用道具
4、逃跑
`)
for true {
var choice int
fmt.Printf("请输入选择:")
fmt.Scanln(&choice)
//fmt.Println(choice,reflect.TypeOf(choice))
switch choice {
case 1:
fmt.Println("普通攻击")
case 2:
fmt.Println("超级攻击")
case 3:
fmt.Println("使用道具")
case 4:
fmt.Println("逃跑")
default:
fmt.Println("输入有误!")
}
}
}
7.3 三要素for循环(核心)
for init;condition;post {
// 循环体语句
}
for i := 1; i < 10; i++ {
fmt.Println(i)
}
7.4 遍历for循环
循环作用域:
7.5 退出循环
7.5.1 break 退出整个循环
7.5.2 continue 退出当前循环 后面有代码才有实际作用
7.6 循环嵌套
7.6.1 独立嵌套
7.6.2 关联嵌套
三,重要的数据类型
8.1指针类型(核心类型):接收地址值
var x = 100
// 取址符:& 取值符:* *指针变量 不能接普通变量
fmt.Println("x的地址:",&x);
// 将地址值赋值给的变量称为指针变量
var p *int = &x
fmt.Println("p的值:",p);
fmt.Println("p的地址",&p)
fmt.Println("p地址对应的值",*p)
8.2 new函数
基本数据类型属于值类型
值类型(整型,浮点型,字符串,布尔类型,数组,结构体)的特点:当声明未赋值之前存在默认值。
引用类型(指针类型,切片,map,channel)的特点:当声明未赋值之前不存在默认值。
new函数
var p *int
p = new(int)
fmt.Println(*p)
*p = 10
fmt.Println(*p)
8.3 数组
数组的特点:
一致性:数组只能保存相同的数据类型元素,元素的数据类型可以是任意相同的数据类型
有序性:数组中的元素是有序的,通过下标访问
不可变性:数组一旦初始化,则长度不可变
// (1)先声明再赋值
// 数组的声明
// 数组必须限制长度
var arr [3]int
fmt.Println(arr) // [0 0 0]
// 赋值 数组[索引]
fmt.Println(arr[0])
fmt.Println(arr[1])
fmt.Println(arr[2])
// 索引赋值
arr[0] = 10
fmt.Println(arr) // [10 0 0 ]
arr[1] = 11
arr[2] = 12
fmt.Println(arr) // [10 11 12]
//(2)数组的声明并赋值
var names = [3]string{"yuan", "rain", "alvin"}
fmt.Println(names) // [yuan rain alvin]
var ages = [3]int{22, 23, 24}
fmt.Println(ages) // [22 23 24]
// (3) 省略长度赋值
var names2 = [...]string{"yuan", "rain", "alvin"}
fmt.Println(names2) // [yuan rain alvin]
// (4) 索引赋值
var names3 = [...]string{0: "yuan", 2: "rain"}
fmt.Println(names3) // [yuan rain]
// go len函数:计算容器数据的长度
fmt.Println(len("hello"))
fmt.Println(len(names3))
数组操作:
var names = [3]string{"yuan", "rain", "alvin"}
// (1)索引操作 数组[索引]
fmt.Println(names[2])
names[2] = "Alvin"
fmt.Println(names)
// (2) 切片操作 数组[start索引:end索引]
var arr = [5]int{11, 12, 13, 14, 15}
s1 := arr[1:3]
fmt.Println(s1, reflect.TypeOf(s1)) // [12 13] []int
s2 := arr[1:]
fmt.Println(s2, reflect.TypeOf(s2))
s3 := arr[:3]
fmt.Println(s3, reflect.TypeOf(s3)) // [11 12 13] []int
s4 := arr[2:4]
fmt.Println(s4)
// (3) 遍历数组
//三要素for循环
var arr2 = [5]int{20, 21, 22, 23, 24}
for i := 0; i < len(arr2); i++ {
fmt.Println(i, arr2[i])
}
// range循环(首推,更好理解)
for _, v := range arr2 {
//fmt.Println(i, v)
v = 0
fmt.Println(v)
}
fmt.Println(arr2)
8.4 切片(slice)
切片是一个动态数组,因为数组的长度是固定的,所以操作起来很不方便。切片能更改长度。
切片是对数组的引用
每个切片存储3个值:起始地址 长度 容量
// 构建切片方式一:通过数组切片操作获得切片对象
/*var arr = [3]string{"rain", "eric", "alvin"}
fmt.Println(arr, reflect.TypeOf(arr))
s1 := arr[0:2]
fmt.Println(s1, reflect.TypeOf(s1)) // [rain eric] []string
s2 := arr[1:]
fmt.Println(s2, reflect.TypeOf(s2)) // [eric alvin] []string
*/
/*s2[0] = "yuan"
fmt.Println(s1)
fmt.Println(arr)*/
// (1) 切片是对数组的引用
/*var a = [5]int{1, 2, 3, 4, 5}
var slice = a[:] // 起始地址 长度 容量
fmt.Println(len(slice), cap(slice))
var slice2 = a[:2]
fmt.Println(len(slice2), cap(slice2))
fmt.Println(slice) // [1 2 3 4 5]
newSlice := slice[1:3]
fmt.Println(newSlice)
fmt.Println(len(newSlice), cap(newSlice))
newSlice[1] = 1000
fmt.Println(a)
fmt.Println(slice)
fmt.Println(slice2)
*/
// (2) 直接声明切片
//arr := [5]int{10, 11, 12, 13, 14}
//s := arr[:]
var s = []int{10, 11, 12, 13, 14}
s1 := s[1:4] // [11, 12, 13]
fmt.Println(len(s1), cap(s1))
s2 := s[3:]
fmt.Println(len(s2), cap(s2))
s3 := s1[1:2]
fmt.Println(len(s3), cap(s3))
//案例1
/*s1 := []int{1, 2, 3} // 切片 3 3 [1 2 3]
s2 := s1[1:] // 2 2 [2 3]
s2[1] = 4
fmt.Println(s1)*/
// 案例2
var a = []int{1, 2, 3} // [1 2 3]
b := a
c := a[1:]
a[0] = 100
fmt.Println(b)
fmt.Println(c)
8.4 make函数
如果需要动态地创建一个切片,可以使用 make() 内建函数,格式 make([]Type, size, cap)
new函数仅仅返回地址
make函数返回切片,即地址,长度,容量
// var s = []int{1,2,3,4,5}
// var s []int
// 初始化创建空间
var s = make([]int, 5, 10)
fmt.Println(len(s), cap(s))
fmt.Println(s)
s[0] = 100
fmt.Println(s)
// 练习题
a := make([]int, 5)
b := a[0:3]
a[0] = 100
fmt.Println(a) // [100 0 0 0 0]
fmt.Println(b) // [100 0 0]
append(重点)
片作为一个动态数组是可以添加元素的,添加方式为内建方法append。
对于容量不够的数组,会动态创建一个新的数组,双倍扩容
对于容量足够的数组,会在原来的数组上追加
var s []int
// 可以追加空切片一个值
s1 := append(s, 0)
fmt.Println(s1)
// 可以追加多个值
s2 := append(s1,1,2,3)
fmt.Println(s2)
// 可以追加一个切片
s3 := append(s2,[]int{4,5,6}...)
fmt.Println(s3)
a := []int{11, 22, 33}
fmt.Println(len(a), cap(a))
c := append(a, 44)
a[0] = 100
fmt.Println(a)
fmt.Println(c)
// make
/*a := make([]int, 3, 10)
fmt.Println(a)
b := append(a, 11, 22)
fmt.Println(a) // 小心a等于多少?
fmt.Println(b)
a[0] = 100
fmt.Println(a)
fmt.Println(b)*/
arr := [4]int{10, 20, 30, 40}
s1 := arr[0:2] // [10, 20]
s2 := s1 // // [10, 20]
s3 := append(append(append(s1, 1), 2), 3)
s1[0] = 1000
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(arr)
append原理
每次 append 操作都会检查 slice 是否有足够的容量,如果足够会直接在原始数组上追加元素并返回一个新的 slice,底层数组不变,但是这种情况非常危险,极度容易产生 bug!而若容量不够,会创建一个新的容量足够的底层数组(双倍扩容),先将之前数组的元素复制过来,再将新元素追加到后面,然后返回新的 slice,底层数组改变而这里对新数组的进行扩容
经典面试题
var s = make([]int, 5, 10)
fmt.Println(len(s), cap(s))
fmt.Println(s)
s[0] = 100
fmt.Println(s)
append其他操作
/*var s = []int{1, 2, 3}
fmt.Printf("%p\n", &s)
s = append(s, 4)
fmt.Printf("%p\n", &s)*/
结果地址都是一样的,原理append时,即是重新给切片s赋值,其实就是给切片三要素重新赋值,初始地址,长度,容量都会改变,切片的结果会变,但是地址不变,内存地址还是原来的那块空间
// 向开头插入值或者切片
var a = []int{1, 2, 3}
fmt.Println(append([]int{0}, a...))
fmt.Println(append([]int{-1, -2, -3}, a...))
// 任意位置插入元素
var b = []int{1, 2, 3, 4, 5}
var i = 2
// var a []int
// a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
// a = append(a[:i], append([]int{1, 2, 3}, a[i:]...)...) // 在第i个位置插入切片
fmt.Println(append(b[:i], append([]int{100}, b[i:]...)...))
// 删除元素
var c = []int{1, 2, 3, 4, 5}
c = append(c[:2], c[2+1:]...)
fmt.Println(c)
8.5 map(映射)类型
var map_name map[key_type]value_type
//声明
//var stus map[string]string
//fmt.Println(stus, reflect.TypeOf(stus))
// (1) 声明并初始化
// var x = []int{1, 2, 3} // 切片的声明并初始化
/*var stu01 = map[string]string{"name": "yuan", "age": "32"}
// 支持key查询 map对象[key]
fmt.Println(stu01) // map[age:32 name:yuan]
fmt.Println(stu01["name"])
fmt.Println(stu01["age"])
fmt.Println(len(stu01))
// 写入一个key-value
stu01["gender"] = "male"
fmt.Println(stu01) // map[age:32 gender:male name:yuan]
stu01["height"] = "180cm"
// 修改一个key-value
stu01["height"] = "190cm"
fmt.Println(stu01) // map[age:32 gender:male height:190cm name:yuan]
// 删除一个key-value
delete(stu01, "gender")
fmt.Println(stu01) // map[age:32 height:190cm name:yuan]
// (2) 基于make函数声明初始化
var p *int
p = new(int)
fmt.Println(*p)
//var s []int
s := make([]int, 3)
s[0] = 100
*/
// 遍历map对象
var s = []int{10, 11, 12, 13, 14}
for i, v := range s {
fmt.Println(i, v)
}
//var stu02 map[string]string // map引用类型
var stu02 = make(map[string]interface{})
stu02["name"] = "rain"
stu02["age"] = 30
stu02["gender"] = "male"
fmt.Println(stu02)
for k, v := range stu02 {
fmt.Println(k, v)
}
// 如何判断键是否存在
v, b := stu02["names"]
fmt.Println(v, b)
map拓展
(1)map嵌套slice
var data = make(map[string][]string)
data["beijing"] = []string{"朝阳", "海淀", "昌平"}
data["shandong"] = []string{"济南", "青岛", "威海"}
data["hebei"] = []string{"唐山", "石家庄", "保定"}
// fmt.Println(data) // map对象
(2)map嵌套map
/*stu01 := map[string]string{"name": "rain", "age": "32"}
stu02 := map[string]string{"name": "eric", "age": "33"}
stu03 := map[string]string{"name": "alvin", "age": "34"}
var stus = make(map[int]map[string]string)
stus[1001] = stu01
stus[1002] = stu02
stus[1003] = stu03
fmt.Println(stus)
(3)切片嵌套map
// 方式1
stu01 := map[string]string{"name": "rain", "age": "32"}
stu02 := map[string]string{"name": "eric", "age": "33"}
stu03 := map[string]string{"name": "alvin", "age": "34"}
// var stus = make([]map[string]string, 3)
var stus = []map[string]string{stu01, stu02, stu03}
// 方式2
//var stus = []map[string]string{{"name": "rain", "age": "32"}, {"name": "eric", "age": "32"}}
8.6 函数初识
函数功能:代码的一种组织形式,实现模块化,避免重复
声明函数:
func 函数名(形式参数) (返回值) {
函数体
return 返回值 // 函数终止语句
}
函数调用:
函数名()
变量 = 函数(实参) // return 返回的值赋值给某个变量,程序就可以使用这个返回值了。
函数参数
func printLing(n int) {// 形式参数 1个参数
函数体
}
func add(a, b int) { // 形式参数 2个参数
fmt.Println(a + b)
}
func add2(s ...int) { // 形式参数 多个参数
fmt.Println(s, reflect.TypeOf(s))
var ret = 0
for _, v := range s {
ret += v
}
函数返回值
// 无返回值的情况
func printLing(n int) {
函数体
}
// 返回一个值的情况
func add(a, b int) int { // 形式参数
c := a + b
return c // 函数的终止语句
}
// 返回值命名
func add2(s ...int) (z int) { // 形式参数
fmt.Println(s, reflect.TypeOf(s))
for _, v := range s {
z += v
}
return
}
}
// 返回多个值的情况
func login(user, pwd string) (bool, string) {
if user == "root" && pwd == "123" {
// 登陆成功
return true, user
} else {
return false, ""
}
}
// 无返回值不能赋值给单独变量
//ret2 := printLing(6)
b, user := login("root", "123")
if b {
fmt.Println(user)
}
// 返回值命名
func add2(s ...int) (z int) { // 形式参数 z声明时默认为0
fmt.Println(s, reflect.TypeOf(s))
for _, v := range s {
z += v
}
return
}
作用域
变量根据所在位置的不同可以划分为全局变量和局部变量
局部变量 :写在{}中或者函数中或者函数的形参, 都是局部变量
全局变量 :函数外面的就是全局变量
:=只能用于局部变量, 不能用于全局变量
局部变量如果没有使用, 编译会报错。全局变量如果没有使用, 编译不会报错
局部变量的作用域是从定义的那一行开始, 直到遇到 } 结束或者遇到return为止
局部变量, 只有执行了才会分配存储空间, 只要离开作用域就会自动释放
全局变量的作用域是从定义的那一行开始, 直到文件末尾为止
全局变量, 只要程序一启动就会分配存储空间, 只有程序关闭才会释放存储空间
外层不能打印里层的变量,如果该函数层没有找到变量,需要向外扩展找变量,不能跨到其他函数找。
先找私域的变量,找不到才会去外层找。
x=20 和 var x=20 不一样
局部没有声明变量,会使用全局的变量,一旦使用 全局的变量值就会被改变,被重新赋值了
注意,if,for语句具备独立开辟作用域的能力,在{}里面
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)