7

超实用的Go语言基础教程,让你快速上手刷题!! - 嘉沐

 1 year ago
source link: https://www.cnblogs.com/Jiamuu/p/17394334.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

工欲善其事,必先利其器。掌握Go的基础语法还不够,还需要勤加练习,修习“外功”,才能达到出奇制胜的效果。

在大致了解Go语言的基本语法后,我就迫不得已地想使用这门语言。可是我发现编程思路不是问题,很大的问题是“手慢”,不熟悉常用写法(可能这就是快速过语法的缺点吧,脑子会了,手没会)φ(* ̄0 ̄)。

在我看来,用Go语言刷算法题是一个非常好的练习“外功”的法门,可以帮助我提高思维的灵敏性和解决抽象化问题的能力。更重要地是复习我学习过的语法知识,不然真的很容易忘。虽然它和C语言有点像,但是我也并不经常使用C,两者不太好建立起清晰的关联图。因此,我会一边勤能补拙,一边总结一些语法知识,一边建立语言之间的联系,方便我加深记忆。

我刷的不是Leetcode形式的题目,而是ACM形式的题目。因为ACM形式需要处理输入输出,这对我的要求会更高点。

刷题平台:洛谷

基础知识🤔

3027725-20230512153405971-579515647.png

Go接收输入的方式有四类,分别是 fmt 包中的 Scan 、Scanf 和Scanln函数以及bufio.Scanner对象实现。

  • Scan函数

使用场景:可以用于读取一段空格分隔的字符串或多个数值类型的输入,例如读取数字或时间等;

示例一:计算浮点数相除的余。

输入格式:输入仅一行,包括两个双精度浮点数a和b。

输入样例:

13.55 24.88

处理方式:

func main() {
	// 接收两个双精度浮点数a,b
	var a, b float64
	_, err := fmt.Scan(&a, &b)
	if err != nil {
		fmt.Println(err)
	}
}
  • Scanf函数

使用场景:适用于需要按特定格式读取和处理输入数据的场景,例如读取时间、日期、金额等;

示例二:数字排序

输入格式:输入三个数字,数字之间用逗号隔开。

输入样例:

1,4,6

处理方式:

package main

import (
	"fmt"
)

func main() {
	var a, b, c int
	fmt.Scanf("%d,%d,%d", &a, &b, &c)
	fmt.Println(a, b, c)
}

如果输入不止三个数字,输入很长怎么办?

我想到的是直接当字符串保存,然后用“,”分割每一个元素,获得一个字符串数组,最后利用Atoi函数将字符串转为整数,存储到一个新的int类型数组中。

具体做法如下:

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	var input string
	scanner := bufio.NewScanner(os.Stdin)
	if scanner.Scan() {
		input = scanner.Text()
	} else {
		fmt.Println("Error")
	}
	strArray := strings.Split(input, ",")
	intArray := make([]int, len(strArray))  // 根据strArray的长度确定intArraye的长度
	for i, v := range strArray {
		var err error
		intArray[i], err = strconv.Atoi(strings.TrimSpace(v))  // strings.TrimSpace 函数去掉字符串中的多余空白字符
		if err != nil {
			fmt.Println("Error")
		}
	}
	fmt.Printf("The input integers are: %v\n", intArray)
}
  • Scanln函数

使用场景:适用于读取空格或换行分隔的字符串或多个数值类型的输入,例如读取单词或名称等。用法和Scan相似,就不举例子了。(~ ̄▽ ̄)~

  • bufio.Scanner对象

使用场景:这个对象可以从标准输入中逐行读取输入,直到遇到文件结尾或输入流关闭为止。特别适合循环读入数据!

示例三:字符串读取,并打印

输入格式:输入多行英文句子。

输入样例:

wow!
you are pretty good at printing!
you win.

处理方式:

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	var strArray []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input := scanner.Text()
		if input == "" {
			break
		}
		strArray = append(strArray, input)
	}
	if err := scanner.Err(); err != nil {
		fmt.Printf("Error reading standard input: %s\n", err.Error())
	}

	fmt.Printf("Read %d lines:\n", len(strArray))
	for i, line := range strArray {
		fmt.Printf("%d: %s\n", i+1, line)
	}
}

Go处理输出的方式根据场景的不同,可以分为以下几种:

  • 终端或控制台中输出一些信息,使用fmt包中的函数。
package main

import (
	"fmt"
)

func main() {
	name := "Tom"
	age := 18
	fmt.Println("name:", name, "age:", age) // Println()函数会自动添加空格
	fmt.Printf("name: %s age: %d\n", name, age)
	str1 := fmt.Sprintf("name: %s age: %d\n", name, age) // Sprintf()函数会返回一个字符串
	fmt.Printf(str1)
}
  • 记录程序运行过程中的日志信息时,可以使用log包中的函数。
package main

import (
	"fmt"
	"log"
)

func main() {
	log.Println("Starting the application...")
	fmt.Println("Hello, World!")
	log.Println("Terminating the application...")
}
  • 读写文件或网络连接时,可以使用os包中的函数。
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	buffer := make([]byte, 1024) // read 1024 bytes at a time
	for {
		bytesRead, err := file.Read(buffer) // read bytes from file
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("bytes read: ", bytesRead)
		fmt.Println("bytes:", buffer[:bytesRead])
		if bytesRead < 1024 {
			break
		}
	}
	fmt.Printf("File contents: %s", buffer) // print file contents
}
  • 执行系统命令或创建进程时,可以使用os包中的函数。
package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command("whoami")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Done")
}

ACM形式的题目更多考察的是第一种在终端/控制台输出信息的格式。这个就要涉及到Go语言格式化字符串的方式的知识点。在我看来,格式化字符串在每种语言里都享有很高的地位。毕竟更美观的打印数据,也有助于我们更好的理解信息。

  • 格式化字符串
格式 描述
%v 表示按照值的默认格式输出,可以输出任意类型的数据。
%s 表示输出字符串类型的数据。
%d 表示输出十进制整数类型的数据。
%f 表示输出浮点数类型的数据。
%t 表示输出布尔类型的数据,true和false分别对应输出1和0。
%p 表示输出指针类型的数据。
%c 表示输出字符类型的数据。
%q 表示输出带引号的字符串类型的数据。
%b 表示输出二进制数类型的数据。
%x 表示输出十六进制数类型的数据。
%o 表示输出八进制数类型的数据。
%05d 表示输出5位,不足的位数用0补齐。
%.2f 表示输出小数点后两位。
%10s 输出10个字符长度,不足的位数用空格补齐
package main

import "fmt"

func main() {
    name := "Tom"
    age := 18
    height := 1.75

    fmt.Printf("My name is %s, I'm %d years old, and I'm %.2f meters tall.\n", name, age, height)
    fmt.Printf("My name is %10s, I'm %05d years old, and I'm %.2f meters tall.\n", name, age, height)
}
My name is Tom, I'm 18 years old, and I'm 1.75 meters tall.
My name is        Tom, I'm 00018 years old, and I'm 1.75 meters tall.

数组?切片?

在Go语言中,数组是一种固定长度的数据结构,一旦定义了数组的长度,就无法再向数组中添加新的元素。如果想动态更改,可以考虑使用切片。根据使用方法可以大致分个类:

共性 差异
下标访问 定义方式不同
循环遍历 切片可以添加/删除元素
长度计算
切片[start:end]
package main

import (
	"fmt"
)

func main() {
	var n int
	fmt.Scan(&n)
	var arr []int
	var max int = -10000000
	var sum int = 0
	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		sum += x
		if max < x {
			max = x
		}
		arr = append(arr, x)
	}
	var count int = 0
	// 找到数组里面最大的数及它出现的次数
	for i := 0; i < n; i++ {
		if max == arr[i] {
			count++
		}
	}
	fmt.Println(sum - max*count)
}
package main

import (
	"fmt"
)

func main() {
	var n int
	fmt.Scan(&n)
	var used [110]int
	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		used[x]++
		if used[x] < 2 {
			fmt.Print(x, " ")
		}
	}
}

字符串处理

  • 字符串长度计算

在Go语言中,字符串的长度是指字符串中字节的个数,而不是字符的个数。对于包含非ASCII字符的字符串,一个字符可能会占用多个字节。

package main

import (
	"fmt"
)

func main() {
	str := "hello world"
	fmt.Println(len(str)) // 输出11
	str = "hello 世界"
	fmt.Println(len(str)) // 输出12
}
  • 字符串遍历

既可以使用传统的下标遍历,也可以使用range遍历。建议使用range遍历,因为当字符串中出现中文时,下标遍历获取的是byte类型的值,也就意味着它是将一个汉字拆成了3个byte类型字节分别输出。

package main

import (
	"fmt"
)

func main() {
	str := "hello world"
	for i, v := range str {
		fmt.Printf("字符串中下标为 %d 的字符是 %c\n", i, v)
	}
}
  • 字符串切片

需要注意的是,在使用字符串切片时,下标是按字节计算的,而不是按字符计算的。

str := "hello world"
slice := str[1:5]  // 获取str中下标为1到4的字符,不包括下标为5的字符
fmt.Println(slice)  // 输出"ello"
  • 字符串连接

可以使用加号运算符或fmt.Sprintf函数来连接字符串。

str1 := "hello"
str2 := "world"
str3 := str1 + " " + str2  // 使用加号运算符连接字符串
fmt.Println(str3)  // 输出"hello world"

str4 := fmt.Sprintf("%s %s", str1, str2)  // 使用fmt.Sprintf函数连接字符串
fmt.Println(str4)  // 输出"hello world"
  • 字符串查找

使用strings包中的函数来查找字符串中的子串。

str := "hello world"
index := strings.Index(str, "world")  // 查找子串"world"在str中的位置
fmt.Println(index)  // 输出6
  • 字符串替换

使用strings包中的函数来替换字符串中的子串。

str := "hello world"
newstr := strings.Replace(str, "world", "golang", -1) // 将子串"world"替换为"golang", -1表示全部替换
fmt.Println(newstr)                                   // 输出"hello golang"
  • 字符串转换

使用strconv包中的函数进行转换。

str := "123"
num, err := strconv.Atoi(str) // 将字符串转换为整型
if err != nil {
    fmt.Println("转换失败")
} else {
    fmt.Printf("转换结果是 %T\n", num)
}

num = 123
str = strconv.Itoa(num) // 将整型转换为字符串
fmt.Printf("转换结果是 %T\n", str)
  • 正则匹配(✨✨✨✨)
预定义字符集 描述
\d 匹配一个数字字符。等价于字符集 [0-9]。
\s 匹配一个空白字符(空格、制表符、换行符等)。等价于字符集 [ \t\n\r\f\v]。
\w 匹配一个单词字符。等价于字符集 [a-zA-Z0-9_]。
\W 匹配一个非单词字符。等价于字符集 [^a-zA-Z0-9_]。
\S 匹配一个非空白字符。等价于字符集 [^ \t\n\r\f\v]。
\D 匹配一个非数字字符。等价于字符集 [^0-9]。
\b 表示单词边界,我的理解是能准确匹配到某个单词,不把包含这个单词的前缀词算在内。比如gotest就无法匹配test。

匹配一个由汉字组成的字符串(数据清洗时常用!):

^[\u4e00-\u9fa5]+$

匹配一个由邮箱地址组成的字符串(匹配恶意URL、匹配钓鱼邮箱常用):

^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$

演示1:匹配一个字符串是否符合某个正则表达式。

import (
	"fmt"
	"regexp"
)

func main() {
	// 定义一个正则表达式
	pattern := "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
	// 编译正则表达式
	reg := regexp.MustCompile(pattern)
	// 要匹配的字符串
	str := "[email protected]"
	// 判断字符串是否匹配
	matched := reg.MatchString(str)
	fmt.Println(matched)
}

演示2:利用正则进行查找和替换字符串

// 查找
str := "hello world"
re := regexp.MustCompile(`\b\w+o\w+\b`) // 匹配包含字母o的单词
newstr := re.FindAllString(str, -1)     // 将查找所有匹配的字符串
fmt.Println(newstr)

// 替换
str := "hello world"
re := regexp.MustCompile(`\b\w+o\w+\b`)  // 匹配包含字母o的单词
newstr := re.ReplaceAllString(str, "golang")  // 将所有匹配的字符串替换为"golang"
fmt.Println(newstr)  // 输出"golang golang"
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	sentence := scanner.Text()
	var count int = 0
	for _, v := range sentence {
		if v >= '0' && v <= '9' {
			count++
		}
	}
	fmt.Println(count)
}

Go语言的结构体和C语言很相似。

  • 结构体定义
type Person struct {
    Name string
    Age int
    Height float32
}
  • 结构体初始化
p1 := Person{Name: "Alice", Age: 20, Height: 1.65}  // 定义一个Person类型的结构体变量p1并初始化
p2 := new(Person)  // 定义一个指向Person类型的指针变量p2,并分配内存空间
  • 结构体元素访问("."号访问)

指针和普通的对象类型都是使用“.”号访问。

p1.Name = "Alice"  // 给p1的Name赋值为"Alice"
p1.Age = 20  // 给p1的Age赋值为20
p1.Height = 1.65  // 给p1的Height赋值为1.65

分界线:———————————————————————————————————————

Go还支持一些面向对象的编程特性,非常的灵活和强大!!!

func (p *Person) GetInfo() string {
    return fmt.Sprintf("Name: %s, Age: %d, Height: %.2f", p.Name, p.Age, p.Height)
}

p1.GetInfo()  // 调用p1的GetInfo方法,返回"Name: Alice, Age: 20, Height: 1.65"

这个方法定义了一个指针类型为Person的方法GetInfo,用来返回一个包含Person对象信息的字符串。我们可以通过调用结构体变量的方法来实现对结构体对象的操作。这种使用方法就很棒!这就有点像类方法,GetInfo函数就是Person结构体的类方法。想要使用这个方法,那么就需要先构造一个Person的结构体对象,然后通过对象调用。

此外,Go还支持封装、继承、多态的特性,用来实现复杂的对象模型和数据结构。

type Person struct {
    name string
    age int
}

func (p *Person) SetName(name string) {
    p.name = name
}

func (p *Person) GetName() string {
    return p.name
}

这个结构体定义了一个名为Person的结构体类型,包含了两个私有的成员变量name和age,以及两个公有的方法SetName和GetName,用来设置和获取name成员变量的值。不同于其它语言使用Public,Private定义公有和私有,Go使用编程规范来定义这个概念。变量名首字母大写代表公有,对外可见;变量名首字母小写代表私有,对外不可见。(经过实验,上面的说法是有一个大前提的。同一个包内,无论是公有变量还是私有变量,在任何地方都可以访问!!!!,只有在不同的包里,才有上面变量名大小写来控制可见性的说法。😣😣😣)Go的变量命名主要使用驼峰命名法,也算是约定俗成吧。

  • 继承和组合
type Person struct {
    name string
    age int
}

type Student struct {
    Person  // 匿名嵌套Person结构体
    id string
}

func (s *Student) SetId(id string) {
    s.id = id
}

这个结构体定义了一个名为Student的结构体类型,通过匿名嵌套Person结构体,实现了从Person结构体继承了name和age成员变量和方法,并添加了一个id成员变量和SetId方法。这样,我们就可以通过Student结构体来访问和操作Person结构体的成员变量和方法。匿名嵌套是继承,不匿名就是组合的使用方法了。

声明一个Shape类型的接口,该接口里定义了Area()函数。Rectangle和Circle实现了Shape类型接口里的Area()的方法,可以认定为是一个实现类。PrintArea方法接受一个Shape类型的数据,然后输出面积。这个形参是Shape类型,因此,就有了一个“向上转型”的效果。

package main

import (
	"fmt"
	"math"
)

type Shape interface {
	Area() float64
}

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

func PrintArea(s Shape) {
	fmt.Println(s.Area())
}

func main() {
	r := Rectangle{Width: 3, Height: 4}
	c := Circle{Radius: 5}

	PrintArea(r) // 输出 12
	PrintArea(c) // 输出 78.53981633974483
}
package main

import (
	"fmt"
	"math"
)

type coordinate struct {
	x, y     int
	isMarked bool
}

func distence(x1 int, y1 int, x2 int, y2 int) float64 {
	return math.Sqrt(math.Pow(float64(x1-x2), 2) + math.Pow(float64(y1-y2), 2))
}

func main() {
	var n, k, t int
	fmt.Scan(&n, &k, &t)
	coordinates := make([]coordinate, n)
	for i := 0; i < n; i++ {
		fmt.Scan(&coordinates[i].x, &coordinates[i].y)
	}

	for i := 0; i < k; i++ {
		var x, y int
		fmt.Scan(&x, &y)
		for j := 0; j < n; j++ {
			if x == coordinates[j].x && y == coordinates[j].y {
				coordinates[j].isMarked = true
				break
			}
		}
	}

	// 记录最远距离的坐标,以及最远距离
	var maxDistence float64 = 0.0
	var maxDistenceid int = -1
	var res int = 0
	for i := 0; i < t; i++ {
		var x, y int
		fmt.Scan(&x, &y)
		for j := 0; j < n; j++ {
			if distence(x, y, coordinates[j].x, coordinates[j].y) > maxDistence {
				// fmt.Println(x, y, coordinates[j].x, coordinates[j].y)
				// fmt.Println("distence:", distence(x, y, coordinates[j].x, coordinates[j].y))
				maxDistence = distence(x, y, coordinates[j].x, coordinates[j].y)
				maxDistenceid = j
			}
		}
		if coordinates[maxDistenceid].isMarked {
			res++
		}
		// 更新最远距离
		maxDistence = 0.0
		maxDistenceid = -1
	}
	fmt.Println(res)
}

函数的内容并没有专门拎出来讲是两个原因。第一个原因是有编程基础的人瞄一眼语法就会用了,不太需要刻意的写。第二个原因是每部分的代码都或多或少的用到了函数,不用解释也能看得懂。当然,函数要讲起来还是比较多的,比如传值和传址,数组指针,指针数组这种,这确实是重难点。如果有机会的话,我可能会专门去总结这部分的内容。

如果我总结的东西对你能产生帮助,那么请帮我点个推荐,让更多想要学习Go的人也能获得帮助。

3027725-20230512155119233-1680738023.gif

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK