这是我参与「第五届青训营 」笔记创作活动的第1天
之前有过Golang语言基础的学习,这里基于原来的理解以及课程简单总结一下Golang语言的基础知识点
一、本堂课重点内容
- 程序结构
- 数据类型
- 函数&方法&接口
- 进程相关
二、详细知识点介绍
1、程序结构
1.1 变量/函数命名
在Golang语言中,我们一般采用驼峰命名法。(如userId、getUser()等等)
而在Golang中有一个独特的特性是,没有采用其他编程语言的private、public等关键字标志开放的函数或变量,而是采用命名时的首字母大小写来决定其可见性是否跨包对外开放。如果名称是以大写字母的开头,它是导出的,意味着它对外包来说是可见的可访问的,可以被自己包外的其他程序所引用。(这里也可以叫大写驼峰命名)
1.2 声明
Golang中采用 var 变量名 类型的方式来声明变量
或是采用变量名 := 值来直接声明并初始化变量
var test1 string
test2 := "Hello"
fmt.Println(test1)
fmt.Println(test2)
1.3 注释
与C/C++/Java等老牌编程体系一样,Golang采用//来进行单行注释,/* ...... */来进行多行注释
1.4 引号
Golang中的引号有三种: ''、""、``,下面来分别通过一段代码来理解一下这三种引号的区别
var d rune
d = '1'
a := 'a'
b := "a"
c := `'a'':"b"` //可以把单引号和双引号整括起来
fmt.Printf("%T\n", d) // int32
fmt.Printf("%T\n", a) // int32
fmt.Printf("%T\n", b) // string
fmt.Printf("%T\n", c) // string
fmt.Println(c) // 'a'':"b"
由上面这段代码可以知道:
''的默认类型是int32 (也就是字符对应的ascii码)
""的默认类型是string (也就是我们所常见的字符串)
``的默认类型也是string,但是可以更方便的在字符串中写入以上两种引号,而不会发生错误。
2、数据类型
2.1 整型
Golang中的整型分为很多类型:
- 有符号整数
- int
- int8
- int16
- int32/rune
- int64
- 无符号整数
- uint
- uint8/byte
- uint32
- uint64
不同位数的整型区别在于能够保存的整型数字范围的大小,我们需要根据业务需求来选择使用何种类型的整型进行使用。而int和uint的大小和系统有关(如32位的系统表示int32和uint32) byte与uint8类似,一半用来存储单个字符,其余场景多用默认的int/uint
2.2 浮点数
- float64 对应存储字长64位也就是对应double
- float32 对应存储字长32位也就是其他语言中的float
Golang默认的浮点数类型为float64
2.3 复数
Golang中有complex64和complex128 , 二者分别由float32和float64 构成,内置的complex函数根据给定的实部和虚部创建复数,而内置的real函数和img函数则分别提取复数的实部和虚部:
var x complex128 = complex(1, 2) //1+2i
var y complex128 = complex(3, 4) //3+4i
fmt.Println(x * y) //-5+10i
fmt.Println(real(x * y)) //-5
fmt.Println(imag(x * y)) //10
z := 1 + 2i
fmt.Println(z)
2.4 布尔型
简单带过,应该都懂
var fanOne true
var xiaoSheng false
2.5 字符串
直接上样例吧!
s := "SkyDog"
fmt.Println(len(s)) // 6
// 用法与python类似 (但参数不能为负)
fmt.Println(s[:3]) // Sky
fmt.Println(s[3:]) // Dog
// 默认类型为uint8|byte
fmt.Printf("%T\n", s[1])
fmt.Println(s[1], s[2]) // 107 121
拼接字符串,go语言与java等语言类似,可以使用+直接拼接字符串,
当然为了保证更好的性能,可以使用builder来进行字符串的构造
s := "Hello" + "Go"
fmt.Println(s)
String与int互转 (strconv库的使用)
// String转int
fmt.Println(strconv.Atoi("123"))
// int转String
fmt.Println(strconv.Itoa(456))
2.6 常量
简单易懂,不会改变的量即常量。
const 常量名 = 值
2.7 数组
var a [3]int //3个整数的数组
//var b [3]int{1,2,3}
b := [3]int{1, 2, 3}
var c = [...]int{4, 5, 6}
for i, v := range a {
// i -> index索引
// v -> value值
fmt.Println(i, v)
}
fmt.Println(b)
fmt.Println(c)
2.8 切片
即可自动扩容的数组
定义切片时的默认值为nil(空指针)
使用make()函数创建和初始化切片(给切片申请空间) 也可以使用引用类型(slice、map、chan、数组)直接创建切片
不能使用new来初始化切片, 切片底层是一个指针,通过指针来访问系统为切片申请的空间做到动态扩缩容
使用make时,需要传入一个参数指定切片的长度,如果只指定长度,则切片的容量和长度相等。也可以传入两个参数分别指定长度和容量。不允许创建容量小于长度的切片。
tips: 按照需求提前申请好空间可以提高系统性能
// 通过字面量声明切片,其长度和容量都为5。以下输出:“len: 5, cap: 5”
s := []int{1, 2, 3, 4, 5}
fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
// 可以在声明切片时利用索引来给出所需的长度和容量。
// 通过指定索引为99的元素,来创建一个长度和容量为100的切片
s := []int{99: 0}
以及一道经典例题:删除切片(slice)中的一个元素
func main() {
var numSlice []int
var n int
var temp int
fmt.Print("请输入要输入切片的整数个数:")
fmt.Scanln(&n)
for i := 0; i < n+1; i++ {
numSlice = append(numSlice, i)
}
fmt.Print("请输入要删除的元素索引:")
fmt.Scanln(&temp)
numSlice = append(numSlice[:temp], numSlice[temp+1:]...)
fmt.Print(numSlice)
}
2.9 map
map是一种key-value映射关系,我们同样通过代码来看看map 例:输入一个字符串,统计每个字符出现的次数并输出
func main() {
var str string
hash := make(map[int32]int)
_, err := fmt.Scanf("%s", &str)
if err != nil {
return
}
for _, char := range str {
//fmt.Printf("%c", char)
hash[char]++
}
for k, v := range hash {
fmt.Printf(" %c 出现了 %d 次\n", k, v)
}
//fmt.Println(str)
}
2.10 结构体
简单的例子, 有点像对象内味了
同样和之前说的一样,大写驼峰表示对外开放变量
type Person struct{
name string
age int
sex string
}
func main(){
person := Person{ //初始化
name: "fanOne",
age: 16,
sex: "male",
}
fmt.Println(person.name) //引用
}
2.11 Json
网络请求必备!
首先需要构造一个与json相对应的结构体,便于轻松快捷的接收json中的数据。 然后通过标准库"encoding/json"中的Marshal()函数来将json转化为十六进进制的字节流
同样可以使用Unmarshal来将十六进制的字节流以一定的格式序列化为结构体
type Person struct{
Name string `json:"name"` //序列化成string类型
Age int `json:"age"`
Sex string `json:"sex"`
}
func main(){
a := Person{Name: "lear", Age: 18, Sex: "male"}
// 不处理err 序列化对象 产出字节流
buf, _ :=json.Marshal(a)
fmt.Println(buf) // [123 34 78 97 ....] 十六进制字节流
fmt.Println(string(buf)) // {Name: "lear", Age: 18, Sex: "male"}
var b Person
_ = json.Unmarshal(buf, &b) // 将这个十六进制字节流序列化成person的结构体,并传入其中
fmt.Printf("%#v\n".n) // main.Person{Name: "lear", Age: 18, Sex: "male"}
}
2.12 时间
通过下面这个简单而较为全面的例子能够很好的理解time库
func main() {
now := time.Now()
fmt.Println(now) // 2023-01-13 13:40:01.1337949 +0800 CST m=+0.005252701
t1 := time.Date(2023, 1, 13, 13, 40, 40, 0, time.UTC)
t2 := time.Date(2023, 1, 27, 14, 40, 40, 0, time.UTC)
fmt.Println(t1) // 2023-01-13 13:40:40 +0000 UTC
fmt.Println(t1.Year(), t1.Month(), t1.Hour(), t1.Minute()) // 2023 January 13 40
fmt.Println(t1.Format("2006-01-02 15:04:05")) // 2023-01-13 13:40:40
diff := t2.Sub(t1)
fmt.Println(diff) // 337h0m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 20220 1.2132e+06
t3, err := time.Parse("2006-01-02 15:04:05", "2023-01-13 13:40:40")
if err != nil {
return
}
fmt.Println(t3 == t1) // true
fmt.Println(now.Unix()) // 1673588802
}
3、函数&接口&方法
3.1 函数
func name(parameter-list)(result-list){
body
}
3.2. 方法
在Golang中,接口体就像是类的简化形式,而方法可以说是成员函数的一种形式,这是一种特殊的函数,可以指定调用函数的类型,其实际效果和视觉效果也就是像一个成员函数一样。
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix
定义:
func (recv receiver_type) methodName(parameter_list) (return_value_list) {
...
}
在方法名之前,func 关键字之后的括号中指定 receiver。
如果 recv是receiver的实例,Method1是它的方法名,那么方法调用遵循传统的object.name 选择器符号:recv.Method1()。
如果recv 是一个指针,Go 会自动解引用。
如果方法不需要使用 recv 的值,可以用 _ 替换
3.3. 接口
Go 语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。
接口是golang中实现多态性的好途径。接口类型是对其他类型行为的概括与抽象,对于一个具体的类型,无需声明它实现了哪些接口,只提供接口所必须的方法即可。
4、进程
fmt.Println(os.Args) // 运行的命令行参数
fmt.Println(os.Getenv("PATH")) //读取环境变量PATH
fmt.Println(os.Setenv("key", "value")) // 设置环境变量
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput() // 启动子进程
if err != nil {
return
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
三、实践练习例子
在上述知识点介绍中已经涉及到很多实践的例子。
对于上课所提到的那些例子,也就是留下的作业做一下解答
-
第一题:猜字游戏
将输入改为如下即可使用scanf完成输入
var input string _, err := fmt.Scanf("%s\n", &input) if err != nil { fmt.Println("An error occured while reading input. Please try again", err) continue } -
第二题: 字典 使用如上课所示的方式轻松(cv)使用go完成网络请求,并采取waitgroup的方式保证多线程的执行(在最后加上如下)
waitGroup := sync.WaitGroup{} waitGroup.Add(2) go queryCaiyunAPI(word, &waitGroup) go queryBaiduAPI(word, &waitGroup) waitGroup.Wait()
课后个人总结
-
golang协程的概念真的很迷人,可以很轻松的做到并发编程,这是许多其他语言所做不到的。
(就像我用Java只能通过单独拉取一个线程,或是通过线程池中调用,真的太浪费性能了) -
经过之前的学习经过这次课之后巩固了golang语言的基础,有些东西不常用就容易忘了,还是需要温故而知新