Golang 语言基础 | 青训营笔记

145 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第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中有complex64complex128 , 二者分别由float32float64 构成,内置的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。

如果 recvreceiver的实例,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语言的基础,有些东西不常用就容易忘了,还是需要温故而知新

引用参考

官方文档

(很喜欢的一个博主同时也是我的学长) 七天入门Go语言