背景😎
在大致了解Go语言的基本语法后,我就迫不得已地想使用这门语言。可是我发现编程思路不是问题,很大的问题是“手慢”,不熟悉常用写法(可能这就是快速过语法的缺点吧,脑子会了,手没会)φ(* ̄0 ̄。
我刷的不是Leetcode形式的题目,而是ACM形式的题目。因为ACM形式需要处理输入输出,这对我的要求会更高点。
基础知识🤔
输入处理
- 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
}
如果输入不止三个数字,输入很长怎么办?
具体做法如下:
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] |
- 题号:B2094 不与最大数相同的数字之和
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
}
- 题号:B2098 整数去重
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 := "abc123@11-2.com"
// 判断字符串是否匹配
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"
- 题号:B2109 统计数字字符个数
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
分界线:———————————————————————————————————————
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的结构体对象,然后通过对象调用。
- 封装
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结构体的成员变量和方法。匿名嵌套是继承,不匿名就是组合的使用方法了。
- 接口多态
向上转型”的效果。
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
}
- 题号:B3679 [语言月赛202211] Zone Selection
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的人也能获得帮助。